Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add queue example #460

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 91 additions & 0 deletions examples/queue.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/// FreeRTOS queue example
/// The core concepts covered in this example include:
/// - Creating and using queues to exchange data between an ISR(Interrupt Service Routine) and a thread (both directions).
/// - Efficiently waiting for messages in the thread using queues.
///
/// This is demonstrated with a periodic timer ISR and the rust main thread, but it can be used in any thread or ISR context.
use esp_idf_hal::delay::BLOCK;
use esp_idf_hal::peripherals::Peripherals;
use esp_idf_hal::task;
use esp_idf_hal::task::queue::Queue;
use esp_idf_hal::timer::{TimerConfig, TimerDriver};
use esp_idf_sys::{uxQueueMessagesWaiting, EspError};

fn main() -> Result<(), EspError> {
// It is necessary to call this function once. Otherwise some patches to the runtime
// implemented by esp-idf-sys might not link properly. See https://github.com/esp-rs/esp-idf-template/issues/71
esp_idf_hal::sys::link_patches();

let per = Peripherals::take()?;

let mut timer = TimerDriver::new(per.timer00, &TimerConfig::new().auto_reload(true))?;

// create two queue's that can hold 20/100 elements with the element size of an u8
let queue_send = Queue::<u8>::new(20);
let queue_recv = Queue::<u8>::new(100);

// SAFETY: as long as queue_send/recv live we can use isr_receive & isr_send.
let isr_recv: Queue<u8> = unsafe { Queue::new_borrowed(queue_send.as_raw()) };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit - as per my comment in the main PR thread, this is just not necessary. Just borrowing the queues with e.g. &queue_send and &queue_recv is good enough.

let isr_send: Queue<u8> = unsafe { Queue::new_borrowed(queue_recv.as_raw()) };

// Every half a second
timer.set_alarm(timer.tick_hz() / 2)?;

// SAFTEY: make sure the timer is droped and thouse end the subscription before we drop queue_send/recv.
// E,g don't send stuff/recv stuff from a borrowd handle after that point. When the timer object is droped it will auto unsubscribe.
//
// Safe since we never end main and thouse never unsubscribe nor drop queue_send/queue_recv
unsafe {
timer.subscribe(move || {
Copy link
Collaborator

@ivmarkov ivmarkov Aug 5, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since & is copy (unlike &mut) you can just push the references into subscribe.

  • If indeed subscribe is called, then & need to be transmuted to &'static OR cast to *const Queue<u8> (same outcome)
  • If you use subscribe_nonstatic here, none of the above ^^^ would be necessary, as *_nonstatic can use nonstatic borrows (but yes, don't core::mem::forget it then, but your example is even now un-"forgettable" because new_borrowed is just a hyper-complex way to type & and then unsafely transmute the & lifetime to whatever you want)
  • Finally, if you just Arc the queues (or even better, put then in a 'static context with static_cell) also none of the above would be necessary and you can keep the usage of subscribe as opposed to switching to subscribe_nonstatic.

// timeout value is ignored in ISR context by the API, a ISR cannot ever be allowed to block.
if let Some((value, higher_isr_awoken)) = isr_recv.recv_front(BLOCK) {
for i in 0..5 {
let send_back = value.wrapping_add(i);
// returns a error when queue is full
if isr_send.send_back(send_back, BLOCK).is_err() {
break;
}
}

// good practice and benifical for system performance
if higher_isr_awoken {
task::do_yield();
}
}
})?;
}

println!("Start Timer!");
timer.enable_interrupt()?;
timer.enable_alarm(true)?;
timer.enable(true)?;

let mut val = 42;
loop {
println!("Sending {val}");
if queue_send.send_back(val, BLOCK).is_err() {
println!("Qeueu is full, could not send next value to ISR");
}

// Waiting from the ISR to send us stuff back.
//
// We will go into sleep and get automatically awoken by the scheduler
// as soon as something was added to the queue.
match queue_recv.recv_front(BLOCK) {
Some((value, _)) => {
println!("Got {value} from ISR!");
}
None => println!("Timeout while waiting for a new message"),
}

// we can now check if there are still more items in the queue and read the rest.
let unread_item_count = unsafe { uxQueueMessagesWaiting(queue_recv.as_raw()) };
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we expose this as a safe method?

for _ in 0..unread_item_count {
// we read exactly the number of unread items so we can unwrap and we know its not None
let (val, _) = queue_recv.recv_front(BLOCK).unwrap();
println!("Got additional {val} from ISR!");
}

val = val.wrapping_add(1);
}
}
Loading