Skip to content

Commit b87881a

Browse files
committed
Add bluepill-prog (stm32f103)
Not tested on flash chip yet but does not fail miserably. The API to use the CDC ACM seems to differ slightly from RP where waiting for connection needs to happen first. Signed-off-by: Arthur Heymans <arthur@aheymans.xyz>
1 parent 8b88f50 commit b87881a

File tree

9 files changed

+635
-63
lines changed

9 files changed

+635
-63
lines changed

Cargo.lock

Lines changed: 312 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
[workspace]
22
members = [
3+
"bluepill-prog",
34
"picoprog",
45
"serprog"
56
]
@@ -9,30 +10,37 @@ resolver = "2"
910
assign-resources = "0.4.1"
1011
cortex-m = { version = "0.7.7", features = ["inline-asm", "critical-section"] }
1112
cortex-m-rt = "0.7.5"
13+
critical-section = "1.2.0"
14+
defmt = "0.3.10"
15+
defmt-rtt = "0.4.1"
1216
embassy-executor = { version = "0.7.0", features = ["arch-cortex-m", "executor-thread", "executor-interrupt", "nightly"] }
1317
embassy-futures = "0.1.1"
14-
embedded-hal = "1.0.0"
15-
embedded-hal-async = "1.0.0"
16-
serprog = { path = "./serprog" }
1718
embassy-rp = { version = "0.3.0", features = ["unstable-pac", "time-driver", "critical-section-impl", "rom-func-cache", "rom-v2-intrinsics", "rp2040"] }
19+
embassy-stm32 = { version = "0.2.0" }
1820
embassy-sync = "0.6.2"
1921
embassy-time = "0.4.0"
2022
embassy-usb = { version = "0.4.0", features = ["max-handler-count-6", "max-interface-count-6"] }
2123
embassy-usb-logger = "0.4.0"
24+
embedded-hal = "1.0.0"
25+
embedded-hal-async = "1.0.0"
2226
futures = { version = "0.3.31", default-features = false, features = ["async-await", "cfg-target-has-atomic", "unstable"] }
2327
heapless = { version = "0.8.0", features = ["portable-atomic-critical-section", "ufmt"] }
2428
log = "0.4.26"
29+
panic-probe = { version = "0.3.2", features = ["print-defmt"] }
2530
portable-atomic = { version = "1.11.0", features = ["critical-section"] }
31+
serprog = { path = "./serprog" }
2632
static_cell = "2.1.0"
33+
stm32-fmc = { version = "0.4.0" }
34+
tock-registers = "0.9.0"
2735
ufmt = "0.2.0"
2836
zerocopy = { version = "0.8", features = ["derive"] }
29-
tock-registers = "0.9.0"
3037
num_enum = { version = "0.7.3", default-features = false }
3138

3239
[patch.crates-io]
3340
embassy-executor = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }
3441
embassy-futures = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }
3542
embassy-rp = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }
43+
embassy-stm32 = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }
3644
embassy-sync = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }
3745
embassy-time = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }
3846
embassy-usb = { git = "https://github.com/embassy-rs/embassy", rev = "17301c00e986c5b8536435ea31ebf5aaf13aed17" }

bluepill-prog/.cargo/config.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[target.'cfg(all(target_arch = "arm", target_os = "none"))']
2+
linker = "flip-link"
3+
runner = "probe-rs run --chip STM32F103C8"
4+
5+
[build]
6+
target = "thumbv7m-none-eabi"
7+
8+
[unstable]
9+
build-std = ["core"]
10+
build-std-features = ["panic_immediate_abort"]
11+
12+
[env]
13+
DEFMT_LOG = "debug"

bluepill-prog/Cargo.toml

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
[package]
2+
name = "bluepill-prog"
3+
version = "0.1.0"
4+
edition = "2021"
5+
license = "Apache-2.0"
6+
authors = [ "Arthur Heymans <arthur@aheymans.xyz>" ]
7+
8+
[dependencies]
9+
assign-resources.workspace = true
10+
cortex-m = { workspace = true, features = ["inline-asm", "critical-section-single-core"] }
11+
cortex-m-rt.workspace = true
12+
critical-section.workspace = true
13+
defmt-rtt.workspace = true
14+
defmt.workspace = true
15+
embassy-executor = { workspace = true, features = ["defmt", "arch-cortex-m", "executor-thread", "nightly"] }
16+
embassy-futures = { workspace = true, features = ["defmt"] }
17+
embassy-stm32 = { workspace = true, features = ["defmt", "stm32f103c8", "memory-x", "rt", "time-driver-any"] }
18+
embassy-sync = { workspace = true, features = ["defmt"] }
19+
embassy-time = { workspace = true, features = ["defmt", "defmt-timestamp-uptime", "tick-hz-32_768"] }
20+
embassy-usb = { workspace = true, features = ["defmt"] }
21+
futures.workspace = true
22+
heapless = { workspace = true, features = ["defmt-03", "portable-atomic", "portable-atomic-critical-section", "ufmt"] }
23+
panic-probe.workspace = true
24+
portable-atomic.workspace = true
25+
serprog.workspace = true
26+
static_cell.workspace = true
27+
stm32-fmc = { workspace = true, features = ["defmt"] }
28+
ufmt.workspace = true
29+
30+
[profile.release]
31+
debug = true
32+
incremental = false
33+
codegen-units = 1
34+
opt-level = "s"
35+
lto = "fat"

bluepill-prog/build.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
fn main() {
2+
println!("cargo:rustc-link-arg-bins=--nmagic");
3+
println!("cargo:rustc-link-arg-bins=-Tlink.x");
4+
println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
5+
}

bluepill-prog/src/main.rs

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
#![no_std]
2+
#![no_main]
3+
#![allow(async_fn_in_trait)]
4+
#![allow(incomplete_features)]
5+
#![feature(impl_trait_in_assoc_type)]
6+
#![feature(type_alias_impl_trait)]
7+
8+
use defmt_rtt as _; // global logger
9+
10+
use assign_resources::assign_resources;
11+
use core::panic::PanicInfo;
12+
use cortex_m::peripheral::SCB;
13+
use embassy_executor::Spawner;
14+
use embassy_stm32::bind_interrupts;
15+
use embassy_stm32::gpio::{Level, Output, Speed};
16+
use embassy_stm32::peripherals::{self, USB};
17+
use embassy_stm32::spi::{Config as SpiConfig, Spi};
18+
use embassy_stm32::time::Hertz;
19+
use embassy_stm32::usb::{Driver, InterruptHandler as USBInterruptHandler};
20+
use embassy_time::Timer;
21+
use embassy_usb::class::cdc_acm::{CdcAcmClass, State};
22+
use embassy_usb::driver::EndpointError;
23+
use embassy_usb::{Config as UsbConfig, UsbDevice};
24+
use heapless::String;
25+
use static_cell::StaticCell;
26+
use ufmt::uwrite;
27+
28+
use defmt::{error, info};
29+
30+
bind_interrupts!(struct Irqs {
31+
USB_LP_CAN1_RX0 => USBInterruptHandler<USB>;
32+
});
33+
34+
assign_resources! {
35+
usb: UsbResources {
36+
peripheral: USB,
37+
dm: PA11,
38+
dp: PA12,
39+
}
40+
spi: SpiResources{
41+
peripheral: SPI1,
42+
clk: PA5,
43+
mosi: PA7,
44+
mosi_dma: DMA1_CH3,
45+
miso: PA6,
46+
miso_dma: DMA1_CH2,
47+
cs: PA4,
48+
led: PC13,
49+
}
50+
}
51+
52+
// According to Serial Flasher Protocol Specification - version 1
53+
#[embassy_executor::main]
54+
async fn main(spawner: Spawner) {
55+
let mut config = embassy_stm32::Config::default();
56+
{
57+
use embassy_stm32::rcc::*;
58+
config.rcc.hse = Some(Hse {
59+
freq: Hertz(8_000_000),
60+
// Oscillator for bluepill, Bypass for nucleos.
61+
mode: HseMode::Oscillator,
62+
});
63+
config.rcc.pll = Some(Pll {
64+
src: PllSource::HSE,
65+
prediv: PllPreDiv::DIV1,
66+
mul: PllMul::MUL9,
67+
});
68+
config.rcc.sys = Sysclk::PLL1_P;
69+
config.rcc.ahb_pre = AHBPrescaler::DIV1;
70+
config.rcc.apb1_pre = APBPrescaler::DIV2;
71+
config.rcc.apb2_pre = APBPrescaler::DIV1;
72+
}
73+
let p = embassy_stm32::init(config);
74+
let mut r = split_resources!(p);
75+
76+
{
77+
// BluePill board has a pull-up resistor on the D+ line.
78+
// Pull the D+ pin down to send a RESET condition to the USB bus.
79+
// This forced reset is needed only for development, without it host
80+
// will not reset your device when you upload new firmware.
81+
let _dp = Output::new(&mut r.usb.dp, Level::Low, Speed::Low);
82+
Timer::after_millis(10).await;
83+
}
84+
85+
info!("hello");
86+
87+
let driver = Driver::new(r.usb.peripheral, Irqs, r.usb.dp, r.usb.dm);
88+
89+
let uid: [u8; 8] = embassy_stm32::uid::uid()[..8].try_into().unwrap();
90+
91+
static UID_STR: StaticCell<String<16>> = StaticCell::new();
92+
let uid_str = UID_STR.init(String::<16>::new());
93+
for byte in uid.iter() {
94+
uwrite!(uid_str, "{:02X}", *byte).unwrap_or_default();
95+
}
96+
97+
let config = {
98+
let mut config = UsbConfig::new(0x1ced, 0xc0fe);
99+
config.manufacturer = Some("9elements");
100+
config.product = Some("bluepill-prog");
101+
config.serial_number = Some(uid_str.as_str());
102+
config.max_power = 100;
103+
config.max_packet_size_0 = 64;
104+
105+
// Required for windows compatibility.
106+
// https://developer.nordicsemi.com/nRF_Connect_SDK/doc/1.9.1/kconfig/CONFIG_CDC_ACM_IAD.html#help
107+
config.device_class = 0xEF;
108+
config.device_sub_class = 0x02;
109+
config.device_protocol = 0x01;
110+
config.composite_with_iads = true;
111+
config
112+
};
113+
114+
let mut builder = {
115+
static CONFIG_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
116+
static BOS_DESCRIPTOR: StaticCell<[u8; 256]> = StaticCell::new();
117+
static CONTROL_BUF: StaticCell<[u8; 64]> = StaticCell::new();
118+
119+
let builder = embassy_usb::Builder::new(
120+
driver,
121+
config,
122+
CONFIG_DESCRIPTOR.init([0; 256]),
123+
BOS_DESCRIPTOR.init([0; 256]),
124+
&mut [], // no msos descriptors
125+
CONTROL_BUF.init([0; 64]),
126+
);
127+
builder
128+
};
129+
130+
let serprog_class = {
131+
static STATE: StaticCell<State> = StaticCell::new();
132+
let state = STATE.init(State::new());
133+
CdcAcmClass::new(&mut builder, state, 64)
134+
};
135+
136+
let usb = builder.build();
137+
138+
// We can't really recover here so just unwrap
139+
spawner.spawn(usb_task(usb)).unwrap();
140+
spawner.spawn(serprog_task(serprog_class, r.spi)).unwrap();
141+
142+
loop {
143+
embassy_time::Timer::after(embassy_time::Duration::from_secs(1)).await;
144+
}
145+
}
146+
147+
type CustomUsbDriver = Driver<'static, USB>;
148+
type CustomUsbDevice = UsbDevice<'static, CustomUsbDriver>;
149+
150+
struct Disconnected {}
151+
152+
impl From<EndpointError> for Disconnected {
153+
fn from(val: EndpointError) -> Self {
154+
match val {
155+
EndpointError::BufferOverflow => defmt::panic!("USB buffer overflow"),
156+
EndpointError::Disabled => Disconnected {},
157+
}
158+
}
159+
}
160+
161+
#[embassy_executor::task]
162+
async fn usb_task(mut usb: CustomUsbDevice) -> ! {
163+
usb.run().await
164+
}
165+
166+
#[embassy_executor::task]
167+
async fn serprog_task(mut class: CdcAcmClass<'static, CustomUsbDriver>, r: SpiResources) -> ! {
168+
let mut config = SpiConfig::default();
169+
config.frequency = Hertz(12_000_000); // 12 MHz
170+
171+
let spi = Spi::new(
172+
r.peripheral,
173+
r.clk,
174+
r.mosi,
175+
r.miso,
176+
r.mosi_dma,
177+
r.miso_dma,
178+
config,
179+
);
180+
let cs = Output::new(r.cs, Level::High, Speed::Low);
181+
let led = Output::new(r.led, Level::Low, Speed::Low);
182+
183+
loop {
184+
class.wait_connection().await;
185+
let serprog = serprog::Serprog::new(
186+
spi,
187+
cs,
188+
led,
189+
class,
190+
None::<fn(&mut Spi<'_, embassy_stm32::mode::Async>, u32)>,
191+
);
192+
serprog.run_loop().await
193+
}
194+
}
195+
196+
#[panic_handler]
197+
fn panic(info: &PanicInfo) -> ! {
198+
// Print out the panic info
199+
error!("Panic occurred: {:?}", info);
200+
201+
// Reboot the system
202+
SCB::sys_reset();
203+
}

rust-toolchain.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,5 @@ channel = "nightly-2025-02-01"
33
components = [ "rust-src", "rustfmt", "llvm-tools", "miri" ]
44
targets = [
55
"thumbv6m-none-eabi",
6+
"thumbv7m-none-eabi",
67
]

serprog/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ license = "Apache-2.0"
88
embassy-usb.workspace = true
99
embedded-hal.workspace = true
1010
embedded-hal-async.workspace = true
11-
log.workspace = true
11+
#log.workspace = true
12+
defmt.workspace = true
1213
num_enum.workspace = true
1314
tock-registers.workspace = true
1415
zerocopy.workspace = true

0 commit comments

Comments
 (0)