Skip to content

Commit 5e46ff0

Browse files
committed
[examples] Add stm32g0-lilos-node example
1 parent 183a2d8 commit 5e46ff0

File tree

14 files changed

+1713
-0
lines changed

14 files changed

+1713
-0
lines changed

.github/workflows/rust.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,6 @@ jobs:
2828
run: cargo fmt --check
2929
- name: Run tests
3030
run: cargo test --verbose
31+
- name: Build stm32g0-lilos-node example
32+
working-directory: examples/stm32g0-lilos-node
33+
run: cargo build
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
2+
runner = "probe-rs run --always-print-stacktrace --chip STM32G0B1CBTx"
3+
4+
rustflags = [
5+
"-C", "link-arg=-Tlink.x",
6+
"-C", "link-arg=-Tdefmt.x",
7+
]
8+
9+
[build]
10+
target = "thumbv6m-none-eabi"
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[package]
2+
name = "stm32g0-lilos-node"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
# Don't include this project in the workspace above it
7+
[workspace]
8+
9+
[dependencies]
10+
# Local
11+
zencan-node = { path = "../../zencan-node", default-features = false, features = ["defmt"]}
12+
13+
# External
14+
cortex-m = { version = "0.7.4", features=["critical-section-single-core"] }
15+
cortex-m-rt = "0.7.5"
16+
critical-section = "1.2.0"
17+
defmt = "1.0.1"
18+
embedded-io = "0.6.1"
19+
fdcan = { version = "0.2.1", features = ["fdcan_g0_g4_l5"] }
20+
stm32-metapac = { version = "15.0.0", features = ["stm32g0b1cb", "rt"] }
21+
hash32 = "1.0.0"
22+
lilos = "1.3"
23+
panic-probe = { version = "1.0.0", features = ["print-defmt"] }
24+
rtt-target = { version = "0.6.1", features = ["defmt"] }
25+
26+
[build-dependencies]
27+
zencan-build = { path = "../../zencan-build" }
28+
29+
[profile.dev]
30+
codegen-units = 1 # better optimizations
31+
debug = true
32+
lto = true # better optimizations
33+
opt-level = "s"
34+
35+
[profile.release]
36+
codegen-units = 1 # better optimizations
37+
debug = true # symbols are nice and they don't increase the size on Flash
38+
lto = true # better optimizations
39+
opt-level = "s"
40+
overflow-checks = true
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
[default.general]
2+
chip = "STM32G0B1CBTx"
3+
4+
[default.rtt]
5+
enabled = true
6+
up_channels = [
7+
{ channel = 0, name = "Defmt Output", up_mode = "NoBlockTrim", format = "Defmt" },
8+
]
9+
10+
[connect.flashing]
11+
enabled = false
12+
13+
[connect.reset]
14+
enabled = false

examples/stm32g0-lilos-node/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# stm32g0-lilos-node
2+
3+
An example node targetting an STM32G0B1 cortex M0 MCU, using lilos as an async runtime.
4+
5+
It reads ADC channels and stores them into objects.

examples/stm32g0-lilos-node/build.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
fn main() {
2+
if let Err(e) =
3+
zencan_build::build_node_from_device_config("ZENCAN_CONFIG", "zencan_config.toml")
4+
{
5+
eprintln!("Failed to parse zencan_config.toml: {}", e.to_string());
6+
std::process::exit(-1);
7+
}
8+
}

examples/stm32g0-lilos-node/memory.x

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
MEMORY
2+
{
3+
/* NOTE K = KiBi = 1024 bytes */
4+
/* FLASH is 128k, but last two 2k pages are reserved for config data */
5+
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 124K
6+
RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 128K
7+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
[toolchain]
2+
channel = "1.86"
3+
targets = [ "thumbv6m-none-eabi" ]
4+
profile = "default"
5+
components = [ "rustfmt", "rust-analyzer", "clippy", "llvm-tools" ]
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
use stm32_metapac as pac;
2+
3+
/// Setup the ADC for reading the analog channels
4+
pub fn configure_adc() {
5+
pac::RCC.apbenr2().modify(|w| w.set_adcen(true));
6+
pac::ADC1.cr().modify(|w| {
7+
w.set_advregen(true);
8+
});
9+
10+
// Delay 1/40th of a second for regulator to turn on
11+
cortex_m::asm::delay(16_000_000 / 40);
12+
13+
pac::ADC1.cr().modify(|w| w.set_adcal(true));
14+
15+
// Wait for calibration to complete
16+
while pac::ADC1.cr().read().adcal() {}
17+
18+
// Clear ADRDY IRQ
19+
pac::ADC1.isr().write(|w| w.set_adrdy(true));
20+
// Enable
21+
pac::ADC1.cr().modify(|w| w.set_aden(true));
22+
23+
// Wait for ADRDY signal
24+
while !pac::ADC1.isr().read().adrdy() {}
25+
// Clear the flag again
26+
pac::ADC1.isr().write(|w| w.set_adrdy(true));
27+
28+
pac::ADC1.cfgr1().modify(|w| {
29+
w.set_cont(false);
30+
});
31+
32+
// Enable oversampling
33+
pac::ADC1.cfgr2().modify(|w| {
34+
w.set_ovse(true);
35+
// 16x oversample
36+
w.set_ovsr(3);
37+
// shift by 4 bits
38+
w.set_ovss(4);
39+
});
40+
41+
pac::ADC1
42+
.smpr()
43+
.modify(|w| w.set_smp1(pac::adc::vals::SampleTime::CYCLES39_5));
44+
}
45+
46+
pub fn read_adc(channel: usize) -> u16 {
47+
// Configure channel
48+
pac::ADC1.chselr().write(|w| w.set_chsel(1 << channel));
49+
// Clear EOC
50+
pac::ADC1.isr().write(|w| w.set_eoc(true));
51+
// Start sampling
52+
pac::ADC1.cr().modify(|w| w.set_adstart(true));
53+
// Wait for complete
54+
while !pac::ADC1.isr().read().eoc() {}
55+
// Read result
56+
pac::ADC1.dr().read().regular_data()
57+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
//! Flash driver for STM32G0 family
2+
3+
#![allow(dead_code)]
4+
5+
use core::{
6+
ptr::slice_from_raw_parts,
7+
sync::atomic::{Ordering, fence},
8+
};
9+
10+
use crate::{
11+
pac::flash::Flash,
12+
persist::{FlashAccess, Page},
13+
};
14+
15+
const PAGE_SIZE: usize = 2048;
16+
17+
pub struct FlashError {}
18+
19+
pub struct Stm32g0Flash {
20+
flash: Flash,
21+
22+
page_a: usize,
23+
page_b: usize,
24+
}
25+
26+
pub struct Stm32g0FlashUnlocked<'a> {
27+
flash: &'a Flash,
28+
29+
cache: [u8; 8],
30+
active_page: Page,
31+
32+
page_a: usize,
33+
page_b: usize,
34+
35+
write_pos: usize,
36+
}
37+
38+
impl Stm32g0Flash {
39+
pub fn new(flash: Flash, page_a: usize, page_b: usize) -> Self {
40+
Self {
41+
flash,
42+
page_a,
43+
page_b,
44+
}
45+
}
46+
47+
fn page_slice(&self, n: usize) -> &[u8] {
48+
let addr = (crate::pac::FLASH_BASE + n * PAGE_SIZE) as *const u8;
49+
// Safety: I don't think the flash is going anywhere
50+
unsafe { slice_from_raw_parts(addr, PAGE_SIZE).as_ref().unwrap() }
51+
}
52+
53+
pub fn page(&self, page: Page) -> &[u8] {
54+
match page {
55+
Page::A => self.page_slice(self.page_a),
56+
Page::B => self.page_slice(self.page_b),
57+
}
58+
}
59+
60+
pub fn unlock<'a>(&'a mut self) -> Stm32g0FlashUnlocked<'a> {
61+
self.flash.keyr().write_value(0x45670123);
62+
self.flash.keyr().write_value(0xCDEF89AB);
63+
Stm32g0FlashUnlocked {
64+
flash: &self.flash,
65+
cache: [0; 8],
66+
active_page: Page::A,
67+
page_a: self.page_a,
68+
page_b: self.page_b,
69+
write_pos: 0,
70+
}
71+
}
72+
}
73+
74+
impl<'a> Stm32g0FlashUnlocked<'a> {
75+
pub fn lock(self) {
76+
self.flash.cr().modify(|w| w.set_lock(true));
77+
}
78+
}
79+
80+
impl<'a> Drop for Stm32g0FlashUnlocked<'a> {
81+
fn drop(&mut self) {
82+
self.flash.cr().modify(|w| w.set_lock(true));
83+
}
84+
}
85+
86+
impl<'a> Stm32g0FlashUnlocked<'a> {
87+
fn page_slice(&self, n: usize) -> &[u8] {
88+
let addr = (crate::pac::FLASH_BASE + n * PAGE_SIZE) as *const u8;
89+
// Safety: I don't think the flash is going anywhere
90+
unsafe { slice_from_raw_parts(addr, PAGE_SIZE).as_ref().unwrap() }
91+
}
92+
93+
fn page_ptr_mut(&mut self, n: usize, offset: usize) -> *mut u32 {
94+
(crate::pac::FLASH_BASE + n * PAGE_SIZE + offset) as *mut u32
95+
}
96+
97+
fn active_page_num(&self) -> usize {
98+
match self.active_page {
99+
Page::A => self.page_a,
100+
Page::B => self.page_b,
101+
}
102+
}
103+
104+
fn write_cache(&mut self, offset: usize) {
105+
let word1 = u32::from_le_bytes(self.cache[0..4].try_into().unwrap());
106+
let word2 = u32::from_le_bytes(self.cache[4..8].try_into().unwrap());
107+
108+
let dst1 = self.page_ptr_mut(self.active_page_num(), offset);
109+
let dst2 = self.page_ptr_mut(self.active_page_num(), offset + 4);
110+
self.clear_errors();
111+
self.wait_busy();
112+
113+
self.flash.cr().modify(|w| w.set_pg(true));
114+
115+
// Writing to flash must be done as a sequence of two 32-bit writes, starting on a 64-bit
116+
// aligned address
117+
fence(Ordering::SeqCst);
118+
unsafe { core::ptr::write_volatile(dst1, word1) };
119+
fence(Ordering::SeqCst);
120+
unsafe { core::ptr::write_volatile(dst2, word2) };
121+
fence(Ordering::SeqCst);
122+
self.wait_busy();
123+
124+
self.flash.sr().write(|w| w.set_eop(true));
125+
self.flash.cr().modify(|w| w.set_pg(false));
126+
}
127+
128+
fn clear_errors(&mut self) -> u32 {
129+
let sr = self.flash.sr().read().0;
130+
// Clear error flags
131+
self.flash.sr().modify(|w| {
132+
w.set_fasterr(true);
133+
w.set_miserr(true);
134+
w.set_operr(true);
135+
w.set_pgserr(true);
136+
w.set_pgaerr(true);
137+
w.set_progerr(true);
138+
w.set_rderr(true);
139+
w.set_sizerr(true);
140+
w.set_wrperr(true);
141+
});
142+
143+
sr
144+
}
145+
146+
fn wait_busy(&mut self) {
147+
// note: bsy() here is bsy1, bit 16
148+
while self.flash.sr().read().bsy() {}
149+
}
150+
}
151+
152+
impl<'a> FlashAccess for Stm32g0FlashUnlocked<'a> {
153+
type Error = FlashError;
154+
155+
fn page(&self, page: Page) -> &[u8] {
156+
match page {
157+
Page::A => self.page_slice(self.page_a),
158+
Page::B => self.page_slice(self.page_b),
159+
}
160+
}
161+
162+
fn set_write_page(&mut self, page: Page) {
163+
self.active_page = page;
164+
self.write_pos = 0;
165+
}
166+
167+
fn erase(&mut self) {
168+
self.wait_busy();
169+
self.clear_errors();
170+
171+
self.flash.cr().modify(|w| {
172+
w.set_per(true);
173+
w.set_pnb(self.active_page_num() as u8);
174+
});
175+
self.flash.cr().modify(|w| {
176+
w.set_strt(true);
177+
});
178+
179+
self.wait_busy();
180+
181+
self.flash.cr().modify(|w| w.set_per(false));
182+
}
183+
184+
fn write(&mut self, data: &[u8]) {
185+
// Data has to be written in 64-bit chunks, aligned to 64-bit words.
186+
187+
let mut in_pos = 0;
188+
189+
while in_pos < data.len() {
190+
let buf_pos = self.write_pos % 8;
191+
let to_copy = (8 - buf_pos).min(data.len() - in_pos);
192+
self.cache[buf_pos..buf_pos + to_copy].copy_from_slice(&data[in_pos..in_pos + to_copy]);
193+
in_pos += to_copy;
194+
self.write_pos += to_copy;
195+
if self.write_pos % 8 == 0 {
196+
self.write_cache(self.write_pos - 8);
197+
}
198+
}
199+
}
200+
201+
fn finalize(&mut self) {
202+
// Pad remaining bytes with 0s
203+
let buf_pos = self.write_pos % 8;
204+
if buf_pos == 0 {
205+
return;
206+
}
207+
self.cache[buf_pos..8].fill(0);
208+
self.write_cache(self.write_pos & !0x7);
209+
}
210+
}

0 commit comments

Comments
 (0)