-
Notifications
You must be signed in to change notification settings - Fork 1
How to Add a Function
Let's say we want to add a function which sets a pin which is connected to a led. First, we will have to edit the src/server/func_man.rs file. In there is a function called init which looks like this:
pub fn init() -> Serial<USART2, (PA2<Alternate<AF7, Input<Floating>>>, PA3<Alternate<AF7, Input<Floating>>>)>{
...
let mut gpioa = dp.GPIOA.split(&mut ahb2);
...
let led1 = gpioa.pa5.into_push_pull_output(&mut gpioa.moder,&mut gpioa.otyper);
let led2 = gpioa.pa6.into_push_pull_output(&mut gpioa.moder,&mut gpioa.otyper);
let led3 = gpioa.pa7.into_push_pull_output(&mut gpioa.moder,&mut gpioa.otyper);
let led4 = gpioa.pa8.into_push_pull_output(&mut gpioa.moder,&mut gpioa.otyper);
// Replacing the Shared Peripheral
// Also change here to if you changed SharedPeripherals
cortex_m::interrupt::free(|cs|{
SHARED_PER.borrow(cs).replace(Some(
SharedPeripherals{led1,led2,led3,led4}
));
});
...
}With this hal crate we will write this line to add a push pull output pin object.
let led5 = gpioa.pa9.into_push_pull_output(&mut gpioa.moder,&mut gpioa.otyper);Now we should add this object to our SharedPeripherals struct like this. SharedPeripherals is also in "func_man.rs".
/// Change Here If An External Function Needs To Access Peripheral Data
pub struct SharedPeripherals{
pub led1:PA5<Output<PushPull>>,
pub led2:PA6<Output<PushPull>>,
pub led3:PA7<Output<PushPull>>,
pub led4:PA8<Output<PushPull>>,
pub led5:PA9<Output<PushPull>>, // new object added
} After this, we should change how we crate out SHARED_PER object. Which is the object we will use to share our peripherals. Please note that the fields here are public.
// Replacing the Shared Peripheral
// Also change here to if you changed SharedPeripherals
cortex_m::interrupt::free(|cs|{
SHARED_PER.borrow(cs).replace(Some( // added led5 here
SharedPeripherals{led1,led2,led3,led4,led5}
));
});This is how SHARED_PER variable is initialized in the cortex_m::interrupt::free function which takes a closure. In this closure, SHARED_PER is borrowed and replaced by the initial object which is an instance of the struct SharedPeripheral.
Now that we made our peripheral object accessible we should start writing our function in func_man.rs. Every function that is added should have this signature;
/// FuncId = "new_func"
pub fn func(args:&Vec::<u8>) -> Result<(),Error>{
...
}Here Error is the enum defined in pus/src/error.rs. All the return types should be in this format. Also if a shared variable is going to used cortex_m::interrupt::free function will have to be used to change the shared objects safely. This will be our function.
/// FuncId = "new_led"
pub fn new_led(args:&Vec::<u8>) -> Result<(),Error>{
if args.len() != 1 {
return Err(Error::InvalidArg);
}
cortex_m::interrupt::free(|cs| -> Result<(),Error> {
if args[0] != 0 {
SHARED_PER.borrow(cs).try_borrow_mut()?.as_mut()?.led5.set_high()?;
Ok(())
}
else {
SHARED_PER.borrow(cs).try_borrow_mut()?.as_mut()?.led5.set_low()?;
Ok(())
}
})
}As seen cortex_m::interrupt::free function is being used here. What it does is get a closure (lambda exp.) as input and executes it safely.
cortex_m::interrupt::free(|cs| -> Result<(),Error> {
...
})Most of our functions will have this form. Here cs a variable to show the scope of the critical section and it will be needed to borrow our shared variables. Some of the error types can be converted to error type defined in the pus crate so "?" can be used to propagate the error. Finally set_low/high is being called on our object to change the pin output. All the errors are propagated and if the function executes succesfully unit type Ok(()) should be returned. If we never checked the arg array the function would have the potential to panic. In case of a panic where the error is not propagated the entire service provider will crash.
/// FuncId = "new_led"
pub fn new_led(args:&Vec::<u8>) -> Result<(),Error>{
cortex_m::interrupt::free(|cs| -> Result<(),Error> {
if args[0] != 0 {
SHARED_PER.borrow(cs).try_borrow_mut()?.as_mut()?.led5.set_high()?;
Ok(())
}
else {
SHARED_PER.borrow(cs).try_borrow_mut()?.as_mut()?.led5.set_low()?;
Ok(())
}
})
}The final step is to add the function name and a function pointer to the function map. In server.rs there will be a function called handle packets. The function name and pointer should be added like this:
// Function reads the packet and parses it and sends the parsed packet.
pub fn handle_packets() -> ! {
/* FUNCTION MAP AREA START */
let funcs:HashMap<FuncId,fn(&Vec::<u8>)->Result<(),Error>> = pus::map!(
create_func_id("turn_led") => turn_led as fn(&Vec::<u8>)->Result<(),Error>,
create_func_id("set_led") => set_led as fn(&Vec::<u8>)->Result<(),Error>,
create_func_id("new_led") => new_led as fn(&Vec::<u8>)->Result<(),Error> // new line here
);
/* FUNCTION MAP AREA END */
...
}Here helper function "create_func_id("new_led")" function is used to create a function id from a string and new_led is casted to a function pointer type fn(&Vec::)->Result<(),Error>. These are the instructions to add a new function to the service. You can test is with the current client program. For example with this command "cargo run new_led 1" you can set the led but the client code currently only takes the function name and the arguments one by one as a byte array. With the current client code, the output will be like this.
selman@selman-G3-3590:~/Documents/Prust/client$ cargo run new_led 0
Finished dev [unoptimized + debuginfo] target(s) in 0.01s
Running `target/x86_64-unknown-linux-gnu/debug/client new_led 0`
Success
PrimaryHeader { ver_no: 0, type_flag: false, sec_header_flag: true, apid: 2, seq_flags: (true, true), packet_name: 0, data_len: 14 }
selman@selman-G3-3590:~/Documents/Prust/client$ cargo run new_led 1
Finished dev [unoptimized + debuginfo] target(s) in 0.03s
Running `target/x86_64-unknown-linux-gnu/debug/client new_led 1`
Success
PrimaryHeader { ver_no: 0, type_flag: false, sec_header_flag: true, apid: 2, seq_flags: (true, true), packet_name: 0, data_len: 14 }
The rules to add another function to the function map are below
- Add the function to src/server/func_man/functions.rs. Any description to the function can be added as a comment above the function with a "///" comment. Important Note: Main function name shouldn't start with pre word. For example
pub fn prefix()or pub fnpre_init()functions are not valid with this convention. Example:
/// Uses user1_1 from SHARED_PER global variable.
pub fn turn_led(turn: bool) -> Result<(), Error> {
cortex_m::interrupt::free(|cs| -> Result<(), Error> {
if turn {
SHARED_PER
.borrow(cs)
.try_borrow_mut()?
.as_mut()?
.user1_1
.set_high()
.unwrap();
Ok(())
} else {
SHARED_PER
.borrow(cs)
.try_borrow_mut()?
.as_mut()?
.user1_1
.set_low()
.unwrap();
Ok(())
}
})
}- Write a wrapper function that complies to the rust
pub fn pre_xxx(args:&Vec::<u8>) -> Result<(),Error>signature. Parsing and converting the arguments should be done here. For example:
/// Parses the arguments for turn_led
pub fn pre_turn_led(args: &Vec<u8>) -> Result<(), Error> {
if args.len() != 1 {
return Err(Error::InvalidArg);
} else {
turn_led(args[0] != 0)
}
}- Add the wrapper function you created to the funcs hashmap in src/server.rs file handle_packets function.
For example:
/* FUNCTION MAP AREA START */
let funcs: HashMap<FuncId, fn(&Vec<u8>) -> Result<(), Error>> = pus::map!(
create_func_id("turn_led") => pre_turn_led as fn(&Vec::<u8>)->Result<(),Error>,
create_func_id("set_led") => pre_set_led as fn(&Vec::<u8>)->Result<(),Error>
);
/* FUNCTION MAP AREA END */In the repository, there is a python script which lists all the functions it can be used as below:
$~/Documents/Prust$ python3 list_funcs.py
User functions defined are listed below:
Signature: fn turn_led(turn: bool) -> Result<(), Error>
Description: Uses user1_1 from SHARED_PER global variable.
Signature: fn set_led(led_no: u8, turn: bool) -> Result<(), Error>
Description: Uses user1_1, user1_2 from SHARED_PER global variable.