commit 57a257d47bcf5834cd741f7f7e4c06eaf488d395 Author: wieerwill Date: Tue Nov 12 08:46:50 2024 +0100 first commit diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..069c835 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,21 @@ +[target.thumbv6m-none-eabi] +runner = 'probe-rs run --chip STM32L010RBTx' + +rustflags = [ + "-C", "link-arg=--nmagic", + "-C", "link-arg=-Tlink.x", + "-C", "link-arg=-Tdefmt.x", + "-C", "link-arg=--no-rosegment" +] + +[build] +target = "thumbv6m-none-eabi" + +[env] +DEFMT_LOG = "trace" + +[alias] +main = "embed --release --bin main" +rb = "embed --release --bin" +s = "size --bin main -- -B -x" +sr = "size --release --bin main -- -B -x" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6985cf1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +# Generated by Cargo +# will have compiled files and executables +debug/ +target/ + +# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries +# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html +Cargo.lock + +# These are backup files generated by rustfmt +**/*.rs.bk + +# MSVC Windows builds of rustc generate these, which store debugging information +*.pdb diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..087b898 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,72 @@ +[package] +name = "stm32_app" +version = "0.1.0" +edition = "2021" +authors = ["wieerwill "] +resolver = "2" + +[dependencies] +embassy-stm32 = { version = "0.1.0", features = [ + "defmt", + "memory-x", + "stm32l010rb", + "time-driver-any", + "exti", + "unstable-pac", + "exti", + "time", +] } +cortex-m = { version = "0.7.6", features = [ + "inline-asm", + "critical-section-single-core", +] } +cortex-m-rt = "0.7.0" +defmt = "0.3" +defmt-rtt = "0.4" +panic-probe = { version = "0.3", features = ["print-defmt"] } +embassy-sync = { version = "0.6.0", features = [ + "defmt", +] } +embassy-executor = { version = "0.6.0", features = [ + "arch-cortex-m", + "executor-thread", + "executor-interrupt", + "defmt", + "integrated-timers", +] } +embassy-time = { version = "0.3.1", features = [ + "defmt", + "defmt-timestamp-uptime", + "tick-hz-32_768", +] } +embassy-futures = { version = "0.1.1", features = [ + "defmt", +] } +static_cell = "2" +portable-atomic = { version = "1.5", features = ["unsafe-assume-single-core"] } +heapless = "0.8.0" + +# cargo build/run +[profile.dev] +codegen-units = 1 +debug = 2 +debug-assertions = true +incremental = false +opt-level = 0 +overflow-checks = true + +# cargo build/run --release +[profile.release] +codegen-units = 1 # reduce Code Generation Units +debug = true # symbols are nice and they don't increase the size on Flash +debug-assertions = false +incremental = false +lto = true # Link Time Optimization +opt-level = "z" # Optimize for size "s" or "z" +overflow-checks = true +# strip = "symbols" # or true, removes all output from rtt +# panic = "abort" # abort immediately rather than unwind +# location-detail="none" # Remove Location Details + +[target.stm32l010rb] +runner = 'probe-rs run --chip stm32l010rbtx' diff --git a/Embed.toml b/Embed.toml new file mode 100644 index 0000000..46f21b3 --- /dev/null +++ b/Embed.toml @@ -0,0 +1,103 @@ +[default.probe] +# USB vendor ID +# usb_vid = "1337" +# USB product ID +# usb_pid = "1337" +# Serial number +# serial = "12345678" +# The protocol to be used for communicating with the target. +protocol = "Swd" +# The speed in kHz of the data link to the target. +# speed = 1337 + +[default.flashing] +# Whether or not the target should be flashed. +enabled = true +# Whether or not bytes erased but not rewritten with data from the ELF +# should be restored with their contents before erasing. +restore_unwritten_bytes = false +# The path where an SVG of the assembled flash layout should be written to. +# flash_layout_output_path = "out.svg" +# Triggers a full chip erase instead of a page by page erase. +do_chip_erase = false +# Whether to disable double buffering +disable_double_buffering = false +# Whether to verify flash contents after downloading +verify = true + +[default.reset] +# Whether or not the target should be reset. +# When flashing is enabled as well, the target will be reset after flashing regardless of this setting. +enabled = true +# Whether or not the target should be halted after reset. +halt_afterwards = false + +[default.general] +# The chip name of the chip to be debugged. +chip = "STM32L010RBTx" +# A list of chip descriptions to be loaded during runtime. +chip_descriptions = [] +# The default log level to be used. Possible values are one of: +# "OFF", "ERROR", "WARN", "INFO", "DEBUG", "TRACE" +# If not set, the `RUST_LOG` environment variable will be used. +log_level = "WARN" +# Use this flag to assert the nreset & ntrst pins during attaching the probe to the chip. +connect_under_reset = false + +[default.rtt] +# Whether or not an RTTUI should be opened after flashing. +enabled = true +# The duration in ms for which the logger should retry to attach to RTT. +timeout = 10000 +# Whether to save rtt history buffer on exit. +log_enabled = true +# Where to save rtt history buffer relative to manifest path. +log_path = "./target/logs" +# A list of up (target -> host) channel settings associations to be displayed. If left empty, all channels are displayed. +# object key - RTT channel identifier number +# mode (Optional) - RTT operation mode. Describes how the target handles RTT outputs that won't +# fit in the buffer. If left unset, the firmware will determine the default +# for each RTT up channel. +# * NoBlockSkip - Skip writing the data completely if it doesn't fit in its +# entirety. +# * NoBlockTrim - Write as much as possible of the data and ignore the rest. +# * BlockIfFull - Spin until the host reads data. Can result in app freezing. +# format (Optional) - How to interpret data from target firmware. One of: +# * String - Directly show output from the target (default) +# * Defmt - Format output on the host, see https://defmt.ferrous-systems.com/ +# * BinaryLE - Display as raw hex +# show_location (Optional) - Whether to show the location of defmt messages in the UI. +# show_timestamps (Optional) - Whether to show the timestamps of String and Defmt messages in the UI, if available. +# socket (Optional) - Server socket address (for optional external frontend or endpoint). +# log_format (Optional) - Control the output format for `format = Defmt`. +up_channels = [ + # { channel = 0, mode = "BlockIfFull", format = "Defmt", show_location = true }, + # { channel = 1, mode = "BlockIfFull", format = "String", show_timestamps = false, socket = "127.0.0.1:12345" }, +] + +# A list of down (host -> target) channel settings. You can select a down channel for each UI tab, +# which will be used to send data to the target. +# object key - RTT channel identifier number +# mode (Optional) - RTT operation mode. Describes how the target handles RTT outputs that won't +# fit in the buffer. If left unset, the firmware will determine the default +# for each RTT down channel. +down_channels = [ + # { channel = 0, mode = "BlockIfFull" } +] + +# UI tab settings. All up channels are displayed, except when hidden here. You can specify how each +# tab is displayed and whether they allow sending data to the target. +# up_channel - The channel_number of the RTT up channel to display +# hide (Optional) - Whether to hide the tab. Defaults to false. +# down_channel (Optional) - The channel_number of the RTT down channel to use for this tab. +# name (Optional) - String to be displayed in the RTTUI tab. Defaults to the channel name. +tabs = [ + # { up_channel = 0, down_channel = 0, name = "My channel" }, + # { up_channel = 1, hide = true }, +] + +[default.gdb] +# Whether or not a GDB server should be opened after flashing. +enabled = false +# The connection string in host:port format wher the GDB server will open a socket. +gdb_connection_string = "127.0.0.1:1337" diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c4efd9 --- /dev/null +++ b/README.md @@ -0,0 +1,19 @@ +# STM32 Firmware with Rust and Embassy +Used Chip in this Project was a STM32L0, the low power edition. Other targets are easily exchangeable. + +# Dev Tools +- rustup 1.27.1 +- rustc 1.81.0 +- Cargo 1.81.0 +- Probe-RS + +# Binaries +All binaries are compiled and then transfered to the connected board (if exist). Then the RTT Console will be opened to read and debug. + +The ready binary is a .elf file in the `target` folder. + +```sh +cargo rb blinky # blinky example +cargo main # main program +./elf-to-hex.sh # convert main program to Hex and versionize +``` \ No newline at end of file diff --git a/elf-to-hex.sh b/elf-to-hex.sh new file mode 100755 index 0000000..7a7573e --- /dev/null +++ b/elf-to-hex.sh @@ -0,0 +1,15 @@ +#!/bin/bash +objcopy -O ihex target/thumbv6m-none-eabi/release/main target/main.hex -S + +# read version number +version=$(awk -F ' = ' '$1 ~ /version/ { gsub(/[\"]/, "", $2); printf("%s",$2) }' Cargo.toml) +# check if version was found +if [[ -z "$version" ]]; then + echo "Error: Version not found in Cargo.toml" + exit 1 +fi +echo "Firmware Version $version" + +# write version into filename +mv target/main.hex target/FW-$version-main.hex +echo "target/FW-$version-main.hex" \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml new file mode 100644 index 0000000..df7f7f2 --- /dev/null +++ b/rust-toolchain.toml @@ -0,0 +1,6 @@ +# Before upgrading check that everything is available on all tier1 targets here: +# https://rust-lang.github.io/rustup-components-history +[toolchain] +channel = "stable" +components = [ "rustfmt" ] +targets = [ "thumbv6m-none-eabi" ] diff --git a/src/bin/adc.rs b/src/bin/adc.rs new file mode 100644 index 0000000..d4d6e99 --- /dev/null +++ b/src/bin/adc.rs @@ -0,0 +1,37 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::adc::{Adc, SampleTime}; +use embassy_stm32::peripherals::ADC1; +use embassy_stm32::{adc, bind_interrupts}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct AdcIrqs { + ADC1 => adc::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let mut adc = Adc::new(p.ADC1, AdcIrqs); + adc.set_sample_time(SampleTime::CYCLES79_5); + let mut pin = p.PA1; + + let mut vrefint = adc.enable_vref(); + let vrefint_sample = adc.read(&mut vrefint).await; + let convert_to_millivolts = |sample| { + // Embedded reference voltage + const VREFINT_MV: u32 = 1224; // mV + (u32::from(sample) * VREFINT_MV / u32::from(vrefint_sample)) as u16 + }; + + loop { + let v = adc.read(&mut pin).await; + info!("--> {} - {} mV", v, convert_to_millivolts(v)); + Timer::after_millis(100).await; + } +} \ No newline at end of file diff --git a/src/bin/blinky.rs b/src/bin/blinky.rs new file mode 100644 index 0000000..271c395 --- /dev/null +++ b/src/bin/blinky.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let mut led = Output::new(p.PA9, Level::High, Speed::Low); + + loop { + info!("high"); + led.set_high(); + Timer::after_millis(300).await; + + info!("low"); + led.set_low(); + Timer::after_millis(300).await; + } +} \ No newline at end of file diff --git a/src/bin/button.rs b/src/bin/button.rs new file mode 100644 index 0000000..5b8ea84 --- /dev/null +++ b/src/bin/button.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::exti::ExtiInput; +use embassy_stm32::gpio::Pull; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + + // Configure the button pin and obtain handler. + let mut button = ExtiInput::new(p.PC2, p.EXTI2, Pull::Up); + + info!("Press the button..."); + loop { + button.wait_for_falling_edge().await; + info!("Pressed!"); + button.wait_for_rising_edge().await; + info!("Released!"); + } +} diff --git a/src/bin/hello.rs b/src/bin/hello.rs new file mode 100644 index 0000000..2b7f94e --- /dev/null +++ b/src/bin/hello.rs @@ -0,0 +1,16 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let _p = embassy_stm32::init(Default::default()); + loop { + Timer::after_secs(1).await; + info!("Hello World"); + } +} \ No newline at end of file diff --git a/src/bin/main.rs b/src/bin/main.rs new file mode 100644 index 0000000..fe16588 --- /dev/null +++ b/src/bin/main.rs @@ -0,0 +1,20 @@ +#![no_std] +#![no_main] + +use defmt::{info, debug}; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let _p = embassy_stm32::init(Default::default()); + + info!("Hello World"); + debug!("Start your application here"); + + loop { + Timer::after_secs(1).await; + info!("."); + } +} \ No newline at end of file diff --git a/src/bin/pwm.rs b/src/bin/pwm.rs new file mode 100644 index 0000000..7b99567 --- /dev/null +++ b/src/bin/pwm.rs @@ -0,0 +1,46 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::OutputType::PushPull; +use embassy_stm32::time::hz; +use embassy_stm32::timer::low_level::CountingMode::EdgeAlignedUp; +use embassy_stm32::timer::simple_pwm::{PwmPin, SimplePwm}; +use embassy_stm32::timer::Channel; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) -> ! { + let p = embassy_stm32::init(Default::default()); + + let buzz_pin = PwmPin::new_ch1(p.PB13, PushPull); + let mut pwm = SimplePwm::new( + p.TIM21, + Some(buzz_pin), + None, + None, + None, + hz(2000), + EdgeAlignedUp, + ); + + let max_duty = pwm.get_max_duty(); + // Configure the Duty Cycle to 50% + pwm.set_duty(Channel::Ch2, max_duty / 2); + + pwm.enable(Channel::Ch2); + + let mut frequence = 300; + loop { + info!("on, freq {}hz", frequence); + pwm.set_frequency(hz(frequence)); + pwm.enable(Channel::Ch2); + Timer::after_millis(1000).await; + info!("off"); + pwm.disable(Channel::Ch2); + Timer::after_millis(500).await; + frequence += 20; + } +} diff --git a/src/bin/spawner.rs b/src/bin/spawner.rs new file mode 100644 index 0000000..ce721f7 --- /dev/null +++ b/src/bin/spawner.rs @@ -0,0 +1,28 @@ +#![no_std] +#![no_main] + +use defmt::info; +use embassy_executor::Spawner; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::task] +async fn run() { + loop { + info!("Tick"); + Timer::after_secs(1).await; + } +} + +#[embassy_executor::main] +async fn main(spawner: Spawner) -> ! { + let _p = embassy_stm32::init(Default::default()); + + spawner.spawn(run()).unwrap(); + Timer::after_secs(1).await; + spawner.spawn(run()).unwrap(); + loop { + Timer::after_secs(2).await; + info!("Tack"); + } +} diff --git a/src/bin/spi.rs b/src/bin/spi.rs new file mode 100644 index 0000000..520d713 --- /dev/null +++ b/src/bin/spi.rs @@ -0,0 +1,31 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::gpio::{Level, Output, Speed}; +use embassy_stm32::spi::{Config, Spi}; +use embassy_stm32::time::Hertz; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + let mut spi_config = Config::default(); + spi_config.frequency = Hertz(1_000_000); + + let mut spi = Spi::new_blocking(p.SPI1, p.PB3, p.PA7, p.PA6, spi_config); + + let mut cs = Output::new(p.PA15, Level::High, Speed::VeryHigh); + + loop { + let mut buf = [0x0Au8; 4]; + cs.set_low(); + unwrap!(spi.blocking_transfer_in_place(&mut buf)); + cs.set_high(); + info!("xfer {=[u8]:x}", buf); + Timer::after_millis(300).await; + } +} diff --git a/src/bin/uart.rs b/src/bin/uart.rs new file mode 100644 index 0000000..e495172 --- /dev/null +++ b/src/bin/uart.rs @@ -0,0 +1,42 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::usart::{Config, InterruptHandler, Uart}; +use embassy_stm32::{bind_interrupts, peripherals}; +use {defmt_rtt as _, panic_probe as _}; + +bind_interrupts!(struct Irqs{ + LPUART1 => InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let p = embassy_stm32::init(Default::default()); + + info!("Starting system"); + + let mut config = Config::default(); + config.baudrate = 9600; + + let mut usart = Uart::new( + p.LPUART1, p.PC0, p.PC1, Irqs, p.DMA1_CH2, p.DMA1_CH3, config, + ) + .unwrap(); + + unwrap!(usart.write(b"Hello Embassy World!\r\n").await); + + let mut buf = [0u8; 300]; + loop { + let result = usart.read_until_idle(&mut buf).await; + match result { + Ok(size) => { + info!("result {}, size {}", result, size); + } + Err(_err) => { + info!("error..."); + } + } + } +} diff --git a/src/bin/watchdog.rs b/src/bin/watchdog.rs new file mode 100644 index 0000000..f4917a1 --- /dev/null +++ b/src/bin/watchdog.rs @@ -0,0 +1,25 @@ +#![no_std] +#![no_main] + +use defmt::*; +use embassy_executor::Spawner; +use embassy_stm32::wdg::IndependentWatchdog; +use embassy_time::Timer; +use {defmt_rtt as _, panic_probe as _}; + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + // Initialize and create handle for devicer peripherals + let p = embassy_stm32::init(Default::default()); + + // Configure the independent watchdog timer + let mut wdg = IndependentWatchdog::new(p.IWDG, 2_000_000); + + info!("Watchdog start"); + wdg.unleash(); + + loop { + Timer::after_secs(1).await; + wdg.pet(); + } +}