Skip to main content

stm_32/peripherals/
sbus.rs

1// ******************************************************************************
2// * File     : platforms/stm_32/src/peripherals/sbus.rs
3// * Date     : June 28, 2026
4// ******************************************************************************
5// *
6// * Copyright (c) 2023, AeroVironment, Inc.
7// * All rights reserved.
8// *
9// * Redistribution and use in source and binary forms, with or without
10// * modification, are permitted provided that the following conditions are met:
11// *
12// * 1.Redistributions of source code must retain the above copyright notice, this
13// * list of conditions and the following disclaimer.
14// *
15// * 2.Redistributions in binary form must reproduce the above copyright notice,
16// * this list of conditions and the following disclaimer in the documentation
17// * and/or other materials provided with the distribution.
18// *
19// * 3.Neither the name of the copyright holder nor the names of its
20// * contributors may be used to endorse or promote products derived from
21// * this software without specific prior written permission.
22// *
23// * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
24// * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
25// * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
26// * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
27// * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
28// * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
29// * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
30// * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31// * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32// * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33// *
34// ******************************************************************************
35
36use core::sync::atomic::{AtomicU32, Ordering};
37
38use embassy_stm32::mode::Async;
39use embassy_stm32::usart::UartRx;
40use embassy_sync::blocking_mutex::raw::CriticalSectionRawMutex;
41use embassy_sync::channel::Channel;
42use embassy_time::Duration;
43use embassy_time::Instant;
44
45use veloxity_core::errors;
46use veloxity_core::packets;
47
48const RC_QUEUE_CAPACITY: usize = 8;
49
50pub static RC_CHANNEL: Channel<
51    CriticalSectionRawMutex,
52    Result<packets::RcPacket, errors::SensorError>,
53    RC_QUEUE_CAPACITY,
54> = Channel::new();
55
56async fn publish_rc(result: Result<packets::RcPacket, errors::SensorError>) {
57    #[cfg(feature = "runtime-diagnostics")]
58    let error = result.is_err();
59    #[cfg(feature = "runtime-diagnostics")]
60    let wait_started = RC_CHANNEL.is_full().then(Instant::now);
61    RC_CHANNEL.send(result).await;
62    #[cfg(feature = "runtime-diagnostics")]
63    crate::runtime_diagnostics::record_rc_queue_publish(
64        error,
65        wait_started.map(|started| started.elapsed().as_micros().min(u32::MAX as u64) as u32),
66        RC_CHANNEL.len(),
67    );
68}
69
70pub const SBUS_11_BIT_CHANNELS: usize = 16;
71pub const SBUS_BINARY_CHANNELS: usize = 2; // does not include status information
72pub const SBUS_CHANNELS: usize = SBUS_11_BIT_CHANNELS + SBUS_BINARY_CHANNELS;
73
74static SBUS_READ_OK: AtomicU32 = AtomicU32::new(0);
75static SBUS_READ_ERR: AtomicU32 = AtomicU32::new(0);
76static SBUS_LAST_READ_SIZE: AtomicU32 = AtomicU32::new(0);
77static SBUS_SIZE_25: AtomicU32 = AtomicU32::new(0);
78static SBUS_VALID_FRAME: AtomicU32 = AtomicU32::new(0);
79static SBUS_BAD_HEADER: AtomicU32 = AtomicU32::new(0);
80static SBUS_BAD_FOOTER: AtomicU32 = AtomicU32::new(0);
81static SBUS_SIGNAL: AtomicU32 = AtomicU32::new(0);
82static SBUS_TIMEOUT: AtomicU32 = AtomicU32::new(0);
83static SBUS_LAST_STATUS: AtomicU32 = AtomicU32::new(0);
84
85#[derive(Clone, Copy, Debug, Default)]
86pub struct SbusDiagnostics {
87    pub read_ok: u32,
88    pub read_err: u32,
89    pub last_read_size: u32,
90    pub size_25: u32,
91    pub valid_frame: u32,
92    pub bad_header: u32,
93    pub bad_footer: u32,
94    pub signal: u32,
95    pub timeout: u32,
96    pub last_status: u32,
97}
98
99pub fn diagnostics() -> SbusDiagnostics {
100    SbusDiagnostics {
101        read_ok: SBUS_READ_OK.load(Ordering::Relaxed),
102        read_err: SBUS_READ_ERR.load(Ordering::Relaxed),
103        last_read_size: SBUS_LAST_READ_SIZE.load(Ordering::Relaxed),
104        size_25: SBUS_SIZE_25.load(Ordering::Relaxed),
105        valid_frame: SBUS_VALID_FRAME.load(Ordering::Relaxed),
106        bad_header: SBUS_BAD_HEADER.load(Ordering::Relaxed),
107        bad_footer: SBUS_BAD_FOOTER.load(Ordering::Relaxed),
108        signal: SBUS_SIGNAL.load(Ordering::Relaxed),
109        timeout: SBUS_TIMEOUT.load(Ordering::Relaxed),
110        last_status: SBUS_LAST_STATUS.load(Ordering::Relaxed),
111    }
112}
113
114pub struct SbusRC {
115    pub uart: UartRx<'static, Async>,
116}
117
118// Brute force extract of 11 bits
119pub fn extract_chan(bytes: &[u8], bit_offset: usize) -> f32 {
120    // Calculate the start byte and the bit position within that byte
121    let byte_offset = bit_offset / 8;
122    let bit_start = bit_offset % 8;
123
124    let mut value: u16 = 0;
125    for i in 0..11 {
126        let byte_index = byte_offset + (bit_start + i) / 8;
127        let bit_index = (bit_start + i) % 8;
128        let bit = (bytes[byte_index] >> bit_index) & 1;
129        value |= (bit as u16) << i;
130    }
131
132    value as f32
133}
134
135impl SbusRC {
136    pub async fn run(&mut self) {
137        let mut buffer = [0u8; 25];
138
139        let mut chan = [0f32; SBUS_CHANNELS];
140        let mut timeout = Instant::now() + Duration::from_secs(1);
141        let mut rc_chan = [0.0f32; packets::RC_PACKET_CHANNELS];
142        loop {
143            // Read a packet
144            let result = self.uart.read_until_idle(&mut buffer).await;
145            if let Ok(size) = result {
146                SBUS_READ_OK.fetch_add(1, Ordering::Relaxed);
147                SBUS_LAST_READ_SIZE.store(size as u32, Ordering::Relaxed);
148                if size == buffer.len() {
149                    SBUS_SIZE_25.fetch_add(1, Ordering::Relaxed);
150                }
151                timeout = Instant::now() + Duration::from_secs(1);
152                let valid_header = buffer[0] == 0x0F;
153                let valid_footer = (buffer[24] == 0x00)
154                    || (buffer[24] == 0x04)
155                    || (buffer[24] == 0x14)
156                    || (buffer[24] == 0x24)
157                    || (buffer[24] == 0x34);
158                if valid_header && valid_footer {
159                    SBUS_VALID_FRAME.fetch_add(1, Ordering::Relaxed);
160                    let dig = buffer[23] as u16;
161                    SBUS_LAST_STATUS.store(dig as u32, Ordering::Relaxed);
162
163                    // get 16 servo (11-bit) channels
164                    for i in 0..SBUS_11_BIT_CHANNELS {
165                        chan[i] = (extract_chan(&buffer, 8 + i * 11) - 172.0) / 1639.0;
166                    }
167                    // get the two binary channels
168                    chan[0 + SBUS_11_BIT_CHANNELS] = ((((dig) & 0x01) as f32) - 172.0) / 1639.0; // rosflight weird scaling
169                    chan[1 + SBUS_11_BIT_CHANNELS] =
170                        ((((dig >> 1) & 0x01) as f32) - 172.0) / 1639.0; // rosflight weird scaling
171
172                    let header = packets::RosflightPacketHeader {
173                        timestamp: Instant::now().as_micros(),
174                        status: dig,
175                    };
176
177                    rc_chan = [0.0f32; packets::RC_PACKET_CHANNELS];
178
179                    let mut len = SBUS_CHANNELS;
180
181                    if SBUS_CHANNELS > packets::RC_PACKET_CHANNELS {
182                        len = packets::RC_PACKET_CHANNELS;
183                    }
184
185                    for i in 0..len {
186                        rc_chan[i] = chan[i];
187                    }
188
189                    let rc_packet = packets::RcPacket {
190                        header,
191                        n_chan: 24,
192                        chan: rc_chan,
193                        lol: (dig & 0x0C) != 0, // either bit 2 or 3 will signal a loss of link.
194                    };
195                    SBUS_SIGNAL.fetch_add(1, Ordering::Relaxed);
196                    publish_rc(Ok(rc_packet)).await;
197                } else {
198                    if !valid_header {
199                        SBUS_BAD_HEADER.fetch_add(1, Ordering::Relaxed);
200                    }
201                    if valid_header && !valid_footer {
202                        SBUS_BAD_FOOTER.fetch_add(1, Ordering::Relaxed);
203                    }
204                }
205            } else {
206                SBUS_READ_ERR.fetch_add(1, Ordering::Relaxed);
207            }
208
209            if Instant::now() > timeout {
210                timeout = Instant::now() + Duration::from_secs(1);
211                let dig = 0x1C; // set bitfield for timeout
212                SBUS_TIMEOUT.fetch_add(1, Ordering::Relaxed);
213                SBUS_LAST_STATUS.store(dig as u32, Ordering::Relaxed);
214                let header = packets::RosflightPacketHeader {
215                    timestamp: Instant::now().as_micros(),
216                    status: dig,
217                };
218                let rc_packet = packets::RcPacket {
219                    header,
220                    n_chan: 24,
221                    chan: rc_chan,          // last known good values
222                    lol: (dig & 0x1C) != 0, // signal a loss of link bits
223                };
224                SBUS_SIGNAL.fetch_add(1, Ordering::Relaxed);
225                publish_rc(Ok(rc_packet)).await;
226            }
227        }
228    }
229}
230
231#[embassy_executor::task]
232pub async fn task(mut sbus: SbusRC) {
233    sbus.run().await;
234}