Pi-tillery

Nume: Aldea George-Dănuț
Grupa: 333CD

Introducere

Proiectul constă într-o mică turetă controlată din telecomandă ce va arunca cu precizie mingi de ping pong la o anumită distanță și într-o anumită direcție. Ideea proiectului a plecat de la o discuție la lucru în care am ajuns la concluzia că o astfel de “jucărie” ar fi super amuzantă la birou. Te enervează colegu'? Jbang!

Descriere generală

Principalele module ale proiectului vor fi:

Telecomanda ce va funcționa pe baterii de ceas, și va avea un joystick pentru a controla mișcarea turetei, cât și un display ce va arata distanta de tragere.

Controller-ul turetei ce va comunica cu telecomanda și va acționa atât mecanismul de control al turetei, cât și mecanismul de tragere.

Mecanica turetei ce va fi alcătuită din sistemul de contol al direcției și distanței ce va folosi două servomotoare pentru a ajusta unghiul de tragere cât și direcția, și mecanismul de tragere ce va folosi un set de roți ce să dea viteză mingii plasate pe lateralele unui tub din plastic.

Hardware Design

Pentru implementare voi folosi:
- 2 * Raspberry Pi Pico W
- LCD 1602
- adaptor I2C pentru LCD 1602
- 2 * servomotor
- modul joystick
- 2 * motor DC cu 10.000 RPM
- modul cu Driver de Motoare Dual L298N
- 2 * modul coborare tensiune LM2596
- 3 * soclu + 3 * baterie CR2032

Schema electrică pentru telecomandă:

Schema electrică pentru turetă:

Hardware telecomandă

pi-ctrl.jpeg

Hardware turetă

pi-turr.jpeg

Software Design

Codebase-ul este împărțit în două crate-uri, cel al telecomenzii în crate-ul controller, iar cel al turetei in turret. Cele două device-uri comunică între ele prin sockeți TCP peste WiFi, folosindu-se de infrastructura pusă la dispoziție de framework-ul Embassy-rs.

Telecomanda se comportă ca un client ce caută să se conecteze la “server-ul” turretei, și să îi trimită constant comenzi, pe baza unui protocol simplu bazat pe transmisia de un byte, abstractizat mai jos prin enum-urie de Rust:

#[derive(Clone, Copy, Debug)]
pub enum Direction {
    Left = 108,
    Right = 114,
}
 
#[derive(Clone, Copy, Debug)]
pub enum Packet {
    Shoot,
    Move(Direction),
    None
}
 
impl Packet {
    pub fn to_u8(&self) -> u8 {
        match self {
            Packet::Shoot => 115,
            Packet::Move(m) => *m as u8,
            Packet::None => 110
        }
    }
}

User-ul va interacționa cu tureta prin intermediul joystick-ului, cu care îi va controla rotația turetei și a ecranului LCD 1602, controlat prin I2C ce oferă feedback legat de starea conexiunii la internet, și dintre telecomandă și turetă. Semnalele analogice de la joystick sunt interpretatea folosind perifericul de ADC.

// Configure I2C for LCD 1602
let sda = p.PIN_4;
let scl = p.PIN_5;
 
let mut i2c_conf = I2cConfig::default();
i2c_conf.frequency = 100_000;
let mut i2c = I2c::new_blocking(p.I2C0, scl, sda, i2c_conf);
 
// Setup LCD
let mut delayer = embassy_time::Delay;
let mut sender = lcd1602_driver::sender::I2cSender::new(&mut i2c, LCD_ADDRESS);
let lcd_config = lcd1602_driver::lcd::Config::default()
    .set_data_width(lcd1602_driver::command::DataWidth::Bit4);
let mut lcd = lcd1602_driver::lcd::Lcd::new(&mut sender, &mut delayer, lcd_config, 10);
lcd.set_cursor_blink_state(lcd1602_driver::command::State::On);
 
// Configure ADC and switch for Joystick
let mut adc = Adc::new(p.ADC, Irqs, AdcConfig::default());
let mut x = embassy_rp::adc::Channel::new_pin(p.PIN_26, embassy_rp::gpio::Pull::None);
let mut y = embassy_rp::adc::Channel::new_pin(p.PIN_27, embassy_rp::gpio::Pull::None);
let trigger = Input::new(p.PIN_28, embassy_rp::gpio::Pull::Up);
 
...
 
loop {
    let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
    socket.set_timeout(Some(Duration::from_secs(10)));
    defmt::println!("Configured TCP Socket!");
 
    // Configure IP address and Port
    let endpoint = (network_constants::TURRET_IP_ADDRESS, network_constants::PORT);
 
    if let Err(e) = socket.connect(endpoint).await {
        defmt::println!("accept error: {:?}", e);
        continue;
    }
 
    loop {
        // Read ADC
        let pos_x = adc.read(&mut x).await;
        let pos_y = adc.read(&mut y).await;
 
        let mut first_line: String<16> = String::new();
        let mut second_line: String<16> = String::new();
 
        let mut pack: Packet = Packet::None;
 
        if trigger.is_low() {
            pack = Packet::Shoot;
            defmt::println!("Shoot!");
 
            lcd.clean_display();
            lcd.write_str_to_pos("     Shoot!     ", (0, 0));
        } else {
            match (pos_x, pos_y) {
                (Err(_), _) | (_, Err(_)) => warn!("Conversion failed!"),
                (Ok(pos_x), Ok(pos_y)) => {
                    defmt::println!("x = {} y = {}", pos_x, pos_y);
                    core::write!(&mut first_line, "    x = {}    ", pos_x).unwrap();
                    core::write!(&mut second_line, "    y = {}    ", pos_y).unwrap();
 
                    pack = match Direction::from_samples(pos_x, pos_y) {
                        None => Packet::None,
                        Some(dir) => Packet::Move(dir)
                    };
 
                    lcd.clean_display();
                    lcd.write_str_to_pos(&first_line, (0, 0));
                    lcd.write_str_to_pos(&second_line, (0, 1));
                }
            }
        }
 
        match socket.write_all(&[pack.to_u8()]).await {
            Ok(_) => (),
            Err(e) => {
                defmt::println!("Package transmission error: {:?}", e);
                break;
            }
         }
 
        Timer::after_millis(500).await;
        if let Packet::Shoot = pack {
            break;
        }
    }
}

Tureta se va comporta ca server-ul TCP și, bazat pe același protocol rudimetar de comunicație, va primi comenzile și le va executa controlând semnalul PWM transmis servomotoarelor și motoarelor DC, în funcție de comanda în cauză:

// Configure PWM
let mut trigger_config = PwmConfig::default();
trigger_config.top = 25000 - 1;
trigger_config.divider = FixedU16::<U4>::from_num(100);
let mut pwm = Pwm::new_output_a(p.PWM_SLICE0, p.PIN_16, trigger_config.clone());
 
let mut rotation_config = PwmConfig::default();
rotation_config.top = 25000 - 1;
rotation_config.compare_a = 1875;
rotation_config.divider = FixedU16::<U4>::from_num(100);
let mut rotation = Pwm::new_output_a(p.PWM_SLICE3, p.PIN_22, rotation_config.clone());
 
let mut motor_config = PwmConfig::default();
motor_config.top = 100;
motor_config.compare_a = 0;
let mut motor1 = Pwm::new_output_a(p.PWM_SLICE1, p.PIN_18, motor_config.clone());
let _in2 = Output::new(p.PIN_19, Level::Low);
let mut motor2 = Pwm::new_output_a(p.PWM_SLICE2, p.PIN_20, motor_config.clone());
let _in4 = Output::new(p.PIN_21, Level::Low);
Timer::after_secs(1).await;
 
let mut rx_buffer = [0; 1024];
let mut tx_buffer = [0; 1024];
 
let mut socket = TcpSocket::new(stack, &mut rx_buffer, &mut tx_buffer);
socket.set_timeout(Some(Duration::from_secs(10)));
defmt::println!("Configured TCP Socket!");
 
while let Err(e) = socket.accept(network_constants::PORT).await {
    defmt::println!("Accept error {}", e);
}
 
let mut buf= [0];
 
loop {
    match socket.read(&mut buf).await {
       Ok(0) => {
           defmt::println!("read EOF");
           break;  
       },
       Ok(_n) => {
           let pack = Packet::from_u8(buf[0]);
 
           match pack {
                None | Some(Packet::None) => continue,
                Some(Packet::Shoot) => break,
                Some(Packet::Move(Direction::Right)) => {
                   if rotation_config.compare_a - 125 >= 625 {
                        rotation_config.compare_a -= 125;
                        rotation.set_config(&rotation_config);
                    }
                },
                Some(Packet::Move(Direction::Left)) => {
                    if rotation_config.compare_a + 125 <= 3125 {
                        rotation_config.compare_a += 125;
                        rotation.set_config(&rotation_config);
                    }
                }
            };
        },
        Err(e) => {
            core::panic!("Read error :(((\n{:?}", e);
        }
    };
}
 
motor_config.compare_a = 80;
motor1.set_config(&motor_config);
motor2.set_config(&motor_config);
 
for _ in 0..20 {
    trigger_config.compare_a = 1875;
    pwm.set_config(&trigger_config);
    Timer::after_millis(200).await;
    trigger_config.compare_a = 3125;
    pwm.set_config(&trigger_config);
    Timer::after_millis(200).await;
}
 
trigger_config.compare_a = 1875;
pwm.set_config(&trigger_config);
 
// Slow stop
motor_config.compare_a = 60;
motor1.set_config(&motor_config);
motor2.set_config(&motor_config);
Timer::after_millis(200).await;
 
motor_config.compare_a = 40;
motor1.set_config(&motor_config);
motor2.set_config(&motor_config);
Timer::after_millis(200).await;
 
motor_config.compare_a = 20;
motor1.set_config(&motor_config);
motor2.set_config(&motor_config);
Timer::after_millis(200).await;
 
motor_config.compare_a = 0;
motor1.set_config(&motor_config);
motor2.set_config(&motor_config);
Timer::after_millis(200).await;

Download

Arhiva zip poate fi descărcată aici.

Bibliografie/Resurse

pm/prj2024/iotelea/george_danut.aldea.txt · Last modified: 2024/05/27 21:15 by george_danut.aldea
CC Attribution-Share Alike 3.0 Unported
www.chimeric.de Valid CSS Driven by DokuWiki do yourself a favour and use a real browser - get firefox!! Recent changes RSS feed Valid XHTML 1.0