MicroPython on the STM32F411CE core board

6 min readUpdated Tutorial

A walk through MicroPython on the STM32F411CE core board: every peripheral, its pin mapping, and a working snippet for each.

MicroPython

MicroPython is a complete implementation of Python 3, written in C and optimised to run on microcontrollers. It provides a full Python compiler and runtime on the hardware itself, plus an interactive prompt (REPL) so you can execute supported commands immediately. Alongside a selected subset of the core Python library, it exposes modules that give you direct access to low-level hardware. See micropython.org for details.

Peripherals on the core board

STM32F411CE block diagram

The silkscreen drops the P prefix: A0 means PA0. MicroPython addresses these I/O as PAx, PBx, PCx. STM32F411CE block diagramSTM32F411CE block diagram

GPIO

pyb.Pin

Definitions

PA0–PA15, PB0–PB10, PB12–PB15, PC13–PC15 Pin.IN — configure the pin for input; Pin.OUT_PP — configure the pin for output, with push-pull control; Pin.OUT_OD — configure the pin for output, with open-drain control; Pin.AF_PP — configure the pin for alternate function, push-pull; Pin.AF_OD — configure the pin for alternate function, open-drain; Pin.ANALOG — configure the pin for analog.

pull can be one of:

Pin.PULL_NONE — no pull-up or pull-down; Pin.PULL_UP — enable the pull-up; Pin.PULL_DOWN — enable the pull-down.

Usage

GPIO

Python
from pyb import Pin
C13 = Pin('PC13',Pin.OUT_PP,Pin.PULL_NONE) # PC13 push-pull output, no pull
C13 # print the PC13 configuration in the REPL
C13.high() # drive high
C13.value() # read the PC13 level

External interrupts

Definitions

ExtInt.IRQ_RISING ExtInt.IRQ_FALLING ExtInt.IRQ_RISING_FALLING pyb.Pin.PULL_NONE pyb.Pin.PULL_UP pyb.Pin.PULL_DOWN

Usage

GPIO external interrupt

Python
from pyb import Pin, ExtInt

callback = lambda e: print("PA0 <KEY> intr")
ext = ExtInt(Pin('PA0'), ExtInt.IRQ_RISING, pyb.Pin.PULL_NONE, callback)
ext.swint() # trigger the callback by hand, runs callback() once

ADC

pyb.ADC

Definitions

STM32F411 I/O map

ADC0

External channels: PA0, PA1, PA2, PA3, PA4, PA5, PA6, PA7, PB0, PB1 Internal channels: temperature, VBAT, VREF

Usage

ADC external channels

Python
from pyb import Pin,ADC
IN0  = ADC(Pin('PA0'))
IN0.read()

IN1  = ADC(Pin('PA1'))
IN1.read()

ADC internal channels

Python
from pyb import Pin,ADC
adc_in = pyb.ADCAll(12, 0x70000) # 12-bit resolution, internal channels
adc_in.read_core_temp() # temperature
adc_in.read_core_vbat() # VBAT
adc_in.read_vref() # VREF

Timer

pyb.Timer

Definitions

STM32F411 I/O map

Timer pinout on the STM32F4x1C:

When USB is in use, PA11 is taken.

TimerChannel 1Channel 2Channel 3Channel 4
TIM1PA8PA9PA10PA11
TIM2PA0, PA5, PA15PA1, PB3PA2,PB10PA3
TIM3PA6, PB4PA7, PB5PB0PB1
TIM4PB6PB7PB8PB9
TIM5PA0PA1PA2PA3
TIM9PA2PA3
TIM10PB8
TIM11PB9

Timer input clocks:

96 MHz (411) / 84 MHz (401): TIM1, TIM9, TIM10, TIM11 48 MHz (411) / 42 MHz (401): TIM2, TIM3, TIM4, TIM5

Usage

Timer as a periodic interrupt

Python
import pyb
from pyb import Timer
TIM1 = Timer(1, freq=2) # 2 Hz
TIM1.counter() # get counter value
TIM1.callback(lambda t: pyb.LED(1).toggle())
TIM1.freq(5) # change the frequency to 5 Hz
TIM1.deinit() # release TIM1

Timer PWM

Python
import pyb
from pyb import Pin, Timer
TIM2 = Timer(2, freq=1000) # 1 kHz
# Timer.PWM_INVERTED: 0% -> high, 100% -> low
# Timer.PWM:          0% -> low,  100% -> high
TIM2_CH1 = Pin('PA0',Pin.PULL_NONE) # PA0: TIM2, CH1, no pull
TIM2_CH1 = TIM2.channel(1, Timer.PWM, pin=TIM2_CH1,pulse_width_percent = 0) # 0% duty
TIM2_CH1.pulse_width_percent(50) # 50% duty, 0-100%
TIM2_CH1.pulse_width(int(TIM2.period()/2)) # set the compare value directly, 50% duty

Timer input capture

polarity can be one of:

Timer.RISING — capture on the rising edge Timer.FALLING — capture on the falling edge Timer.BOTH — capture on both edges

Python
from pyb import Pin, Timer
import pyb

TIM1 = Timer(1,prescaler=96-1, period=65535-1,mode=Timer.UP) # /96 -> 1 MHz, reload 65535-1, count up
TIM1_CH1 = Pin('PA8',Pin.PULL_NONE) # PA8: TIM1, CH1, no pull
TIM1_CH1 = TIM1.channel(1, Timer.IC,polarity=Timer.BOTH, pin=TIM1_CH1)
TIM1_CH1.callback(lambda t: print(TIM1_CH1.capture()))
pyb.delay(5000)
TIM1_CH1.callback(None)
TIM1.deinit()
print('TIM over')

RTC (real-time clock)

pyb.RTC

Definitions

None.

Usage

Python
from pyb import RTC

RTC = RTC()
RTC.datetime((2020, 1, 1, 3, 12, 00, 0, 0)) # set the time: 2020-01-01, Wednesday, 12:00:00
RTC.datetime() # read the time
hex(RTC.info()) # boot time and reset source
callback = lambda e: print("RTC WakeUP")
RTC.wakeup(2000,callback)
# pyb.stop() # stop mode; if USB is connected, USB CDC hangs,
# RTC.wakeup(0)
pyb.standby() # if USB is connected, USB CDC disconnects; on wake the board resets

UART

pyb.UART

Definitions

Cross-connect with the USB-to-serial adapter.

STM32F411 I/O map

UART1: TX-PA9, RX-PA10 UART2: TX-PA2, RX-PA3 UART6: TX-PA11, RX-PA12 (taken by USB) UART_REPL: UART1

Usage

UART2

Python
from pyb import UART

uart2 = UART(2, 9600)
uart2.write('hello')
uart2.read(5) # read 5 bytes; returns None if nothing is buffered
uart2.readline() # read one line, terminated by a newline
uart2.any()   # number of bytes available

SPI

pyb.SPI

Definitions

STM32F411 I/O map

SPI2: NSS-PB12, SCK-PB13, MISO-PB14, MOSI-PB15 SPI4: NSS-PB12, SCK-PB13, MISO-PA1, MOSI-PA11 (taken by USB) SPI5: NSS-PB1, SCK-PA10, MISO-PA12, MOSI-PB0 (taken by USB)

Usage

Python
from pyb import SPI

spi2 = SPI(2, SPI.MASTER, baudrate=200000, polarity=1, phase=0)
spi2.send('hello')
spi2.recv(5) # receive 5 bytes on the bus
spi2.send_recv('hello') # send and receive 5 bytes

I2C

machine.I2C

Definitions

STM32F411 I/O map

I2C1: SCL-PB6, SDA-PB7 I2C2: SCL-PB10, SDA-PB9 I2C3: SCL-PA8, SDA-PB8

Usage

Python
from machine import I2C

i2c = I2C(1, freq=400000)                 # hardware I2C: I2C1, PB6, PB7
i2c = I2C(scl='PB6', sda='PB7', freq=100000)  # software I2C

i2c.scan()                          # returns a list of slave addresses
i2c.writeto(0x42, 'hello')          # write 5 bytes to the slave at 0x42
i2c.readfrom(0x42, 5)               # read 5 bytes from the slave

i2c.readfrom_mem(0x42, 0x10, 2)     # read 2 bytes from memory 0x10 of slave 0x42
i2c.writeto_mem(0x42, 0x10, 'xy')   # write 2 bytes to memory 0x10 of slave 0x42

Servo control

pyb.Servo

Definitions

The Servo module uses TIM5, so the two cannot be used at the same time — pick one. Servo IDs 1–4 map to PA0, PA1, PA2, PA3.

Usage

Python
from pyb import Servo
s1 = Servo(1) # servo on position 1 (PA0, VIN, GND)
s1.angle(45) # move to 45 degrees
s1.angle(-60, 1500) # move to -60 degrees over 1500 ms
s1.speed(50) # for continuous-rotation servos

Switch

pyb.Switch

Definitions

On the STM32F4x1Cx core board the button (KEY) is PA0.

Usage

Python
from pyb import Switch

sw = Switch()
sw.value() # returns True or False
sw.callback(lambda: pyb.LED(1).toggle())

LED

pyb.LED

Definitions

On the STM32F4x1 core board the blue LED (C13) is PC13.

Usage

Python
from pyb import LED

led = LED(1) # 1 = blue
led.toggle()
led.on()
led.off()