Skip to content
Snippets Groups Projects
Select Git revision
  • 1b4e30f6cddd48246716cfaa623016a4fa53c998
  • master default
2 results

README.md

Blame
  • Forked from Per Lindgren / e7020e_2020
    31 commits behind the upstream repository.
    README.md 39.38 KiB

    app

    Examples and exercises for the Nucleo STM32F401re/STM32F11re devkits.


    Dependencies

    • Rust 1.40, or later. Run the following commands to update you Rust tool-chain and add the target for Arm Cortex M4 with hardware floating point support.
    > rustup update
    > rustup target add thumbv7em-none-eabihf
    • For programming (flashing) and debugging

      • openocd debug host, (install using your package manager)

      • arm-none-eabi tool-chain (install using your package manager). In the following we refer the arm-none-eabi-gdb as just gdb for brevity.

      • stlink (optional) tools for erasing and programming ST microcontrollers (install using your package manager).

    • itm tools for ITM trace output, install by:

    > cargo install itm
    • vscode editor/ide and cortex-debug plugin. Install vscode using your package manager and follow the instructions at cortex-debug (optional for an integrated debugging experience)

    • rust-analyzer install following instructions at rust-analyzer (optional for Rust support in vscode)


    Examples


    Hello World! Building and Debugging an Application

    1. Connect your devkit using USB. To check that it is found you can run:
    > lsusb
    ...
    Bus 001 Device 004: ID 0483:374b STMicroelectronics ST-LINK/V2.1
    ...

    (Bus/Device/ID may vary.)

    1. Run in a terminal (in the app project folder):
    > openocd -f openocd.cfg
    ...
    Info : Listening on port 6666 for tcl connections
    Info : Listening on port 4444 for telnet connections
    Info : clock speed 2000 kHz
    Info : STLINK V2J20M4 (API v2) VID:PID 0483:374B
    Info : Target voltage: 3.254773
    Info : stm32f4x.cpu: hardware has 6 breakpoints, 4 watchpoints
    Info : Listening on port 3333 for gdb connections

    openocd should connect to your target using the stlink programmer (onboard your Nucleo devkit). See the Trouble Shooting section if you run into trouble.

    1. In another terminal (in the same app folder) run:
    > cargo run --example hello

    The cargo sub-command run looks in the .cargo/config file on the configuration (runner = "arm-none-eabi-gdb -q -x openocd.gdb").

    We can also do this manually.

    > cargo build --example hello
    > arm-none-eabi-gdb target/thumbv7em-none-eabihf/debug/examples/hello -x openocd.gdb

    This starts gdb with file being the hello (elf) binary, and runs the openocd.gdb script, which loads (flashes) the binary to the target (our devkit). The script connects to the openocd server, enables semihosting and ITM tracing, sets breakpoints at main (as well as some exception handlers, more on those later), finally it flashes the binary and runs the first instruction (stepi). (You can change the startup behavior in the openocd.gdb script, e.g., to continue instead of stepi.)

    1. You can now continue debugging of the program:
    ...
    Note: automatically using hardware breakpoints for read-only addresses.
    halted: PC: 0x08000a72
    DefaultPreInit ()
        at /home/pln/.cargo/registry/src/github.com-1ecc6299db9ec823/cortex-m-rt-0.6.12/src/lib.rs:571
    571     pub unsafe extern "C" fn DefaultPreInit() {}
    
    (gdb) c
    Continuing.
    
    Breakpoint 1, main () at examples/hello.rs:12
    12      #[entry]

    The cortex-m-rt run-time initializes the system and your global variables (in this case there are none). After that it calls the [entry] function. Here you hit a breakpoint.

    1. You can continue debugging:
    (gdb) c
    Continuing.
    halted: PC: 0x0800043a
    ^C
    Program received signal SIGINT, Interrupt.
    hello::__cortex_m_rt_main () at examples/hello.rs:15
    15          loop {

    At this point, the openocd terminal should read something like:

     Thread
    xPSR: 0x01000000 pc: 0x08000a1a msp: 0x20008000, semihosting
    Info : halted: PC: 0x08000a72
    Info : halted: PC: 0x0800043a
    Hello, world!

    Your program is now stuck in an infinite loop (doing nothing).

    1. Press CTRL-c in the gdb terminal:
    Program received signal SIGINT, Interrupt.
    0x08000624 in main () at examples/hello.rs:14
    14          loop {}
    (gdb)

    You have now compiled and debugged a minimal Rust hello example. gdb is a very useful tool so lookup some tutorials/docs (e.g., gdb-doc, and the GDB Cheat Sheet.


    ITM Tracing

    The hello.rs example uses the semihosting interface to emit the trace information (appearing in the openocd terminal). The drawback is that semihosting is incredibly slow as it involves a lot of machinery to process each character. (Essentially, it writes a character to a given position in memory, runs a dedicated break instruction, openocd detects the break, reads the character at the given position in memory and emits the character to the console.)

    A better approach is to use the ARM ITM (Instrumentation Trace Macrocell), designed to more efficiently implement tracing. The onboard stlink programmer can put up to 4 characters into an ITM package, and transmit that to the host (openocd). openocd can process the incoming data and send it to a file or FIFO queue. The ITM package stream needs to be decoded (header + data). To this end we use the itmdump tool.

    In a separate terminal, create a named fifo:

    > mkfifo /tmp/itm.fifo
    > itmdump -f /tmp/itm.fifo
    Hello, again!

    Now you can compile and run the itm.rs application using the same steps as the hello program. In the itmdump console you should now have the trace output.

    > cargo run --example itm

    Under the hood there is much less overhead, the serial transfer rate is set to 2Mbit/s in between the ITM (inside of the MCU) and stlink programmer (onboard the Nucleo devkit). So in theory we can transmit some 200kByte/s data over ITM. However, we are limited by the USB interconnection and openocd to receive and forward packages.

    The stlink programmer, buffers packages but has limited buffer space. Hence in practice, you should keep tracing to short messages, else the buffer will overflow. See trouble shooting section if you run into trouble.


    Rust panic Handling

    The rust compiler statically analyses your code, but in cases some errors cannot be detected at compile time (e.g., array indexing out of bounds, division by zero etc.). The rust compiler generates code checking such faults at run-time, instead of just crashing (or even worse, continuing with faulty/undefined values like a C program would) . A fault in Rust will render a panic, with an associated error message (useful to debugging the application). We can choose how such panics should be treated, e.g., transmitting the error message using semihosting, ITM, some other channel (e.g. a serial port), or simply aborting the program.

    The panic example demonstrates some possible use cases.

    The openocd.gdb script sets a breakpoint at rust_begin_unwind (a function in the rust core library, used to recover errors.)

    When running the example (see above how to compile and run), the gdb terminal will show:

    ...
    Breakpoint 2, main () at examples/panic.rs:27
    27          panic!("Oops")
    (gdb) c
    Continuing.
    halted: PC: 0x08000404
    
    Breakpoint 1, rust_begin_unwind (_info=0x20017fb4)
        at /home/pln/.cargo/registry/src/github.com-1ecc6299db9ec823/panic-halt-0.2.0/src/lib.rs:33
    33              atomic::compiler_fence(Ordering::SeqCst);
    (gdb) p *_info
    $1 = core::panic::PanicInfo {payload: core::any::&Any {pointer: 0x8000760 <.Lanon.21a036e607595cc96ffa1870690e4414.142> "\017\004\000", vtable: 0x8000760 <.Lanon.21a036e607595cc96ffa1870690e4414.142>}, message: core::option::Option<&core::fmt::Arguments>::Some(0x20017fd0), location: core::panic::Location {file: <error reading variable>, line: 27, col: 5}}

    Here p *_info prints the argument to rust_begin_unwind, at the far end you will find line: 27, col 5, which corresponds to the source code calling panic("Ooops"). (gdb is not (yet) Rust aware enough to figure out how the file field should be interpreted, but at least we get some useful information).

    Alternatively we can trace the panic message over semihosting (comment out extern crate panic_halt and uncomment extern crate panic_semihosting).

    The openocd console should now show:

    Info : halted: PC: 0x080011a0
    panicked at 'Oops', examples/panic.rs:27:5

    Under the hood, this approach involves formatting of the panic message, which implementation occupies a bit of flash memory (in our case we have 512kB so plenty enough, but for the smallest of MCUs this may be a problem). Another drawback is that it requires a debugger to be connected and active.

    Another alternative is to use ITM (uncomment extern crate panic_itm), this is faster, but be aware, the message may overflow the ITM buffer, so it may be unreliable. Also it assumes, that the ITM stream is actively monitored.

    A third alternative would be to store the panic message in some non-volatile memory (flash, eeprom, etc.). This allows for true post-mortem debugging of a unit put in production. This approach is used e.g. in automotive applications where the workshop can read-out error codes of your vehicle.