haptic_skin_firmware/
main.rs1#![no_std]
2#![no_main]
3
4use defmt::*;
5use embassy_executor::Spawner;
6use embassy_futures::join::join;
7use embassy_futures::select::{select, Either};
8use embassy_rp::bind_interrupts;
9use embassy_rp::gpio::{Level, Output};
10use embassy_rp::peripherals::USB;
11use embassy_rp::pwm::{Config as PwmConfig, Pwm};
12use embassy_rp::usb::{Driver, InterruptHandler};
13use embassy_time::{Duration, Timer};
14use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
15use embassy_usb::{Builder, Config};
16use {defmt_rtt as _, panic_probe as _};
17
18mod config;
19mod drivers;
20mod proto;
21mod tasks;
22
23use config::{
24 MOTOR_COUNT, MOTOR_MAX_DUTY, PWM_TOP, STATUS_LED_PIN_DESC, USB_MANUFACTURER, USB_PID,
25 USB_PRODUCT, USB_VID,
26};
27use drivers::motors::Motors;
28use drivers::status_led::{self, Status};
29use proto::{Command, Event, FrameParser};
30use tasks::power_guard;
31
32bind_interrupts!(struct Irqs {
33 USBCTRL_IRQ => InterruptHandler<USB>;
34});
35
36fn pwm_cfg() -> PwmConfig {
38 let mut c = PwmConfig::default();
39 c.top = PWM_TOP; c.enable = true; c
42}
43
44async fn self_test(motors: &mut Motors<'_>) {
47 info!("self-test : balayage des {} moteurs", MOTOR_COUNT);
48 for m in 0..MOTOR_COUNT {
49 for duty in (0..=MOTOR_MAX_DUTY).step_by(5) {
50 motors.set(m, duty);
51 Timer::after(Duration::from_millis(4)).await;
52 }
53 motors.set(m, 0);
54 Timer::after(Duration::from_millis(80)).await;
55 }
56}
57
58#[embassy_executor::main]
59async fn main(spawner: Spawner) {
60 let p = embassy_rp::init(Default::default());
61 info!("HAPTIC.SKIN — boot, {} moteurs PWM @ ~20 kHz", MOTOR_COUNT);
62
63 info!("LED de statut sur {}", STATUS_LED_PIN_DESC);
66 let status_led = Output::new(p.PIN_16, Level::Low);
67 spawner.must_spawn(status_led::run(status_led));
68 status_led::set(Status::Boot); let mut motors = Motors::new([
75 Pwm::new_output_a(p.PWM_SLICE0, p.PIN_0, pwm_cfg()),
76 Pwm::new_output_a(p.PWM_SLICE1, p.PIN_2, pwm_cfg()),
77 Pwm::new_output_a(p.PWM_SLICE2, p.PIN_4, pwm_cfg()),
78 Pwm::new_output_a(p.PWM_SLICE3, p.PIN_6, pwm_cfg()),
79 Pwm::new_output_a(p.PWM_SLICE4, p.PIN_8, pwm_cfg()),
80 Pwm::new_output_a(p.PWM_SLICE5, p.PIN_10, pwm_cfg()),
81 Pwm::new_output_a(p.PWM_SLICE6, p.PIN_12, pwm_cfg()),
82 Pwm::new_output_a(p.PWM_SLICE7, p.PIN_14, pwm_cfg()),
83 ]);
84
85 self_test(&mut motors).await;
87 status_led::set(Status::Idle); let driver = Driver::new(p.USB, Irqs);
91
92 let mut usb_config = Config::new(USB_VID, USB_PID);
93 usb_config.manufacturer = Some(USB_MANUFACTURER);
94 usb_config.product = Some(USB_PRODUCT);
95 usb_config.serial_number = Some("0001");
96 usb_config.max_power = 250; usb_config.max_packet_size_0 = 64;
98
99 let mut config_descriptor = [0u8; 256];
102 let mut bos_descriptor = [0u8; 16];
103 let mut msos_descriptor: [u8; 0] = [];
104 let mut control_buf = [0u8; 64];
105 let mut state = State::new();
106
107 let mut builder = Builder::new(
108 driver,
109 usb_config,
110 &mut config_descriptor,
111 &mut bos_descriptor,
112 &mut msos_descriptor,
113 &mut control_buf,
114 );
115
116 let mut class = CdcAcmClass::new(&mut builder, &mut state, 64);
117 let mut usb = builder.build();
118
119 let usb_fut = usb.run();
121
122 const COMMAND_TIMEOUT: Duration = Duration::from_millis(1500);
128 const WRITE_TIMEOUT: Duration = Duration::from_millis(200);
129
130 let comms_fut = async {
131 let mut parser = FrameParser::new();
132 let mut buf = [0u8; 64];
133 let mut requested = [0u8; MOTOR_COUNT];
138 loop {
139 class.wait_connection().await;
140 info!("hôte connecté");
141 status_led::set(Status::Connected);
142 loop {
143 match select(class.read_packet(&mut buf), Timer::after(COMMAND_TIMEOUT)).await {
144 Either::First(Ok(n)) => {
145 for &byte in &buf[..n] {
146 match parser.push(byte) {
147 Ok(Some(cmd)) => match cmd {
148 Command::Ping => {
149 let mut out = [0u8; 4];
150 let len = proto::encode_event(Event::Pong, &[], &mut out);
151 let _ = select(
152 class.write_packet(&out[..len]),
153 Timer::after(WRITE_TIMEOUT),
154 )
155 .await;
156 }
157 Command::SetMotor { idx, intensity } => {
158 if (idx as usize) < MOTOR_COUNT {
159 requested[idx as usize] = intensity;
160 let mut guarded = requested;
161 power_guard::apply(&mut guarded);
162 motors.set_all(&guarded);
163 }
164 }
165 Command::SetAll(vals) => {
166 requested = vals;
167 let mut guarded = requested;
168 power_guard::apply(&mut guarded);
169 motors.set_all(&guarded);
170 }
171 Command::StopAll => {
172 requested = [0u8; MOTOR_COUNT];
173 motors.stop_all();
174 }
175 },
176 Ok(None) => {}
177 Err(e) => {
178 warn!("trame invalide : {:?}", e);
179 status_led::set(Status::Error);
180 let mut out = [0u8; 5];
182 let len = proto::encode_event(Event::Error, &[e.code()], &mut out);
183 let _ = select(
184 class.write_packet(&out[..len]),
185 Timer::after(WRITE_TIMEOUT),
186 )
187 .await;
188 }
189 }
190 }
191 }
192 Either::First(Err(_)) => break, Either::Second(_) => {
194 requested = [0u8; MOTOR_COUNT];
196 motors.stop_all();
197 }
198 }
199 }
200 info!("hôte déconnecté → arrêt moteurs");
201 requested = [0u8; MOTOR_COUNT];
202 motors.stop_all();
203 status_led::set(Status::Idle);
204 }
205 };
206
207 join(usb_fut, comms_fut).await;
208}