diff --git a/Cargo.toml b/Cargo.toml index 210228ff90279434f3e6ee69d5ac64f29fc74853..b7f933de09fd618f148cb7a26d31e960dc906816 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ readme = "README.md" name = "app" version = "0.1.0" -[patch.crates-io] -stm32f4xx-hal = { path = "../stm32f4xx-hal" } +# [patch.crates-io] +# stm32f4xx-hal = { path = "../stm32f4xx-hal" } [dependencies] cortex-m-rt = "0.6.7" @@ -35,7 +35,6 @@ features = ["stm32f411", "rt"] version = "0.2.8" features = ["stm32f411", "rt"] - # this lets you use `cargo fix`! [[bin]] name = "app" diff --git a/README.md b/README.md index 851f1e245e1f5f4c09a6a10ca9eb4255fafb1bbd..bbe8d24fd6ef386cb72e54c58dc69748d987e62f 100644 --- a/README.md +++ b/README.md @@ -274,6 +274,43 @@ Compiles `stm32f4` (a generic library for all STMF4 MCUs) with `features = featu --- +### Hardware Abstraction Layer + +For convenience common functionality can be implemented for a specific MCU (or family of MCUs). The `stm32f4xx-hal` is a Work In Progress, implementing a Hardware Abstraction Layer (hal) for the `stm32f4` family. It implements the `https://crates.io/search?q=embedded-hal` serial trait (interface), to read and write single bytes over a serial port. However, setting up communication is out of scope for the `embedded-hal`. + +The `serial.rs` example showcase a simple echo application, +repeating back incoming data over a serial port (byte by byte). You will also get trace output over the ITM. + +Looking closer at the example, `rcc` is a *singleton* (`constrain` consumes the `RCC` and returns a singleton. The `freeze` consumes the singleton (`rcc`) and sets the MCU clock tree according to the (default) `cfgr`. (Later in the exercises you will change this.) + +This pattern ensures that the clock configuration will remain unchanged (the `freeze` function cannot be called again, as the `rcc` is consumed, also you cannot get a new `rcc` as the `RCC` was consumed by `contstrain`). + +Why is this important you may ask? Well, this *pattern* allows the compiler to check and ensure that your code (or some library that you use) does not make changes to the system (in this case the clocking), wich reduces the risk of errors and improves robustness. + +Similarly, we `split` the `GPIOA` into its parts (pins), and select the operating mode to `af7` for `tx` (the transmit pin `pa2`), and `rx` (the receive pin `pa3`). For details see, RM0368, figure 17 (page 151), and table 9 in STM32F401xD STM32F401xE. The GPIO pins `pa2` and `pa3` are (by default) connected to the `stlink` programmer, see section 6.8 of the Nucleo64 user manual `UM1724`. When the `stlink` programmer is connected to a linux host, the device `\dev\ttyACM0` appears as a *virtual com port* (connected to `pa2`/`pa3` by default). + +Now we can call `Serial::usart2` to setup the serial communication, (according to table 9 in STM32F401xD STM32F401xE it is USART2). + +Following the *singleton* pattern it consumes the `USART2` peripheral (to ensure that only one configuration can be active at any time). The second parameter is the pair of pins `(tx, px)` that we setup earlier. +The third parameter is the USART configuration. By defualt its set to 8 bit data, and one stop bit. We set the baudrate to 115200. We also pass the `clocks` (holding information about the MCU clock setup). + +At this point `tx` and `rx` is owned by `serial`. We can get access to them again by `serial.split()`. + +In the loop we match on the result of `block!(rx.read())`. `block!` repeatedly calls `rx.read()` until ether a byte is received or an error returned. In case `rx.read()` succeeded, we trace the received byte over the ITM, and echo it by `tx.write(byte)`, ignoring the result (we just assume sending will always succeed). In case `rx.read` returned with an error, we trace the error message. + +As the underlying hardware implementation buffers only a single byte, the input buffer may ofverlow (resulting in `tx.read` returning an error). + +You can now compile and run the example. Start `moserial` (or some other serial communication tool), connect to `/dev/ttyACM0` with 115200 8N1). Now write a single character `a` in the `Outgoing` pane, followed by pressing <Enter>. You should receive back an `a` from the target. In the ITM trace output you should see, + +``` +[2019-01-08T10:31:36.867Z] Ok 97. +``` +Depending if <Enter> was encoded as CR+LF, CR, LF, TAB, ... you will get additional bytes sent (and received). Try sending multiple characters at once, e.g. `abcd`, you will see that the you well get a buffer overflow. + +the `read` is a method on the `rx`, + +--- + # Trouble Shooting Working with embedded targets involves a lot of tooling, and many things can go wrong. diff --git a/examples/bare6.rs b/examples/bare6.rs index 32eab268ca1d6c0114d910da5c73bee8844c11f0..925ca53f5bc88ef04f5b187470c3edceb19f99de 100644 --- a/examples/bare6.rs +++ b/examples/bare6.rs @@ -30,8 +30,7 @@ use cortex_m::{iprint, iprintln, peripheral::itm::Stim, peripheral::syst::SystCl use cortex_m_rt::{entry, exception}; use stm32f4::stm32f411; -use stm32f411::{interrupt, Interrupt, DWT, GPIOA, GPIOC, ITM, NVIC, RCC, SYST}; - +use stm32f411::{interrupt, Interrupt, DWT, GPIOA, GPIOC, ITM, NVIC, RCC, SYST}; #[entry] fn main() -> ! { @@ -68,16 +67,15 @@ fn idle(stim: &mut Stim, rcc: RCC, gpioa: GPIOA) { // at 16 Mhz, 8_000_000 cycles = period 0.5s // at 64 Mhz, 4*8_000_000 cycles = period 0.55s // let cycles = 8_000_000; - let cycles = 4*8_000_000; - + let cycles = 4 * 8_000_000; + loop { - iprintln!(stim, "on {}", DWT::get_cycle_count()); + iprintln!(stim, "on {}", DWT::get_cycle_count()); // set PA5 high, RM0368 8.4.7 gpioa.bsrr.write(|w| w.bs5().set_bit()); wait_cycles(cycles); - - iprintln!(stim, "off {}", DWT::get_cycle_count()); + iprintln!(stim, "off {}", DWT::get_cycle_count()); // set PA5 low, RM0368 8.4.7 gpioa.bsrr.write(|w| w.br5().set_bit()); wait_cycles(cycles); @@ -111,14 +109,12 @@ fn clock_out(rcc: &RCC, gpioc: &GPIOC) { // AF0, gpioc reset value = AF0 // configure PC9 as alternate function 0b10, RM0368 6.2.10 - gpioc.moder.modify(|_, w| w.moder9().bits(0b10) ); + gpioc.moder.modify(|_, w| w.moder9().bits(0b10)); // otyper reset state push/pull, in reset state (don't need to change) // ospeedr 0b11 = high speed - gpioc - .ospeedr - .modify(|_, w| w.ospeedr9().bits(0b11) ); + gpioc.ospeedr.modify(|_, w| w.ospeedr9().bits(0b11)); } // 1. Compile and run the example, in 16Mhz diff --git a/examples/serial.rs b/examples/serial.rs index 21833f2f162414811b84c22eb887760c6c45b101..ccf0d218860d58edcfebae083ca0ab46b246757e 100644 --- a/examples/serial.rs +++ b/examples/serial.rs @@ -1,44 +1,49 @@ -//! Serial interface loopback test +//! Serial echo //! -//! You have to short the TX and RX pins to make this program work +//! Connect using e.g., `moserial` to `/dev/ttyACM0` +//! 115200 8N1 +//! +//! The MCU will echo incoming data and send a trace over ITM. +//! Notice, as the hardware has a single byte buffer only, the input +//! buffer may overflow. -// #![deny(unsafe_code)] +#![deny(unsafe_code)] #![feature(uniform_paths)] -// #![deny(warnings)] +#![deny(warnings)] #![no_main] #![no_std] extern crate panic_halt; -use cortex_m::asm; - -use nb::block; - use cortex_m_rt::entry; -// use stm32f4xx_hal::stm32f4::stm32f411 as device; - +use nb::block; extern crate stm32f4xx_hal as hal; -// #[macro_use(block)] -// extern crate nb; use crate::hal::prelude::*; use crate::hal::serial::{config::Config, Serial}; -//use crate::rt::ExceptionFrame; +use cortex_m::iprintln; #[entry] fn main() -> ! { + let mut c = hal::stm32::CorePeripherals::take().unwrap(); + let stim = &mut c.ITM.stim[0]; + let p = hal::stm32::Peripherals::take().unwrap(); - let mut flash = p.FLASH.constrain(); - let mut rcc = p.RCC.constrain(); + // let mut flash = p.FLASH.constrain(); + let rcc = p.RCC.constrain(); - let mut gpioa = p.GPIOA.split(); - // let mut gpioa = p.GPIOA.split(&mut rcc.ahb1) + let gpioa = p.GPIOA.split(); - let clocks = rcc.cfgr.freeze(&mut flash.acr); + // let clocks = rcc.cfgr.freeze(&mut flash.acr); + let clocks = rcc.cfgr.freeze(); + // let _clocks = rcc.cfgr.freeze(); let tx = gpioa.pa2.into_alternate_af7(); - let rx = gpioa.pa3.into_alternate_af7(); + + let rx = gpioa.pa3.into_alternate_af7(); // try comment out + + // let rx = gpioa.pa3.into_alternate_af0(); // try uncomment let serial = Serial::usart2( p.USART2, @@ -52,25 +57,14 @@ fn main() -> ! { let (mut tx, mut rx) = serial.split(); loop { - // Read character and echo it back - let received = block!(rx.read()).unwrap(); - block!(tx.write(received)).ok(); + match block!(rx.read()) { + Ok(byte) => { + iprintln!(stim, "Ok {:?}", byte); + let _ = tx.write(byte); + } + Err(err) => { + iprintln!(stim, "Error {:?}", err); + } + } } - - // let (mut tx, mut rx) = serial.split(); - - // loop { - // //ipln!("wait"); - // //spln!("wait"); - // if let Ok(byte) = block!(rx.read()) { - // //ipln!("got {:?}", byte); - // //spln!("got {:?}", byte); - // block!(tx.write(byte)).ok(); - // } else { - // //ipln!("buffer overflow"); - // //spln!("buffer overflow"); - // } - // } - - // loop {} }