LPM, ADC, RTC etc. on STM32L1

Hi everyone,

We are using RIOT in a commercial project - LoRa modules based on Semtech SX1276 transceiver and STM32L151CC MCU; previously, we had some experience with Contiki and didn't like it at all. RIOT is much more attractive, but unfortunately, there's only basic support for STM32L1: no low-power modes at all, I2C driver hangs on any bus error including NACK, etc. Current RIOT version doesn't even builds for STM32L1-based boards.

So we fixed it.

Here it is: https://github.com/unwireddevices/RIOT/tree/stm32l1_lpm

What has been fixed/improved:

1) Low-Power Modes (LPM). DEEPSLEEP with configurable core clock (default is 65 kHz MSI), STOP, STANDBY, switching to MSI clock, including LP-RUN mode. GPIOs are automatically configured before entering sleep to minimize power consumptions according to the rules:     a) if SPI interface is enabled, switch SCK and MOSI to DO, 0; NSS to DO, 1     b) if UART interface is enabled, switch TX to DO, 1     c) disable USART controllers (otherwise we received some garbage on L152RE - but not on L151CC - while playing with GPIOs)     d) keep state of GPIOs configured for EXTI (external interrupts - you may have some buttons etc. on them used to wake up MCU, so you don't want them to be disabled during sleep)     e) keep state of GPIOs configured with lpm_arch_add_gpio_exclusion(gpio_t gpio) and lpm_arch_del_gpio_exclusion(gpio_t gpio) functions (e.g. you have some peripheral devices attached to GPIOs and don't want them to change their state during sleep)      f) switch all other GPIOs to AIN, no pull-up, no pull-down

After waking up, all GPIOs will be restored to their original state.

NB: there's CPU_NUMBER_OF_PORTS define, it should be set according to your MCU. For example, L152RE has 8 ports (GPIOA...GPIOH) while L151CC only 6 ports (GPIOA...GPIOE, GPIOH). Although for some reason they didn't mention it in datasheet nor RM0038, accessing GPIOF on L151CC will result in Bus Fault (but if you want to get rid of CPU_NUMBER_OF_PORTS, you can always catch HardFaults manually).

NB: to get the best power consumption possible, you have to set digital inputs of external devices to 0 or 1 if they don't have pullups (e.g. I2C have external pullups as a rule, but SPI doesn't). Otherwise, if you just switch corresponding GPIO to AIN, input connected to it will be in some state defined by random noises, probably switching between 0 and 1 and consuming few dozens microamperes (e.g. if you just switch SPI off and corresponding pins to AIN with SX1276 connected to it, it will consume about 100-200 uA instead of expected 1 uA).

NB: it is possible to switch between core frequencies by calling lpm_set(LPM_IDLE) (switch to MSI) and lpm_set(LPM_RUN) (back to default clock, usually HSE or HSI). Timer frequencies and STDIO UART baudrate will be recalculated and preserved if possible (e.g. don't expect 460 kbps UART with 32 kHz MSI clock), but other clocks have to be fixed manually.

2) ADC. Support for special channels - on-chip temperature (not very useful) and reference voltage (extremely useful).

NB: reference voltage is NOT reported, instead Vdd is calculated. As a rule, you don't need to know Vref, but you have to know Vdd to convert ADC readings to volts.

3) I2C. Two major problems solved:

    a) inability to handle NACK. Default I2C driver _always_ waits for ACK, so any problem on a bus will result in an endless loop. NACK is needed to check if device is present on a bus, and some devices use NACK as part of their regular protocol (SHT21, for example)     b) inability to use 2 devices on a same I2C bus. Each device tries to initialize the bus, and initializing already initialized I2C bus results in BERR. Simplest solution is to leave initialization as it is, but turn off I2C before it.

4) RTC timer can be used to wake up CPU from SLEEP/STOP/STANDBY.

5) Small fixes. Proper GPIO operations (with intermediary variable and without intermediary glitches), CMSIS updated, BRR register enabled for HD/XL devices only, etc.

NB: everything is provided AS IS. Code was mostly tested on our own boards with our own RIOT build. It builds for BOARD=nucleo-l1, but only some functions were actually tested on it.