Created
October 21, 2024 04:00
-
-
Save dcoles/4929ba2250695d46e076474e1533018b to your computer and use it in GitHub Desktop.
Flipper Zero serial reader (work in progress)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#![no_main] | |
#![no_std] | |
// Required for panic handler | |
extern crate flipperzero_rt; | |
use core::{ffi::{c_void, CStr}, ops::{Deref, DerefMut}, ptr}; | |
use flipperzero::{furi::{self, stream_buffer::FuriStreamBuffer, thread::{self, ThreadId}, time::Duration}, info, log, println}; | |
use flipperzero_rt::{entry, manifest}; | |
// Required for log macros | |
use flipperzero::__macro_support::ufmt; | |
use flipperzero_sys as sys; | |
// Define the FAP Manifest for this application | |
manifest!( | |
name = "Try UART From Rust", | |
app_version = 1, | |
has_icon = false, | |
); | |
// Define the entry function | |
entry!(entry); | |
// Entry point | |
fn entry(_args: Option<&CStr>) -> i32 { | |
unsafe { main() } | |
0 | |
} | |
#[derive(Debug, Clone, Copy, PartialEq, Eq)] | |
struct WorkerEvent(u32); | |
impl WorkerEvent { | |
pub const FLAG_RESERVED: u32 = (1 << 0); | |
pub const FLAG_STOP: u32 = (1 << 1); | |
pub const FLAG_DATA: u32 = (1 << 2); | |
pub const FLAG_IDLE: u32 = (1 << 3); | |
pub const FLAG_OVERRUN_ERROR: u32 = (1 << 4); | |
pub const FLAG_FRAMING_ERROR: u32 = (1 << 5); | |
pub const FLAG_NOISE_ERROR: u32 = (1 << 6); | |
pub const MASK: u32 = Self::FLAG_STOP | Self::FLAG_DATA | Self::FLAG_IDLE | Self::FLAG_OVERRUN_ERROR | Self::FLAG_FRAMING_ERROR | Self::FLAG_NOISE_ERROR; | |
pub fn is_stop(self) -> bool { | |
self.0 & Self::FLAG_STOP != 0 | |
} | |
pub fn is_rx_data(self) -> bool { | |
self.0 & Self::FLAG_DATA != 0 | |
} | |
pub fn is_rx_idle(self) -> bool { | |
self.0 & Self::FLAG_IDLE != 0 | |
} | |
pub fn is_error(self) -> bool { | |
self.0 & (Self::FLAG_OVERRUN_ERROR | Self::FLAG_FRAMING_ERROR | Self::FLAG_NOISE_ERROR) != 0 | |
} | |
pub fn is_overrun_error(self) -> bool { | |
self.0 & Self::FLAG_OVERRUN_ERROR != 0 | |
} | |
pub fn is_framing_error(self) -> bool { | |
self.0 & Self::FLAG_FRAMING_ERROR != 0 | |
} | |
pub fn is_noise_error(self) -> bool { | |
self.0 & Self::FLAG_NOISE_ERROR != 0 | |
} | |
} | |
pub struct AsyncSerialReceiver<F> | |
where F: FnMut(&[u8]) | |
{ | |
handle: *mut sys::FuriHalSerialHandle, | |
context: FuriBox<Context<F>>, | |
} | |
struct Context<F: FnMut(&[u8])> { | |
rx_stream: FuriStreamBuffer, | |
on_rx: F, | |
worker_thread: *mut sys::FuriThread, | |
} | |
#[derive(PartialEq, Eq)] | |
pub struct FuriBox<T>(*mut T); | |
impl<T> FuriBox<T> { | |
pub fn new(value: T) -> Self { | |
let ptr; | |
unsafe { | |
ptr = sys::aligned_malloc(size_of::<T>(), align_of::<T>()) as *mut T; | |
assert!(!ptr.is_null()); | |
ptr.write(value); | |
}; | |
FuriBox(ptr) | |
} | |
pub fn as_ptr(boxed: &FuriBox<T>) -> *mut T { | |
boxed.0 | |
} | |
} | |
impl<T> Drop for FuriBox<T> { | |
fn drop(&mut self) { | |
let ptr = self.0; | |
unsafe { sys::aligned_free(ptr as _); } | |
} | |
} | |
impl<T> AsRef<T> for FuriBox<T> { | |
fn as_ref(&self) -> &T { | |
unsafe { & *self.0 } | |
} | |
} | |
impl<T> AsMut<T> for FuriBox<T> { | |
fn as_mut(&mut self) -> &mut T { | |
unsafe { &mut *self.0 } | |
} | |
} | |
impl<T> Deref for FuriBox<T> { | |
type Target = T; | |
fn deref(&self) -> &Self::Target { | |
unsafe { & *self.0 } | |
} | |
} | |
impl<T> DerefMut for FuriBox<T> { | |
fn deref_mut(&mut self) -> &mut Self::Target { | |
unsafe { &mut *self.0 } | |
} | |
} | |
impl<F> AsyncSerialReceiver<F> | |
where F: FnMut(&[u8]) | |
{ | |
pub fn new(serial_handle: *mut sys::FuriHalSerialHandle, on_rx: F) -> Self { | |
let rx_stream = FuriStreamBuffer::new(2048, 1); | |
let mut context = FuriBox::new(Context { | |
rx_stream, | |
on_rx, | |
worker_thread: ptr::null_mut(), | |
}); | |
context.worker_thread = unsafe { sys::furi_thread_alloc_ex(c"AsyncSerialReceiverWorker".as_ptr(), 1024, Some(async_serial_receiver_worker::<F>), FuriBox::as_ptr(&context).cast()) }; | |
unsafe { sys::furi_thread_start(context.worker_thread) }; | |
AsyncSerialReceiver { | |
handle: serial_handle, | |
context, | |
} | |
} | |
pub fn start(&mut self) { | |
unsafe { | |
sys::furi_hal_serial_async_rx_start(self.handle, Some(async_serial_receiver_rx_callback::<F>), FuriBox::as_ptr(&self.context).cast(), true); | |
} | |
} | |
pub fn stop(&mut self) { | |
unsafe { | |
sys::furi_hal_serial_async_rx_stop(self.handle); | |
} | |
} | |
} | |
impl<F: FnMut(&[u8])> Drop for AsyncSerialReceiver<F> { | |
fn drop(&mut self) { | |
if !self.context.worker_thread.is_null() { | |
let thread_id = unsafe { thread::ThreadId::from_furi_thread(self.context.worker_thread) }; | |
furi::thread::set_flags(thread_id, WorkerEvent::FLAG_STOP).unwrap(); | |
unsafe { | |
sys::furi_thread_join(self.context.worker_thread); | |
sys::furi_thread_free(self.context.worker_thread); | |
} | |
self.context.worker_thread = ptr::null_mut(); | |
} | |
} | |
} | |
unsafe extern "C" fn async_serial_receiver_rx_callback<F: FnMut(&[u8])>(handle: *mut sys::FuriHalSerialHandle, event: sys::FuriHalSerialRxEvent, context: *mut c_void) { | |
let context = &mut *context.cast::<Context<F>>(); | |
let mut flags = 0u32; | |
if event & sys::FuriHalSerialRxEvent_FuriHalSerialRxEventData != 0 { | |
let data = [ sys::furi_hal_serial_async_rx(handle) ]; | |
(*context).rx_stream.send(&data, Duration::ZERO); | |
flags |= WorkerEvent::FLAG_DATA; | |
} | |
if event & sys::FuriHalSerialRxEvent_FuriHalSerialRxEventIdle != 0 { | |
flags |= WorkerEvent::FLAG_IDLE; | |
} | |
if event & sys::FuriHalSerialRxEvent_FuriHalSerialRxEventNoiseError != 0 { | |
flags |= WorkerEvent::FLAG_FRAMING_ERROR; | |
} | |
if event & sys::FuriHalSerialRxEvent_FuriHalSerialRxEventNoiseError != 0 { | |
flags |= WorkerEvent::FLAG_FRAMING_ERROR; | |
} | |
if event & sys::FuriHalSerialRxEvent_FuriHalSerialRxEventNoiseError != 0 { | |
flags |= WorkerEvent::FLAG_FRAMING_ERROR; | |
} | |
let thread_id = unsafe { ThreadId::from_furi_thread((*context).worker_thread) }; | |
furi::thread::set_flags(thread_id, flags).unwrap(); | |
} | |
const SERIAL_WORKER_BUFFER_LEN: usize = 64; | |
unsafe extern "C" fn async_serial_receiver_worker<F: FnMut(&[u8])>(context: *mut c_void) -> i32 { | |
info!("Starting async worker"); | |
assert!(!context.is_null()); | |
let context: &mut Context<F> = &mut *context.cast(); | |
loop { | |
let events = WorkerEvent(furi::thread::wait_any_flags(WorkerEvent::MASK, true, Duration::MAX).unwrap_or(0)); | |
info!("WorkerEvent: {}", events.0); | |
if events.is_stop() { | |
break; | |
} | |
if events.is_rx_data() { | |
loop { | |
let mut data = [0u8; SERIAL_WORKER_BUFFER_LEN]; | |
let len = context.rx_stream.receive(&mut data, Duration::ZERO); | |
if len == 0 { | |
break; | |
} | |
(context.on_rx)(&data[..len]) | |
} | |
} | |
if events.is_rx_idle() { | |
} | |
if events.is_error() { | |
if events.is_overrun_error() { | |
} | |
if events.is_framing_error() { | |
} | |
if events.is_noise_error() { | |
} | |
} | |
} | |
0 | |
} | |
unsafe fn main() { | |
let serial_handle = | |
sys::furi_hal_serial_control_acquire(sys::FuriHalSerialId_FuriHalSerialIdLpuart); | |
assert!(!serial_handle.is_null()); | |
sys::furi_hal_serial_init(serial_handle, 9600); | |
let mut buffer = furi::string::FuriString::new(); | |
let mut serial = AsyncSerialReceiver::new(serial_handle, |data| { | |
let ch = data[0] as char; | |
println!("{:?}", data); | |
if ch == '\r' || ch == '\n' { | |
println!("> {}", buffer); | |
buffer.clear(); | |
} else { | |
buffer.push(ch); | |
} | |
}); | |
serial.start(); | |
info!("And now we run"); | |
sys::furi_delay_ms(10_000); | |
serial.stop(); | |
sys::furi_hal_serial_deinit(serial_handle); | |
sys::furi_hal_serial_control_release(serial_handle); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment