Since I came across MicroPython last year, I have been obsessed with it because it can run Python code on a microcontroller. Although it cannot support all Python libraries, it is sufficient for general electronic control systems, greatly improving programming and debugging efficiency.
I have always wanted to design and make a robot controller by myself. For someone like me who has obsessive-compulsive disorder, the robot's main control microcontroller must have the highest main frequency, and the Flash and RAM must be as large as possible. Although there are high-configuration development boards such as Raspberry Pi on the market, they need to install an operating system, which is very troublesome to use. For simple control, I still prefer to run bare metal. Therefore, I plan to transplant micropython on the Apollo STM32H743 development board of Zhengdian Atom. The core board of this development board uses STM32H743IIT6, with Winbond 25Q256 32M QSPI flash and Winbond W9825G6KH-6 32M 16-bit SDRAM.
I have ported micropython to the STM32H429DISC and NUCLEO_H743ZI development boards in the early stage, but I have never successfully configured QSPI flash and SDRAM. Recently, I bought OpenMV 4P, which has integrated 32M QSPI and 32-bit 32M SDRAM, also developed based on micropython. At the same time, I also saw some articles and source codes about micropython supporting external SDRAM of STM32F429DISC and STM32F7DISC development boards. In other words, there is no problem with external QSPI and SDRAM from a technical point of view, which brings me hope.
Referring to the source code of STM32F429DISC, STM32F7DISC development boards and OpenMV 4P, after two days of copycat transplantation experiments, it was finally successful. The transplantation process is now recorded for your reference:
1. Build the compilation environment
The best compilation environment is Linux system. Windows system users can install Ubuntu virtual machine. For installation method, please refer to this article: "Compile MicroPython in Ubuntu subsystem of Win10" . I use the server Ubuntu16.04 environment, and the subsequent operation method is the same.
You also need to install a text editor. It is recommended to install vim. Vim is the best text editor under Linux. It needs to be operated with commands. It is very efficient. Use the command sudo apt-get install vim to install it. For specific usage methods, go to Baidu.
2. Modify the source code
1. Copy the source code
Before doing this, you must git the micropython source code, including submodules. First copy a copy of the NUCLEO_H743ZI source code. In the micropython root directory, enter the command:
cd ports/stm32/boards //Enter the development board directory
cp NUCLEO_H743ZI/ MYBOARD/ //Copy the source code of NUCLEO_H743ZI to the MYBOARD directory
cd MYBOARD //Enter your own development board directory
vim mpconfigboard.h //Modify the mpconfigboard.h configuration file
The folder name "MYBOARD" can be changed to any name you want, and it is also the name of the motherboard in future compilations.
2. Modify the source code
2.1 Modify the mpconfigboard.h file
Modify the mpconfigboard.h configuration file, mainly modify the name of the development board, configure clock parameters, define QSPI and SDRAM interfaces, and configure related parameters. This configuration file supports 32-bit SDRAM, and this development board has 16 bits. Temporarily comment out D16~D31, and temporarily comment out other peripherals. Open the settings later as needed. The modified mpconfigboard.h source code is as follows:
#define MICROPY_HW_BOARD_NAME "MYBOARD" //You can name the board as you like
#define MICROPY_HW_MCU_NAME "STM32H743"
#define MICROPY_HW_ENABLE_RTC (1)
#define MICROPY_HW_ENABLE_RNG (1)
#define MICROPY_HW_ENABLE_TIMER (1)
#define MICROPY_HW_ENABLE_ADC (1)
#define MICROPY_HW_ENABLE_DAC (1)
#define MICROPY_HW_ENABLE_USB (1)
#define MICROPY_HW_ENABLE_SDCARD (0)
#define MICROPY_HW_HAS_SWITCH (1)
#define MICROPY_HW_HAS_FLASH (1)
#define MICROPY_FATFS_EXFAT (1)
#define MICROPY_BOARD_EARLY_INIT NUCLEO_H743ZI_board_early_init
void NUCLEO_H743ZI_board_early_init(void);
// The board has an 25MHz HSE, the following gives 480MHz CPU speed
#define MICROPY_HW_CLK_PLLM (5) //Configure to 480M main frequency
#define MICROPY_HW_CLK_PLLN (192)
#define MICROPY_HW_CLK_PLLP (2)
#define MICROPY_HW_CLK_PLLQ (4)
#define MICROPY_HW_CLK_PLLR (2)
// The USB clock is set using PLL3, the USB main frequency must be 48M
#define MICROPY_HW_CLK_PLL3M (5)
#define MICROPY_HW_CLK_PLL3N (48)
#define MICROPY_HW_CLK_PLL3P (2)
#define MICROPY_HW_CLK_PLL3Q (5)
#define MICROPY_HW_CLK_PLL3R (2)
// 4 wait states
#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_4
//UART config
#define MICROPY_HW_UART2_TX (pin_A2)
#define MICROPY_HW_UART2_RX (pin_A3)
//#define MICROPY_HW_UART2_RTS (pin_D4)
//#define MICROPY_HW_UART2_CTS (pin_D3)
#define MICROPY_HW_UART3_TX (pin_B10)
#define MICROPY_HW_UART3_RX (pin_B11)
//#define MICROPY_HW_UART5_TX (pin_B6)
//#define MICROPY_HW_UART5_RX (pin_B12)
//#define MICROPY_HW_UART6_TX (pin_C6)
//#define MICROPY_HW_UART6_RX (pin_C7)
//#define MICROPY_HW_UART7_TX (pin_F7)
//#define MICROPY_HW_UART7_RX (pin_F6)
//#define MICROPY_HW_UART8_TX (pin_E1)
//#define MICROPY_HW_UART8_RX (pin_E0)
#define MICROPY_HW_UART_REPL PYB_UART_3
#define MICROPY_HW_UART_REPL_BAUD 115200
//I2C busses
//#define MICROPY_HW_I2C1_SCL (pin_B8)
//#define MICROPY_HW_I2C1_SDA (pin_B9)
//#define MICROPY_HW_I2C2_SCL (pin_F1)
//#define MICROPY_HW_I2C2_SDA (pin_F0)
//#define MICROPY_HW_I2C4_SCL (pin_F14)
//#define MICROPY_HW_I2C4_SDA (pin_F15)
//SPI
//#define MICROPY_HW_SPI3_NSS (pin_A4)
//#define MICROPY_HW_SPI3_SCK (pin_B3)
//#define MICROPY_HW_SPI3_MISO (pin_B4)
//#define MICROPY_HW_SPI3_MOSI (pin_B5)
// USRSW is pulled low. Pressing the button makes the input go high.
#define MICROPY_HW_USRSW_PIN (pin_A0)
#define MICROPY_HW_USRSW_PULL (GPIO_NOPULL)
#define MICROPY_HW_USRSW_EXTI_MODE (GPIO_MODE_IT_RISING)
#define MICROPY_HW_USRSW_PRESSED (1)
//LEDs
#define MICROPY_HW_LED1 (pin_B1) // green
#define MICROPY_HW_LED2 (pin_B0) // blue
//#define MICROPY_HW_LED3 (pin_B14) // red
#define MICROPY_HW_LED_OFF(pin) (mp_hal_pin_high(pin))
#define MICROPY_HW_LED_ON(pin) (mp_hal_pin_low(pin))
//USB config
#define MICROPY_HW_USB_FS (1)
#define MICROPY_HW_USB_VBUS_DETECT_PIN (pin_A9)
#define MICROPY_HW_USB_OTG_ID_PIN (pin_A10)
//FDCAN bus
/*#define MICROPY_HW_CAN1_NAME "FDCAN1"
#define MICROPY_HW_CAN1_TX (pin_D1)
#define MICROPY_HW_CAN1_RX (pin_D0)
*/
// SD card detect switch
//#define MICROPY_HW_SDCARD_DETECT_PIN (pin_G2)
//#define MICROPY_HW_SDCARD_DETECT_PULL (GPIO_PULLUP)
//#define MICROPY_HW_SDCARD_DETECT_PRESENT (GPIO_PIN_RESET)
// Use external SPI flash for storage
#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)
// QSPI Flash 256MBits
#define MICROPY_HW_SPIFLASH_SIZE_BITS (256 * 1024 * 1024)
#define MICROPY_HW_QSPIFLASH_SIZE_BITS_LOG2 (28)
#define MICROPY_HW_QSPIFLASH_CS (pin_B6)
#define MICROPY_HW_QSPIFLASH_SCK (pin_B2)
#define MICROPY_HW_QSPIFLASH_IO0 (pin_F8)
#define MICROPY_HW_QSPIFLASH_IO1 (pin_F9)
#define MICROPY_HW_QSPIFLASH_IO2 (pin_F7)
#define MICROPY_HW_QSPIFLASH_IO3 (pin_F6)
// block device config for SPI flash
extern const struct _mp_spiflash_config_t spiflash_config;
extern struct _spi_bdev_t spi_bdev;
#define MICROPY_HW_BDEV_IOCTL(op, arg) (\
(op) == BDEV_IOCTL_NUM_BLOCKS ? (MICROPY_HW_SPIFLASH_SIZE_BITS / 8 / FLASH_BLOCK_SIZE) : \
(op) == BDEV_IOCTL_INIT ? spi_bdev_ioctl(&spi_bdev, (op), (uint32_t)&spiflash_config) : \
spi_bdev_ioctl(&spi_bdev, (op), (arg)) \
)
#define MICROPY_HW_BDEV_READBLOCKS(dest, bl, n) spi_bdev_readblocks(&spi_bdev, (dest), (bl), (n))
#define MICROPY_HW_BDEV_WRITEBLOCKS(src, bl, n) spi_bdev_writeblocks(&spi_bdev, (src), (bl), (n))
// Use external SDRAM
#define MICROPY_HW_SDRAM_SIZE (32 * 1024 * 1024)
#define MICROPY_HW_SDRAM_STARTUP_TEST (1)
#define MICROPY_HEAP_START ((sdram_valid) ? sdram_start() : &_heap_start)
#define MICROPY_HEAP_END ((sdram_valid) ? sdram_end() : &_heap_end)
//The above two lines are critical to get the RAM start and end addresses. Without these two lines, only the built-in RAM can be recognized in the future
// Timing configuration for 240MHz/2=120MHz (8.3ns)
#define MICROPY_HW_SDRAM_CLOCK_PERIOD 2
#define MICROPY_HW_SDRAM_CAS_LATENCY 2
#define MICROPY_HW_SDRAM_FREQUENCY (120000) // 120 MHz
#define MICROPY_HW_SDRAM_TIMING_TMRD (2)
#define MICROPY_HW_SDRAM_TIMING_TXSR (8)
#define MICROPY_HW_SDRAM_TIMING_TRAS (6)
#define MICROPY_HW_SDRAM_TIMING_TRC (6)
#define MICROPY_HW_SDRAM_TIMING_TWR (2)
#define MICROPY_HW_SDRAM_TIMING_TRP (2)
#define MICROPY_HW_SDRAM_TIMING_TRCD (2)
// 16-bit SDRAM
#define MICROPY_HW_SDRAM_ROW_BITS_NUM 13
#define MICROPY_HW_SDRAM_MEM_BUS_WIDTH 16
#define MICROPY_HW_SDRAM_REFRESH_CYCLES 8192
#define MICROPY_HW_SDRAM_COLUMN_BITS_NUM 9
#define MICROPY_HW_SDRAM_INTERN_BANKS_NUM 4
#define MICROPY_HW_SDRAM_RPIPE_DELAY 1
#define MICROPY_HW_SDRAM_RBURST (1)
#define MICROPY_HW_SDRAM_WRITE_PROTECTION (0)
#define MICROPY_HW_SDRAM_AUTOREFRESH_NUM (8)
#define MICROPY_HW_SDRAM_BURST_LENGTH 1
#define MICROPY_HW_SDRAM_REFRESH_RATE (64) // ms
#define MICROPY_HW_FMC_SDCKE0 (pin_C3)
#define MICROPY_HW_FMC_SDNE0 (pin_C2)
#define MICROPY_HW_FMC_SDCLK (pin_G8)
#define MICROPY_HW_FMC_SDNCAS (pin_G15)
#define MICROPY_HW_FMC_SDNRAS (pin_F11)
#define MICROPY_HW_FMC_SDNWE (pin_C0)
#define MICROPY_HW_FMC_BA0 (pin_G4)
#define MICROPY_HW_FMC_BA1 (pin_G5)
#define MICROPY_HW_FMC_NBL0 (pin_E0)
#define MICROPY_HW_FMC_NBL1 (pin_E1)
//#define MICROPY_HW_FMC_NBL2 (pin_I4)
//#define MICROPY_HW_FMC_NBL3 (pin_I5)
#define MICROPY_HW_FMC_A0 (pin_F0)
#define MICROPY_HW_FMC_A1 (pin_F1)
#define MICROPY_HW_FMC_A2 (pin_F2)
#define MICROPY_HW_FMC_A3 (pin_F3)
#define MICROPY_HW_FMC_A4 (pin_F4)
#define MICROPY_HW_FMC_A5 (pin_F5)
#define MICROPY_HW_FMC_A6 (pin_F12)
#define MICROPY_HW_FMC_A7 (pin_F13)
#define MICROPY_HW_FMC_A8 (pin_F14)
#define MICROPY_HW_FMC_A9 (pin_F15)
#define MICROPY_HW_FMC_A10 (pin_G0)
#define MICROPY_HW_FMC_A11 (pin_G1)
#define MICROPY_HW_FMC_A12 (pin_G2)
#define MICROPY_HW_FMC_D0 (pin_D14)
#define MICROPY_HW_FMC_D1 (pin_D15)
#define MICROPY_HW_FMC_D2 (pin_D0)
#define MICROPY_HW_FMC_D3 (pin_D1)
#define MICROPY_HW_FMC_D4 (pin_E7)
#define MICROPY_HW_FMC_D5 (pin_E8)
#define MICROPY_HW_FMC_D6 (pin_E9)
#define MICROPY_HW_FMC_D7 (pin_E10)
#define MICROPY_HW_FMC_D8 (pin_E11)
#define MICROPY_HW_FMC_D9 (pin_E12)
#define MICROPY_HW_FMC_D10 (pin_E13)
#define MICROPY_HW_FMC_D11 (pin_E14)
#define MICROPY_HW_FMC_D12 (pin_E15)
#define MICROPY_HW_FMC_D13 (pin_D8)
#define MICROPY_HW_FMC_D14 (pin_D9)
#define MICROPY_HW_FMC_D15 (pin_D10)
//If it is 32-bit SDRAM, you need to set the following ports
/*#define MICROPY_HW_FMC_D16 (pin_H8)
#define MICROPY_HW_FMC_D17 (pin_H9)
#define MICROPY_HW_FMC_D18 (pin_H10)
#define MICROPY_HW_FMC_D19 (pin_H11)
#define MICROPY_HW_FMC_D20 (pin_H12)
#define MICROPY_HW_FMC_D21 (pin_H13)
#define MICROPY_HW_FMC_D22 (pin_H14)
#define MICROPY_HW_FMC_D23 (pin_H15)
#define MICROPY_HW_FMC_D24 (pin_I0)
#define MICROPY_HW_FMC_D25 (pin_I1)
#define MICROPY_HW_FMC_D26 (pin_I2)
#define MICROPY_HW_FMC_D27 (pin_I3)
#define MICROPY_HW_FMC_D28 (pin_I6)
#define MICROPY_HW_FMC_D29 (pin_I7)
#define MICROPY_HW_FMC_D30 (pin_I9)
#define MICROPY_HW_FMC_D31 (pin_I10)*/
2.2 Modify the stm32h7xx_hal_conf.h file
Next, modify the external clock frequency in the stm32h7xx_hal_conf.h file to 25M:
// Oscillator values in Hz
#define HSE_VALUE (25000000)
2.3 Modify the pins.csv file
Then modify the pin definitions of LEDs, buttons, etc. in the pins.csv file, add QSPI and SDRAM port pin definitions, and other peripheral pins are not set yet. You can modify them as needed. The file I modified is as follows:
A0 PA3
A1 PC0
A2 PC3
A3 PB1
A4 PC2
A5 PF10
A6 PF4
A7 PF5
A8 PF6
D0 PB7
D1 PB6
D2 PG14
D3 PE13
D4 PE14
D5 PE11
D6 PE9
D7 PG12
D8 PF3
D9 PD15
D10 PD14
D11 PB5
D12 PA6
D13 PA7
D14 PB9
D15 PB8
D16 PC6
D17 PB15
D18 PB13
D19 PB12
D20 PA15
D21 PC7
D22 PB5
D23 PB3
D24 PA4
D25 PB4
D26 PG6
D27 PB2
D28 PD13
D29 PD12
D30 PD11
D31 PE2
D32 PA0
D33 PB0
D34 PE0
D35 PB11
D36 PB10
D37 PE15
D38 PE6
D39 PE12
D40 PE10
D41 PE7
D42 PE8
D43 PC8
D44 PC9
D45 PC10
D46 PC11
D47 PC12
D48 PD2
D49 PG2
D50 PG3
D51 PD7
D52 PD6
D53 PD5
D54 PD4
D55 PD3
D56 PE2
D57 PE4
D58 PE5
D59 PE6
D60 PE3
D61 PF8
D62 PF7
D63 PF9
D64 PG1
D65 PG0
D66 PD1
D67 PD0
D68 PF0
D69 PF1
D70 PF2
D71 PE9
D72 PB2
DAC1 PA4
DAC2 PA5
LED1 PB1
LED2 PB0
SW PA0
SD_D0 PC8
SD_D1 PC9
SD_D2 PC10
SD_D3 PC11
SD_CMD PD2
SD_CK PC12
SD_SW PG2
OTG_FS_POWER PG6
OTG_FS_OVER_CURRENT PG7
USB_VBUS PA9
USB_ID PA10
USB_DM PA11
USB_DP PA12
UART2_TX PA2
UART2_RX PA3
UART3_TX PB10
UART3_RX PB11
QSPIFLASH_CS PB6
QSPIFLASH_SCK PB2
QSPIFLASH_IO0 PF8
QSPIFLASH_IO1 PF9
QSPIFLASH_IO2 PF7
QSPIFLASH_IO3 PF6
SDRAM_SDCKE0 PC3
SDRAM_SDNE0 PC2
SDRAM_SDCLK PG8
SDRAM_SDNCAS PG15
SDRAM_SDNRAS PF11
SDRAM_SDNWE PC0
SDRAM_BA0 PG4
SDRAM_BA1 PG5
SDRAM_NBL0 PE0
SDRAM_NBL1 PE1
SDRAM_A0 PF0
SDRAM_A1 PF1
SDRAM_A2 PF2
SDRAM_A3 PF3
SDRAM_A4 PF4
SDRAM_A5 PF5
SDRAM_A6 PF12
SDRAM_A7 PF13
SDRAM_A8 PF14
SDRAM_A9 PF15
SDRAM_A10 PG0
SDRAM_A11 PG1
SDRAM_A12 PG2
SDRAM_D0 PD14
SDRAM_D1 PD15
SDRAM_D2 PD0
SDRAM_D3 PD1
SDRAM_D4 PE7
SDRAM_D5 PE8
SDRAM_D6 PE9
SDRAM_D7 PE10
SDRAM_D8 PE11
SDRAM_D9 PE12
SDRAM_D10 PE13
SDRAM_D11 PE14
SDRAM_D12 PE15
SDRAM_D13 PD8
SDRAM_D14 PD9
SDRAM_D15 PD10
2.4 Modify the board_init.c file
Open the board_init.c file and add the header files and configuration functions required by the QSPI flash. Comment out the lines USB_PowerSwitchOn, which is the OTG power-on code for the NUCLEO_H743ZI development board. You can delete this function. At the same time, comment out or delete the two lines in the mpconfigboard.h file:
#define MICROPY_BOARD_EARLY_INIT NUCLEO_H743ZI_board_early_init
void NUCLEO_H743ZI_board_early_init(void);
I will keep the function here first, and put any initialization code here later. The code is as follows:
#include "py/mphal.h"
#include "qspi.h"
#include "storage.h"
void NUCLEO_H743ZI_board_early_init(void) {
// Turn off the USB switch
//#define USB_PowerSwitchOn pin_G6
//mp_hal_pin_output(USB_PowerSwitchOn);
//mp_hal_pin_low(USB_PowerSwitchOn);
}
char _ffs_cache;
STATIC const mp_soft_qspi_obj_t qspi_bus = {
.cs = MICROPY_HW_QSPIFLASH_CS,
.clk = MICROPY_HW_QSPIFLASH_SCK,
.io0 = MICROPY_HW_QSPIFLASH_IO0,
.io1 = MICROPY_HW_QSPIFLASH_IO1,
.io2 = MICROPY_HW_QSPIFLASH_IO2,
.io3 = MICROPY_HW_QSPIFLASH_IO3,
};
const mp_spiflash_config_t spiflash_config = {
.bus_kind = MP_SPIFLASH_BUS_QSPI,
.bus.u_qspi.data = (void*)&qspi_bus,
.bus.u_qspi.proto = &qspi_proto,
//NOTE: The FFS cache is not used when QSPI is enabled.
.cache = (mp_spiflash_cache_t *) &_ffs_cache,
};
spi_bdev_t spi_bdev;
2.5 Modify the sdram.c file
. In addition, to support SDRAM for STM32H7, you need to modify the sdram.c file under ports/stm32, which is also the most critical step. In the static void sdram_init_seq(SDRAM_HandleTypeDef hsdram, FMC_SDRAM_CommandTypeDef command) initialization sequence function (line 244), find #if defined (STM32F7) and change it to:
#if (defined(STM32F7) || defined(STM32H7)) //Add support for STM32H7
The purpose is to set the starting address of the SDRAM after the initialization sequence. Without this step, only the built-in RAM will be visible in the future. This is the step that has kept me from succeeding before.
3. Compile the source code and test
1. Compile the source code
. Now that the source code has been modified, we can start compiling, downloading and testing. Go back to the micropython root directory and enter the following command to start compiling the source code:
make -C ports/stm32 BOARD=MYBOARD
. After the compilation is completed, you will get firmware.dfu, firmware.hex and other firmware files in the ports/stm32/build-MYBOARD folder. The hex file can be downloaded to the chip using the ST-Link downloader. I choose the dfu mode to download, which only requires a USB cable. Transfer these files to the computer desktop. For the method of transferring files from the virtual machine to the local Windows computer, refer to the link article in the previous section on building the compilation environment.
2. Download the firmware
. Open DfuSe Demo software (if you don't have this software, you can download and install it separately), unplug the BOOT0 routing cap, use Dupont wire to connect RST and BOOT0 of Apollo STM32H743 development board, then turn on the power, plug in the USB cable to enter DFU download mode. Click Choose to select the firmware.dfu firmware file just transferred, and then click Upgrade to download it to the chip.
After the download is complete, turn off the power, remove the RST and BOOT0 routes, plug in the BOOT0 and GND route caps, and then use a Dupont line to connect the 3.3V and PA9 pins. This is the identification port for inserting the USB cable, and can also be modified in the USB config in the mpconfigboard.h file.
3. Power-on test
. Next, plug in the USB cable to connect to the computer and a 32M virtual disk PYBFLASH will appear. The written Python code can be saved in boot.py and main.py to run.
In addition, you can see a virtual serial port in the device manager: Pyboard USB Comm Port (COM_X). The name of this serial port can be modified by yourself. It can be found at the bottom of pybcdc.inf in the virtual disk.
DESCRIPTION="Pyboard USB Comm Port"
Modify the content you want to display, then update the driver and select the file you just modified. Then test whether the SDRAM expansion is successful, connect with SecureCRT software, configure the serial port parameters, and enter in REPL:
import pyb,gc
pyb.info()
Press Enter to view the motherboard information:
enter:
gc.mem_free()
You can see that there is 32M of SDRAM remaining, indicating that the SDRAM expansion was successful.
So far, STM32H743 has successfully transplanted micropython and expanded 32M QSPI Flash and 32M SDRAM. You can play with other functions of the micropython board by yourself.
Copyright Statement: This article may not be reproduced or used for other purposes without my consent