Skip to content
GitLab
Explore
Sign in
Register
Primary navigation
Search or go to…
Project
R
rtfm-app
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Model registry
Operate
Environments
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
Per Lindgren
rtfm-app
Commits
99e5cfef
Commit
99e5cfef
authored
7 years ago
by
Per
Browse files
Options
Downloads
Patches
Plain Diff
loopback commented
parent
421a0557
No related branches found
No related tags found
No related merge requests found
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
examples/loopback.rs
+249
-28
249 additions, 28 deletions
examples/loopback.rs
with
249 additions
and
28 deletions
examples/loopback.rs
+
249
−
28
View file @
99e5cfef
...
...
@@ -115,7 +115,7 @@ fn usart2_handler(_t: &mut Threshold, r: USART2::Resources) {
match
r
.SEND
.dequeue
()
{
Some
(
b
)
=>
{
// we (still) have something in the queue to send
let
_
=
serial
.write
(
b
);
let
_
=
serial
.write
(
b
);
// will clear Txe bit
}
_
=>
{
// the que was empty so the last item was already sent
...
...
@@ -152,12 +152,12 @@ fn command(
_
=>
{}
}
//
here we assume that
//
re-enable the USART2 Txe to trigger the USART2 handler
USART2
.claim
(
t
,
|
usart
,
_
|
{
Serial
(
&**
usart
)
.listen
(
Event
::
Txe
);
});
// trigger the usart to send queued data
rtfm
::
set_pending
(
f4
::
stm32f40x
::
Interrupt
::
USART2
);
//
rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
}
fn
error_receive_buffer_full
()
{
...
...
@@ -172,30 +172,251 @@ fn rxne_not_set() {
ip!
(
"-"
);
}
#[derive(Debug)]
enum
Command
{
Start
,
Stop
,
Freq
(
u32
),
}
// loopback example
// NOTICE, this example is made to show that a seemingly trivial
// function, like an interrutp driven loopback faces you with
// non-trivial concurrency/real-time challenges
//
// the setup is the following
// SEND/RECEIVE buffers are circular, each holding both
// producer index and a consumer index
//
// only when a full frame (ending with a Carrige return or Newline)
// has been received, the command handler is notified (from the USART)
// interrupt handler.
//
// to stress the problems the RECEIVE buffer is larger than the
// SEND buffer, hence we can run in the problem that
// receiving (at full speed), and trying to loop that back
// will overflow the SEND buffer
//
// initially we enqueque "abc", and trigger the USART2 interrupt
//
// let _ = r.SEND.enqueue('a' as u8);
// let _ = r.SEND.enqueue('b' as u8);
// let _ = r.SEND.enqueue('c' as u8);
// rtfm::set_pending(f4::stm32f40x::Interrupt::USART2); // trigger USART2 task
//
// the hardware provides only a single point entry for a USART2 interrupt
// initially enabled by
//
// serial.listen(Event::Rxne); // data received interrupt
// serial.listen(Event::Txe); // data transmitted interrupt
//
// in the USART2 handler we have to determine by reading the RXNE if data has arrived
//
// if sr.rxne().bit_is_set()
//
// in this case not, so we check if we have data to send
//
// match r.SEND.dequeue()
//
// in this case we have (the 'a' of "abc", so a write will occur)
//
// serial.write(b); // will clear Txe bit
//
//
// the interrupt handler will exit (with the Txe bit cleared),
// and the processor will be put to sleep (in idle)
//
// when the 'a' has been transmitted, we get another USART2 interrupt (Txe bit set)
// the above procedure is repeated, and we will write a 'b'
//
// again we get an interrupt and this time write the 'c'
//
// and when 'c' has been written we get a new USART2 interrupt (Txe bit set).
//
// here we have the case of the SEND buffer being empty and match
//
// serial.unlisten(Event::Txe);
//
// this is needed in order to avoid that the USART2 interrupt handler is
// re-entered. (Still the Txe bit will be Set at this point)
// the only way to clear the Txe is by writing and we have nothing to write
// so the only option here is to "unlisten" to it....
// already here we see that we need to fully understand the HARDWARE in order
// to implement correct software, so Read the Manual
//
// you should now see "abc" in the moserial console
// in the swo trace you should have something like
//
// [2018-02-23T10:28:30.735Z] init
// [2018-02-23T10:28:30.735Z] --
//
// the two (2) dashes `-` are due to the triggering of the EXTI4
// why not three (3), we have triggered it three (3), times when writing
// `a`, `b` and `c`. Well the hardware has a single buffer for each interrupt
// so if the interrupt was already pended (but not yet executed)
// the events are *coalesced* (if we need to handle an event without coalescing
// we need to implement this by a sofware overlay/buffer)
//
// and this was the easy part....
//
// now lets give an input from `moserial`. Set it to generate CR end.
// enter `1234567<return>`
//
// in the trace output you should get something like...
//
// [2018-02-23T10:41:54.644Z] command
// [2018-02-23T10:41:54.645Z] SEND BufferFullError
// [2018-02-23T10:41:54.646Z] error_receive_buffer_full
// [2018-02-23T10:41:54.652Z] error_receive_buffer_full
//
// and in `moserial` you should get
//
// abc123
//
// there is much in play, let's try to break it down
// a USART2 Rxne interrupt was initally generated, and we enter the
// interrupt handler, and detect this
//
// if sr.rxne().bit_is_set() {
// // interrupt due to data received
// match serial.read() {
// Ok(b) => {
// if b == '\n' as u8 || b == '\r' as u8 {
// // we have received a `return`, trigger command to process it
// rtfm::set_pending(f4::stm32f40x::Interrupt::EXTI1);
// } else {
// match r.RECEIVE.enqueue(b) {
// Err(_) => {
// // trigger error_handler on buffer overflow
// rtfm::set_pending(f4::stm32f40x::Interrupt::EXTI2);
// }
// _ => {}
// }
// }
// }
//
// here the serial read was successful (Some('1')), its not a Carrige return so we enque on the BUFFER
// as a side effect of serial.read(), the Rxne bit was cleared and we exit the interrupt handler
// and sleep until the next byte arrives (`2`).
//
// this is repeated (wake, enque, sleep) until the buffer is full (that happens before we
// receive the carrige return)
//
// when the buffer is full we trigger EXTI2 (that makes a trace output of the error)
// the buffer is of lenght 6, but the implementation allows only N-1 elements
// so both the `6` and `7` are dropped, and we produce two error events.
// in this case they were not coalesced (both printed)
//
// we will recieve the carrige return, and and pend the `command` (EXTI1), i.e., our listener.
//
// the `command` interrupt handler
// {
// ipln!("command");
// match SEND.claim_mut(t, |send, t1| {
// RECEIVE.claim_mut(t1, |receive, _| {
// let mut err = Ok(());
// while let Some(e) = receive.dequeue() {
// err = send.enqueue(e);
// }
// err
// })
// }) {
// Err(err) => {
// // error handling outside the critical section
// ipln!("SEND {:?}", err);
// }
// _ => {}
// }
//
// // re-enable Txe
// USART2.claim(t, |usart, _| {
// Serial(&**usart).listen(Event::Txe);
// });
// // trigger the usart to send queued data
// rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
// }
// we trace `command` and claim both SEND and RECEIVE
// in the critical section we copy the RECIEVE buffer elements to the SEND buffer
// (this is actually the *loopback* behavior, we could also copy to another local buffer
// to do further parsing or simiar)
//
// here its important that we keep the crical section as short as possible
// so we just copy nothing else... since we hold a shared resource we will block
// the USART2 from executing, and may loose incoming data
//
// we also do error handling here if the send buffer is full we will return from
// the claim(s) withe an error (else with an Ok(())),
// result is matched
// {
// Err(err) => {
// // error handling outside the critical section
// ipln!("SEND {:?}", err);
// }
// _ => {}
// rendering a `SEND BufferFullError` (or nonthig)
//
// finally we need to re-enable the USART2 Txe, so that interrupts due to USART2 writes
// will cause USART2 interrupts
//
// // re-enable Txe
// USART2.claim(t, |usart, _| {
// Serial(&**usart).listen(Event::Txe);
// });
//
// recall that at an earlier point, we left a Txe interrupt condition hanging,
// and filtered it out by *unlisten*
//
// alternative we could add a triggering event
// rtfm::set_pending(f4::stm32f40x::Interrupt::USART2);
//
// but it is not needed for this use case
//
// so what DO this implementatin promise
//
// 1. graceful handling of RECIEVE buffer overflow
// 2. graceful handling of SEND buffer overflow
// 3. no data loss, (e.g., we never miss detecting the carrige return)
// (you can stress this by entering a long input)
// oahuesthueoasnthueonthueoantueoaueoasnthueoasnthueoasnueoasnthueoasnthueoa
// the output should be some errors, in the trace log but the `moserial` should receive
// `oah`, i.e. the firs three bytes
//
// what it DOES NOT promise
// handling of hardware level errors (framing, etc.), this could be added if needed
//
// of course you need to adjust the SEND/RECEIVE buffer lenghts to the requirements
// of the application
//
// so lessons learned
// 1. seemingly trivial problems may be harder than expected
// 2. Reading the Manual(s) are required to write correct low level code
// 3. RTFM provides a means to implment correct low level code
//
// On a side note, it took me quite some time to implment, debug and comment
// the example, so be prepared, writing correct low level code is HARD
//
// On the possitive side, the safety enforced by Rust RTFM
// makes our lifes much easier, as we don't need to worry about
// - race conditions, on the shared data structures
// - deadlocks, for the reasource managenent
//
// As a comparison, in RTFM we write the low level code INSIDE the model of computation
// hence we get the luxyry of letting RTFM manage the resource management
// In a threaded environment (e.g. FreeRTOS), the driver (interrupt handling etc.) would
// be largely OUTSIDE the threaded model of computation, so correctness would
// be up to you....
//
// In the arduino world there is no protection mechanisms at all, what you write is what you get:
// hence rubustness can never be argued else than for the specic application at hand.
//
// The outsets are very different, in the FreeRTOS/arduino word we are typically happy to see things
// working, without concerns of roubustness (simly put robustness can never be argued, unless you
// somehow make a proof over the complete code base).
//
// Using RTFM, we can establish critera for correctness, and from there argue robustness etc.
// (The theorecical task/resource underpinning, gives us a solid fundament for reasoning)
//
// If one would want to prove correctness for the above implementation the way forward is
// 1 Derive the possible sates of the system. *regarding both HW and software*
// 2 Formulate invariants for each state.
// 3 Show that each possible state transition upholds the invariants.
//
// We have not gone the length of doing so for this application.. but its indeed possible but
// takes a lot of effort.
fn
parse
(
s
:
&
str
)
->
Result
<
Command
,
&
str
>
{
let
mut
iter
=
s
.split
(
' '
)
.filter
(|
c
|
!
(
c
==
&
""
));
match
iter
.next
()
{
Some
(
"Stop"
)
=>
Ok
(
Command
::
Stop
),
Some
(
"Start"
)
=>
Ok
(
Command
::
Start
),
Some
(
"Freq"
)
=>
match
iter
.next
()
{
Some
(
fs
)
=>
{
if
let
Ok
(
f
)
=
fs
.parse
::
<
u32
>
()
{
Ok
(
Command
::
Freq
(
f
))
}
else
{
Err
(
"Invalid frequency"
)
}
}
None
=>
Err
(
"No frequency"
),
},
Some
(
_
)
=>
Err
(
"Invalid command"
),
None
=>
Err
(
"No input"
),
}
}
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment