summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Lechner <david@pybricks.com>2022-06-14 22:32:44 -0500
committerDamien George <damien@micropython.org>2022-08-06 00:01:31 +1000
commit10f85fee183404a3cba2795e6cd22c12be742fb3 (patch)
treebd6b531e1ba15b1fe9b16f4360653e1cbd41978a
parent0f0f2351bb3c45801250352e72e026e1671fd5d8 (diff)
stm32/boards/LEGO_HUB_NO7: Add LEGO Hub No. 7 board definition.
This adds support for the LEGO Hub No. 7, aka LEGO Technic Small hub, aka LEGO SPIKE Essential hub. This board is largely similar to Hub No. 6: - Same MCU (STM32F413 - different packaging with fewer pins). - Same Bluetooth chip (TI CC2564). - Same IMU chip. - Similar external flash chip - 4MiB instead of 32MiB. - 2 I/O ports instead of 6. - No display - only status and battery LEDs. - Different LED driver chip. - Only 1 button which is also the power button. - No speaker. Signed-off-by: David Lechner <david@pybricks.com>
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/README.md135
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/bdev.c3
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/bluetooth_init_cc2564C_1.5.c3
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/board.json13
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/board_init.c212
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/cc2564.c6
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/hub_display.c190
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/hub_display.h4
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/manifest.py5
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.h152
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.mk50
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/pins.csv114
-rw-r--r--ports/stm32/boards/LEGO_HUB_NO7/stm32f4xx_hal_conf.h23
13 files changed, 910 insertions, 0 deletions
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/README.md b/ports/stm32/boards/LEGO_HUB_NO7/README.md
new file mode 100644
index 000000000..8159cf3f4
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/README.md
@@ -0,0 +1,135 @@
+LEGO Hub No.7
+=============
+
+This board definition is for the LEGO Hub No. 7, a LEGO control unit with 1 button,
+2 RGB LEDs, 2 Powered Up ports, 6-DOF sensor, Bluetooth, USB, 4MiB external SPI
+flash storage, and a rechargeable battery.
+
+Features that are currently supported:
+- standard MicroPython
+- machine and bluetooth modules
+- filesystem
+- USB VCP, MSC and HID
+
+The Hub has a bootloader preinstalled at 0x08000000 (which is 32kiB in size) which
+cannot be erased. This bootloader is entered by holding down the button for 5 seconds,
+at which point the USB DFU device appears. If the battery is installed then the
+RGB LED will flash purple. If the battery is not installed, the LED will flash orange
+briefly and then the hub will turn off (so having the battery installed is required).
+When this bootloader is active, the flash from 0x08008000 and up can be erased
+and programmed via USB DFU.
+
+The built-in bootloader has some drawbacks: it cannot be entered programmatically,
+and it does not keep the Hub powered up when running from battery (which requires
+keeping BAT_PWR_EN high). As such, this board is configured to work with mboot as
+a secondary bootloader: mboot is placed at 0x08008000 and the main application
+firmware at 0x08010000. When mboot is installed it can be entered programatically
+via machine.bootloader().
+
+Backing up original Hub firmware
+--------------------------------
+
+Before installing MicroPython it is advised to backup the original LEGO firmware that
+the Hub comes installed with. To do this, enter the built-in bootloader by holding
+down the power button for 5 seconds while powering up the Hub via USB (you may
+need to take out the battery and disconnect USB to power off the Hub first). Then
+run the following command from the root of this repository:
+
+ $ cd ports/stm32
+ $ make BOARD=LEGO_HUB_NO7 backup-hub-firmware
+
+This will create a file called `lego_hub_firmware.dfu`. Put this file in a safe
+location. To restore it, enter the built-in bootloader again and run:
+
+ $ make BOARD=LEGO_HUB_NO7 restore-hub-firmware
+
+This will restore the original firmware but not the filesystem. To recreate the
+original filesystem the Hub must be updated using the appropriate LEGO PC
+application.
+
+Installing MicroPython
+----------------------
+
+You first need to build and install mboot, which only needs to be done once. From
+the root of this repository run:
+
+ $ cd ports/stm32/mboot
+ $ make BOARD=LEGO_HUB_NO7
+
+Now enter the built-in bootloader by holding down the power button for 5
+seconds while powering up the Hub via USB (you may need to take out the battery
+and disconnect USB to power off the Hub first). Then run:
+
+ $ make BOARD=LEGO_HUB_NO7 deploy
+
+mboot should now be installed. To enter mboot, remove USB and the battery.
+Connect the USB cable (the other end of the USB cable must be connected to
+something that provides power). The status light should start cycling through
+different colors. Replace the battery (the button will not work without the
+battery present). Press the button to activate the desired boot mode:
+
+- Status light is red - run application (normal boot).
+- Status light is green - run application in factory file system mode.
+- Status light is blue - run application in safe mode.
+- Status light is white - start DFU on the USB port.
+
+
+Now build MicroPython (start at the root of this repository):
+
+ $ cd mpy-cross
+ $ make
+ $ cd ../ports/stm32
+ $ make submodules
+ $ make BOARD=LEGO_HUB_NO7
+
+And deploy to the Hub (making sure mboot DFU is active, the center button is
+blinking red):
+
+ $ make BOARD=LEGO_HUB_NO7 deploy
+
+If successful, the Hub should now appear as a USB serial and mass storage device.
+
+Using MicroPython on the Hub
+----------------------------
+
+Access the MicroPython REPL using mpremote (pip install mpremote), or with any
+serial terminal program.
+
+To scan for BLE devices:
+
+ >>> import bluetooth
+ >>> ble = bluetooth.BLE()
+ >>> ble.irq(lambda *x: print(*x))
+ >>> ble.active(1)
+ >>> ble.gap_scan(2000, 625, 625)
+
+Use help("modules") to see available built-in modules.
+
+Updating MicroPython from the Hub's filesystem
+----------------------------------------------
+
+You can update the MicroPython application firmware using the instructions above
+for installing the firmware for the first time. The Hub also supports updating
+the application firmware from within MicroPython itself, using the on-board
+filesystem.
+
+To use this feature, build the firmware (see above for details) then gzip it and
+copy the resulting file to the Hub (eg using mpremote):
+
+ $ make BOARD=LEGO_HUB_NO7
+ $ gzip build-LEGO_HUB_NO7/firmware.dfu
+ $ mpremote cp build-LEGO_HUB_NO7/firmware.dfu.gz :
+
+Then get a REPL on the Hub and execute:
+
+ >>> import appupdate
+ >>> appupdate.update_app("firmware.dfu.gz")
+
+You can alternatively run this REPL command using mpremote:
+
+ $ mpremote exec --no-follow "import appupdate; appupdate.update_app('firmware.dfu.gz')"
+
+At that point the Hub should restart and the LED on the central button will flash
+different colours. Once the update is complete the LED will stop flashing and the
+Hub will appear again as a USB device. The application firmware is now updated
+and you can remove the firmware.dfu.gz file if desired.
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/bdev.c b/ports/stm32/boards/LEGO_HUB_NO7/bdev.c
new file mode 100644
index 000000000..f104c0f3f
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/bdev.c
@@ -0,0 +1,3 @@
+// LEGO_HUB_NO7 is identical to LEGO_HUB_NO6 in this regard.
+
+#include "../LEGO_HUB_NO6/bdev.c"
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/bluetooth_init_cc2564C_1.5.c b/ports/stm32/boards/LEGO_HUB_NO7/bluetooth_init_cc2564C_1.5.c
new file mode 100644
index 000000000..6ea298f68
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/bluetooth_init_cc2564C_1.5.c
@@ -0,0 +1,3 @@
+// LEGO_HUB_NO7 is identical to LEGO_HUB_NO6 in this regard.
+
+#include "../LEGO_HUB_NO6/bluetooth_init_cc2564C_1.5.c"
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/board.json b/ports/stm32/boards/LEGO_HUB_NO7/board.json
new file mode 100644
index 000000000..1ef02c17a
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/board.json
@@ -0,0 +1,13 @@
+{
+ "deploy": [
+ "../deploy.md"
+ ],
+ "docs": "",
+ "features": [],
+ "images": [],
+ "mcu": "stm32f4",
+ "product": "Hub No.7",
+ "thumbnail": "",
+ "url": "",
+ "vendor": "LEGO"
+}
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/board_init.c b/ports/stm32/boards/LEGO_HUB_NO7/board_init.c
new file mode 100644
index 000000000..7b155b1c3
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/board_init.c
@@ -0,0 +1,212 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021-2022 Damien P. George
+ * Copyright (c) 2022 David Lechner <david@pybricks.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/mphal.h"
+#include "irq.h"
+
+void board_init(void) {
+ if (query_irq() == IRQ_STATE_DISABLED) {
+ enable_irq(IRQ_STATE_ENABLED);
+ }
+
+ // Enable 3V3 for all ports
+ mp_hal_pin_output(pyb_pin_PORT_3V3_EN);
+ mp_hal_pin_high(pyb_pin_PORT_3V3_EN);
+
+ // Port A
+ // Enable RX/TX buffer
+ mp_hal_pin_output(pyb_pin_PORTA_EN);
+ mp_hal_pin_low(pyb_pin_PORTA_EN);
+
+ // Port B
+ // Enable RX/TX buffer
+ mp_hal_pin_output(pyb_pin_PORTB_EN);
+ mp_hal_pin_low(pyb_pin_PORTB_EN);
+}
+
+#if BUILDING_MBOOT
+
+#include "drivers/memory/spiflash.h"
+#include "mboot/mboot.h"
+#include "boardctrl.h"
+#include "adc.h"
+#include "hub_display.h"
+
+#define RESET_MODE_NUM_STATES (4)
+#define RESET_MODE_TIMEOUT_CYCLES (8)
+
+// Location and value for the SPI flash update key. If this key exists at the defined
+// location then mboot will attempt to do a filesystem-load update of the main firmware.
+// This makes the update robust to power failures: if the update does not complete then
+// it will be restarted the next time it powers up. Only when it fully completes will
+// this key be erased, and then the application can run.
+#define SPIFLASH_UPDATE_KEY_ADDR (1020 * 1024)
+#define SPIFLASH_UPDATE_KEY_VALUE (0x12345678)
+
+static void board_led_pattern(int reset_mode, uint16_t brightness) {
+ switch (reset_mode) {
+ case BOARDCTRL_RESET_MODE_NORMAL:
+ // set status light to red
+ hub_display_set(3, brightness);
+ hub_display_set(4, 0);
+ hub_display_set(5, 0);
+ break;
+ case BOARDCTRL_RESET_MODE_SAFE_MODE:
+ // set status light to green
+ hub_display_set(3, 0);
+ hub_display_set(4, brightness);
+ hub_display_set(5, 0);
+ break;
+ case BOARDCTRL_RESET_MODE_FACTORY_FILESYSTEM:
+ // set status light to blue
+ hub_display_set(3, 0);
+ hub_display_set(4, 0);
+ hub_display_set(5, brightness);
+ break;
+ case BOARDCTRL_RESET_MODE_BOOTLOADER:
+ // set status light to white
+ hub_display_set(3, brightness);
+ hub_display_set(4, brightness);
+ hub_display_set(5, brightness);
+ break;
+ }
+
+ hub_display_update();
+}
+
+static void board_battery_init(void) {
+ mp_hal_pin_config(pyb_pin_BAT_VMON_ADC, MP_HAL_PIN_MODE_ADC, MP_HAL_PIN_PULL_NONE, 0);
+ adc_config(ADC1, 12);
+}
+
+// returns true if the battery is pressed, otherwise false
+static int board_battery_state(void) {
+ uint16_t value = adc_config_and_read_u16(ADC1, 6, ADC_SAMPLETIME_15CYCLES);
+ // If battery voltage is above USB voltage, then we consider the battery
+ // to be present.
+ return value > 41100; // 41100 is approx 5.5V
+}
+
+static void board_button_init(void) {
+ mp_hal_pin_input(pyb_pin_BUTTON);
+}
+
+// returns true if the button is pressed, otherwise false
+static int board_button_state(void) {
+ // button is active low
+ return !mp_hal_pin_read(pyb_pin_BUTTON);
+}
+
+void board_mboot_cleanup(int reset_mode) {
+ board_led_pattern(0, 0);
+ hub_display_off();
+}
+
+void board_mboot_led_init(void) {
+ hub_display_on();
+}
+
+void board_mboot_led_state(int led, int state) {
+ if (state) {
+ hub_display_set(3 + led, 0x7fff);
+ } else {
+ hub_display_set(3 + led, 0);
+ }
+
+ hub_display_update();
+}
+
+int board_mboot_get_reset_mode(uint32_t *initial_r0) {
+ board_battery_init();
+ board_button_init();
+ int reset_mode = BOARDCTRL_RESET_MODE_NORMAL;
+
+ if (board_battery_state()) {
+ // Battery is present, check flash for update key and start an update if the key exists.
+ // Otherwise, boot normally.
+
+ // Initialise the external SPI flash.
+ MBOOT_SPIFLASH_SPIFLASH->config = MBOOT_SPIFLASH_CONFIG;
+ mp_spiflash_init(MBOOT_SPIFLASH_SPIFLASH);
+
+ // Read in the key.
+ uint32_t buf;
+ mp_spiflash_read(MBOOT_SPIFLASH_SPIFLASH, SPIFLASH_UPDATE_KEY_ADDR, 4, (uint8_t *)&buf);
+
+ if (buf == SPIFLASH_UPDATE_KEY_VALUE) {
+ // The key has the correct value, so read in the FS-load elements and enter the bootloader.
+ mp_spiflash_read(MBOOT_SPIFLASH_SPIFLASH, SPIFLASH_UPDATE_KEY_ADDR + 4, ELEM_DATA_SIZE, ELEM_DATA_START);
+ *initial_r0 = MBOOT_INITIAL_R0_KEY_FSLOAD;
+ reset_mode = BOARDCTRL_RESET_MODE_BOOTLOADER;
+ }
+ } else {
+ // Battery is not present. Cycle through reset modes until button is pressed.
+ systick_init();
+ hub_display_on();
+ reset_mode = 0;
+ for (int i = 0; i < (RESET_MODE_NUM_STATES * RESET_MODE_TIMEOUT_CYCLES + 1) * 64; i++) {
+ if (i % 64 == 0) {
+ if (++reset_mode > RESET_MODE_NUM_STATES) {
+ reset_mode = BOARDCTRL_RESET_MODE_NORMAL;
+ }
+ board_led_pattern(reset_mode, 0x7fff);
+ }
+
+ if (board_button_state()) {
+ break;
+ }
+
+ mp_hal_delay_ms(19);
+ }
+
+ // Flash the selected reset mode.
+ for (int i = 0; i < 6; i++) {
+ board_led_pattern(reset_mode, 0x0fff);
+ mp_hal_delay_ms(50);
+ board_led_pattern(reset_mode, 0x7fff);
+ mp_hal_delay_ms(50);
+ }
+
+ mp_hal_delay_ms(300);
+ }
+
+ board_led_pattern(0, 0);
+ return reset_mode;
+}
+
+void board_mboot_state_change(int state, uint32_t arg) {
+ if (state == MBOOT_STATE_FSLOAD_END) {
+ // The FS-load update completed (either with success or failure), so erase the
+ // update key and write the result of the FS-load operation into flash.
+ mp_spiflash_erase_block(MBOOT_SPIFLASH_SPIFLASH, SPIFLASH_UPDATE_KEY_ADDR);
+ mp_spiflash_write(MBOOT_SPIFLASH_SPIFLASH, SPIFLASH_UPDATE_KEY_ADDR + 4, 4, (const uint8_t *)&arg);
+ }
+
+ mboot_state_change_default(state, arg);
+}
+
+#endif
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/cc2564.c b/ports/stm32/boards/LEGO_HUB_NO7/cc2564.c
new file mode 100644
index 000000000..e5bfe1b4d
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/cc2564.c
@@ -0,0 +1,6 @@
+// LEGO_HUB_NO7 is nearly identical to LEGO_HUB_NO6 in this regard.
+
+#define CC2564_TIMER_BT_SLOWCLOCK_TIM 2
+#define CC2564_TIMER_BT_SLOWCLOCK_TIM_CH 2
+
+#include "../LEGO_HUB_NO6/cc2564.c"
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/hub_display.c b/ports/stm32/boards/LEGO_HUB_NO7/hub_display.c
new file mode 100644
index 000000000..e19485b06
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/hub_display.c
@@ -0,0 +1,190 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2021 Damien P. George
+ * Copyright (c) 2022 David Lechner <david@pybricks.com>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+#include "py/mphal.h"
+#include "hub_display.h"
+
+#include STM32_HAL_H
+
+#define I2C_ADDR 0x28
+
+// Registers
+#define DEVICE_CONFIG0 0x00
+#define DEVICE_CONFIG1 0x01
+#define LED_CONFIG0 0x02
+#define BANK_BRIGHTNESS 0x03
+#define BANK_A_COLOR 0x04
+#define BANK_B_COLOR 0x05
+#define BANK_C_COLOR 0x06
+#define LED0_BRIGHTNESS 0x07
+#define LED1_BRIGHTNESS 0x08
+#define LED2_BRIGHTNESS 0x09
+#define LED3_BRIGHTNESS 0x0A
+#define OUT0_COLOR 0x0B
+#define OUT1_COLOR 0x0C
+#define OUT2_COLOR 0x0D
+#define OUT3_COLOR 0x0E
+#define OUT4_COLOR 0x0F
+#define OUT5_COLOR 0x10
+#define OUT6_COLOR 0x11
+#define OUT7_COLOR 0x12
+#define OUT8_COLOR 0x13
+#define OUT9_COLOR 0x14
+#define OUT10_COLOR 0x15
+#define OUT11_COLOR 0x16
+#define RESET 0x17
+
+// Flags
+#define DEVICE_CONFIG0_CHIP_EN (1 << 6)
+#define DEVICE_CONFIG1_LOG_SCALE_EN (1 << 5)
+#define DEVICE_CONFIG1_POWER_SAVE_EN (1 << 4)
+#define DEVICE_CONFIG1_AUTO_INCR_EN (1 << 3)
+#define DEVICE_CONFIG1_PWM_DITHERING_EN (1 << 2)
+#define DEVICE_CONFIG1_MAX_CURRENT_OPTION (1 << 1)
+#define DEVICE_CONFIG1_LED_GLOBAL_OFF (1 << 0)
+#define LED_CONFIG0_LED3_BANK_EN (1 << 3)
+#define LED_CONFIG0_LED2_BANK_EN (1 << 2)
+#define LED_CONFIG0_LED1_BANK_EN (1 << 1)
+#define LED_CONFIG0_LED0_BANK_EN (1 << 0)
+
+#define LP50XX_NUM_CH 6
+
+// channel mapping:
+// CH 0 = battery LED - red
+// CH 1 = battery LED - green
+// CH 2 = battery LED - blue
+// CH 3 = status LED - red
+// CH 4 = status LED - green
+// CH 5 = status LED - blue
+
+#define FMPI2C_CONVERT_TIMINGS(PRESC, SCLDEL, SDADEL, SCLH, SCLL) \
+ (((PRESC) << FMPI2C_TIMINGR_PRESC_Pos) | \
+ ((SCLDEL) << FMPI2C_TIMINGR_SCLDEL_Pos) | \
+ ((SDADEL) << FMPI2C_TIMINGR_SDADEL_Pos) | \
+ ((SCLH) << FMPI2C_TIMINGR_SCLH_Pos) | \
+ ((SCLL) << FMPI2C_TIMINGR_SCLL_Pos))
+
+static FMPI2C_HandleTypeDef hub_display_i2c;
+static bool hub_display_init;
+
+static struct {
+ uint8_t reg;
+ uint8_t values[LP50XX_NUM_CH];
+} __attribute__((packed)) hub_display_data = {
+ .reg = OUT0_COLOR,
+};
+
+void HAL_FMPI2C_MspInit(FMPI2C_HandleTypeDef *hfmpi2c) {
+ __HAL_RCC_FMPI2C1_CLK_ENABLE();
+ mp_hal_pin_config(pyb_pin_LED_SCL, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_NONE, 4);
+ mp_hal_pin_config(pyb_pin_LED_SDA, MP_HAL_PIN_MODE_ALT_OPEN_DRAIN, MP_HAL_PIN_PULL_NONE, 4);
+}
+
+void HAL_FMPI2C_MspDeInit(FMPI2C_HandleTypeDef *hfmpi2c) {
+ __HAL_RCC_FMPI2C1_CLK_DISABLE();
+ __HAL_RCC_FMPI2C1_FORCE_RESET();
+ __HAL_RCC_FMPI2C1_RELEASE_RESET();
+ mp_hal_pin_config(pyb_pin_LED_SCL, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
+ mp_hal_pin_config(pyb_pin_LED_SDA, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
+}
+
+static void hub_display_i2c_init(void) {
+ hub_display_i2c.Instance = FMPI2C1;
+ hub_display_i2c.Init.Timing = FMPI2C_CONVERT_TIMINGS(0, 4, 0, 19, 28);
+ hub_display_i2c.Init.OwnAddress1 = 0;
+ hub_display_i2c.Init.AddressingMode = FMPI2C_ADDRESSINGMODE_7BIT;
+ hub_display_i2c.Init.DualAddressMode = FMPI2C_DUALADDRESS_DISABLE;
+ hub_display_i2c.Init.OwnAddress2 = 0;
+ hub_display_i2c.Init.OwnAddress2Masks = FMPI2C_OA2_NOMASK;
+ hub_display_i2c.Init.GeneralCallMode = FMPI2C_GENERALCALL_DISABLE;
+ hub_display_i2c.Init.NoStretchMode = FMPI2C_NOSTRETCH_DISABLE;
+ HAL_FMPI2C_Init(&hub_display_i2c);
+}
+
+void hub_display_set(uint8_t led, uint16_t value) {
+ if (led >= LP50XX_NUM_CH) {
+ return;
+ }
+
+ hub_display_data.values[led] = value >> 8;
+}
+
+void hub_display_update(void) {
+ if (!hub_display_init) {
+ return;
+ }
+
+ HAL_FMPI2C_Master_Transmit(&hub_display_i2c, I2C_ADDR, (uint8_t *)&hub_display_data,
+ sizeof(hub_display_data), HAL_MAX_DELAY);
+}
+
+void hub_display_on(void) {
+ if (hub_display_init) {
+ return;
+ }
+
+ hub_display_i2c_init();
+ mp_hal_pin_output(pyb_pin_LED_EN);
+ mp_hal_pin_high(pyb_pin_LED_EN);
+
+ static const struct {
+ uint8_t reg;
+ uint8_t values[11];
+ } __attribute__((packed)) init_data = {
+ .reg = DEVICE_CONFIG0,
+ .values = {
+ [DEVICE_CONFIG0] = DEVICE_CONFIG0_CHIP_EN,
+ [DEVICE_CONFIG1] = DEVICE_CONFIG1_POWER_SAVE_EN | DEVICE_CONFIG1_PWM_DITHERING_EN | DEVICE_CONFIG1_AUTO_INCR_EN,
+ [LED_CONFIG0] = 0,
+ [BANK_BRIGHTNESS] = 0,
+ [BANK_A_COLOR] = 0,
+ [BANK_B_COLOR] = 0,
+ [BANK_C_COLOR] = 0,
+ [LED0_BRIGHTNESS] = 51, // battery LED
+ [LED1_BRIGHTNESS] = 38, // status LED
+ [LED2_BRIGHTNESS] = 0,
+ [LED3_BRIGHTNESS] = 0,
+ }
+ };
+
+ HAL_FMPI2C_Master_Transmit(&hub_display_i2c, I2C_ADDR, (uint8_t *)&init_data,
+ sizeof(init_data), HAL_MAX_DELAY);
+
+ hub_display_init = true;
+}
+
+void hub_display_off(void) {
+ if (!hub_display_init) {
+ return;
+ }
+
+ HAL_FMPI2C_DeInit(&hub_display_i2c);
+
+ mp_hal_pin_config(pyb_pin_LED_EN, MP_HAL_PIN_MODE_ANALOG, MP_HAL_PIN_PULL_NONE, 0);
+
+ hub_display_init = false;
+}
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/hub_display.h b/ports/stm32/boards/LEGO_HUB_NO7/hub_display.h
new file mode 100644
index 000000000..7623e128a
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/hub_display.h
@@ -0,0 +1,4 @@
+void hub_display_on(void);
+void hub_display_off(void);
+void hub_display_update(void);
+void hub_display_set(uint8_t led, uint16_t value);
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/manifest.py b/ports/stm32/boards/LEGO_HUB_NO7/manifest.py
new file mode 100644
index 000000000..d74638163
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/manifest.py
@@ -0,0 +1,5 @@
+include("$(PORT_DIR)/boards/manifest.py")
+
+# Modules for application firmware update.
+freeze("$(PORT_DIR)/mboot", "fwupdate.py", opt=3)
+freeze("$(PORT_DIR)/boards/LEGO_HUB_NO6", ("spiflash.py", "appupdate.py"), opt=3)
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.h b/ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.h
new file mode 100644
index 000000000..50fb6c060
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.h
@@ -0,0 +1,152 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ * The MIT License (MIT)
+ * Copyright (c) 2021 Damien P. George
+ */
+
+#include <stdint.h>
+
+#define MICROPY_HW_BOARD_NAME "LEGO Technic Hub No.7"
+#define MICROPY_HW_MCU_NAME "STM32F413"
+
+#define MICROPY_HW_HAS_SWITCH (0)
+#define MICROPY_HW_HAS_FLASH (1)
+#define MICROPY_PY_PYB_LEGACY (0)
+#define MICROPY_HW_ENTER_BOOTLOADER_VIA_RESET (0)
+#define MICROPY_HW_ENABLE_INTERNAL_FLASH_STORAGE (0)
+#define MICROPY_HW_ENABLE_RTC (1)
+#define MICROPY_HW_ENABLE_RNG (1)
+#define MICROPY_HW_ENABLE_DAC (1)
+#define MICROPY_HW_ENABLE_USB (1)
+#define MICROPY_HW_FLASH_FS_LABEL "HUB_NO7"
+
+// HSE is 16MHz, CPU freq set to 100MHz, buses at maximum freq
+#define MICROPY_HW_CLK_PLLM (16)
+#define MICROPY_HW_CLK_PLLN (200)
+#define MICROPY_HW_CLK_PLLP (RCC_PLLP_DIV2)
+#define MICROPY_HW_CLK_PLLQ (4)
+#define MICROPY_HW_CLK_AHB_DIV (RCC_SYSCLK_DIV1)
+#define MICROPY_HW_CLK_APB1_DIV (RCC_HCLK_DIV2)
+#define MICROPY_HW_CLK_APB2_DIV (RCC_HCLK_DIV1)
+
+// For 2.7 to 3.6 V, 75 to 100 MHz: 3 wait states.
+#define MICROPY_HW_FLASH_LATENCY FLASH_LATENCY_3
+
+// UART buses
+// Bluetooth HCI
+#define MICROPY_HW_UART2_CTS (pyb_pin_BT_CTS)
+#define MICROPY_HW_UART2_RTS (pyb_pin_BT_RTS)
+#define MICROPY_HW_UART2_TX (pyb_pin_BT_TX)
+#define MICROPY_HW_UART2_RX (pyb_pin_BT_RX)
+// Port B
+#define MICROPY_HW_UART3_TX (pyb_pin_PORTB_TX)
+#define MICROPY_HW_UART3_RX (pyb_pin_PORTB_RX)
+// Port A
+#define MICROPY_HW_UART5_TX (pyb_pin_PORTA_TX)
+#define MICROPY_HW_UART5_RX (pyb_pin_PORTA_RX)
+
+// SPI buses
+#define MICROPY_HW_SPI2_NSS (pyb_pin_FLASH_NSS)
+#define MICROPY_HW_SPI2_SCK (pyb_pin_FLASH_SCK)
+#define MICROPY_HW_SPI2_MISO (pyb_pin_FLASH_MISO)
+#define MICROPY_HW_SPI2_MOSI (pyb_pin_FLASH_MOSI)
+
+// USB config
+#define MICROPY_HW_USB_VBUS_DETECT_PIN (pyb_pin_USB_VBUS)
+#define MICROPY_HW_USB_FS (1)
+#define MICROPY_HW_USB_MSC (1)
+
+// Bluetooth config
+#define MICROPY_HW_BLE_UART_ID (PYB_UART_2)
+#define MICROPY_HW_BLE_UART_BAUDRATE (115200)
+#define MICROPY_HW_BLE_UART_BAUDRATE_SECONDARY (921600)
+#define MICROPY_HW_BLE_BTSTACK_CHIPSET_INSTANCE btstack_chipset_cc256x_instance()
+
+// SPI flash, for R/W storage
+// The first 1MiB is skipped because it's used by the built-in bootloader
+// Note: MICROPY_HW_SPIFLASH_OFFSET_BYTES must be a multiple of MP_SPIFLASH_ERASE_BLOCK_SIZE
+#define MICROPY_HW_SPIFLASH_OFFSET_BYTES (1024 * 1024)
+#define MICROPY_HW_SPIFLASH_BLOCKMAP(bl) ((bl) + MICROPY_HW_SPIFLASH_OFFSET_BYTES / FLASH_BLOCK_SIZE)
+#define MICROPY_HW_SPIFLASH_BLOCKMAP_EXT(bl) ((bl) + MICROPY_HW_SPIFLASH_OFFSET_BYTES / MP_SPIFLASH_ERASE_BLOCK_SIZE)
+#define MICROPY_HW_SPIFLASH_ENABLE_CACHE (1)
+#define MICROPY_HW_SPIFLASH_SIZE_BITS (32 * 1024 * 1024 - MICROPY_HW_SPIFLASH_OFFSET_BYTES * 8)
+#define MICROPY_HW_SPIFLASH_CS (MICROPY_HW_SPI2_NSS)
+#define MICROPY_HW_SPIFLASH_SCK (MICROPY_HW_SPI2_SCK)
+#define MICROPY_HW_SPIFLASH_MISO (MICROPY_HW_SPI2_MISO)
+#define MICROPY_HW_SPIFLASH_MOSI (MICROPY_HW_SPI2_MOSI)
+
+// SPI flash, block device config
+#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)) \
+ )
+
+// Configuration for stardard block protocol (block size FLASH_BLOCK_SIZE).
+#define MICROPY_HW_BDEV_READBLOCKS(dest, bl, n) \
+ spi_bdev_readblocks(&spi_bdev, (dest), MICROPY_HW_SPIFLASH_BLOCKMAP(bl), (n))
+#define MICROPY_HW_BDEV_WRITEBLOCKS(src, bl, n) \
+ spi_bdev_writeblocks(&spi_bdev, (src), MICROPY_HW_SPIFLASH_BLOCKMAP(bl), (n))
+
+// Configuration for extended block protocol (block size MP_SPIFLASH_ERASE_BLOCK_SIZE).
+#define MICROPY_HW_BDEV_BLOCKSIZE_EXT (MP_SPIFLASH_ERASE_BLOCK_SIZE)
+#define MICROPY_HW_BDEV_READBLOCKS_EXT(dest, bl, off, len) \
+ (spi_bdev_readblocks_raw(&spi_bdev, (dest), MICROPY_HW_SPIFLASH_BLOCKMAP_EXT(bl), (off), (len)))
+#define MICROPY_HW_BDEV_WRITEBLOCKS_EXT(src, bl, off, len) \
+ (spi_bdev_writeblocks_raw(&spi_bdev, (src), MICROPY_HW_SPIFLASH_BLOCKMAP_EXT(bl), (off), (len)))
+#define MICROPY_HW_BDEV_ERASEBLOCKS_EXT(bl, len) \
+ (spi_bdev_eraseblocks_raw(&spi_bdev, MICROPY_HW_SPIFLASH_BLOCKMAP_EXT(bl), (len)))
+
+// Board control config
+#define MICROPY_BOARD_STARTUP board_init
+
+/******************************************************************************/
+// Bootloader configuration
+
+// Configure CPU frequency to 96MHz, to make updates from SPI flash faster
+#define MBOOT_CLK_PLLM (MICROPY_HW_CLK_VALUE / 1000000)
+#define MBOOT_CLK_PLLN (192)
+#define MBOOT_CLK_PLLP (RCC_PLLP_DIV2)
+#define MBOOT_CLK_PLLQ (4)
+#define MBOOT_CLK_AHB_DIV (RCC_SYSCLK_DIV1)
+#define MBOOT_CLK_APB1_DIV (RCC_HCLK_DIV4)
+#define MBOOT_CLK_APB2_DIV (RCC_HCLK_DIV2)
+#define MBOOT_FLASH_LATENCY FLASH_LATENCY_3
+
+#define MBOOT_FSLOAD (1)
+#define MBOOT_VFS_FAT (1)
+#define MBOOT_LEAVE_BOOTLOADER_VIA_RESET (0)
+
+#define MBOOT_SPIFLASH_ADDR (0x80000000)
+#define MBOOT_SPIFLASH_BYTE_SIZE (4 * 1024 * 1024)
+#define MBOOT_SPIFLASH_LAYOUT "/0x80000000/1024*4Kg"
+#define MBOOT_SPIFLASH_ERASE_BLOCKS_PER_PAGE (1)
+#define MBOOT_SPIFLASH_SPIFLASH (&board_mboot_spiflash)
+#define MBOOT_SPIFLASH_CONFIG (&board_mboot_spiflash_config)
+
+#define MBOOT_LED1 0
+#define MBOOT_LED2 1
+#define MBOOT_LED3 2
+#define MBOOT_BOARD_LED_INIT board_mboot_led_init
+#define MBOOT_BOARD_LED_STATE board_mboot_led_state
+
+#define MBOOT_BOARD_EARLY_INIT(initial_r0) board_init()
+#define MBOOT_BOARD_CLEANUP board_mboot_cleanup
+#define MBOOT_BOARD_GET_RESET_MODE board_mboot_get_reset_mode
+#define MBOOT_BOARD_STATE_CHANGE board_mboot_state_change
+
+/******************************************************************************/
+// Function declarations
+
+extern const struct _mp_spiflash_config_t spiflash_config;
+extern struct _spi_bdev_t spi_bdev;
+extern const struct _mp_spiflash_config_t board_mboot_spiflash_config;
+extern struct _mp_spiflash_t board_mboot_spiflash;
+
+void board_init(void);
+void board_mboot_cleanup(int reset_mode);
+void board_mboot_led_init(void);
+void board_mboot_led_state(int led, int state);
+int board_mboot_get_reset_mode(uint32_t *initial_r0);
+void board_mboot_state_change(int state, uint32_t arg);
+void *btstack_chipset_cc256x_instance(void);
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.mk b/ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.mk
new file mode 100644
index 000000000..c06f9a26a
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/mpconfigboard.mk
@@ -0,0 +1,50 @@
+MCU_SERIES = f4
+CMSIS_MCU = STM32F413xx
+AF_FILE = boards/stm32f413_af.csv
+LD_FILES = boards/LEGO_HUB_NO6/stm32f413xg.ld boards/common_bl.ld
+TEXT0_ADDR = 0x08010000
+
+BOOTLOADER_DFU_USB_VID ?= 0x0694
+BOOTLOADER_DFU_USB_PID ?= 0x000C
+
+# MicroPython settings
+MICROPY_PY_BLUETOOTH ?= 1
+MICROPY_BLUETOOTH_NIMBLE ?= 0
+MICROPY_BLUETOOTH_BTSTACK ?= 1
+MICROPY_VFS_LFS2 ?= 1
+
+# Board specific frozen modules
+FROZEN_MANIFEST ?= $(BOARD_DIR)/manifest.py
+
+SRC_HAL += $(STM32LIB_HAL_BASE)/Src/stm32f4xx_hal_fmpi2c.c
+
+ifneq ($(BUILDING_MBOOT),1)
+LIB_SRC_C += lib/btstack/chipset/cc256x/btstack_chipset_cc256x.c
+endif
+
+# Bootloader settings
+MBOOT_TEXT0_ADDR = 0x08008000
+MBOOT_LD_FILES = ../boards/LEGO_HUB_NO6/mboot_memory.ld stm32_sections.ld
+
+# Backup/restore original Hub firmware
+
+HUB_FIRMWARE = lego_hub_firmware.dfu
+HUB_FIRMWARE_ADDR = $(MBOOT_TEXT0_ADDR)
+HUB_FIRMWARE_SIZE = 0xf8000
+
+backup-hub-firmware:
+ $(Q)$(DFU_UTIL) -a 0 \
+ -d $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) \
+ -U $(HUB_FIRMWARE).bin \
+ -s $(HUB_FIRMWARE_ADDR):$(HUB_FIRMWARE_SIZE)
+ $(Q)$(PYTHON) $(DFU) \
+ -b $(HUB_FIRMWARE_ADDR):$(HUB_FIRMWARE).bin \
+ -D $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) \
+ $(HUB_FIRMWARE)
+ $(Q)$(RM) $(HUB_FIRMWARE).bin
+ $(ECHO) "Backup created in $(HUB_FIRMWARE)"
+
+restore-hub-firmware:
+ $(Q)$(DFU_UTIL) -a 0 \
+ -d $(BOOTLOADER_DFU_USB_VID):$(BOOTLOADER_DFU_USB_PID) \
+ -D $(HUB_FIRMWARE)
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/pins.csv b/ports/stm32/boards/LEGO_HUB_NO7/pins.csv
new file mode 100644
index 000000000..dd19518b7
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/pins.csv
@@ -0,0 +1,114 @@
+BT_CTS,PA0
+BT_RTS,PA1
+BT_TX,PA2
+BT_RX,PA3
+,PA4
+,PA5
+BAT_VMON_ADC,PA6
+BAT_IMON_ADC,PA7
+LSM6_SCL,PA8
+USB_VBUS,PA9
+CHGMODE,PA10
+USB_DM,PA11
+USB_DP,PA12
+,PA13
+,PA14
+,PA15
+BAT_NTC,PB0
+BAT_PWR_EN,PB1
+BUTTON,PB2
+BT_SLOWCLK,PB3
+PORTB_M1,PB4
+PORTB_M2,PB5
+PORTA_M1,PB6
+PORTA_M2,PB7
+PORTB_EN,PB8
+PORTA_EN,PB9
+FLASH_SCK,PB10
+,PB11
+FLASH_NSS,PB12
+LED_EN,PB13
+LED_SDA,PB14
+LED_SCL,PB15
+,PC0
+,PC1
+FLASH_MISO,PC2
+FLASH_MOSI,PC3
+CHG_IMON_ADC,PC4
+,PC5
+CHGOK,PC6
+PORT_3V3_EN,PC7
+BT_ENABLE,PC8
+LSM6_SDA,PC9
+PORTB_TX,PC10
+PORTB_RX,PC11
+PORTA_TX,PC12
+LSM6_INT1,PC13
+,PC14
+,PC15
+,PD0
+,PD1
+PORTA_RX,PD2
+,PD3
+,PD4
+,PD5
+,PD6
+,PD7
+,PD8
+,PD9
+,PD10
+,PD11
+,PD12
+,PD13
+,PD14
+,PD15
+,PE0
+,PE1
+,PE2
+,PE3
+,PE4
+,PE5
+,PE6
+,PE7
+,PE8
+,PE9
+,PE10
+,PE11
+,PE12
+,PE13
+,PE14
+,PE15
+,PF0
+,PF1
+,PF2
+,PF3
+,PF4
+,PF5
+,PF6
+,PF7
+,PF8
+,PF9
+,PF10
+,PF11
+,PF12
+,PF13
+,PF14
+,PF15
+,PG0
+,PG1
+,PG2
+,PG3
+,PG4
+,PG5
+,PG6
+,PG7
+,PG8
+,PG9
+,PG10
+,PG11
+,PG12
+,PG13
+,PG14
+,PG15
+,PH0
+,PH1
diff --git a/ports/stm32/boards/LEGO_HUB_NO7/stm32f4xx_hal_conf.h b/ports/stm32/boards/LEGO_HUB_NO7/stm32f4xx_hal_conf.h
new file mode 100644
index 000000000..bd7091274
--- /dev/null
+++ b/ports/stm32/boards/LEGO_HUB_NO7/stm32f4xx_hal_conf.h
@@ -0,0 +1,23 @@
+/* This file is part of the MicroPython project, http://micropython.org/
+ * The MIT License (MIT)
+ * Copyright (c) 2019 Damien P. George
+ */
+#ifndef MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H
+#define MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H
+
+#include "boards/stm32f4xx_hal_conf_base.h"
+
+#include "stm32f4xx_hal_fmpi2c.h"
+
+#define HAL_FMPI2C_MODULE_ENABLED
+
+// Oscillator values in Hz
+#define HSE_VALUE (16000000)
+#define LSE_VALUE (32768)
+#define EXTERNAL_CLOCK_VALUE (12288000)
+
+// Oscillator timeouts in ms
+#define HSE_STARTUP_TIMEOUT (100)
+#define LSE_STARTUP_TIMEOUT (5000)
+
+#endif // MICROPY_INCLUDED_STM32F4XX_HAL_CONF_H