Category Archives: MicroPython

MicroPython and STM32F407 Discovery Board

Compiling MicroPython and installing on the Discovery board was very easy after setting up for the PyBoard. The STM32F407 is nearly the same as the PyBoard with a few extra features.

  • 168 MHz ARM M4 that does 210 DMIPS.
  • Three 12 bit ADC that can convert at over 2MHz each or be combined for nearly 7 MHz.
  • Many more GPIO pins exposed. 5V and 3.3V (3V3) available on the pins.

Use a jumper from PA9 to 5V to provide power from the Micro-USB connector CN5 (or connect a second USB to the other mini-USB connector). Connect your Discovery board to the Micro-USB from your PC and ignore Debian’s request to open it with Files. Be sure you have a jumper for DFU mode – connect pins Boot0 and VDD. They are next to each other. To compile and program the board, get in the STMHAL directory (see the PyBoard page) and use this make command:

cd stmhal
make BOARD=STM32F4DISC deploy

The download of MicroPython to the Discovery Board Flash will proceed automatically. When finished, remove power and the Boot0 jumper. Launch a terminal program like Minicom, and reconnect the Discover Board. Minicom will find Micropython on /dev/ttyACM0 and you will see the boot message and the Python prompt >>>  My Minicom found it automatically. You can use arguments when starting Minicom if you have to

minicom -D /dev/ttyACM0

Debian will (should) prompt you to open the device with files. Go ahead. Wait for it to read and sync, and then open main.py with a text editor. I just use gedit for simple tasks. You can put any test code here.   Blink LEDs or send text in an infinite loop, or execute lines as if you typed on a terminal.

while TRUE:
    print("Test")

Save in the editor and switch to the Mincom terminal. ^C to stop any running main.py and get the >>> interactive Python prompt. ^D will launch the edited main.py. Repeat as needed. You can also use reset if something goes really wrong. Save, unmount the board, reset (the black button on the Discovery).

This is the development cycle and it goes pretty fast. Each time you reset to run new code, Debian will also offer to open the device in Files. You can go ahead and open and edit while the board is running your current code.

My real test task is to use an LTC2400 24 bit ADC on a break-out from an eBay seller. The device uses a 3-wire interface compatible with SPI and this means figuring out SPI with MicroPython and printing and calibrated voltage from 0 to 4.096 volts. (The eBay beak-outs are also available with a voltage reference for 3.3 volts).

The SPI interface uses 3 or 4 lines.

  1. MOSI   Master Out Slave In
  2.   SCK   Data clock
  3. MISO   Master In Slave Out
  4.   NSS   A Chip Select and/or Data Mode line.

And the LTC2400 only uses MISO, SCK, and an optional NSS. The Python interface to SPI does not have NSS or a chip select (CS) signal and CS will have to be provided by a GPIO pin and manipulated in the data collection code.

From the pinout and alternate functions list for the Discovery Board, SPI1 is used by one of the USB. SPI2 is available and can be configured to use some GPIO pins. PB7 is not used on Port B and is OK for a CS output. Here is function name, port name and pin, and header connector-pin number.

  • MISO PB14 P1-38
  • MOSI PB15 P1-39
  • SCK   PB13 P1-37
  • CS     PB7  P2-24

This extra GPIO for CS is forced by the nature of the LTC2400. In order to run in external clock mode where we control the data conversions, SCK must be low when CS goes low, otherwise it goes into an internally clocked mode. Rather than take the time to understand the complicated port timing, I chose to just make my own CS with GPIO and experiment for lowest noise clock and select rates. The LTC2400 data is clocked out on the falling edge of SCK and latched by SPI on the rising edge. There are some other simplifications here that I will explain later.

I’ll try posting the code then explaining it in detail line by line. I usually hate that, but in this case I’ll include the SPI hardware setup and description so there is more than just code talk. The format for the LTC2400 output is most significant byte first with upper 4 bits of the first byte beings status and the last 4 bits on the last byte being extra conversions bits that may have value in long averages but are normally discarded.

# main.py -- put your code here!

from pyb import Pin, SPI

spi = SPI(2, SPI.MASTER)
spiCS = pyb.Pin(pyb.Pin.cpu.B7, Pin.OUT_PP, Pin.PULL_NONE)
print("Init CS")

spiCS.init(Pin.OUT_PP, pull=Pin.PULL_NONE)
print("Init SPI")
spiCS.low()

spi.init(SPI.MASTER, prescaler=64, polarity=0, phase=1, bits=8)
print(spi)
     
buf = bytearray(4)
signBit = 0
rangeExtended = 0

# Clear last conversion
spiCS.low()                                                                                          
spi.recv(buf, timeout = 1000)
py.delay(500)
spi.recv(buf, timeout = 1000)                                                                        
spiCS.high() 
py.delay(500)

while True:
    sum=0
    for i in range(0,64,1):  
        spiCS.low()                                                                                          
        spi.recv(buf, timeout = 1000)                                                                         
        spiCS.high() 
        pyb.delay(250)

        signBit = (buf[0] & 0b00100000) >> 5         # Find sign bit
        rangeExtended = (buf[0] & 0b00010000) >> 4   # Find extended range bit

        adcval = (buf[3] + (buf[2]<<8) + (buf[1]<<16) + ((buf[0]&0xF)<<24))>>4  # Shift the bytes to get 24 bit data.
        if signBit<1:
            adcval = -adcval
        if rangeExtended > 0:
            print("Extended Range")
        sum += adcval
    raw = sum>>6
    print("sum: %12d, Raw: %9d, Volts: %8.7f" % (sum,raw,(raw*4.096)/0xFFFFFF))

Only two PyBoard resources are needed. The Pin and SPI libraries are imported. Then an SPI device is created spi = SPI(2, SPI.MASTER) with augments that choose SPI channel 2 and define the SPI as a master device. Then a GPIO pin is given a name, spiCS, and initialized as a push-pull output (most like a CMOS output that toggles high/low). spiCS = pyb.Pin(pyb.Pin.cpu.B7, Pin.OUT_PP, Pin.PULL_NONE)

The initialization can take place when the device is created or with an init method later. Here I have done both for the GPIO pin. The SPI init call shows some of the options for SPI. In this case, it is a Master device, speed of the SPI SCK clock is set by the prescaler, polarity is 0 which means the SCK idles low (as needed by LTC2400), phase is 1 which means data is latched in SCK rising edge, and the data will be read in 8 bit hunks. There us a 16 bit mode which I have not tried. If I was doing this in assembler, I would use 16 bit mode. The ARM has a barrel shifter which means any size shift of bits left or right has the same cost – free. It is part of any instruction since barrel shift logic is basically instant. I hope the MicroPython internals take advantage of this.

There are other parameters like baud rate that can be entered optionally but as far as I can see, prescaller over-rides any value in baudrate. (I will be looking at the core C code soon). Now print(spi) will print the parameter list and values for this SPI channel, SPI(2)

spi.init(SPI.MASTER, prescaler=64, polarity=0, phase=1, bits=8)
print(spi)

Set up some variables:

buf = bytearray(4)
signBit = 0
rangeExtended = 0
meanCount=64

buf is a place to put the 4 bytes that will be read from the LTC2400 each time a reading is taken. There are two status bits of any use, sign, and a bit that says the device is in extended range. meanCount is for averaging readings to reduce noise. Assuming noise is Gaussian, noise is reduced by the square root of the number of readings added up. 64 readings is an 8 times reduction in noise.

Then there is a double reading of the LTC2400 to clear it out before starting data collection. Note: The LTC2400 is always holding the data from the last time it did a conversion. When you read data, it then starts a new conversion and holds the value until you read again. spi.recv(buf, timeout = 1000) does four readings of one byte each in order to fill buf, which is 4 bytes long.

Viewing the main data taking loop again, while true: starts an infinite loop

while True:
    sum=0
    for i in range(0,64,1): 
        spiCS.low() 
        spi.recv(buf, timeout = 1000) 
        spiCS.high() 
        pyb.delay(250)

        signBit = (buf[0] & 0b00100000) >> 5       # Find sign bit
        rangeExtended = (buf[0] & 0b00010000) >> 4 # Find extended range bit

        adcval = (buf[3] + (buf[2]<<8) + (buf[1]<<16) + ((buf[0]&0xF)<<24))>>4 # Shift the bytes to get 24 bit data.
        if signBit<1:
            adcval = -adcval
        if rangeExtended > 0:
            print("Extended Range")
        sum += adcval
    raw = sum>>6
    print("sum: %12d, Raw: %9d, Volts: %8.7f" % (sum,raw,(raw*4.096)/0xFFFFFF))

In this infinite loop, the sum for the average is set to zero. A reading is made by setting CS low, reading four bytes into buf[], and setting CS high followed by a delay of 250ms.

The bye order is highest byte first, which has status in the 4 highest order data bits. The sign bit is isolated and shifted to the lowest order bit, which makes it a 1. Then the range extension bit is isolated. I like to use binary representation of masks in order to picture the bit positions. Yes, I can do math in hex in my sleep, but the binary is a better representation of the physical device.

Then the ADC data has to be extracted and formed into a 32 bit number with the low 24 bits containing the data and the upper 8 set to zero. The least significant byte is in buf[3] and it can be used as-is. Add buf[3] to the buf[2] value with its binary value shifted 8 places to the left. Add buf[1] that has been shifted 16 places left. Finally for buf[0], wipe the upper 4 bits with the binary AND operation and shift left 24 places and add. If you like to see the bits like I do, the line can be written this way.

adcval = (buf[3] + (buf[2]<<8) + (buf[1]<<16) + ((buf[0]&0b00001111)<<24))>>4

If MicroPython is using the ARM barrel shifter for integer shifts, then this is very fast. In assembly code on the Discovery board this whole line will execute in about 10 instructions or 16 to 21 million times a second. [The ARM M4 on the Discovery does more than one instruction per clock due to look-ahead and pipe-lining. I would have to test to see the real speed.]

The readings are totaled up and the result is divided by meanCount, in this case 64, which is the same as a right shift of 6 bits. To generalize, divide the floating value of the total by meanCount as a regular floating point math calculation.

I print three values to inspect the process. The sum: is the total integer sum of the readings. The Raw: is the integer value of the average of the readings without scaling to a voltage. Volts: is the raw value scaled to a voltage. The voltage reference on this board is 4.096 volts. To scale, simply multiply by 4.096 and divide by the maximum reading of the LTC2400. To minimize round-off and truncation errors, multiply first, then divide. The max reading has all bits on. In binary this (24 bit number) is 111111111111111111111111 and in hex it is FFFFFF. No need to figure what that is in decimal (16,777,215). [If you remove the 4 bit shift >>4  for adcval, you can use all 28 data bits. Divide by hex FFFFFFF.]

Here is some output from a partially discharged Li-Ion battery.

sum:   1010304193, Raw:  15786003, Volts: 3.8540051
sum:   1010303548, Raw:  15785992, Volts: 3.8540025
sum:   1010307666, Raw:  15786057, Volts: 3.8540182
sum:   1010310359, Raw:  15786099, Volts: 3.8540285
sum:   1010312272, Raw:  15786129, Volts: 3.8540359
sum:   1010313298, Raw:  15786145, Volts: 3.8540397
sum:   1010315626, Raw:  15786181, Volts: 3.8540485
sum:   1010316534, Raw:  15786195, Volts: 3.8540518
sum:   1010320641, Raw:  15786260, Volts: 3.8540678
sum:   1010316923, Raw:  15786201, Volts: 3.8540535
sum:   1010320659, Raw:  15786260, Volts: 3.8540678
sum:   1010320845, Raw:  15786263, Volts: 3.8540686
sum:   1010322418, Raw:  15786287, Volts: 3.8540745

This is using all 24 bits and averaging 1024 samples:

sum: 258696696256, Raw:  15789593, Volts: 3.8548815
sum: 258697241294, Raw:  15789626, Volts: 3.8548897
sum: 258697294828, Raw:  15789629, Volts: 3.8548903
sum: 258697876649, Raw:  15789665, Volts: 3.8548992
sum: 258697751112, Raw:  15789657, Volts: 3.8548973
sum: 258697695754, Raw:  15789654, Volts: 3.8548964
sum: 258697819403, Raw:  15789661, Volts: 3.8548983
sum: 258697499242, Raw:  15789642, Volts: 3.8548935
sum: 258697389996, Raw:  15789635, Volts: 3.8548918
sum: 258697665284, Raw:  15789652, Volts: 3.8548958
sum: 258697085561, Raw:  15789617, Volts: 3.8548875
sum: 258696741473, Raw:  15789596, Volts: 3.8548822
sum: 258696909367, Raw:  15789606, Volts: 3.8548846
sum: 258696821528, Raw:  15789600, Volts: 3.8548832
sum: 258697302451, Raw:  15789630, Volts: 3.8548906
sum: 258697008351, Raw:  15789612, Volts: 3.8548861
sum: 258696827087, Raw:  15789601, Volts: 3.8548834
sum: 258696894048, Raw:  15789605, Volts: 3.8548843
sum: 258697492970, Raw:  15789641, Volts: 3.8548932
sum: 258697605363, Raw:  15789648, Volts: 3.8548948
sum: 258697647702, Raw:  15789651, Volts: 3.8548956
sum: 258697203213, Raw:  15789624, Volts: 3.8548891

There are some nice improvements that can be made to this setup, including powering the LCT2440 from a GPIO pin and reading the data out without clocking to find when conversion is done. For a serious meter or precision measurement, a better layout, better reference chip, and good shielding/grounding practice can bring it up to the full potential of the LTC2400. The chip can also be used with the MicroPython SPI interface by connecting the LTC2400 CS to ground and either reading at a rate a little slower than the chip, or monitoring the data pin. It goes low when data conversion is complete. This can also be used as an interrupt.

Now to clean this up a little, find out why grounding the Vin causes Extended Range, do an example with dynamic checking of the conversion done bit by switching pin mode on the MISO pin to GPIO and back to SPI, do an interrupt driven version, then finish up with interrupt driven DMA for data collection! That should be a good start for MicroPython in real–world data collection. After that, do it on the ESP8266 and send the data to a PC with WiFi. And just one more thing. Mesh network a dozen ESP8266 all collecting data and see what the data collection bandwidth is. Can I get audio from a dozen microphones in an array and track animals on my property?

 

 

 

MicroPython and Debian Wheezy

MicroPython is a full Python 3.4 written in C and able to run on bare metal microprocessors like the ARM M Series. Micropython gives you Python access to most of the GPIO on many great little processor boards.

upython-with-micro

I was a supporter of the MicroPython crowd funded project by Damien George and I am VERY pleased with the results. While updating various boards I ran into some trouble with the project’s Github readme and Wiki instructions. Here is what I did to start with a fresh Debian Wheezy, create needed directories, install the tools, get the source code, compile for target boards, and burn the latest version of MicroPython to FLASH. MicroPython is actively updated, and new board support is added all time. Updating your boards is a common task and should be made as easy as possible. Here are some details on MicroPython.

Do you have a favorite place to put tool chains and other executables and source repositories in Debian? Feel free to use that instead of my suggestions. Note: These instructions are for the latest version of MicroPython, at this time

With Debian and Ubuntu, if the directory $HOME/bin exists, it will be automatically included in $PATH and is a good place for executable tools. $HOME is your home directory with your user name: the same one you get when you cd ~. If $HOME/bin is not there, create it.

cd ~
mkdir bin

Install the gcc-arm-gnu-eabi tool chain. Go to the page that lists the gcc-arm-none-eabi for various OS’s and select the Linux button  (Looks like…nux. Hover over the buttons to read the file name). Download and unpack the Tape Archive (tarball) in ~/bin. (I had to edit my .profile file and add the full path to the C compiler in the gcc tool set. This is not supposed to be required and maybe I messed up $PATH settings earlier.) Here is how .profile checks for ~/bin and it shows the path I extended to include the compiler /bin file in the gcc directory. The current URL for download is https://launchpad.net/gcc-arm-embedded/4.9/4.9-2015-q1-update/+download/gcc-arm-none-eabi-4_9-2015q1-20150306-linux.tar.bz2 if you want to do it in a terminal.

# set PATH so it includes user's private bin if it exists
if [ -d "$HOME/bin" ] ; then
    PATH="$HOME/bin/gcc-arm-none-eabi-4_9-2015q1/bin:$HOME/bin:$PATH"
fi

After saving, refresh the $PATH

source .profile
echo $PATH

or restart your terminal session. echo $PATH to check it out.

The USB communications require dfu-util (DFU is Device Firmware Upgrade protocol for USB). I have version 0.5 for these tests. (There is a pydfu tool that is supposed to be used automatically. It is turned off in the make file. I enabled it to test and it failed.)

apt-get-install dfu-util

Get the MicroPython Source. The source includes the code for all supported boards in a Git repository. Don’t use the zip file. It fails with the tool chain on Debian. If you don’t have Git, install it – and get yourself a free Github repository while you are at it though you won’t need it for this. Sparkfun has a nice simple intro to Git in their tutorials. You will be able to pull complete updates very easily.

apt-get-install git

It’s that easy. Now to get a copy of the source. In Git, this is called cloning. You can clone to your home directory and everything will go into a directory called micropython. Make sure you are in your home directory (or wherever you like to keep source code packages or Git repositories). The repo page is here. Use the button on the right to copy the clone URL.

cd ~
git clone https://github.com/micropython/micropython.git

Compile the source. My first test is for the Pyboard itself, which is a STM32F4 and uses the code in the stmhal directory in the source code files. I’m using a quad core PC and the -j option for make specifies the number of threads to use. Things with lots of small files like a Linux kernel or MicroPython compile much faster with more threads. Two threads per core is about optimal.

cd micropython/stmhal
make -j8

USB access without resorting to sudo and its inherent dangers can be handled with a udev rule. Dave Hylands, one of the MicroPython gurus, has a set of scripts for communications. Included are three that will set udev rules for the Pyboard, STM Discovery, and Teensy. I cloned his Git repository to the micropython directory where it will create a directory called usb-ser-mon. The repository is here and you can get the clone URL on the right of the page. Change to the usb-ser-mon directory and execute the appropriate script. I did all three of the rules scripts for the boards. These rules are permanent and you don’t have to repeat them.

cd ..
git clone https://github.com/dhylands/usb-ser-mon.git
cd usb-ser-mon
./mk-udev-rules-pyboard.sh
./mk-udev-rules-stm32.sh
./mk-udev-rules-teensy.sh
cd ..
cd stmhal

Program the flash in the Pyboard. After I added the full path for the c compiler, this build went just fine and only took a few seconds. Then to burn to flash, the board must be in DFU mode. On the Pyboard the 3V3 pin is connected to the DFU pin with a jumper. Reset the Pyboard and the board will be recognized. You should not have to mount the board as a volume. Program the on-board flash with your newly compiled MicroPython. (See below for the newest firmware versions that do not require the jumper.)

make deploy
# If you have not made the udev rules, use
make && sudo make deploy

Any of the readme or tutorial instructions that say simply make deploy or a complicated longer statement failed without the udev-rules. I also added my user to the dialout group. Minicom will run and communicate with the boards without sudo.

Run MicroPython. Eject or unmount the board, remove the Bot0 jumper and reset. The board will show as a USB flash drive and at /dev/ttyACM0 in Minicom. You can edit the Python sample program, main.py, with examples from the Repository or Wiki pages, save, and unmount/eject the board. The main.py file will run after boot. Obviously, editing and running this way can get tedious, but wait! You can leave the main.py file open for editing and save when you make changes. ^C (control-c) will stop the program and put you in interactive mode and ^D will start main.py again, but with your changes. This gets pretty fast and easy. Edit, save, ^C, ^D, edit, save……..

If you do not need real-world GPIO when testing, there are QEMU files in the source repository and you can do development in emulation. You can also compile Micropython directly for your Debian PC and develop algorithms with your favorite IDE. I suggest making Micropython for your PC in a venv so that you don’t get conflicts with Python libraries (I use IPython Notebook and have loads of libraries and modules but keep it all in a venv).

You can also run a terminal program and use the board interactively, or put your main program on a uSD card. A main.py file on the uSD will be executed instead of the main.py in the on-board flash. The files on the uSD CAN execute files from Flash with execfile. So, you can use resources from the Flash when running your application from a uSD. Well, it’s Python after all. You can do just about anything!

Next, some other boards and some less common examples like reading a 22 bit ADC.

Notes: Programming and testing without the DFU jumper. As of firmware version xxxx ……

 

 

 

 

https://github.com/micropython/micropython/wiki/Getting-Started