In one of my project, I want to use a small microcontroller. Because of it's low cost, low power use and simplicity, I decided to use a STM32l011. With it's TSSOP footprint, I can solder it manually.
The MCU will be sleeping most of the time, and waken up by another MCU via I2C. So I have to put it in STOP mode with wake up from I2C.
Rust HAL do not have support for I2C slave. And anyway for fine tuning, it is easier to use PAC crates and access registers direcly. As all official documentation uses register names, it also makes documentation navigation easier.
The main issue I had is with STOP mode, I could never wake the device on I2C.
The solution while simple, took me hours to find. Instead of going in STOP
mode with WIF
we must go in STOP mode with SLEEPONEXIT
and the device
will go to sleep when there is no more interrupt waiting.
Below is some example working code for an STM32L011K4Tx.
#![no_main]
#![no_std]
// Use RTT for log and panic
use panic_rtt_target as _;
use rtt_target::rtt_init_print;
use rtt_target::rprintln;
use cortex_m::asm;
use rtic::app;
use stm32l0xx_hal::pac;
pub struct Buffer {
data: [u8; 4],
data_idx: usize,
}
pub struct Power {
pwr: pac::PWR,
rcc: pac::RCC,
scb: pac::SCB,
}
#[app(device = stm32l0xx_hal::pac, peripherals = true)]
const APP: () = {
struct Resources {
i2c: pac::I2C1,
buffer: Buffer,
power: Power,
}
#[init]
fn init(cx: init::Context) -> init::LateResources {
let rcc = cx.device.RCC;
let scb = cx.core.SCB;
let pwr = cx.device.PWR;
let gpio = cx.device.GPIOA;
let i2c = cx.device.I2C1;
// Enable HSI16 clock
rcc.cr.modify(|_, w| w.hsi16on().set_bit());
// Enable GPIOA clock
rcc.iopenr.modify(|_, w| w.iopaen().enabled());
// Enable peripheral clock
rcc.apb1enr.modify(|_, w| w.pwren().enabled());
// Enable I2C clock
rcc.apb1enr.modify(|_, w| w.i2c1en().enabled());
// Set I2C clock source as hsi16
rcc.ccipr.modify(|_, w| w.i2c1sel().hsi16());
// Reset I2C
rcc.apb1rstr.modify(|_, w| w.i2c1rst().set_bit());
rcc.apb1rstr.modify(|_, w| w.i2c1rst().clear_bit());
// Disable RTC register protection
pwr.cr.modify(|_, w| w.dbp().set_bit());
// Enable LSE (external crystal) and set it as RTC source
rcc.csr.modify(|_, w| {
w.lseon().set_bit();
w.rtcsel().lse();
w.rtcen().set_bit()
});
// Restore RTC register protection
pwr.cr.modify(|_, w| w.dbp().clear_bit());
// Use HSI16 as wakeup clock and system clock
rcc.cfgr.modify(|_, w| {
w.stopwuck().set_bit();
w.sw().hsi16()
});
// SCL - PA9 AF1
// SDA - PA10 AF1
// Configure SDA/SCL as open drain
gpio.otyper.modify(|_, w| {
w.ot9().open_drain(); // Open drain on SCL
w.ot10().open_drain() // Open drain on SDA
});
// Alternate function 1
gpio.afrh.modify(|_, w| {
w.afsel9().af1(); // Alternate function 1 on SCL
w.afsel10().af1() // Alternate function 1 on SDA
});
// Configure SDA/SCL as alternate function
gpio.moder.modify(|_, w| {
w.mode9().alternate();
w.mode10().alternate()
});
// Timing register, per AN4235
i2c.timingr.modify(|_, w| {
w.presc().bits(0x06);
w.scll().bits(0x13);
w.sclh().bits(0x0f);
w.sdadel().bits(0x02);
w.scldel().bits(0x04)
});
// I2C configuration
i2c.cr1.modify(|_, w| {
w.addrie().enabled(); // Address match interrupt
w.txie().enabled(); // Transmit interrupt
w.stopie().enabled(); // Stop interrupt
w.wupen().enabled(); // Wakeup from STOP
// Both are required for WUPEN
w.dnf().no_filter(); // Filter cannot be on with wake up
w.nostretch().clear_bit(); // Enable stretch on SCL
w.pe().enabled() // Peripheral enable
});
// I2C own address
i2c.oar1.modify(|_, w| {
w.oa1().bits(0b0011001000); // Set address
w.oa1mode().bit7(); // Address is 7 bits
w.oa1en().enabled() // Enable address 1
});
rtt_init_print!();
rprintln!("I2C configured");
rprintln!("I2C {:?}", i2c.txdr.read().bits());
rprintln!("I2C {:?}", i2c.cr1.read().bits());
init::LateResources {
power: Power { scb, pwr, rcc },
i2c,
buffer: Buffer {
data: [1, 2, 3, 4],
data_idx: 0,
},
}
}
#[idle()]
fn idle(cx: idle::Context) -> ! {
loop {}
}
#[task(resources=[power])]
fn sleep(cx: sleep::Context) {
let power = cx.resources.power;
power.scb.set_sleepdeep();
power.scb.set_sleeponexit();
power.pwr.cr.modify(|_, w| {
// Clear wakeup flag
w.cwuf().set_bit();
// Ultra low power off
w.ulp().clear_bit();
// STOP mode when deepsleep
w.pdds().stop_mode();
// Regulator in MAIN mode in STOP mode
w.lprun().clear_bit();
// Regulator ON in SLEEP
w.lpsdsr().clear_bit();
// Regulator in MAIN mode in STOP mode
w.lpds().clear_bit()
});
}
#[task(binds=I2C1,spawn=[sleep], resources=[i2c, buffer, power])]
fn i2c(cx: i2c::Context) {
let power = cx.resources.power;
let i2c = cx.resources.i2c;
let buf = cx.resources.buffer;
if i2c.isr.read().addr().is_match_() {
power.scb.clear_sleeponexit();
power.scb.clear_sleepdeep();
i2c.icr.write(|w| w.addrcf().clear());
buf.data_idx = 0;
let b = buf.data.get(buf.data_idx).unwrap_or(&0);
i2c.isr.modify(|_, w| w.txe().set_bit());
i2c.txdr.write(|w| w.txdata().bits(*b));
buf.data_idx += 1;
}
if i2c.isr.read().txis().is_empty() {
let b = buf.data.get(buf.data_idx).unwrap_or(&0);
i2c.txdr.write(|w| w.txdata().bits(*b));
buf.data_idx += 1;
}
if i2c.isr.read().stopf().is_stop() {
i2c.icr.write(|w| w.stopcf().clear());
cx.spawn.sleep().unwrap();
}
}
extern "C" {
fn TIM3();
}
};