WeAct STM32F411CEU6 black pill

Discussion and questions about boards that can run MicroPython but don't have a dedicated forum.
Target audience: Everyone interested in running MicroPython on other hardware.
User avatar
RobH
Posts: 30
Joined: Fri Mar 23, 2018 3:37 pm
Location: Netherlands
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by RobH » Sat Aug 08, 2020 9:36 am

With the PyBoard I was used to specify SPI parameters by name like SPI("Y", "Y5",..) for the second SPI module. With the black pill these names are not accepted. I succeeded with SPI(2,"PB12",..), but I wonder if it is possible to use a symbolic name for the SPI (and other) busses. If so where can I find these names?
Rob.

scruss
Posts: 15
Joined: Sat Aug 12, 2017 2:27 pm

Re: WeAct STM32F411CEU6 black pill

Post by scruss » Mon Aug 10, 2020 12:35 am


User avatar
dhylands
Posts: 3464
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by dhylands » Mon Aug 10, 2020 5:31 pm

The "X" and "Y" names come from the boards mpconfigboard.h file.

For the pyboard, it's here:
https://github.com/micropython/micropyt ... oard.h#L56

The WeAct board config file doesn't provide any names and I'm not sure what you'd call them anyways since there isn't anything obvious on the board to use other than the SPI port number (i.e. 1, 2, or 3).

User avatar
RobH
Posts: 30
Joined: Fri Mar 23, 2018 3:37 pm
Location: Netherlands
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by RobH » Tue Aug 11, 2020 2:21 pm

Agreed, the logic for symbolic names of the 2 SPI interfaces for the original PyBoard (X or Y side) doesn't work for the black pill. The first 2 could be called 'A' and 'B', but the remaining have also 'A' and/or 'B' pins. So it will be 1,2,..etc

[addition]
A related 'issue': I see different pins with the same symbolic names. For example Pin B6 and B8 have both SCL1. When printing the I2C(1) object I see that B6 is used. Explicit selection of B8 is refused. When (for example) using B6/B7 as serial port, how can I use B8/B9 for I2C(1)?

Rob.

User avatar
RobH
Posts: 30
Joined: Fri Mar 23, 2018 3:37 pm
Location: Netherlands
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by RobH » Tue Aug 11, 2020 6:16 pm

RobH wrote:
Tue Aug 11, 2020 2:21 pm

For example Pin B6 and B8 have both SCL1. When printing the I2C(1) object I see that B6 is used. Explicit selection of B8 is refused. When (for example) using B6/B7 as serial port, how can I use B8/B9 for I2C(1)?
I think the answer is "use alternate pin function", something like for SCL1:
p = Pin("B8", mode=Pin.AF_PP, af = Pin.AF4_I2C1)

User avatar
dhylands
Posts: 3464
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by dhylands » Tue Aug 11, 2020 8:00 pm

One option is to create a custom board type and modify the mpconfigboard.h file:
https://github.com/mcauser/WEACT_F411CE ... .h#L39-L44

Another option is to open I2C1 and then modify the pin mux afterwards.

Here's some example code that does this:

Code: Select all

# Example which runs on the WeACT STM32F411CEU6 board.
#
# By default I2C(1) uses B6/B7
#
# This example shows how to move this to pins B8/B9

import machine
from machine import Pin

b6 = Pin('B6')
b7 = Pin('B7')
b8 = Pin('B8')
b9 = Pin('B9')

def dump(label):
  print('-----', label)
  print('  ', b6)
  print('  ', b7)
  print('  ', b8)
  print('  ', b9)
  print('')


dump('Before opening I2C')

i2c = machine.I2C(1)  # uses B6/B7

dump('After opening I2C')

# Switch B6/B7 back to being GPIO

b6.init(Pin.IN)
b7.init(Pin.IN)

dump('After making B6/B7 GPIO')

b8.init(Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, alt=Pin.AF4_I2C1)
b9.init(Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, alt=Pin.AF4_I2C1)

dump('After enabling I2C(1) on B8/B9')
Running this produced this output for me:

Code: Select all

>>> import weact_i2c
----- Before opening I2C
   Pin(Pin.cpu.B6, mode=Pin.IN)
   Pin(Pin.cpu.B7, mode=Pin.IN)
   Pin(Pin.cpu.B8, mode=Pin.IN)
   Pin(Pin.cpu.B9, mode=Pin.IN)

----- After opening I2C
   Pin(Pin.cpu.B6, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B7, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B8, mode=Pin.IN)
   Pin(Pin.cpu.B9, mode=Pin.IN)

----- After making B6/B7 GPIO
   Pin(Pin.cpu.B6, mode=Pin.IN)
   Pin(Pin.cpu.B7, mode=Pin.IN)
   Pin(Pin.cpu.B8, mode=Pin.IN)
   Pin(Pin.cpu.B9, mode=Pin.IN)

----- After enabling I2C(1) on B8/B9
   Pin(Pin.cpu.B6, mode=Pin.IN)
   Pin(Pin.cpu.B7, mode=Pin.IN)
   Pin(Pin.cpu.B8, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B9, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
Note that there is a bug in the current version of Micropython which I submitted a PR for: https://github.com/micropython/micropython/pull/6329

This causes the prints for B6 and B7 to look like this instead:

Code: Select all

----- After making B6/B7 GPIO
   Pin(Pin.cpu.B6, mode=Pin.ALT_OPEN_DRAIN, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B7, mode=Pin.ALT_OPEN_DRAIN, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B8, mode=Pin.IN)
   Pin(Pin.cpu.B9, mode=Pin.IN)

----- After enabling I2C(1) on B8/B9
   Pin(Pin.cpu.B6, mode=Pin.ALT_OPEN_DRAIN, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B7, mode=Pin.ALT_OPEN_DRAIN, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B8, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B9, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
the dump I did was from a patched version of micropython.

User avatar
RobH
Posts: 30
Joined: Fri Mar 23, 2018 3:37 pm
Location: Netherlands
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by RobH » Wed Aug 12, 2020 11:00 am

Hello Dave,

Great service to explain and demonstrate the alternate pin functionality! Saves me a lot of time. I'm sure I would have struggled with the sequence, certainly with the MicroPython issue.

Rob.

User avatar
RobH
Posts: 30
Joined: Fri Mar 23, 2018 3:37 pm
Location: Netherlands
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by RobH » Thu Aug 13, 2020 1:44 pm

Hello Dave,

When I run your example under 1.12 665 it seems your issue is fixed, but there may be a problem still.

The last line in your example says: 'dump('After enabling I2C1 on B8/B9'), but the I2C object is not yet initialized.
After a i2c.init(pyb.I2C.MASTER) the dump shows:

Code: Select all

---- After init I2C1
   Pin(Pin.cpu.B6, mode=Pin.ALT_OPEN_DRAIN, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B7, mode=Pin.ALT_OPEN_DRAIN, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B8, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
   Pin(Pin.cpu.B9, mode=Pin.ALT_OPEN_DRAIN, pull=Pin.PULL_UP, af=Pin.AF4_I2C1)
And I'm not sure if I2C is really on ports B8/B9 (I have not yet done a hardware test).

Note: I imported I2C from 'pyb', because the I2C.init() from 'machine' asks for scl/sda, and I want to see if the alternate 'default' Pins are used.

User avatar
dhylands
Posts: 3464
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by dhylands » Thu Aug 13, 2020 1:56 pm

It looks to me like I2C1 should be on pins B8/B9 (based on your dump), and B6/B7 are configured back as GPIOs (the pull parameter being dropped is the clue here).

Just FYI: when you use machine.I2C and an id of -1, then it will do software I2C which uses the pins in GPIO mode and this mode can use any pins, whereas the B8/B9 thing is for using the pins with HW I2C, which can only go on pins that the HW supports.

The big thing is that when using HW mode, you generally only want the SCL/SDA pins to be present on one set of pins (which is why you should configure B6/B7 back to being GPIO).

User avatar
RobH
Posts: 30
Joined: Fri Mar 23, 2018 3:37 pm
Location: Netherlands
Contact:

Re: WeAct STM32F411CEU6 black pill

Post by RobH » Sun Aug 16, 2020 7:13 pm

Hello Dave,

the proof of the pudding...

I connected a small I2C OLED display with scl/sda on B8/B9 and extended the code with an i2c.scan().
This showed the correct I2C address of the display. After wiring the display to B6/B7 i2c.scan() shows an empty list, OK!
And print(i2c) shows:

Code: Select all

I2C(1, I2C.MASTER, baudrate=480000)
So the alternate pin facility works! This was with pyb.I2C.

It also works with machine.I2C. In that case I left out the i2c.init() to avoid having to specify scl/sda pins. The docs of machine.I2C do not tell me if the i2c object is initialised or not during creation (unlike the docs of pyb.I2C), but I assume yes.
Even without this i2c.init() the correct I2C address is shown with the i2c.scan() with the scl/sda lines of the display on pins B8/B9. One peculiarity: a print(i2c) shows:

Code: Select all

I2C(1, scl=B6, sda=B7, freq=480000)
Still the old pins, even though the display is really wired to B8/B9 and responding to its I2C address!

Then I tried a little test program for the OLED display using the SH1106 library of
Radomir Dopieralski (@deshipu), Robert Hammelrath (@robert-hh).
Originally this library insisted on specifying pins for scl/sda. After opening an issue with a request to be able to simply specify I2C(1) I received a fix. Then I used the alternate function facility conform your suggestions and now B8/B9 are working fine as default scl/sda pins with I2C(1)!

Post Reply