diff --git a/resources/downloads/Makefile b/resources/downloads/Makefile index 5130d507..33716e4a 100644 --- a/resources/downloads/Makefile +++ b/resources/downloads/Makefile @@ -44,7 +44,7 @@ pico2_w_dashboard_full pico2_w_dashboard_minimal: pico2_w_dashboard_%: define ST_DASHBOARD_RULE $(1)_dashboard_$(2): $(TOP)/tutorials/stm32/$(1)/cubemx/$(1).ioc mkdir -p $$@ - cp $$^ $$@ + cp $(TOP)/tutorials/stm32/$(1)/cubemx/* $$@ 2>/dev/null || true echo config load `realpath $$@/$(1).ioc` > gen.mx echo project generate >> gen.mx echo exit >> gen.mx diff --git a/resources/downloads/integrate_mongoose_cubemx.sh b/resources/downloads/integrate_mongoose_cubemx.sh old mode 100644 new mode 100755 index b2222ff6..b9b70f5f --- a/resources/downloads/integrate_mongoose_cubemx.sh +++ b/resources/downloads/integrate_mongoose_cubemx.sh @@ -2,6 +2,7 @@ # IOC="${1:-}" DIR="${1:-}" +ROOT_DIR=$DIR DASH=${2:-} CURL="curl -fsSL" RAW="https://raw.githubusercontent.com/cesanta/mongoose/HEAD/" @@ -12,6 +13,7 @@ fail() { echo $@ >&2 ; exit 1 ; } test -d "$DIR" || fail "Usage: $0 DIRECTORY" test -f $DIR/FSBL/Core/Src/main.c && DIR=$DIR/FSBL +test -f $DIR/CM7/Core/Src/main.c && DIR=$DIR/CM7 # Add mongoose core files test -d $DIR/Mongoose || mkdir -p $DIR/Mongoose @@ -29,12 +31,54 @@ grep -q 'mg_mgr_init' $MAIN_C || perl -i -ne 'print; print " struct mg_mgr mgr; grep -q 'mg_mgr_poll' $MAIN_C || perl -i -ne 'print " mg_mgr_poll(&mgr, 0);\n" if /USER CODE END WHILE/; print;' $MAIN_C test -f $DIR/STM32H723XG_FLASH.ld && (grep -q eth_ram $DIR/STM32H723XG_FLASH.ld || perl -i -ne 'print ; print "\n /* Mongoose Ethernet driver */\n .eth_ram : { *(.eth_ram .eth_ram*) } > RAM_D2 AT > FLASH\n" if /_sidata =/' $DIR/STM32H723XG_FLASH.ld) test -f $DIR/LICENSE || $CURL $RAW/LICENSE -o $DIR/LICENSE +case $DIR in portenta*) ( + grep -q 'void hwspecific_sdio_init(void);' "$MAIN_C" || \ + perl -i -ne 'print; print "void hwspecific_sdio_init(void);\n" if /USER CODE BEGIN 0/' "$MAIN_C" + grep -q 'hwspecific_sdio_init();' "$MAIN_C" || \ + perl -i -ne 'print " hwspecific_sdio_init();\n" if /mg_mgr_init\(&mgr\);/; print;' "$MAIN_C" +) ;; esac +case $DIR in *u5a5*) ( + grep -q 'void hwspecific_spi_init(void);' "$MAIN_C" || \ + perl -i -ne 'print; print "void hwspecific_spi_init(void);\n" if /USER CODE BEGIN 0/' "$MAIN_C" + grep -q 'hwspecific_spi_init();' "$MAIN_C" || \ + perl -i -ne 'print " hwspecific_spi_init();\n" if /mg_mgr_init\(&mgr\);/; print;' "$MAIN_C" +) ;; esac # Add Mongoose dir to the build test -f $DIR/CMakeLists.txt && (grep -q 'Mongoose/$' $DIR/CMakeLists.txt || perl -i -ne 'print; print " \${CMAKE_SOURCE_DIR}/Mongoose/\n" if /Add user defined include paths/' $DIR/CMakeLists.txt) test -f $DIR/CMakeLists.txt && (grep -q 'mongoose.c$' $DIR/CMakeLists.txt || perl -i -ne 'print; print " \${CMAKE_SOURCE_DIR}/Mongoose/mongoose.c\n" if /Add user sources here/' $DIR/CMakeLists.txt) +case $DIR in portenta*) ( + test -f $ROOT_DIR/mongoose_config.h && mv $ROOT_DIR/mongoose_config.h $DIR/Mongoose + test -f $ROOT_DIR/wifi.c && mv $ROOT_DIR/wifi.c $DIR/Mongoose + test -f $ROOT_DIR/getfw.cmake && mv $ROOT_DIR/getfw.cmake $DIR/ + test -f $DIR/CMakeLists.txt && (grep -q 'Mongoose/wifi.c$' "$DIR/CMakeLists.txt" || \ + perl -i -ne 'print; print " \${CMAKE_SOURCE_DIR}/Mongoose/wifi.c\n" if /Add user sources here/' "$DIR/CMakeLists.txt") + test -f $DIR/CMakeLists.txt && (grep -q '^[[:space:]]*\${CMAKE_SOURCE_DIR}[[:space:]]*$' "$DIR/CMakeLists.txt" || \ + perl -i -ne 'print; print " \${CMAKE_SOURCE_DIR}\n" if /Add user defined include paths/' "$DIR/CMakeLists.txt") + test -f $DIR/CMakeLists.txt && (grep -q 'COMPONENT_WHD$' "$DIR/CMakeLists.txt" || \ + perl -i -ne 'print; print " \${CMAKE_SOURCE_DIR}/mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD\n" if /Add user defined include paths/' "$DIR/CMakeLists.txt") + test -f $DIR/CMakeLists.txt && (grep -q '^add_custom_target(getfw' "$DIR/CMakeLists.txt" || \ + cat >> "$DIR/CMakeLists.txt" <<'EOF' + +add_custom_target(getfw + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_SOURCE_DIR}/getfw.cmake + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} +) + +add_dependencies(${CMAKE_PROJECT_NAME} getfw) +EOF + ) +) ;; esac +case $DIR in *u5a5*) ( + test -f $ROOT_DIR/mongoose_config.h && mv $ROOT_DIR/mongoose_config.h $DIR/Mongoose + test -f $ROOT_DIR/wifi.c && mv $ROOT_DIR/wifi.c $DIR/Mongoose + test -f $DIR/CMakeLists.txt && (grep -q 'Mongoose/wifi.c$' "$DIR/CMakeLists.txt" || \ + perl -i -ne 'print; print " \${CMAKE_SOURCE_DIR}/Mongoose/wifi.c\n" if /Add user sources here/' "$DIR/CMakeLists.txt") + +) ;; esac # If dashboard is specified, copy it to the project +#if test "$DASH" = "full" -o "$DASH" = "minimal" ; then if test "$DASH" == "full" -o "$DASH" == "minimal" ; then test -f $DIR/Mongoose/dashboard.c || $CURL $RAW/tutorials/device-dashboard/$DASH/dashboard.c -o $DIR/Mongoose/dashboard.c test -f $DIR/Mongoose/dashboard.html || $CURL $RAW/tutorials/device-dashboard/$DASH/dashboard.html -o $DIR/Mongoose/dashboard.html diff --git a/tutorials/stm32/nucleo_u5a5zj_q/cubemx/README.md b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/README.md new file mode 100644 index 00000000..f4c6af4c --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/README.md @@ -0,0 +1,75 @@ +# Nucleo-U5A5ZJ-Q Mongoose Integration + +This guide explains how to bring up a production-grade Web UI dashboard on the +Nucleo-U5A5ZJ-Q board with the X-NUCLEO-67W61M1 Wi-Fi +expansion board for STM32 Nucleo boards, based on the ST67W611M1 Wi-Fi module. + +A pre-configured, ready-to-run project zip is available for download at +https://mongoose.ws. + +To integrate the Web UI dashboard into an existing CubeMX project, or to create a +new project from scratch: + +1. Configure the peripherals (described below). +2. Regenerate your project code and integrate Mongoose core by following + https://mongoose.ws/docs/guides/stm32-tcpip-stack/. +3. Add the Web UI dashboard by following + https://mongoose.ws/docs/guides/web-dashboard-guide/. + +Note: to setup your WiFi login credentials, open the `wifi.c` file located in the +`Mongoose` directory and edit the `WIFI_NAME` and `WIFI_PASS` macros. + +## Configure peripherals + +An `.ioc` file is included in this directory for your reference. It was created +using the following steps: + +- Open CubeMX. If you are starting a new project, select the STM32U5A5ZJT6Q + microcontroller and create a new project. +- In Clock Configuration, set the maximum clock frequency to 160 MHz. +- In Pinout & Configuration / Connectivity, enable USART1 in asynchronous mode. + Configure pins PA9 and PA10 for USART. + +## Wi-Fi module firmware setup + +Before running this project, the correct firmware must be flashed onto the Wi-Fi board. + +Steps: + +1. Install the X-CUBE-ST67W61 package. + + Open `STM32CubeMX`, then navigate to: + `Help -> Manage embedded software packages`. + + In the `STMicroelectronics` tab, locate `X-CUBE-ST67W61`. + Select the latest version and click `Install`. + +2. Connect the X-NUCLEO-67W61M1 board to the Nucleo-U5A5ZJ-Q board using the + Arduino connectors CN7, CN8, CN9 and CN10. + + See Figure 5 in the Wi-Fi board + [user manual](https://www.st.com/resource/en/user_manual/um3475-getting-started-with-xcubest67w61-stmicroelectronics.pdf) + for the connector layout. + +3. Program the firmware to the ST67W61 module. + + Connect the Nucleo board to your PC using a USB cable. + + Navigate to the STM32Cube repository directory. The installed package is located at: + + `Packs/STMicroelectronics/X-CUBE-ST67W61//` + + Then navigate to: + + `/Projects/ST67W6X_Utilities/Binaries` + + Run the appropriate script depending on your operating system: + + - `NCP_update_mission_profile_t02.sh` (Linux) + - `NCP_update_mission_profile_t02.bat` (Windows) + + Ensure that `STM32CubeProgrammer` is installed and available in your system `PATH`. + + For more details on the programming process, see Section 4.3 and Table 2 of the + Wi-Fi board + [user manual](https://www.st.com/resource/en/user_manual/um3475-getting-started-with-xcubest67w61-stmicroelectronics.pdf). diff --git a/tutorials/stm32/nucleo_u5a5zj_q/cubemx/mongoose_config.h b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/mongoose_config.h new file mode 100644 index 00000000..ab967969 --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/mongoose_config.h @@ -0,0 +1,5 @@ +#pragma once +#define MG_ARCH MG_ARCH_CUBE +#define MG_ENABLE_DRIVER_ST67W6 1 +extern void wifi_setconfig(void *data); +#define MG_SET_WIFI_CONFIG(data) wifi_setconfig(data) \ No newline at end of file diff --git a/tutorials/stm32/nucleo_u5a5zj_q/cubemx/nucleo_u5a5zj_q.ioc b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/nucleo_u5a5zj_q.ioc new file mode 100644 index 00000000..1b31bfd4 --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/nucleo_u5a5zj_q.ioc @@ -0,0 +1,252 @@ +#MicroXplorer Configuration settings - do not modify +CAD.formats= +CAD.pinconfig= +CAD.provider= +CORTEX_M33_NS.userName=CORTEX_M33 +File.Version=6 +GPIO.groupedBy=Group By Peripherals +KeepUserPlacement=false +MMTAppReg1.MEMORYMAP.AP=RW_priv_only +MMTAppReg1.MEMORYMAP.AppRegionName=RAM +MMTAppReg1.MEMORYMAP.ContextName=CortexM33 +MMTAppReg1.MEMORYMAP.CoreName=ARM Cortex-M33 +MMTAppReg1.MEMORYMAP.DefaultDataRegion=true +MMTAppReg1.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,DefaultDataRegion,ContextName,Name,AP +MMTAppReg1.MEMORYMAP.Name=RAM +MMTAppReg1.MEMORYMAP.Size=2555904 +MMTAppReg1.MEMORYMAP.StartAddress=0x20000000 +MMTAppReg2.MEMORYMAP.AppRegionName=RAM Reserved Alias Region +MMTAppReg2.MEMORYMAP.CoreName=ARM Cortex-M33 +MMTAppReg2.MEMORYMAP.DefaultDataRegion=false +MMTAppReg2.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,DefaultDataRegion,ReservedRegion,Name +MMTAppReg2.MEMORYMAP.Name=RAM Reserved Alias Region +MMTAppReg2.MEMORYMAP.ReservedRegion=true +MMTAppReg2.MEMORYMAP.Size=2555904 +MMTAppReg2.MEMORYMAP.StartAddress=0x0A000000 +MMTAppReg3.MEMORYMAP.AP=RO_priv_only +MMTAppReg3.MEMORYMAP.AppRegionName=FLASH +MMTAppReg3.MEMORYMAP.Cacheability=WTRA +MMTAppReg3.MEMORYMAP.ContextName=CortexM33 +MMTAppReg3.MEMORYMAP.CoreName=ARM Cortex-M33 +MMTAppReg3.MEMORYMAP.DefaultCodeRegion=true +MMTAppReg3.MEMORYMAP.DefaultDataRegion=false +MMTAppReg3.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,DefaultDataRegion,MemType,ContextName,Name,AP,Cacheability,DefaultCodeRegion +MMTAppReg3.MEMORYMAP.MemType=ROM +MMTAppReg3.MEMORYMAP.Name=FLASH +MMTAppReg3.MEMORYMAP.Size=4194304 +MMTAppReg3.MEMORYMAP.StartAddress=0x08000000 +MMTAppRegionsCount=3 +MMTConfigApplied=false +MMTSectionSuffix= +Mcu.CPN=STM32U5A5ZJT6Q +Mcu.ContextProject=TrustZoneDisabled +Mcu.Family=STM32U5 +Mcu.IP0=CORTEX_M33_NS +Mcu.IP1=LPBAM +Mcu.IP2=LPBAMQUEUE +Mcu.IP3=MEMORYMAP +Mcu.IP4=NVIC +Mcu.IP5=PWR +Mcu.IP6=RCC +Mcu.IP7=RNG +Mcu.IP8=SYS +Mcu.IP9=USART1 +Mcu.IPNb=10 +Mcu.Name=STM32U5A5ZJTxQ +Mcu.Package=LQFP144 +Mcu.Pin0=PA5 +Mcu.Pin1=PA6 +Mcu.Pin10=VP_RNG_VS_RNG +Mcu.Pin11=VP_SYS_VS_Systick +Mcu.Pin12=VP_LPBAM_VS_SIG1 +Mcu.Pin13=VP_LPBAM_VS_SIG4 +Mcu.Pin14=VP_MEMORYMAP_VS_MEMORYMAP +Mcu.Pin2=PA7 +Mcu.Pin3=PE11 +Mcu.Pin4=PE13 +Mcu.Pin5=PD14 +Mcu.Pin6=PA9 +Mcu.Pin7=PA10 +Mcu.Pin8=VP_LPBAMQUEUE_VS_QUEUE +Mcu.Pin9=VP_PWR_VS_SECSignals +Mcu.PinsNb=15 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32U5A5ZJTxQ +MxCube.Version=6.17.0 +MxDb.Version=DB.6.0.170 +NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false +NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA10.Mode=Asynchronous +PA10.Signal=USART1_RX +PA5.GPIOParameters=GPIO_Speed,GPIO_PuPd,GPIO_Label +PA5.GPIO_Label=SPI_SCK +PA5.GPIO_PuPd=GPIO_PULLDOWN +PA5.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH +PA5.Locked=true +PA5.Signal=SPI1_SCK +PA6.GPIOParameters=GPIO_Speed,GPIO_Label +PA6.GPIO_Label=SPI_MISO +PA6.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH +PA6.Locked=true +PA6.Signal=SPI1_MISO +PA7.GPIOParameters=GPIO_Speed,GPIO_Label +PA7.GPIO_Label=SPI_MOSI +PA7.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH +PA7.Locked=true +PA7.Signal=SPI1_MOSI +PA9.Mode=Asynchronous +PA9.Signal=USART1_TX +PD14.GPIOParameters=GPIO_Speed,GPIO_Label +PD14.GPIO_Label=SPI_CS +PD14.GPIO_Speed=GPIO_SPEED_FREQ_HIGH +PD14.Locked=true +PD14.Signal=GPIO_Output +PE11.GPIOParameters=GPIO_Speed,GPIO_Label +PE11.GPIO_Label=SPI_CHIP_EN +PE11.GPIO_Speed=GPIO_SPEED_FREQ_HIGH +PE11.Locked=true +PE11.Signal=GPIO_Output +PE13.GPIOParameters=GPIO_Label +PE13.GPIO_Label=SPI_RDY +PE13.Locked=true +PE13.Signal=GPIO_Input +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerLinker=GCC +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=false +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32U5A5ZJTxQ +ProjectManager.FirmwarePackage=STM32Cube FW_U5 V1.8.0 +ProjectManager.FreePins=false +ProjectManager.FreePinsContext= +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x10000 +ProjectManager.KeepUserCode=true +ProjectManager.LPBAM.generateCode= +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=1 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain=STM32CubeIDE +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=nucleo_u5a5zj_q.ioc +ProjectManager.ProjectName=nucleo_u5a5zj_q +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=0x2000 +ProjectManager.TargetToolchain=CMake +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=false +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_RNG_Init-RNG-false-HAL-true,4-MX_USART1_UART_Init-USART1-false-HAL-true,0-MX_CORTEX_M33_NS_Init-CORTEX_M33_NS-false-HAL-true,0-MX_PWR_Init-PWR-false-HAL-true +RCC.ADCFreq_Value=16000000 +RCC.ADF1Freq_Value=160000000 +RCC.AHBFreq_Value=160000000 +RCC.APB1Freq_Value=160000000 +RCC.APB1TimFreq_Value=160000000 +RCC.APB2Freq_Value=160000000 +RCC.APB2TimFreq_Value=160000000 +RCC.APB3Freq_Value=160000000 +RCC.CK48Freq_Value=48000000 +RCC.CRSFreq_Value=48000000 +RCC.CortexFreq_Value=160000000 +RCC.DACFreq_Value=32000 +RCC.EPOD_VALUE=48000000 +RCC.FCLKCortexFreq_Value=160000000 +RCC.FDCANFreq_Value=80000000 +RCC.FamilyName=M +RCC.HCLKFreq_Value=160000000 +RCC.HSE_VALUE=16000000 +RCC.HSI48_VALUE=48000000 +RCC.HSI_VALUE=16000000 +RCC.I2C1Freq_Value=160000000 +RCC.I2C2Freq_Value=160000000 +RCC.I2C3Freq_Value=160000000 +RCC.I2C4Freq_Value=160000000 +RCC.I2C5Freq_Value=160000000 +RCC.I2C6Freq_Value=160000000 +RCC.IPParameters=ADCFreq_Value,ADF1Freq_Value,AHBFreq_Value,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,APB3Freq_Value,CK48Freq_Value,CRSFreq_Value,CortexFreq_Value,DACFreq_Value,EPOD_VALUE,FCLKCortexFreq_Value,FDCANFreq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,HSI48_VALUE,HSI_VALUE,I2C1Freq_Value,I2C2Freq_Value,I2C3Freq_Value,I2C4Freq_Value,I2C5Freq_Value,I2C6Freq_Value,LPTIM2Freq_Value,LPUART1Freq_Value,LSCOPinFreq_Value,LSE_VALUE,LSIDIV_VALUE,LSI_VALUE,MCO1PinFreq_Value,MDF1Freq_Value,MSIClockRange,MSI_VALUE,OCTOSPIMFreq_Value,PLL1R,PLL2FRACN,PLL2PoutputFreq_Value,PLL2QoutputFreq_Value,PLL2RoutputFreq_Value,PLL3FRACN,PLL3PoutputFreq_Value,PLL3QoutputFreq_Value,PLL3RoutputFreq_Value,PLLFRACN,PLLM,PLLN,PLLPoutputFreq_Value,PLLQoutputFreq_Value,PLLRCLKFreq_Value,RNGFreq_Value,SAESFreq_Value,SAI1Freq_Value,SAI2Freq_Value,SDMMCFreq_Value,SPI1Freq_Value,SPI2Freq_Value,SPI3Freq_Value,SYSCLKFreq_VALUE,SYSCLKSource,UART4Freq_Value,UART5Freq_Value,USART1Freq_Value,USART2Freq_Value,USART3Freq_Value,USART6Freq_Value,USBPHYFreq_Value,VCOInput2Freq_Value,VCOInput3Freq_Value,VCOInputFreq_Value,VCOOutputFreq_Value,VCOPLL2OutputFreq_Value,VCOPLL3OutputFreq_Value +RCC.LPTIM2Freq_Value=160000000 +RCC.LPUART1Freq_Value=160000000 +RCC.LSCOPinFreq_Value=32000 +RCC.LSE_VALUE=32768 +RCC.LSIDIV_VALUE=32000 +RCC.LSI_VALUE=32000 +RCC.MCO1PinFreq_Value=160000000 +RCC.MDF1Freq_Value=160000000 +RCC.MSIClockRange=RCC_MSIRANGE_0 +RCC.MSI_VALUE=48000000 +RCC.OCTOSPIMFreq_Value=160000000 +RCC.PLL1R=1 +RCC.PLL2FRACN=0 +RCC.PLL2PoutputFreq_Value=3096000000 +RCC.PLL2QoutputFreq_Value=3096000000 +RCC.PLL2RoutputFreq_Value=3096000000 +RCC.PLL3FRACN=0 +RCC.PLL3PoutputFreq_Value=3096000000 +RCC.PLL3QoutputFreq_Value=3096000000 +RCC.PLL3RoutputFreq_Value=3096000000 +RCC.PLLFRACN=0 +RCC.PLLM=3 +RCC.PLLN=10 +RCC.PLLPoutputFreq_Value=80000000 +RCC.PLLQoutputFreq_Value=80000000 +RCC.PLLRCLKFreq_Value=160000000 +RCC.RNGFreq_Value=48000000 +RCC.SAESFreq_Value=48000000 +RCC.SAI1Freq_Value=3096000000 +RCC.SAI2Freq_Value=3096000000 +RCC.SDMMCFreq_Value=80000000 +RCC.SPI1Freq_Value=160000000 +RCC.SPI2Freq_Value=160000000 +RCC.SPI3Freq_Value=160000000 +RCC.SYSCLKFreq_VALUE=160000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.UART4Freq_Value=160000000 +RCC.UART5Freq_Value=160000000 +RCC.USART1Freq_Value=160000000 +RCC.USART2Freq_Value=160000000 +RCC.USART3Freq_Value=160000000 +RCC.USART6Freq_Value=160000000 +RCC.USBPHYFreq_Value=80000000 +RCC.VCOInput2Freq_Value=48000000 +RCC.VCOInput3Freq_Value=48000000 +RCC.VCOInputFreq_Value=16000000 +RCC.VCOOutputFreq_Value=160000000 +RCC.VCOPLL2OutputFreq_Value=6192000000 +RCC.VCOPLL3OutputFreq_Value=6192000000 +RNG.ClockErrorDetection=RNG_CED_DISABLE +RNG.IPParameters=ClockErrorDetection +USART1.IPParameters=VirtualMode-Asynchronous +USART1.VirtualMode-Asynchronous=VM_ASYNC +VP_LPBAMQUEUE_VS_QUEUE.Mode=QUEUEMODE +VP_LPBAMQUEUE_VS_QUEUE.Signal=LPBAMQUEUE_VS_QUEUE +VP_LPBAM_VS_SIG1.Mode=SmartRun +VP_LPBAM_VS_SIG1.Signal=LPBAM_VS_SIG1 +VP_LPBAM_VS_SIG4.Mode=LPDMA1 +VP_LPBAM_VS_SIG4.Signal=LPBAM_VS_SIG4 +VP_MEMORYMAP_VS_MEMORYMAP.Mode=CurAppReg +VP_MEMORYMAP_VS_MEMORYMAP.Signal=MEMORYMAP_VS_MEMORYMAP +VP_PWR_VS_SECSignals.Mode=Security/Privilege +VP_PWR_VS_SECSignals.Signal=PWR_VS_SECSignals +VP_RNG_VS_RNG.Mode=RNG_Activate +VP_RNG_VS_RNG.Signal=RNG_VS_RNG +VP_SYS_VS_Systick.Mode=SysTick +VP_SYS_VS_Systick.Signal=SYS_VS_Systick +board=custom diff --git a/tutorials/stm32/nucleo_u5a5zj_q/cubemx/wifi.c b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/wifi.c new file mode 100644 index 00000000..27636b88 --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/cubemx/wifi.c @@ -0,0 +1,133 @@ +#include "mongoose.h" +#include "main.h" + +#define WIFISPI SPI1 + +#ifndef WIFI_NAME +#define WIFI_NAME "" +#endif + +#ifndef WIFI_PASS +#define WIFI_PASS "" +#endif + +#ifndef WIFI_AP_NAME +#define WIFI_AP_NAME "Mongoose" +#endif + +#ifndef WIFI_AP_PASS +#define WIFI_AP_PASS "mongoose" +#endif + +#ifndef ENABLE_WIFI_AP +#define ENABLE_WIFI_AP 0 +#endif + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) + +static inline void spin(volatile uint32_t n) { + while (n--) + (void)0; +} + +static inline void hal_spi_init(SPI_TypeDef *spi, uint16_t div) { + (void)spi; + uint32_t br = 0; // Baud rate prescaler: div = 2,4,8...256 -> MBR = 0..7 + while (div > 2) + div >>= 1, br++; + if (br > 7) + br = 7; + + + CLRSET(RCC->CCIPR1, 3 << 20, 1); // SYSCLK source + RCC->APB2ENR |= BIT(12); // SPI1EN + spin(100); + spi->CR1 = 0; // disable SPI + spi->CR1 = SPI_CR1_SSI_Msk; // SSI + spi->CFG1 = (7 << SPI_CFG1_DSIZE_Pos) | (br << SPI_CFG1_MBR_Pos); // DSIZE = 8-bit, MBR + spi->CFG2 = SPI_CFG2_SSM_Msk | SPI_CFG2_MASTER_Msk; + spi->CR1 |= SPI_CR1_SPE_Msk; // Enable SPI + spi->CR1 |= SPI_CR1_CSTART_Msk; // Start transfer +} + +static inline uint8_t hal_spi_xfer_byte(SPI_TypeDef *spi, uint8_t byte) { + (void)spi; + while ((spi->SR & SPI_SR_TXP_Msk) == 0) + spin(1); // TXP + *(volatile uint8_t *) &spi->TXDR = byte; + while (((spi->SR & SPI_SR_RXP_Msk) == 0) && ((spi->SR & SPI_SR_RXWNE_Msk) == 0) && ((spi->SR & SPI_SR_RXPLVL_Msk) == 0)) + spin(1); // RXP + uint8_t rx = *(volatile uint8_t *) &spi->RXDR; + return rx; +} + +static inline void hal_spi_tx_rx(SPI_TypeDef *spi, uint8_t *tx, uint8_t *rx, size_t len) { + for (size_t i = 0; i < len; i++) { + uint8_t rx_ = hal_spi_xfer_byte(spi, tx ? tx[i] : 0); + if (rx) rx[i] = rx_; + } +} + +static inline bool hal_spi_ready(SPI_TypeDef *spi) { + (void) spi; + return HAL_GPIO_ReadPin(SPI_RDY_GPIO_Port, SPI_RDY_Pin); +} + +static inline void hal_spi_select(SPI_TypeDef *spi, bool s) { + (void)spi; + HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, s); +} + +static inline void hal_spi_done(SPI_TypeDef *spi) { + while((spi->SR & SPI_SR_TXC_Msk) == 0) spin(1); + spi->IFCR = 0xffff; // clear all flags +} + +static bool hwspecific_is_ready(void) { + return hal_spi_ready(WIFISPI); +} + +static void hwspecific_spi_begin(void *arg) { + hal_spi_select(WIFISPI, true); + (void) arg; +} + +static void hwspecific_spi_txn(void *arg, uint8_t *txdata, uint8_t *rxdata, size_t len) { + while (len--) { + uint8_t rx = hal_spi_xfer_byte(WIFISPI, txdata != NULL ? *txdata++ : 0); + if (rxdata != NULL) *rxdata++ = rx; + } + hal_spi_done(WIFISPI); + (void) arg; +} + +static void hwspecific_spi_end(void *arg) { + hal_spi_select(WIFISPI, false); + (void) arg; +} + +void hwspecific_spi_init(void) { + HAL_GPIO_WritePin(SPI_CHIP_EN_GPIO_Port, SPI_CHIP_EN_Pin, 0); + hal_spi_init(WIFISPI, 128); // runs at 1.25MHz (SYS_CLK/128) + mg_delayms(10); + HAL_GPIO_WritePin(SPI_CHIP_EN_GPIO_Port, SPI_CHIP_EN_Pin, 1); +} + +static const struct mg_tcpip_spi spi = {NULL, hwspecific_spi_begin, hwspecific_spi_end, hwspecific_spi_txn}; + +void wifi_setconfig(void *data) { + struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) data; + d->wifi.ssid = WIFI_NAME; + d->wifi.pass = WIFI_PASS; + d->wifi.apssid = WIFI_AP_NAME; + d->wifi.appass = WIFI_AP_PASS; + d->wifi.apip = MG_IPV4(192, 168, 169, 1); + d->wifi.apmask = MG_IPV4(255, 255, 255, 0); + d->wifi.security = 0; + d->wifi.apsecurity = 0; + d->wifi.apchannel = 10; + d->wifi.apmode = ENABLE_WIFI_AP; + d->spi = (struct mg_tcpip_spi *) &spi; + d->is_ready = hwspecific_is_ready; +} diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/Makefile b/tutorials/stm32/nucleo_u5a5zj_q/minimal/Makefile new file mode 100644 index 00000000..741327af --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/Makefile @@ -0,0 +1,37 @@ +# Environment setup: https://mongoose.ws/documentation/tutorials/tools/ + +CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_u5/Include +CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nano.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c hal.c wifi.c $(wildcard mongoose/*.c) +SOURCES += cmsis_u5/Source/Templates/gcc/startup_stm32u5a5xx.s + +TOP = ../../../.. +CFLAGS += -I$(TOP) +SOURCES += $(TOP)/mongoose.c + +all build: firmware.bin + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + @echo + @echo "To flash, run 'make flash', or use STM32CubeProgrammer" + +firmware.elf: cmsis_core cmsis_u5 $(SOURCES) $(wildcard *.h) link.ld Makefile + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(LDFLAGS) -o $@ + +flash: firmware.bin + STM32_Programmer_CLI -c port=swd -e all -w $< 0x8000000 -hardRst + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +cmsis_u5: + git clone -q -c advice.detachedHead=false --depth 1 -b v1.4.2 https://github.com/STMicroelectronics/cmsis_device_u5 $@ + +clean: + rm -rf firmware.* diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/hal.c b/tutorials/stm32/nucleo_u5a5zj_q/minimal/hal.c new file mode 100644 index 00000000..6dc65c9f --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/hal.c @@ -0,0 +1,125 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) { + uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry + if (period == 0) return false; // Avoid division by zero + if (diff < period) return false; // Period has not elapsed yet + *t += (diff / period) * period; // Preserve cadence, skip missed periods + return true; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t hal_get_tick(void) { + return s_ticks; +}; + +uint32_t SystemCoreClock = SYS_FREQUENCY; +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/hal.h b/tutorials/stm32/nucleo_u5a5zj_q/minimal/hal.h new file mode 100644 index 00000000..0db12792 --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/hal.h @@ -0,0 +1,322 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved +// +// RM0456 +// https://www.st.com/resource/en/reference_manual/rm0456-stm32u5-series-armbased-32bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/user_manual/um2861-stm32u5-nucleo144-board-mb1549-stmicroelectronics.pdf + +#pragma once + +#define LED_1 PIN('C', 7) // On-board LED pin (green) +#define LED_2 PIN('B', 7) // On-board LED pin (blue) +#define LED_3 PIN('G', 2) // On-board LED pin (red) +#define LED LED_1 // Use green LED for blinking + +#define SPI_RDY PIN('E', 13) +#define SPI_CS PIN('D', 14) +#define SPI_CHIP_EN PIN('E', 11) + +#ifndef HAL_DEBUG_UART +#define HAL_DEBUG_UART USART1 +#endif +#ifndef UART_DEBUG +#define UART_DEBUG HAL_DEBUG_UART +#endif + +#ifndef WIFISPI +#define WIFISPI SPI1 +#endif + +#include + +#include +#include +#include +#include + +extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now); +extern uint64_t hal_get_tick(void); +extern size_t hal_ram_free(void); +extern size_t hal_ram_used(void); +extern uint32_t SystemCoreClock; + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +void hal_init(void); +size_t hal_ram_free(void); +size_t hal_ram_used(void); + +enum { + HAL_D1PRE = 1, // actual divisor value + HAL_HPRE = 1, // actual divisor value + HAL_D2PRE = 1, + HAL_D3PRE = 1, +}; +enum { PLL1_M = 1, PLL1_N = 80, PLL1_P = 2, PLL1_Q = 2, PLL1_R = 2 }; +#define MSIS_FREQUENCY (4 * 1024 * 1024) // 4 Mhz +#define SYS_FREQUENCY (MSIS_FREQUENCY * PLL1_N / PLL1_M / PLL1_P / HAL_D1PRE) // 160MHz +#define AHB_FREQUENCY (SYS_FREQUENCY / HAL_HPRE) +#define APB2_FREQUENCY (AHB_FREQUENCY / HAL_D2PRE) +#define APB1_FREQUENCY (AHB_FREQUENCY / HAL_D1PRE) + +static inline void spin(volatile uint32_t n) { + while (n--) + (void)0; +} + +enum { + HAL_GPIO_MODE_INPUT, + HAL_GPIO_MODE_OUTPUT, + HAL_GPIO_MODE_AF, + HAL_GPIO_MODE_ANALOG +}; +enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN }; +enum { + HAL_GPIO_SPEED_LOW, + HAL_GPIO_SPEED_MEDIUM, + HAL_GPIO_SPEED_HIGH, + HAL_GPIO_SPEED_INSANE +}; +enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN }; + +#define HAL_GPIO(N) ((GPIO_TypeDef *)(GPIOA_BASE_NS + 0x400 * (N))) + +static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) { + return HAL_GPIO(PINBANK(pin)); +} +static inline void hal_gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int hal_gpio_read(uint16_t pin) { + return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void hal_gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint8_t n = (uint8_t)(PINNO(pin)); + RCC->AHB2ENR1 |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t)type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t)speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t)pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t)af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t)mode) << (n * 2)); +} +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, HAL_GPIO_PULL_NONE, 0); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, HAL_GPIO_PULL_NONE, 0); +} + +// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3 +// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks +// must be enabled for CPU access, as the kernel clock drives the BRR, not the +// APB bus interface +static inline void hal_uart_init(USART_TypeDef *uart, unsigned long baud) { + uint8_t af = 7; // Alternate function + uint16_t rx = 0, tx = 0; // pins + uint32_t freq = + SYS_FREQUENCY; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) + freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(14); + if (uart == USART2) + freq = APB1_FREQUENCY, RCC->APB1ENR1 |= BIT(17); + if (uart == USART3) + freq = APB1_FREQUENCY, RCC->APB1ENR1 |= BIT(18); + + if (uart == USART1) + tx = PIN('A', 9), rx = PIN('A', 10); + if (uart == USART2) + tx = PIN('A', 2), rx = PIN('A', 3); + if (uart == USART3) + tx = PIN('D', 8), rx = PIN('D', 9); + +#if 0 // CONSTANT BAUD RATE FOR DEBUGGING + CLRSET(RCC->CCIPR2, 7 << 3, 3 << 3); // use HSI for UART1 + freq = 64000000; +#endif + + hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, 0, af); + hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable this UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE +} +static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & BIT(7)) == 0) + spin(1); +} +static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, + size_t len) { + while (len-- > 0) + hal_uart_write_byte(uart, *(uint8_t *)buf++); +} +static inline int hal_uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) { + return (uint8_t)(uart->RDR & 255); +} + +static inline void hal_rng_init(void) { + RCC->CCIPR2 |= RCC_CCIPR2_RNGSEL_0; // RNG clock source pll1_q_ck + RCC->AHB2ENR1 |= RCC_AHB2ENR1_RNGEN; // Enable RNG clock + RNG->CR = RNG_CR_RNGEN; // Enable RNG +} + +static inline uint32_t hal_rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) + (void)0; + return RNG->DR; +} + +#define MG_HAL_DISABLE_ETHERNET 1 + +static inline unsigned int hal_ahb_div2prescval(unsigned int div) { + if (div == 1) + return 0; + unsigned int val = 7; + while (div >>= 1) + ++val; + return val; +} + +static inline unsigned int hal_apb_div2prescval(unsigned int div) { + if (div <= 1) + return 0; + unsigned int val = 0; + while (div >>= 1) + ++val; + return val + 2; +} + +static inline void hal_clock_init(void) { + FLASH->ACR |= BIT(8); // prefetch + RCC->AHB3ENR |= RCC_AHB3ENR_PWREN; + PWR->SVMCR |= PWR_SVMCR_IO2SV; // Enable VDDIO2 supply + PWR->UCPDR |= PWR_UCPDR_UCPD_DBDIS; // Disable the internal Pull-Up in Dead + // Battery pins of UCPD peripheral + PWR->CR3 |= PWR_CR3_REGSEL; // enable SMPS + while ((PWR->SVMSR & BIT(1)) == 0) + spin(1); + if ((PWR->SVMSR & PWR_SVMSR_ACTVOS) != (PWR_VOSR_VOS_0 | PWR_VOSR_VOS_1)) { + // control voltage scaling + PWR->VOSR |= PWR_VOSR_VOS_0 | PWR_VOSR_VOS_1 | PWR_VOSR_BOOSTEN; + while ((PWR->VOSR & PWR_VOSR_VOSRDY) == 0) + spin(1); + while ((PWR->SVMSR & PWR_SVMSR_ACTVOSRDY) == 0) + spin(1); + } + PWR->DBPR |= PWR_DBPR_DBP; // enable backup access + CLRSET(RCC->BDCR, 3 << 3, 0); // RCC_LSEDRIVE_LOW + RCC->BDCR |= RCC_BDCR_LSEON; // LSE enable + while ((RCC->BDCR & RCC_BDCR_LSERDY) == 0) + spin(1); + RCC->BDCR |= RCC_BDCR_LSESYSEN; // LSESYS enable + while ((RCC->BDCR & RCC_BDCR_LSESYSRDY) == 0) + spin(1); + PWR->VOSR &= ~PWR_VOSR_BOOSTEN; + RCC->CR &= ~BIT(24); // configure PLL1 + while ((RCC->CR & RCC_CR_PLL1RDY) != 0) + spin(1); + RCC->PLL1CFGR = BIT(0) | ((PLL1_M - 1) << 8) | BIT(16) | BIT(17) | + BIT(18); // source, PLL1_M, enable PLL1_P, PLL1_Q + RCC->PLL1DIVR = (PLL1_N - 1) | ((PLL1_P - 1) << 9) | ((PLL1_Q - 1) << 16) | + ((PLL1_R - 1) << 24); + PWR->VOSR |= PWR_VOSR_BOOSTEN; + RCC->CR |= BIT(24); // PLL1ON + while ((RCC->CR & RCC_CR_PLL1RDY) == 0) + spin(1); + CLRSET(FLASH->ACR, 0xf, 4); // flash latency 4 + RCC->CFGR1 |= 3; // set PLL as sysclk source + CLRSET(RCC->CFGR2, 0xf, hal_ahb_div2prescval(HAL_HPRE)); // HPRE + CLRSET(RCC->CFGR2, 0x70, hal_apb_div2prescval(HAL_D1PRE) << 4); // D1PRE + CLRSET(RCC->CFGR2, 0x700, hal_apb_div2prescval(HAL_D2PRE) << 8); // D2PRE + CLRSET(RCC->CFGR3, 0x70, hal_apb_div2prescval(HAL_D3PRE) << 4); // D3PRE +} + +static inline void hal_system_init(void) { + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + __DSB(); + __ISB(); + + SCB->VTOR = FLASH_BASE; // Vector Table Relocation in Internal FLASH +} + +static inline void hal_spi_init(SPI_TypeDef *spi, uint16_t div) { + (void)spi; + uint32_t br = 0; // Baud rate prescaler: div = 2,4,8...256 -> MBR = 0..7 + while (div > 2) + div >>= 1, br++; + if (br > 7) + br = 7; + + CLRSET(RCC->CCIPR1, 3 << 20, 1); // SYSCLK source + RCC->APB2ENR |= BIT(12); // SPI1EN + spin(100); + hal_gpio_init(PIN('A', 5), HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_DOWN, 5); // SCK + hal_gpio_init(PIN('A', 6), HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_NONE, 5); // MISO + hal_gpio_init(PIN('A', 7), HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_NONE, 5); // MOSI + hal_gpio_output(SPI_CS); // CS + hal_gpio_output(SPI_CHIP_EN); // CHIP_EN + hal_gpio_input(SPI_RDY); // RDY + spi->CR1 = 0; // disable SPI + spi->CR1 = SPI_CR1_SSI_Msk; // SSI + spi->CFG1 = (7 << SPI_CFG1_DSIZE_Pos) | (br << SPI_CFG1_MBR_Pos); // DSIZE = 8-bit, MBR + spi->CFG2 = SPI_CFG2_SSM_Msk | SPI_CFG2_MASTER_Msk; + spi->CR1 |= SPI_CR1_SPE_Msk; // Enable SPI + spi->CR1 |= SPI_CR1_CSTART_Msk; // Start transfer +} + +static inline uint8_t hal_spi_xfer_byte(SPI_TypeDef *spi, uint8_t byte) { + (void)spi; + while ((spi->SR & SPI_SR_TXP_Msk) == 0) + spin(1); // TXP + *(volatile uint8_t *) &spi->TXDR = byte; + while (((spi->SR & SPI_SR_RXP_Msk) == 0) && ((spi->SR & SPI_SR_RXWNE_Msk) == 0) && ((spi->SR & SPI_SR_RXPLVL_Msk) == 0)) + spin(1); // RXP + uint8_t rx = *(volatile uint8_t *) &spi->RXDR; + return rx; +} + +static inline void hal_spi_tx_rx(SPI_TypeDef *spi, uint8_t *tx, uint8_t *rx, size_t len) { + for (size_t i = 0; i < len; i++) { + uint8_t rx_ = hal_spi_xfer_byte(spi, tx ? tx[i] : 0); + if (rx) rx[i] = rx_; + } +} + +static inline bool hal_spi_ready(SPI_TypeDef *spi) { + (void) spi; + return hal_gpio_read(SPI_RDY); +} + +static inline void hal_spi_select(SPI_TypeDef *spi, bool s) { + (void)spi; + hal_gpio_write(SPI_CS, s); +} + +static inline void hal_spi_done(SPI_TypeDef *spi) { + while((spi->SR & SPI_SR_TXC_Msk) == 0) spin(1); + spi->IFCR = 0xffff; // clear all flags +} diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/link.ld b/tutorials/stm32/nucleo_u5a5zj_q/minimal/link.ld new file mode 100644 index 00000000..02252dbd --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/link.ld @@ -0,0 +1,31 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 4096k + sram(rwx) : ORIGIN = 0x20000000, LENGTH = 256k +} +_estack = ORIGIN(sram) + LENGTH(sram); /* stack points to end of SRAM */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .data : { + _sdata = .; + *(.first_data) + *(.data SORT(.data.*)) + *(.iram .iram* .iram.*) + _edata = .; + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { + _sbss = .; + *(.bss SORT(.bss.*) COMMON) + _ebss = .; + } > sram + + . = ALIGN(8); + _end = .; +} + diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/main.c b/tutorials/stm32/nucleo_u5a5zj_q/minimal/main.c new file mode 100644 index 00000000..63ef77ff --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/main.c @@ -0,0 +1,71 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" +#include "hal.h" + +#define DEBUG_UART USART1 +#define LED1 PIN('C', 7) // On-board LED pin (green) +#define LED2 PIN('B', 7) // On-board LED pin (blue) +#define LED3 PIN('G', 2) // On-board LED pin (red) + +// Redirect stdout debug output to UART +int _write(int fd, char *ptr, int len) { + if (fd == 1 || fd == 2) hal_uart_write_buf(DEBUG_UART, ptr, (size_t) len); + return len; +} + +static void blink_task(void) { + static uint64_t blink_timer = 0; + if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) { + hal_gpio_toggle(LED1); + } +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { // New HTTP request received + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", + hal_get_tick()); + } + } +} + +int main(void) { + hal_clock_init(); + hal_rng_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + hal_uart_init(DEBUG_UART, 115200); + + extern void hwspecific_spi_init(); + hwspecific_spi_init(); + + // Start a minimal web server + struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + + for (;;) { + mg_mgr_poll(&mgr, 0); + blink_task(); + } + + return 0; +} diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/mongoose_config.h b/tutorials/stm32/nucleo_u5a5zj_q/minimal/mongoose_config.h new file mode 100644 index 00000000..cdd782b2 --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/mongoose_config.h @@ -0,0 +1,14 @@ +#pragma once + +// See https://mongoose.ws/documentation/#build-options +#define MG_ARCH MG_ARCH_ARMGCC + +#define MG_ENABLE_TCPIP 1 +#define MG_TLS MG_TLS_BUILTIN +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_ST67W6 1 + +// Set your Wi-Fi configuration using a custom function: +extern void wifi_setconfig(void *data); +#define MG_SET_WIFI_CONFIG(data) wifi_setconfig(data) diff --git a/tutorials/stm32/nucleo_u5a5zj_q/minimal/wifi.c b/tutorials/stm32/nucleo_u5a5zj_q/minimal/wifi.c new file mode 100644 index 00000000..2605403b --- /dev/null +++ b/tutorials/stm32/nucleo_u5a5zj_q/minimal/wifi.c @@ -0,0 +1,76 @@ +// SPDX-FileCopyrightText: 2026 Cesanta Software Limited +// SPDX-License-Identifier: GPL-2.0-only or commercial +// Generated by Mongoose Wizard, https://mongoose.ws/wizard/ + +#include "hal.h" +#include "mongoose.h" + +#ifndef WIFI_NAME +#define WIFI_NAME "" +#endif + +#ifndef WIFI_PASS +#define WIFI_PASS "" +#endif + +#ifndef WIFI_AP_NAME +#define WIFI_AP_NAME "Mongoose" +#endif + +#ifndef WIFI_AP_PASS +#define WIFI_AP_PASS "mongoose" +#endif + +#ifndef ENABLE_WIFI_AP +#define ENABLE_WIFI_AP 1 +#endif + +static bool hwspecific_is_ready(void) { + return hal_gpio_read(SPI_RDY); +} + +static void hwspecific_spi_begin(void *arg) { + hal_spi_select(WIFISPI, true); + (void) arg; +} + +static void hwspecific_spi_txn(void *arg, uint8_t *txdata, uint8_t *rxdata, size_t len) { + while (len--) { + uint8_t rx = hal_spi_xfer_byte(WIFISPI, txdata != NULL ? *txdata++ : 0); + if (rxdata != NULL) *rxdata++ = rx; + } + hal_spi_done(WIFISPI); + (void) arg; +} + +static void hwspecific_spi_end(void *arg) { + hal_spi_select(WIFISPI, false); + (void) arg; +} + +void hwspecific_spi_init(void) { + hal_gpio_output(SPI_CHIP_EN); + hal_gpio_write(SPI_CHIP_EN, 0); + hal_gpio_input(SPI_RDY); + hal_spi_init(WIFISPI, 128); // runs at 1.25MHz (SYS_CLK/128) + mg_delayms(10); + hal_gpio_write(SPI_CHIP_EN, 1); +} + +static const struct mg_tcpip_spi spi = {NULL, hwspecific_spi_begin, hwspecific_spi_end, hwspecific_spi_txn}; + +void wifi_setconfig(void *data) { + struct mg_tcpip_driver_st67w6_data *d = (struct mg_tcpip_driver_st67w6_data *) data; + d->wifi.ssid = WIFI_NAME; + d->wifi.pass = WIFI_PASS; + d->wifi.apssid = WIFI_AP_NAME; + d->wifi.appass = WIFI_AP_PASS; + d->wifi.apip = MG_IPV4(192, 168, 169, 1); + d->wifi.apmask = MG_IPV4(255, 255, 255, 0); + d->wifi.security = 0; + d->wifi.apsecurity = 0; + d->wifi.apchannel = 10; + d->wifi.apmode = ENABLE_WIFI_AP; + d->spi = (struct mg_tcpip_spi *) &spi; + d->is_ready = hwspecific_is_ready; +} diff --git a/tutorials/stm32/portenta_h7/cubemx/README.md b/tutorials/stm32/portenta_h7/cubemx/README.md new file mode 100644 index 00000000..51a4089f --- /dev/null +++ b/tutorials/stm32/portenta_h7/cubemx/README.md @@ -0,0 +1,30 @@ +# Portenta H7 Mongoose Integration + +This guide explains how to bring up a production-grade Web UI dashboard on the +Portenta H7 board. + +A pre-configured, ready-to-run project zip is available for download at +https://mongoose.ws. + +To integrate the Web UI dashboard into an existing CubeMX project, or to create a +new project from scratch: + +1. Configure the peripherals (described below). +2. Regenerate your project code and integrate Mongoose core by following + https://mongoose.ws/docs/guides/stm32-tcpip-stack/. +3. Add the Web UI dashboard by following + https://mongoose.ws/docs/guides/web-dashboard-guide/. + +Note: to setup your WiFi login credentials, open the `wifi.c` file located in the +`Mongoose` directory and edit the `WIFI_NAME` and `WIFI_PASS` macros. + +## Configure peripherals + +An `.ioc` file is included in this directory for your reference. It was created +using the following steps: + +- Open CubeMX. If you are starting a new project, select the STM32747XI + microcontroller and create a new project. +- In Clock Configuration, set the maximum clock frequency to 200 MHz. +- In Pinout & Configuration / Connectivity, enable USART1 in asynchronous mode. + Configure pins PA9 and PA10 for USART. diff --git a/tutorials/stm32/portenta_h7/cubemx/getfw.cmake b/tutorials/stm32/portenta_h7/cubemx/getfw.cmake new file mode 100644 index 00000000..80f95c94 --- /dev/null +++ b/tutorials/stm32/portenta_h7/cubemx/getfw.cmake @@ -0,0 +1,7 @@ +if(NOT EXISTS mbed/.git) + execute_process(COMMAND git clone --depth 1 --no-checkout -b extrapatches-6.17.0 https://github.com/arduino/mbed-os mbed) +endif() + +execute_process(COMMAND git -C mbed sparse-checkout set targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources) +execute_process(COMMAND git -C mbed checkout) +file(TOUCH wiced_resource.h) diff --git a/tutorials/stm32/portenta_h7/cubemx/mongoose_config.h b/tutorials/stm32/portenta_h7/cubemx/mongoose_config.h new file mode 100644 index 00000000..0f9787d9 --- /dev/null +++ b/tutorials/stm32/portenta_h7/cubemx/mongoose_config.h @@ -0,0 +1,5 @@ +#pragma once +#define MG_ARCH MG_ARCH_CUBE +#define MG_ENABLE_DRIVER_CYW_SDIO 1 +extern void wifi_setconfig(void *data); +#define MG_SET_WIFI_CONFIG(data) wifi_setconfig(data) \ No newline at end of file diff --git a/tutorials/stm32/portenta_h7/cubemx/portenta_h7.ioc b/tutorials/stm32/portenta_h7/cubemx/portenta_h7.ioc new file mode 100644 index 00000000..8367f21b --- /dev/null +++ b/tutorials/stm32/portenta_h7/cubemx/portenta_h7.ioc @@ -0,0 +1,406 @@ +#MicroXplorer Configuration settings - do not modify +CAD.formats= +CAD.pinconfig= +CAD.provider= +CortexM4.IPs=BDMA,DMA,FATFS_M4\:I,FREERTOS_M4\:I,IWDG2\:I,MDMA,NVIC2\:I,RCC,USB_DEVICE_M4\:I,USB_HOST_M4\:I,WWDG2\:I,ADC1\:I,DCMI\:I,TIM3\:I,DEBUG,PDM2PCM_M4\:I,PWR,RESMGR_UTILITY,SYS_M4\:I,CORTEX_M4\:I,OPENAMP_M4\:I,VREFBUF,GPIO +CortexM7.IPs=BDMA\:I,DMA\:I,FATFS_M7\:I,FREERTOS_M7\:I,IWDG1\:I,MDMA\:I,NVIC1\:I,RCC\:I,USB_DEVICE_M7\:I,USB_HOST_M7\:I,WWDG1\:I,CORTEX_M7\:I,DEBUG\:I,PDM2PCM_M7\:I,PWR\:I,RESMGR_UTILITY\:I,SYS\:I,OPENAMP_M7\:I,VREFBUF\:I,GPIO\:I,RNG\:I,USART1\:I,SDMMC1\:I,MEMORYMAP\:I +CortexM7.Pins=PK5,PK6,PK7,PJ1,PJ5 +File.Version=6 +GPIO.groupedBy=Group By Peripherals +KeepUserPlacement=false +MMTAppReg1.MEMORYMAP.AppRegionName=RAM +MMTAppReg1.MEMORYMAP.ContextName=CortexM7 +MMTAppReg1.MEMORYMAP.CoreName=ARM Cortex-M7 +MMTAppReg1.MEMORYMAP.DefaultDataRegion=true +MMTAppReg1.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,DefaultDataRegion,ContextName,Name +MMTAppReg1.MEMORYMAP.Name=RAM +MMTAppReg1.MEMORYMAP.Size=131072 +MMTAppReg1.MEMORYMAP.StartAddress=0x20000000 +MMTAppReg10.MEMORYMAP.AppRegionName=M4 non Shared Region +MMTAppReg10.MEMORYMAP.ContextLink=reserved +MMTAppReg10.MEMORYMAP.ContextName=CortexM7 +MMTAppReg10.MEMORYMAP.CoreName=ARM Cortex-M7 +MMTAppReg10.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,MemType,ContextName,Name,ReservedRegion,ContextLink +MMTAppReg10.MEMORYMAP.MemType=ROM +MMTAppReg10.MEMORYMAP.Name=M4 non Shared Region +MMTAppReg10.MEMORYMAP.ReservedRegion=true +MMTAppReg10.MEMORYMAP.Size=1048576 +MMTAppReg10.MEMORYMAP.StartAddress=0x08100000 +MMTAppReg2.MEMORYMAP.AppRegionName=ITCMRAM +MMTAppReg2.MEMORYMAP.Cacheability=WTRA +MMTAppReg2.MEMORYMAP.ContextName=CortexM7 +MMTAppReg2.MEMORYMAP.CoreName=ARM Cortex-M7 +MMTAppReg2.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,ContextName,Name,Cacheability +MMTAppReg2.MEMORYMAP.Name=ITCMRAM +MMTAppReg2.MEMORYMAP.Size=65536 +MMTAppReg2.MEMORYMAP.StartAddress=0x00000000 +MMTAppReg3.MEMORYMAP.AppRegionName=RAM_D1 +MMTAppReg3.MEMORYMAP.ContextLink=shared +MMTAppReg3.MEMORYMAP.ContextName=CortexM7 +MMTAppReg3.MEMORYMAP.CoreName=ARM Cortex-M7 +MMTAppReg3.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,ContextName,Name,ContextLink +MMTAppReg3.MEMORYMAP.Name=RAM_D1 +MMTAppReg3.MEMORYMAP.Size=524288 +MMTAppReg3.MEMORYMAP.StartAddress=0x24000000 +MMTAppReg4.MEMORYMAP.AppRegionName=M7 non Shared Region +MMTAppReg4.MEMORYMAP.ContextLink=reserved +MMTAppReg4.MEMORYMAP.ContextName=CortexM4 +MMTAppReg4.MEMORYMAP.CoreName=ARM Cortex-M4 +MMTAppReg4.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,ContextName,Name,ReservedRegion,ContextLink +MMTAppReg4.MEMORYMAP.Name=M7 non Shared Region +MMTAppReg4.MEMORYMAP.ReservedRegion=true +MMTAppReg4.MEMORYMAP.Size=524288 +MMTAppReg4.MEMORYMAP.StartAddress=0x24000000 +MMTAppReg5.MEMORYMAP.AP=RO_priv_only +MMTAppReg5.MEMORYMAP.AppRegionName=FLASH +MMTAppReg5.MEMORYMAP.Cacheability=WTRA +MMTAppReg5.MEMORYMAP.ContextLink=shared +MMTAppReg5.MEMORYMAP.ContextName=CortexM7 +MMTAppReg5.MEMORYMAP.CoreName=ARM Cortex-M7 +MMTAppReg5.MEMORYMAP.DefaultCodeRegion=true +MMTAppReg5.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,MemType,ContextName,Name,AP,Cacheability,DefaultCodeRegion,ISRRegion,RootBootRegion,ContextLink +MMTAppReg5.MEMORYMAP.ISRRegion=true +MMTAppReg5.MEMORYMAP.MemType=ROM +MMTAppReg5.MEMORYMAP.Name=FLASH +MMTAppReg5.MEMORYMAP.RootBootRegion=true +MMTAppReg5.MEMORYMAP.Size=786432 +MMTAppReg5.MEMORYMAP.StartAddress=0x08040000 +MMTAppReg6.MEMORYMAP.AppRegionName=M7 non Shared Region +MMTAppReg6.MEMORYMAP.ContextLink=reserved +MMTAppReg6.MEMORYMAP.ContextName=CortexM4 +MMTAppReg6.MEMORYMAP.CoreName=ARM Cortex-M4 +MMTAppReg6.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,MemType,ContextName,Name,ReservedRegion,ContextLink +MMTAppReg6.MEMORYMAP.MemType=ROM +MMTAppReg6.MEMORYMAP.Name=M7 non Shared Region +MMTAppReg6.MEMORYMAP.ReservedRegion=true +MMTAppReg6.MEMORYMAP.Size=786432 +MMTAppReg6.MEMORYMAP.StartAddress=0x8040000 +MMTAppReg7.MEMORYMAP.AppRegionName=RAM +MMTAppReg7.MEMORYMAP.Cacheability=NonCacheable +MMTAppReg7.MEMORYMAP.ContextLink=shared +MMTAppReg7.MEMORYMAP.ContextName=CortexM4 +MMTAppReg7.MEMORYMAP.CoreName=ARM Cortex-M4 +MMTAppReg7.MEMORYMAP.DefaultDataRegion=true +MMTAppReg7.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,ContextName,Name,Cacheability,DefaultDataRegion,ContextLink +MMTAppReg7.MEMORYMAP.Name=RAM +MMTAppReg7.MEMORYMAP.Size=294912 +MMTAppReg7.MEMORYMAP.StartAddress=0x10000000 +MMTAppReg8.MEMORYMAP.AppRegionName=M4 non Shared Region +MMTAppReg8.MEMORYMAP.ContextLink=reserved +MMTAppReg8.MEMORYMAP.ContextName=CortexM7 +MMTAppReg8.MEMORYMAP.CoreName=ARM Cortex-M7 +MMTAppReg8.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,ContextName,Name,ReservedRegion,ContextLink +MMTAppReg8.MEMORYMAP.Name=M4 non Shared Region +MMTAppReg8.MEMORYMAP.ReservedRegion=true +MMTAppReg8.MEMORYMAP.Size=294912 +MMTAppReg8.MEMORYMAP.StartAddress=0x10000000 +MMTAppReg9.MEMORYMAP.AP=RO_priv_only +MMTAppReg9.MEMORYMAP.AppRegionName=FLASH +MMTAppReg9.MEMORYMAP.Cacheability=NonCacheable +MMTAppReg9.MEMORYMAP.ContextLink=shared +MMTAppReg9.MEMORYMAP.ContextName=CortexM4 +MMTAppReg9.MEMORYMAP.CoreName=ARM Cortex-M4 +MMTAppReg9.MEMORYMAP.DefaultCodeRegion=true +MMTAppReg9.MEMORYMAP.IPParameters=StartAddress,Size,CoreName,MemType,ContextName,Name,AP,Cacheability,DefaultCodeRegion,ISRRegion,RootBootRegion,ContextLink +MMTAppReg9.MEMORYMAP.ISRRegion=true +MMTAppReg9.MEMORYMAP.MemType=ROM +MMTAppReg9.MEMORYMAP.Name=FLASH +MMTAppReg9.MEMORYMAP.RootBootRegion=true +MMTAppReg9.MEMORYMAP.Size=1048576 +MMTAppReg9.MEMORYMAP.StartAddress=0x08100000 +MMTAppRegionsCount=10 +MMTConfigApplied=false +MMTProjectManagerApplied=true +MMTSectionSuffix= +Mcu.CPN=STM32H747XIH6 +Mcu.Context0=CortexM7 +Mcu.Context1=CortexM4 +Mcu.ContextNb=2 +Mcu.Family=STM32H7 +Mcu.IP0=CORTEX_M4 +Mcu.IP1=CORTEX_M7 +Mcu.IP2=MEMORYMAP +Mcu.IP3=NVIC1 +Mcu.IP4=NVIC2 +Mcu.IP5=RCC +Mcu.IP6=RNG +Mcu.IP7=SYS +Mcu.IP8=SYS_M4 +Mcu.IP9=USART1 +Mcu.IPNb=10 +Mcu.Name=STM32H747XIHx +Mcu.Package=TFBGA240 +Mcu.Pin0=PK5 +Mcu.Pin1=PC10 +Mcu.Pin10=PC8 +Mcu.Pin11=PC9 +Mcu.Pin12=PH0-OSC_IN (PH0) +Mcu.Pin13=PJ1 +Mcu.Pin14=PJ5 +Mcu.Pin15=VP_RNG_VS_RNG +Mcu.Pin16=VP_SYS_VS_Systick +Mcu.Pin17=VP_SYS_M4_VS_Systick +Mcu.Pin18=VP_MEMORYMAP_VS_MEMORYMAP +Mcu.Pin2=PC11 +Mcu.Pin3=PC14-OSC32_IN (OSC32_IN) +Mcu.Pin4=PK6 +Mcu.Pin5=PC12 +Mcu.Pin6=PK7 +Mcu.Pin7=PD2 +Mcu.Pin8=PA10 +Mcu.Pin9=PA9 +Mcu.PinsNb=19 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32H747XIHx +MxCube.Version=6.17.0 +MxDb.Version=DB.6.0.170 +NVIC1.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.ForceEnableDMAVector=true +NVIC1.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC1.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC1.SysTick_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:false +NVIC1.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.ForceEnableDMAVector=true +NVIC2.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC2.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC2.SysTick_IRQn=true\:0\:0\:false\:false\:true\:false\:true\:false +NVIC2.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA10.GPIOParameters=GPIO_Label,PinAttribute +PA10.GPIO_Label=USART1_RX +PA10.Locked=true +PA10.Mode=Asynchronous +PA10.PinAttribute=CortexM7 +PA10.Signal=USART1_RX +PA9.GPIOParameters=GPIO_Label,PinAttribute +PA9.GPIO_Label=USART1_TX +PA9.Locked=true +PA9.Mode=Asynchronous +PA9.PinAttribute=CortexM7 +PA9.Signal=USART1_TX +PC10.GPIOParameters=GPIO_PuPd,GPIO_Label,PinAttribute +PC10.GPIO_Label=SDIO_D2 +PC10.GPIO_PuPd=GPIO_PULLUP +PC10.Locked=true +PC10.PinAttribute=CortexM7 +PC10.Signal=SDMMC1_D2 +PC11.GPIOParameters=GPIO_PuPd,GPIO_Label,PinAttribute +PC11.GPIO_Label=SDIO_D3 +PC11.GPIO_PuPd=GPIO_PULLUP +PC11.Locked=true +PC11.PinAttribute=CortexM7 +PC11.Signal=SDMMC1_D3 +PC12.GPIOParameters=GPIO_PuPd,GPIO_Label,PinAttribute +PC12.GPIO_Label=SDIO_CLK +PC12.GPIO_PuPd=GPIO_PULLUP +PC12.Locked=true +PC12.PinAttribute=CortexM7 +PC12.Signal=SDMMC1_CK +PC14-OSC32_IN\ (OSC32_IN).GPIOParameters=GPIO_Label,PinAttribute +PC14-OSC32_IN\ (OSC32_IN).GPIO_Label=OSC32_IN +PC14-OSC32_IN\ (OSC32_IN).Locked=true +PC14-OSC32_IN\ (OSC32_IN).Mode=LSE-External-Clock-Source +PC14-OSC32_IN\ (OSC32_IN).PinAttribute=CortexM7 +PC14-OSC32_IN\ (OSC32_IN).Signal=RCC_OSC32_IN +PC8.GPIOParameters=GPIO_PuPd,GPIO_Label,PinAttribute +PC8.GPIO_Label=SDIO_D0 +PC8.GPIO_PuPd=GPIO_PULLUP +PC8.Locked=true +PC8.PinAttribute=CortexM7 +PC8.Signal=SDMMC1_D0 +PC9.GPIOParameters=GPIO_PuPd,GPIO_Label,PinAttribute +PC9.GPIO_Label=SDIO_D1 +PC9.GPIO_PuPd=GPIO_PULLUP +PC9.Locked=true +PC9.PinAttribute=CortexM7 +PC9.Signal=SDMMC1_D1 +PD2.GPIOParameters=GPIO_PuPd,GPIO_Label,PinAttribute +PD2.GPIO_Label=SDIO_MMC +PD2.GPIO_PuPd=GPIO_PULLUP +PD2.Locked=true +PD2.PinAttribute=CortexM7 +PD2.Signal=SDMMC1_CMD +PH0-OSC_IN\ (PH0).GPIOParameters=GPIO_Label,PinAttribute +PH0-OSC_IN\ (PH0).GPIO_Label=OSC25M_IN +PH0-OSC_IN\ (PH0).Locked=true +PH0-OSC_IN\ (PH0).Mode=HSE-External-Clock-Source +PH0-OSC_IN\ (PH0).PinAttribute=Free +PH0-OSC_IN\ (PH0).Signal=RCC_OSC_IN +PJ1.ContextOwner=CortexM7 +PJ1.GPIOParameters=GPIO_Label,PinAttribute +PJ1.GPIO_Label=PWR +PJ1.Locked=true +PJ1.PinAttribute=CortexM7 +PJ1.Signal=GPIO_Output +PJ5.ContextOwner=CortexM7 +PJ5.GPIOParameters=GPIO_Label,PinAttribute +PJ5.GPIO_Label=WAKE +PJ5.Locked=true +PJ5.PinAttribute=CortexM7 +PJ5.Signal=GPIO_Input +PK5.ContextOwner=CortexM7 +PK5.GPIOParameters=PinState,GPIO_Label,PinAttribute +PK5.GPIO_Label=LED R +PK5.Locked=true +PK5.PinAttribute=CortexM7 +PK5.PinState=GPIO_PIN_SET +PK5.Signal=GPIO_Output +PK6.ContextOwner=CortexM7 +PK6.GPIOParameters=PinState,GPIO_Label,PinAttribute +PK6.GPIO_Label=LED G +PK6.Locked=true +PK6.PinAttribute=CortexM7 +PK6.PinState=GPIO_PIN_SET +PK6.Signal=GPIO_Output +PK7.ContextOwner=CortexM7 +PK7.GPIOParameters=PinState,GPIO_Label,PinAttribute +PK7.GPIO_Label=LED B +PK7.Locked=true +PK7.PinAttribute=CortexM7 +PK7.PinState=GPIO_PIN_SET +PK7.Signal=GPIO_Output +PinOutPanel.CurrentBGAView=Top +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.BootMode=boot0 +ProjectManager.CompilerLinker=GCC +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=false +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32H747XIHx +ProjectManager.FirmwarePackage=STM32Cube FW_H7 V1.13.0 +ProjectManager.FreePins=false +ProjectManager.FreePinsContext=CortexM7 +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=M4-0x200,M7-0x10000 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=1 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain=STM32CubeIDE +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=portenta_h7.ioc +ProjectManager.ProjectName=portenta_h7 +ProjectManager.ProjectStructure=M7\:CortexM7 Project\:true;M4\:CortexM4 Project\:true; +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=M4-0x400,M7-0x2000 +ProjectManager.TargetToolchain=CMake +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=false +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false-CortexM7,2-MX_GPIO_Init-GPIO-false-HAL-true-CortexM7,3-MX_USART1_UART_Init-USART1-false-HAL-true-CortexM7,4-MX_RNG_Init-RNG-false-HAL-true-CortexM7,0-MX_CORTEX_M7_Init-CORTEX_M7-false-HAL-true-CortexM7,0-MX_CORTEX_M4_Init-CORTEX_M4-false-HAL-true-CortexM4 +RCC.ADCFreq_Value=80000000 +RCC.AHB12Freq_Value=200000000 +RCC.AHB4Freq_Value=200000000 +RCC.APB1Freq_Value=100000000 +RCC.APB2Freq_Value=100000000 +RCC.APB3Freq_Value=100000000 +RCC.APB4Freq_Value=100000000 +RCC.AXIClockFreq_Value=200000000 +RCC.CECFreq_Value=32000 +RCC.CKPERFreq_Value=64000000 +RCC.CPU2Freq_Value=200000000 +RCC.CPU2SystikFreq_Value=200000000 +RCC.CortexFreq_Value=400000000 +RCC.CpuClockFreq_Value=400000000 +RCC.D1CPREFreq_Value=400000000 +RCC.D1PPRE=RCC_APB3_DIV2 +RCC.D2PPRE1=RCC_APB1_DIV2 +RCC.D2PPRE2=RCC_APB2_DIV2 +RCC.D3PPRE=RCC_APB4_DIV2 +RCC.DFSDMACLkFreq_Value=200000000 +RCC.DFSDMFreq_Value=100000000 +RCC.DIVM1=4 +RCC.DIVM2=4 +RCC.DIVN1=50 +RCC.DIVN2=10 +RCC.DIVP1Freq_Value=400000000 +RCC.DIVP2Freq_Value=80000000 +RCC.DIVP3Freq_Value=129000000 +RCC.DIVQ1=4 +RCC.DIVQ1Freq_Value=200000000 +RCC.DIVQ2Freq_Value=80000000 +RCC.DIVQ3Freq_Value=129000000 +RCC.DIVR1Freq_Value=400000000 +RCC.DIVR2Freq_Value=80000000 +RCC.DIVR3Freq_Value=129000000 +RCC.DSIFreq_Value=62500000 +RCC.DSITXEscFreq_Value=15625000 +RCC.FDCANFreq_Value=200000000 +RCC.FMCFreq_Value=200000000 +RCC.FamilyName=M +RCC.HCLK3ClockFreq_Value=200000000 +RCC.HCLKFreq_Value=200000000 +RCC.HPRE=RCC_HCLK_DIV2 +RCC.HRTIMFreq_Value=200000000 +RCC.I2C123Freq_Value=100000000 +RCC.I2C4Freq_Value=100000000 +RCC.IPParameters=ADCFreq_Value,AHB12Freq_Value,AHB4Freq_Value,APB1Freq_Value,APB2Freq_Value,APB3Freq_Value,APB4Freq_Value,AXIClockFreq_Value,CECFreq_Value,CKPERFreq_Value,CPU2Freq_Value,CPU2SystikFreq_Value,CortexFreq_Value,CpuClockFreq_Value,D1CPREFreq_Value,D1PPRE,D2PPRE1,D2PPRE2,D3PPRE,DFSDMACLkFreq_Value,DFSDMFreq_Value,DIVM1,DIVM2,DIVN1,DIVN2,DIVP1Freq_Value,DIVP2Freq_Value,DIVP3Freq_Value,DIVQ1,DIVQ1Freq_Value,DIVQ2Freq_Value,DIVQ3Freq_Value,DIVR1Freq_Value,DIVR2Freq_Value,DIVR3Freq_Value,DSIFreq_Value,DSITXEscFreq_Value,FDCANFreq_Value,FMCFreq_Value,FamilyName,HCLK3ClockFreq_Value,HCLKFreq_Value,HPRE,HRTIMFreq_Value,I2C123Freq_Value,I2C4Freq_Value,LPTIM1Freq_Value,LPTIM2Freq_Value,LPTIM345Freq_Value,LPUART1Freq_Value,LTDCFreq_Value,MCO1PinFreq_Value,MCO2PinFreq_Value,PLL2FRACN,PLLDSIVCOFreq_Value,PLLFRACN,PWR_Regulator_Voltage_Scale,QSPIFreq_Value,RNGFreq_Value,RTCFreq_Value,SAI1Freq_Value,SAI23Freq_Value,SAI4AFreq_Value,SAI4BFreq_Value,SDMMCFreq_Value,SPDIFRXFreq_Value,SPI123Freq_Value,SPI45Freq_Value,SPI6Freq_Value,SWPMI1Freq_Value,SYSCLKFreq_VALUE,SYSCLKSource,SupplySource,Tim1OutputFreq_Value,Tim2OutputFreq_Value,TraceFreq_Value,USART16Freq_Value,USART234578Freq_Value,USBFreq_Value,VCO1OutputFreq_Value,VCO2OutputFreq_Value,VCO3OutputFreq_Value,VCOInput1Freq_Value,VCOInput2Freq_Value,VCOInput3Freq_Value +RCC.LPTIM1Freq_Value=100000000 +RCC.LPTIM2Freq_Value=100000000 +RCC.LPTIM345Freq_Value=100000000 +RCC.LPUART1Freq_Value=100000000 +RCC.LTDCFreq_Value=129000000 +RCC.MCO1PinFreq_Value=64000000 +RCC.MCO2PinFreq_Value=400000000 +RCC.PLL2FRACN=0 +RCC.PLLDSIVCOFreq_Value=1000000000 +RCC.PLLFRACN=0 +RCC.PWR_Regulator_Voltage_Scale=PWR_REGULATOR_VOLTAGE_SCALE1 +RCC.QSPIFreq_Value=200000000 +RCC.RNGFreq_Value=48000000 +RCC.RTCFreq_Value=32000 +RCC.SAI1Freq_Value=200000000 +RCC.SAI23Freq_Value=200000000 +RCC.SAI4AFreq_Value=200000000 +RCC.SAI4BFreq_Value=200000000 +RCC.SDMMCFreq_Value=200000000 +RCC.SPDIFRXFreq_Value=200000000 +RCC.SPI123Freq_Value=200000000 +RCC.SPI45Freq_Value=100000000 +RCC.SPI6Freq_Value=100000000 +RCC.SWPMI1Freq_Value=100000000 +RCC.SYSCLKFreq_VALUE=400000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.SupplySource=PWR_DIRECT_SMPS_SUPPLY +RCC.Tim1OutputFreq_Value=200000000 +RCC.Tim2OutputFreq_Value=200000000 +RCC.TraceFreq_Value=64000000 +RCC.USART16Freq_Value=100000000 +RCC.USART234578Freq_Value=100000000 +RCC.USBFreq_Value=200000000 +RCC.VCO1OutputFreq_Value=800000000 +RCC.VCO2OutputFreq_Value=160000000 +RCC.VCO3OutputFreq_Value=258000000 +RCC.VCOInput1Freq_Value=16000000 +RCC.VCOInput2Freq_Value=16000000 +RCC.VCOInput3Freq_Value=2000000 +USART1.IPParameters=VirtualMode-Asynchronous +USART1.VirtualMode-Asynchronous=VM_ASYNC +VP_MEMORYMAP_VS_MEMORYMAP.Mode=CurAppReg +VP_MEMORYMAP_VS_MEMORYMAP.Signal=MEMORYMAP_VS_MEMORYMAP +VP_RNG_VS_RNG.Mode=RNG_Activate +VP_RNG_VS_RNG.Signal=RNG_VS_RNG +VP_SYS_M4_VS_Systick.Mode=SysTick +VP_SYS_M4_VS_Systick.Signal=SYS_M4_VS_Systick +VP_SYS_VS_Systick.Mode=SysTick +VP_SYS_VS_Systick.Signal=SYS_VS_Systick +board=STM32H747I-DISCO +boardIOC=true diff --git a/tutorials/stm32/portenta_h7/cubemx/wifi.c b/tutorials/stm32/portenta_h7/cubemx/wifi.c new file mode 100644 index 00000000..2291c464 --- /dev/null +++ b/tutorials/stm32/portenta_h7/cubemx/wifi.c @@ -0,0 +1,153 @@ +#include "mongoose.h" +#include "main.h" + +typedef enum {RESOURCE_IN_MEMORY,RESOURCE_IN_EXTERNAL_STORAGE} resource_location_t; +typedef struct {resource_location_t l;unsigned long sz;union{struct {unsigned long o;const char *f;}fs;struct {const char *m;}mem;}val;} resource_hnd_t; + +#include "resources/firmware/COMPONENT_4343W_FS/4343WA1_clm_blob.c" +#include "resources/firmware/COMPONENT_4343W_FS/4343WA1_bin.c" +#include "resources/nvram/wifi_nvram_image.h" + +#ifndef WIFI_NAME +#define WIFI_NAME "" +#endif + +#ifndef WIFI_PASS +#define WIFI_PASS "" +#endif + +#ifndef WIFI_AP_NAME +#define WIFI_AP_NAME "Mongoose" +#endif + +#ifndef WIFI_AP_PASS +#define WIFI_AP_PASS "mongoose" +#endif + +#ifndef ENABLE_WIFI_AP +#define ENABLE_WIFI_AP 0 +#endif + +// - 58.4 Table 473 +// - 58.5.8 AHB bw >= 3x SDMMC bus bw; Table 492; SD HS => AHB >= ~20MHz +// - SDIO 2.0 section 3.1 +static inline void sdmmc_set_ds(void) { // NO FLOW CONTROL ??? REALLY ??? ******************************* + MG_SET_BITS(SDMMC1->CLKCR, (1 << 17) | 0x3ff, (1 << 14) | ((200000000 / (2 * 25000000)) & 0x3ff)); // 4-bit bus, 25MHz +} +static inline void sdmmc_set_hs(void) { // mbed enables flow control... AIROC does not + MG_SET_BITS(SDMMC1->CLKCR, 0x3ff, (1 << 17) | (1 << 14) | ((200000000 / (2 * 50000000)) & 0x3ff)); // hw flow control, 4-bit bus, 50MHz +} +static inline void sdmmc_init(void) { + RCC->AHB3RSTR |= MG_BIT(16); // Reset SDIO block + RCC->AHB3RSTR &= ~MG_BIT(16); + RCC->D1CCIPR &= ~MG_BIT(16); // 9.7.18, use PLL1Q + RCC->AHB3ENR |= MG_BIT(16); // Enable SDIO clock + // SDIO pins initialized by Cube; Portenta H7 doesn't pull-up the SDIO bus + // 1-bit bus, rising edge, clock always on, no flow control, 400KHz + SDMMC1->CLKCR = (200000000 / (2 * 400000)) & 0x3ff; + SDMMC1->POWER |= 3 << 0; // SDMMC_POWER_PWRCTRL + SDMMC1->DCTRL = MG_BIT(10); // Read Wait control by stopping SDMMCCLK + // wait 74 clocks (~200us) before accessing the "card" +} + +static inline bool sdmmc_transact(uint8_t cmd, uint32_t arg, uint32_t *resp) { + unsigned int wresp = (resp != NULL) ? 1 : 0; // 58.10.4: 2-bit; cmds used + uint32_t cmdr = MG_BIT(12) | (wresp << 8) | ((cmd & 0x3f) << 0); // CPSMEN + SDMMC1->ARG = arg; + MG_SET_BITS(SDMMC1->CMD, 0x00011f3f /* CMDSUSPEND CPSMEN WAITPEND WAITINT WAITRESP CMD */, cmdr); + if (wresp == 0) { + while ((SDMMC1->STA & SDMMC_STA_CMDSENT) == 0) (void)0; + SDMMC1->ICR = 0x002000C5; // BUSYD0END CMDSENT CMDREND CTIMEOUT CCRCFAIL + return true; + } + //while ((SDMMC1->STA & 0x00200045 /*BUSYD0END CMDREND CTIMEOUT CCRCFAIL*/) == 0 || SDMMC1->STA & SDMMC_STA_CPSMACT) spin(1); + while (SDMMC1->STA & SDMMC_STA_CPSMACT) (void)0; + bool res = (SDMMC1->STA & 0x00200005 /*BUSYD0END CTIMEOUT CCRCFAIL*/) == 0; + SDMMC1->ICR = 0x002000C5; // BUSYD0END CMDSENT CMDREND CTIMEOUT CCRCFAIL + *resp = SDMMC1->RESP1; + if (!res || SDMMC1->RESPCMD != cmd) return false; + return true; +} + +static inline unsigned int _log2(uint16_t x) { + unsigned int i = 0; + while (x != 0) x = ((uint16_t)x) >> 1, ++i; + return i - 1; +} + +static inline bool sdmmc_transfer(bool write, uint32_t arg, uint16_t blksz, uint32_t *data, uint32_t length, uint32_t *resp) { + uint32_t cmdr = MG_BIT(12) | (1 << 8) | (53 << 0); // CPSMEN WRESP CMD=53 + bool bmode = (blksz != 0); + uint8_t dblksz = bmode ? (uint8_t)_log2(blksz) : 0; + uint32_t dctrlr = (dblksz << 4) | (bmode ? (0 << 2) : (1 << 2)) | (write ? 0 : MG_BIT(1)); // DBLOCKSIZE=f(blksz) DTMODE=f(bmode) DTDIR=!write !DTEN + SDMMC1->DCTRL = SDMMC_DCTRL_RWMOD; + SDMMC1->IDMACTRL = SDMMC_IDMA_IDMAEN; // single buffer + SDMMC1->IDMABASE0 = (uint32_t)data; + SDMMC1->DTIMER = (uint32_t) 50000000; // 58.10.7, set for 1 sec @50MHz (2 secs @25MHz) + SDMMC1->DLEN = length; // 58.10.8 call with a multiple of blksz (we do) + MG_SET_BITS(SDMMC1->DCTRL, 0xff /* DBLOCKSIZE DTMODE DTDIR DTEN */, dctrlr); + SDMMC1->CMD = SDMMC_CMD_CMDTRANS; // CPSM will perform a data transfer + SDMMC1->ARG = arg; + SDMMC1->CMD |= cmdr; // other flags cleared above + while ((SDMMC1->STA & 0x0020003f) == 0 && SDMMC1->STA & SDMMC_STA_DPSMACT) + (void) 0; // incorrect "card" usage will stall here + bool res = (SDMMC1->STA & 0x0020003f /* BUSYD0END RXOVERR TXUNDERR DTIMEOUT CTIMEOUT DCRCFAIL CCRCFAIL */) == 0; + SDMMC1->CMD = 0; + SDMMC1->DLEN = 0; + SDMMC1->IDMACTRL = 0; + SDMMC1->ICR = (uint32_t) ~0; // clear all flags + *resp = SDMMC1->RESP1; + SDMMC1->DCTRL = SDMMC_DCTRL_RWMOD; + if (!res || SDMMC1->RESPCMD != 53) return false; + return true; +} + + +void hwspecific_sdio_init(void) { + HAL_GPIO_WritePin(PWR_GPIO_Port, PWR_Pin, 0); + mg_delayms(100); + sdmmc_init(); + mg_delayms(1); + HAL_GPIO_WritePin(PWR_GPIO_Port, PWR_Pin, 1); + mg_delayms(50); +} + +void hwspecific_sdio_config(void *sdio, uint8_t cfg) { + (void) sdio; + if (cfg == 0) return sdmmc_set_ds(); + if (cfg == 1) return sdmmc_set_hs(); +} +bool hwspecific_sdio_txn(void *sdio, uint8_t cmd, uint32_t arg, uint32_t *resp) { + (void) sdio; + return sdmmc_transact(cmd, arg, resp); +} +bool hwspecific_sdio_xfr(void *sdio, bool write, uint32_t arg, uint16_t blksz, uint32_t *data, uint32_t len, uint32_t *resp){ + (void) sdio; + return sdmmc_transfer(write, arg, blksz, data, len, resp); +} + + +static const struct mg_tcpip_sdio sdio = {NULL, hwspecific_sdio_config, hwspecific_sdio_txn, hwspecific_sdio_xfr}; + +static const struct mg_tcpip_driver_cyw_firmware fw = { + (const uint8_t *)wifi_firmware_image_data, (size_t)sizeof(wifi_firmware_image_data), + (const uint8_t *)wifi_nvram_image, (size_t)sizeof(wifi_nvram_image), + (const uint8_t *)wifi_firmware_clm_blob_image_data, (size_t)sizeof(wifi_firmware_clm_blob_image_data)}; + + +void wifi_setconfig(void *data) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) data; + d->bus = (struct mg_tcpip_sdio *) &sdio; + d->fw = (struct mg_tcpip_driver_cyw_firmware *)&fw; + d->wifi.ssid = WIFI_NAME; + d->wifi.pass = WIFI_PASS; + d->wifi.apssid = WIFI_AP_NAME; + d->wifi.appass = WIFI_AP_PASS; + d->wifi.apip = MG_IPV4(192, 168, 111, 1); + d->wifi.apmask = MG_IPV4(255, 255, 255, 0); + d->wifi.security = 0; + d->wifi.apsecurity = 0; + d->wifi.apchannel = 10; + d->wifi.apmode = ENABLE_WIFI_AP; + d->hs = false; +} diff --git a/tutorials/stm32/portenta_h7/minimal/Makefile b/tutorials/stm32/portenta_h7/minimal/Makefile new file mode 100644 index 00000000..105aec3a --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/Makefile @@ -0,0 +1,48 @@ +# Environment setup: https://mongoose.ws/documentation/tutorials/tools/ + +CFLAGS = -W -Wall -Wextra -Werror -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -Wno-cast-function-type +CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h7/Include +CFLAGS += -mcpu=cortex-m7 -mthumb -mfloat-abi=hard -mfpu=fpv5-d16 -DCORE_CM7 +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c wifi.c hal.c +SOURCES += cmsis_h7/Source/Templates/gcc/startup_stm32h747xx.s + +TOP = ../../../.. +CFLAGS += -I$(TOP) +SOURCES += $(TOP)/mongoose.c + +# Wi-Fi chip firmware stuff +CFLAGS += -Imbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD # nvram header includes a text file + +all build: firmware.bin + +all: firmware.bin + +firmware.elf: cmsis_core cmsis_h7 $(SOURCES) mbed $(wildcard *.h) link.ld Makefile + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + @echo + @echo "To flash, run 'make flash', or use STM32CubeProgrammer" + +# Wi-Fi chip firmware +mbed: + git clone --depth 1 --no-checkout -b extrapatches-6.17.0 https://github.com/arduino/mbed-os $@ && cd $@ && git sparse-checkout set targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources && git checkout + touch wiced_resource.h + +flash: firmware.bin + dfu-util -a 0 -s 0x8040000:leave -D $< + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +cmsis_h7: + git clone -q -c advice.detachedHead=false --depth 1 -b v1.10.4 https://github.com/STMicroelectronics/cmsis_device_h7 $@ + +clean: + rm -rf firmware.* diff --git a/tutorials/stm32/portenta_h7/minimal/hal.c b/tutorials/stm32/portenta_h7/minimal/hal.c new file mode 100644 index 00000000..6dc65c9f --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/hal.c @@ -0,0 +1,125 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) { + uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry + if (period == 0) return false; // Avoid division by zero + if (diff < period) return false; // Period has not elapsed yet + *t += (diff / period) * period; // Preserve cadence, skip missed periods + return true; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t hal_get_tick(void) { + return s_ticks; +}; + +uint32_t SystemCoreClock = SYS_FREQUENCY; +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/stm32/portenta_h7/minimal/hal.h b/tutorials/stm32/portenta_h7/minimal/hal.h new file mode 100644 index 00000000..981de669 --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/hal.h @@ -0,0 +1,509 @@ +// Copyright (c) 2022-2023 Cesanta Software Limited +// All rights reserved +// +// RM0399 +// https://www.st.com/resource/en/reference_manual/rm0399-stm32h745755-and-stm32h747757-advanced-armbased-32bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/datasheet/stm32h747xi.pdf +// https://docs.arduino.cc/hardware/portenta-h7/ + +#pragma once + +#define LED_1 PIN('K', 5) // On-board LED pin (red) +#define LED_2 PIN('K', 6) // On-board LED pin (green) +#define LED_3 PIN('K', 7) // On-board LED pin (blue) +#define LED LED_2 // Use green LED for blinking + +#ifndef HAL_DEBUG_UART +#define HAL_DEBUG_UART USART1 +#endif +#ifndef UART_DEBUG +#define UART_DEBUG HAL_DEBUG_UART +#endif + +#include + +#include +#include +#include +#include + +extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now); +extern uint64_t hal_get_tick(void); +extern size_t hal_ram_free(void); +extern size_t hal_ram_used(void); +extern uint32_t SystemCoreClock; + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +void hal_init(void); +size_t hal_ram_free(void); +size_t hal_ram_used(void); + +// System clock (2.1, Fig 1; 8.5, Fig 45; 8.5.5, Fig 47; 8.5.6, Fig 49) +// - This board defaults to work with an SMPS regulator, so: +// - SYS_FREQUENCY <= 400 MHz; hclk = SYS_FREQUENCY / HPRE ; hclk <= 200 MHz; +// APB clocks <= 100 MHz. +// - D1 domain bus matrix (and so flash) runs at hclk frequency. Configure flash +// latency (WS) in accordance to hclk freq (4.3.8, Table 17) +// - The Ethernet controller is in D2 domain and runs at hclk frequency +enum { + HAL_D1CPRE = 1, // actual divisor value + HAL_HPRE = 2, // actual divisor value + HAL_D1PPRE = 4, // register values, divisor value = BIT(value - 3) = / 2 + HAL_D2PPRE1 = 4, + HAL_D2PPRE2 = 4, + HAL_D3PPRE = 4 +}; +// PLL1_P: odd division factors are not allowed (8.7.13) (according to Cube, '1' +// is also an "odd division factor"). +// Make sure your chip is revision 'V', otherwise set PLL1_N = 400 +enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 400, PLL1_P = 2, PLL1_Q = 4 }; +#define SYS_FREQUENCY \ + ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / HAL_D1CPRE) * 1000000) +// #define SYS_FREQUENCY ((PLL1_HSI / HAL_D1CPRE) * 1000000) +// #define SYS_FREQUENCY 64000000 +#define AHB_FREQUENCY (SYS_FREQUENCY / HAL_HPRE) +#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE2 - 3))) +#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_D2PPRE1 - 3))) +#define PLL1_Q_FREQUENCY ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_Q) * 1000000) + +static inline void spin(volatile uint32_t n) { + while (n--) + (void)0; +} + +enum { + HAL_GPIO_MODE_INPUT, + HAL_GPIO_MODE_OUTPUT, + HAL_GPIO_MODE_AF, + HAL_GPIO_MODE_ANALOG +}; +enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN }; +enum { + HAL_GPIO_SPEED_LOW, + HAL_GPIO_SPEED_MEDIUM, + HAL_GPIO_SPEED_HIGH, + HAL_GPIO_SPEED_INSANE +}; +enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN }; + +#define HAL_GPIO(N) ((GPIO_TypeDef *)(0x40000000 + 0x18020000UL + 0x400 * (N))) + +static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) { + return HAL_GPIO(PINBANK(pin)); +} +static inline void hal_gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int hal_gpio_read(uint16_t pin) { + return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void hal_gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint8_t n = (uint8_t)(PINNO(pin)); + RCC->AHB4ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t)type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t)speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t)pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t)af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t)mode) << (n * 2)); +} +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, HAL_GPIO_PULL_NONE, 0); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, HAL_GPIO_PULL_NONE, 0); +} + +// D2 Kernel clock (8.7.21) USART1 defaults to pclk2 (APB2), while USART2,3 +// default to pclk1 (APB1). Even if using other kernel clocks, the APBx clocks +// must be enabled for CPU access, as the kernel clock drives the BRR, not the +// APB bus interface +static inline void hal_uart_init(USART_TypeDef *uart, unsigned long baud) { + uint8_t af = 7; // Alternate function + uint16_t rx = 0, tx = 0; // pins + uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) + freq = APB2_FREQUENCY, RCC->APB2ENR |= BIT(4); + if (uart == USART2) + freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(17); + if (uart == USART3) + freq = APB1_FREQUENCY, RCC->APB1LENR |= BIT(18); + + if (uart == USART1) + tx = PIN('A', 9), rx = PIN('A', 10); + if (uart == USART2) + tx = PIN('A', 2), rx = PIN('A', 3); + if (uart == USART3) + tx = PIN('D', 8), rx = PIN('D', 9); + +#if 1 // CONSTANT BAUD RATE FOR DEBUGGING + CLRSET(RCC->D2CCIP2R, 7 << 3, 3 << 3); // use HSI for UART1 + freq = 64000000; +#endif + + hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, 0, af); + hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable this UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 = BIT(0) | BIT(2) | BIT(3); // Set UE, RE, TE +} +static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & BIT(7)) == 0) + spin(1); +} +static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, + size_t len) { + while (len-- > 0) + hal_uart_write_byte(uart, *(uint8_t *)buf++); +} +static inline int hal_uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) { + return (uint8_t)(uart->RDR & 255); +} + +static inline void hal_rng_init(void) { + RCC->D2CCIP2R |= RCC_D2CCIP2R_RNGSEL_0; // RNG clock source pll1_q_ck + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG clock + RNG->CR = RNG_CR_RNGEN; // Enable RNG +} + +static inline uint32_t hal_rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) + (void)0; + return RNG->DR; +} + +// NO Hw pull-ups on PHY RXD0,1,DV; set them to enable autonegotiation +static inline void ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see schematics + uint16_t pins1[] = {PIN('A', 7), PIN('C', 4), PIN('C', 5)}; + for (size_t i = 0; i < sizeof(pins1) / sizeof(pins1[0]); i++) { + hal_gpio_init(pins1[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_UP, + 11); // 11 is the Ethernet function + } + uint16_t pins2[] = {PIN('A', 1), PIN('A', 2), PIN('C', 1), + PIN('G', 11), PIN('G', 12), PIN('G', 13)}; + for (size_t i = 0; i < sizeof(pins2) / sizeof(pins2[0]); i++) { + hal_gpio_init(pins2[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_NONE, + 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + CLRSET(SYSCFG->PMCR, 7 << 21, 4 << 21); // Use RMII (13.3.1) + RCC->AHB1ENR |= BIT(15) | BIT(16) | BIT(17); // Enable Ethernet clocks +} + +static inline char hal_chiprev(void) { + uint16_t rev = (uint16_t)(((uint32_t)DBGMCU->IDCODE) >> 16); + if (rev == 0x1003) + return 'Y'; + if (rev == 0x2003) + return 'V'; + return '?'; +} + +static inline unsigned int hal_div2prescval(unsigned int div) { + // 0 --> /1; 8 --> /2 ... 11 --> /16; 12 --> /64 ... 15 --> /512 + if (div == 1) + return 0; + if (div > 16) + div /= 2; + unsigned int val = 7; + while (div >>= 1) + ++val; + return val; +} + +static inline unsigned int hal_pllrge(unsigned int f) { + unsigned int val = 0; + while (f >>= 1) + ++val; + return val - 1; +} + +// HSE: 25MHz in PH0; LSE: in PC14; both unused +static inline void +hal_clock_init(void) { // Called automatically by startup code + // Set flash latency. RM section 4.9.1, section 4.3.8 table 16 + CLRSET(FLASH->ACR, (FLASH_ACR_WRHIGHFREQ_Msk | FLASH_ACR_LATENCY_Msk), + FLASH_ACR_LATENCY_2WS | FLASH_ACR_WRHIGHFREQ_1); + CLRSET(PWR->CR3, BIT(1), BIT(2)); // select SMPS + while ((PWR->CSR1 & BIT(13)) == 0) + spin(1); // ACTVOSRDY + PWR->D3CR |= BIT(15) | BIT(14); // Select VOS1 + uint32_t f = PWR->D3CR; // fake read to wait for bus clocking + while ((PWR->CSR1 & BIT(13)) == 0) + spin(1); // ACTVOSRDY +#if 0 + SYSCFG->PWRCR |= BIT(0); // ODEN for LDO to go 480MHz + f = SYSCFG->PWRCR; + while ((PWR->CSR1 & BIT(13)) == 0) spin(1); // ACTVOSRDY +#endif + (void)f; + RCC->CFGR &= ~(3 << 0); // Set clock source to HSI (bootloader...) + RCC->CR &= ~BIT(24); // Disable PLL1 + CLRSET(RCC->D1CFGR, (0x0F << 8) | (7 << 4) | (0x0F << 0), + (hal_div2prescval(HAL_D1CPRE) << 8) | (HAL_D1PPRE << 4) | + (hal_div2prescval(HAL_HPRE) << 0)); + RCC->D2CFGR = (HAL_D2PPRE2 << 8) | (HAL_D2PPRE1 << 4); + RCC->D3CFGR = (HAL_D3PPRE << 4); + // DIVQ1EN DIVP1EN PLL1RGE !PLL1VCOSEL !PLL1FRACEN + CLRSET(RCC->PLLCFGR, (3 << 2) | BIT(1) | BIT(0), + BIT(17) | BIT(16) | hal_pllrge(PLL1_HSI / PLL1_M) << 2); + CLRSET(RCC->PLL1DIVR, (0x7F << 16) | (0x7F << 9) | (0x1FF << 0), + ((PLL1_Q - 1) << 16) | ((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0)); + CLRSET(RCC->PLLCKSELR, (0x3F << 4) | (3 << 0), PLL1_M << 4); // PLL1: HSI/M + RCC->CR |= BIT(24); // Enable PLL1 + while ((RCC->CR & BIT(25)) == 0) + spin(1); // Wait until done + RCC->CFGR |= (3 << 0); // Set clock source to PLL1 + while ((RCC->CFGR & (7 << 3)) != (3 << 3)) + spin(1); // Wait until done + RCC->APB4ENR |= RCC_APB4ENR_SYSCFGEN; // Enable SYSCFG +} + +static inline void hal_system_init(void) { + SCB->CPACR |= ((3UL << 10 * 2) | (3UL << 11 * 2)); // Enable FPU + SCB_DisableDCache(); // The bootloader enables caches, disable them + SCB_DisableICache(); + __DSB(); + __ISB(); +} + +#define PWRPIN PIN('J', 1) + +// - 58.4 Table 473 +// - 58.5.8 AHB bw >= 3x SDMMC bus bw; Table 492; SD HS => AHB >= ~20MHz +// - SDIO 2.0 section 3.1 +static inline void hal_sdmmc_set_ds(void) { // NO FLOW CONTROL + CLRSET(SDMMC1->CLKCR, (1 << 17) | 0x3ff, + (1 << 14) | + ((PLL1_Q_FREQUENCY / (2 * 25000000)) & 0x3ff)); // 4-bit bus, 25MHz +} +static inline void +hal_sdmmc_set_hs(void) { // mbed enables flow control... AIROC does not + CLRSET(SDMMC1->CLKCR, 0x3ff, + (1 << 17) | (1 << 14) | + ((PLL1_Q_FREQUENCY / (2 * 50000000)) & + 0x3ff)); // hw flow control, 4-bit bus, 50MHz +} +static inline void hal_sdmmc_init(void) { + RCC->AHB3RSTR |= BIT(16); // Reset SDIO block + RCC->AHB3RSTR &= ~BIT(16); + RCC->D1CCIPR &= ~BIT(16); // 9.7.18, use PLL1Q + RCC->AHB3ENR |= BIT(16); // Enable SDIO clock + // Initialise SDIO pins, see schematics + uint16_t pins[] = {PIN('C', 8), PIN('C', 9), PIN('C', 10), + PIN('C', 11), PIN('C', 12), PIN('D', 2)}; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + // Portenta H7 doesn't pull-up the SDIO bus + hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_UP, + 12); // 12 is the SDMMC1 (SDIO) function + } + hal_gpio_input(PIN('J', 5)); // wake, ignored + // 1-bit bus, rising edge, clock always on, no flow control, 400KHz + SDMMC1->CLKCR = (PLL1_Q_FREQUENCY / (2 * 400000)) & 0x3ff; + SDMMC1->POWER |= 3 << 0; // SDMMC_POWER_PWRCTRL + SDMMC1->DCTRL = BIT(10); // Read Wait control by stopping SDMMCCLK + // wait 74 clocks (~200us) before accessing the "card" +} + +static inline bool hal_sdmmc_transact(uint8_t cmd, uint32_t arg, + uint32_t *resp) { + unsigned int wresp = (resp != NULL) ? 1 : 0; // 58.10.4: 2-bit; cmds used + uint32_t cmdr = BIT(12) | (wresp << 8) | ((cmd & 0x3f) << 0); // CPSMEN + SDMMC1->ARG = arg; + CLRSET(SDMMC1->CMD, + 0x00011f3f /* CMDSUSPEND CPSMEN WAITPEND WAITINT WAITRESP CMD */, + cmdr); + if (wresp == 0) { + while ((SDMMC1->STA & SDMMC_STA_CMDSENT) == 0) + spin(1); + SDMMC1->ICR = 0x002000C5; // BUSYD0END CMDSENT CMDREND CTIMEOUT CCRCFAIL + return true; + } + // while ((SDMMC1->STA & 0x00200045 /*BUSYD0END CMDREND CTIMEOUT CCRCFAIL*/) + // == 0 || SDMMC1->STA & SDMMC_STA_CPSMACT) spin(1); + while (SDMMC1->STA & SDMMC_STA_CPSMACT) + spin(1); + bool res = (SDMMC1->STA & 0x00200005 /*BUSYD0END CTIMEOUT CCRCFAIL*/) == 0; + SDMMC1->ICR = 0x002000C5; // BUSYD0END CMDSENT CMDREND CTIMEOUT CCRCFAIL + *resp = SDMMC1->RESP1; + if (!res || SDMMC1->RESPCMD != cmd) + return false; + return true; +} + +static inline unsigned int hal_log2(uint16_t x) { + unsigned int i = 0; + while (x != 0) + x = ((uint16_t)x) >> 1, ++i; + return i - 1; +} + +static inline bool hal_sdmmc_transfer(bool write, uint32_t arg, uint16_t blksz, + uint32_t *data, uint32_t length, + uint32_t *resp) { + uint32_t cmdr = BIT(12) | (1 << 8) | (53 << 0); // CPSMEN WRESP CMD=53 + bool bmode = (blksz != 0); + uint8_t dblksz = bmode ? (uint8_t)hal_log2(blksz) : 0; + uint32_t dctrlr = + (dblksz << 4) | (bmode ? (0 << 2) : (1 << 2)) | + (write + ? 0 + : BIT(1)); // DBLOCKSIZE=f(blksz) DTMODE=f(bmode) DTDIR=!write !DTEN + SDMMC1->DCTRL = SDMMC_DCTRL_RWMOD; + SDMMC1->IDMACTRL = SDMMC_IDMA_IDMAEN; // single buffer + SDMMC1->IDMABASE0 = (uint32_t)data; + SDMMC1->DTIMER = (uint32_t)50000000; // 58.10.7, set for 1 sec @50MHz (2 secs @25MHz) + SDMMC1->DLEN = length; // 58.10.8 call with a multiple of blksz (we do) + CLRSET(SDMMC1->DCTRL, 0xff /* DBLOCKSIZE DTMODE DTDIR DTEN */, dctrlr); + SDMMC1->CMD = SDMMC_CMD_CMDTRANS; // CPSM will perform a data transfer + SDMMC1->ARG = arg; + SDMMC1->CMD |= cmdr; // other flags cleared above + while ((SDMMC1->STA & 0x0020003f) == 0 && SDMMC1->STA & SDMMC_STA_DPSMACT) + spin(1); // incorrect "card" usage will stall here + bool res = (SDMMC1->STA & 0x0020003f /* BUSYD0END RXOVERR TXUNDERR DTIMEOUT CTIMEOUT DCRCFAIL CCRCFAIL */) == 0; + SDMMC1->CMD = 0; + SDMMC1->DLEN = 0; + SDMMC1->IDMACTRL = 0; + SDMMC1->ICR = (uint32_t)~0; // clear all flags + *resp = SDMMC1->RESP1; + SDMMC1->DCTRL = SDMMC_DCTRL_RWMOD; + if (!res || SDMMC1->RESPCMD != 53) + return false; + return true; +} + +// Bit-banged SPI, pin wiring does not allow using a hw SPI peripheral +#define CYWSPI NULL +#define BBSPI_SPIN 1 + +static inline void hal_spi_init(void *spi, uint8_t div) { + // Initialise bit-banged SPI, see: schematics; CYW DS 4.1.1 Table 5, 13.7 + // Table 22 + hal_gpio_input(PIN('C', 8)); // DO -> MISO + hal_gpio_input(PIN('C', 9)); // IRQ -> ignore + hal_gpio_output(PIN('C', 10)); // D2 <-- strap for SPI mode + hal_gpio_output(PIN('C', 11)); // CS + hal_gpio_output(PIN('C', 12)); // SCLK + hal_gpio_output(PIN('D', 2)); // DI -> MOSI + // select SPI mode at power-on/reset time (not WL_REG_ON, actual POR) + // "Sampling occurs a few milliseconds after an internal POR + // or deassertion of the external POR + hal_gpio_write(PIN('C', 10), 0); + hal_gpio_write(PIN('D', 2), 0); // idle values + hal_gpio_write(PIN('C', 12), 0); + hal_gpio_write(PIN('C', 11), 0); // no power at startup + (void)spi; + (void)div; +} + +static inline void hal_spi_write_byte(void *spi, uint8_t byte) { + unsigned int n = 8; + while (n--) { + hal_gpio_write(PIN('D', 2), byte & 0x80); + spin(BBSPI_SPIN); + hal_gpio_write(PIN('C', 12), 1); + byte <<= 1; + spin(BBSPI_SPIN); + hal_gpio_write(PIN('C', 12), 0); + } + hal_gpio_write(PIN('D', 2), 0); + (void)spi; +} + +static inline uint8_t hal_spi_read_byte(void *spi) { + uint8_t byte = 0; + unsigned int n = 8; + while (n--) { + byte <<= 1; + spin(BBSPI_SPIN); + byte |= hal_gpio_read(PIN('C', 8)) ? 1 : 0; + hal_gpio_write(PIN('C', 12), 1); + spin(BBSPI_SPIN); + hal_gpio_write(PIN('C', 12), 0); + } + (void)spi; + return byte; +} + +static inline void hal_spi_select(void *spi, bool s) { + hal_gpio_write(PIN('C', 11), !s); + (void)spi; +} + +// Current bootloader sets all outputs in the PMIC, no need to use I2C. +// The bootloader inits I2C1 and chips in there, but we change MCU clock +// DS 4 Table 12, schematic PB6,7 +static inline void hal_i2c_init(I2C_TypeDef *i2c) { + CLRSET(RCC->D2CCIP2R, 3 << 12, 2 << 12); // use HSI for I2C1 (64MHz) + RCC->APB1LENR |= BIT(21); // enable I2C clock + hal_gpio_init(PIN('B', 6), HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_OPEN_DRAIN, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_NONE, 4); + hal_gpio_init(PIN('B', 7), HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_OPEN_DRAIN, + HAL_GPIO_SPEED_INSANE, HAL_GPIO_PULL_NONE, 4); + i2c->CR1 = 0; // disable before any config change + i2c->CR1 = BIT(12); // 50.7 disable filters + i2c->TIMINGR = 10U << 28 | 3 << 20 | 2 << 16 | 3 << 8 | 9 << 0; // < 400KHz + i2c->CR1 |= BIT(0); // enable after fully configured +} + +static inline void hal_i2c_write_reg(I2C_TypeDef *i2c, uint8_t addr, + uint8_t reg, uint8_t byte) { + i2c->CR2 = BIT(25) | 2 << 16 | + addr << 1; // write byte to reg in chip at addr, AUTOEND + i2c->CR2 |= BIT(13); // START + while ((i2c->ISR & BIT(0)) == 0) + spin(1); // TXE + i2c->TXDR = reg; + while ((i2c->ISR & BIT(0)) == 0) + spin(1); // TXE + i2c->TXDR = byte; + while ((i2c->ISR & BIT(5)) == 0) + spin(1); // STOPF + i2c->ICR |= BIT(5); // STOPCF, clear STOPF +} + +static inline uint8_t hal_i2c_read_reg(I2C_TypeDef *i2c, uint8_t addr, + uint8_t reg) { + i2c->CR2 = 1 << 16 | addr << 1; // write reg addr to chip at addr + i2c->CR2 |= BIT(13); // START + while ((i2c->ISR & BIT(0)) == 0) + spin(1); // TXE + i2c->TXDR = reg; + while ((i2c->ISR & BIT(6)) == 0) + spin(1); // TC + i2c->CR2 = BIT(13) | BIT(10) | 1 << 16 | addr << 1; // read 1 byte, AUTOEND + i2c->CR2 |= BIT(25); // START + while ((i2c->ISR & BIT(2)) == 0) + spin(1); // RXNE + uint8_t byte = (uint8_t)i2c->RXDR; + while ((i2c->ISR & BIT(5)) == 0) + spin(1); // STOPF + i2c->ICR |= BIT(5); // STOPCF, clear STOPF + return byte; +} diff --git a/tutorials/stm32/portenta_h7/minimal/link.ld b/tutorials/stm32/portenta_h7/minimal/link.ld new file mode 100644 index 00000000..a88f6770 --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/link.ld @@ -0,0 +1,33 @@ +/* DTCMRAM (rwx) : ORIGIN = 0x20000400, LENGTH = 128k - 1k */ /* bootloader starts at 0x20000000 */ + +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08040000, LENGTH = 2048k - 0x40000 /* bootloader starts at 0x08000000 */ + sram(rwx) : ORIGIN = 0x24000000, LENGTH = 512k +} +_estack = ORIGIN(sram) + LENGTH(sram); /* stack points to end of SRAM */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .data : { + _sdata = .; + *(.first_data) + *(.data SORT(.data.*)) + *(.iram .iram* .iram.*) + _edata = .; + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { + _sbss = .; + *(.bss SORT(.bss.*) COMMON) + _ebss = .; + } > sram + + . = ALIGN(8); + _end = .; +} + diff --git a/tutorials/stm32/portenta_h7/minimal/main.c b/tutorials/stm32/portenta_h7/minimal/main.c new file mode 100644 index 00000000..226d9f24 --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/main.c @@ -0,0 +1,71 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" +#include "hal.h" + +#define DEBUG_UART USART1 +#define LED1 PIN('K', 5) // On-board LED pin (red) +#define LED2 PIN('K', 6) // On-board LED pin (green) +#define LED3 PIN('K', 7) // On-board LED pin (blue) + +// Redirect stdout debug output to UART +int _write(int fd, char *ptr, int len) { + if (fd == 1 || fd == 2) hal_uart_write_buf(DEBUG_UART, ptr, (size_t) len); + return len; +} + +static void blink_task(void) { + static uint64_t blink_timer = 0; + if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) { + hal_gpio_toggle(LED1); + } +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { // New HTTP request received + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", + hal_get_tick()); + } + } +} + +int main(void) { + hal_clock_init(); + hal_rng_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + hal_uart_init(DEBUG_UART, 115200); + + extern void hwspecific_sdio_init(void); + hwspecific_sdio_init(); + + // Start a minimal web server + struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + + for (;;) { + mg_mgr_poll(&mgr, 0); + blink_task(); + } + + return 0; +} diff --git a/tutorials/stm32/portenta_h7/minimal/mongoose_config.h b/tutorials/stm32/portenta_h7/minimal/mongoose_config.h new file mode 100644 index 00000000..d3d1864e --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/mongoose_config.h @@ -0,0 +1,16 @@ +#pragma once + +// See https://mongoose.ws/documentation/#build-options +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_TLS MG_TLS_BUILTIN + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_CYW_SDIO 1 + +// MAC address is read from OTP by the driver + +// Set your Wi-Fi configuration using a custom function: +extern void wifi_setconfig(void *data); +#define MG_SET_WIFI_CONFIG(data) wifi_setconfig(data) diff --git a/tutorials/stm32/portenta_h7/minimal/wifi.c b/tutorials/stm32/portenta_h7/minimal/wifi.c new file mode 100644 index 00000000..6b05b01f --- /dev/null +++ b/tutorials/stm32/portenta_h7/minimal/wifi.c @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2025 Cesanta Software Limited +// SPDX-License-Identifier: GPL-2.0-only or commercial +// Generated by Mongoose Wizard, https://mongoose.ws/wizard/ + +#include "hal.h" +#include "mongoose.h" + +#ifndef WIFI_NAME +#define WIFI_NAME "" +#endif + +#ifndef WIFI_PASS +#define WIFI_PASS "" +#endif + +#ifndef WIFI_AP_NAME +#define WIFI_AP_NAME "Mongoose" +#endif + +#ifndef WIFI_AP_PASS +#define WIFI_AP_PASS "mongoose" +#endif + +#ifndef ENABLE_WIFI_AP +#define ENABLE_WIFI_AP 1 +#endif + +void hwspecific_sdio_init(void) { + hal_gpio_output(PWRPIN); + hal_gpio_write(PWRPIN, 0); + mg_delayms(100); + hal_sdmmc_init(); + mg_delayms(1); + hal_gpio_write(PWRPIN, 1); + mg_delayms(50); +} + +static void hwspecific_sdio_config(void *sdio, uint8_t cfg) { + (void) sdio; + if (cfg == 0) return hal_sdmmc_set_ds(); + if (cfg == 1) return hal_sdmmc_set_hs(); +} +static bool hwspecific_sdio_txn(void *sdio, uint8_t cmd, uint32_t arg, uint32_t *resp) { + (void) sdio; + return hal_sdmmc_transact(cmd, arg, resp); +} +static bool hwspecific_sdio_xfr(void *sdio, bool write, uint32_t arg, uint16_t blksz, uint32_t *data, uint32_t len, uint32_t *resp){ + (void) sdio; + return hal_sdmmc_transfer(write, arg, blksz, data, len, resp); +} + +static const struct mg_tcpip_sdio sdio = {NULL, hwspecific_sdio_config, hwspecific_sdio_txn, hwspecific_sdio_xfr}; + +// clang-format off +typedef enum {RESOURCE_IN_MEMORY,RESOURCE_IN_EXTERNAL_STORAGE} resource_location_t; +typedef struct {resource_location_t l;unsigned long sz;union{struct {unsigned long o;const char *f;}fs;struct {const char *m;}mem;}val;} resource_hnd_t; +#include "mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources/firmware/COMPONENT_4343W_FS/4343WA1_bin.c" +#include "mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources/firmware/COMPONENT_4343W_FS/4343WA1_clm_blob.c" +#include "mbed/targets/TARGET_STM/TARGET_STM32H7/TARGET_STM32H747xI/TARGET_PORTENTA_H7/COMPONENT_WHD/resources/nvram/wifi_nvram_image.h" +// clang-format on +static const struct mg_tcpip_driver_cyw_firmware fw = { + (const uint8_t *)wifi_firmware_image_data, (size_t)sizeof(wifi_firmware_image_data), + (const uint8_t *)wifi_nvram_image, (size_t)sizeof(wifi_nvram_image), + (const uint8_t *)wifi_firmware_clm_blob_image_data, (size_t)sizeof(wifi_firmware_clm_blob_image_data)}; + +void wifi_setconfig(void *data) { + struct mg_tcpip_driver_cyw_data *d = (struct mg_tcpip_driver_cyw_data *) data; + d->bus = (struct mg_tcpip_sdio *) &sdio; + d->fw = (struct mg_tcpip_driver_cyw_firmware *)&fw; + d->wifi.ssid = WIFI_NAME; + d->wifi.pass = WIFI_PASS; + d->wifi.apssid = WIFI_AP_NAME; + d->wifi.appass = WIFI_AP_PASS; + d->wifi.apip = MG_IPV4(192, 168, 111, 1); + d->wifi.apmask = MG_IPV4(255, 255, 255, 0); + d->wifi.security = 0; + d->wifi.apsecurity = 0; + d->wifi.apchannel = 10; + d->wifi.apmode = ENABLE_WIFI_AP; + d->hs = false; +} diff --git a/tutorials/stm32/stm32h573i_dk/cubemx/README.md b/tutorials/stm32/stm32h573i_dk/cubemx/README.md new file mode 100644 index 00000000..ab3b09f9 --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/cubemx/README.md @@ -0,0 +1,29 @@ +# STM32H573I-DK Mongoose Integration + +This guide explains how to bring up a production-grade Web UI dashboard on the +STM32H573I-DK development board. + +A pre-configured, ready-to-run project zip is available for download at +https://mongoose.ws. + +To integrate the Web UI dashboard into an existing CubeMX project, or to create a +new project from scratch: + +1. Configure the peripherals (described below). +2. Regenerate your project code and integrate Mongoose core by following + https://mongoose.ws/docs/guides/stm32-tcpip-stack/. +3. Add the Web UI dashboard by following + https://mongoose.ws/docs/guides/web-dashboard-guide/. + +## Configure peripherals + +An `.ioc` file is included in this directory for your reference. It was created +using the following steps: + +- Open CubeMX. If you are starting a new project, select the STM32H573I + microcontroller and create a new project. +- In Clock Configuration, set the maximum clock frequency to 250 MHz. +- In Pinout & Configuration / Connectivity, enable USART1 in asynchronous mode. + Configure pins PA9 and PA10 for USART. +- In Pinout & Configuration / Connectivity, enable ETH in RMII mode and set pins + PA1, PA2, PA7, PC1, PC4, PC5, PG11, PG12, and PG13 for Ethernet. diff --git a/tutorials/stm32/stm32h573i_dk/cubemx/stm32h573i_dk.ioc b/tutorials/stm32/stm32h573i_dk/cubemx/stm32h573i_dk.ioc new file mode 100644 index 00000000..94a6e72d --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/cubemx/stm32h573i_dk.ioc @@ -0,0 +1,237 @@ +#MicroXplorer Configuration settings - do not modify +CAD.formats= +CAD.pinconfig= +CAD.provider= +CORTEX_M33_NS.userName=CORTEX_M33 +ETH.IPParameters=MediaInterface +ETH.MediaInterface=HAL_ETH_RMII_MODE +File.Version=6 +GPIO.groupedBy= +KeepUserPlacement=false +MMTAppRegionsCount=0 +MMTConfigApplied=false +MMTSectionSuffix= +Mcu.CPN=STM32H573IIK3Q +Mcu.ContextProject=TrustZoneDisabled +Mcu.Family=STM32H5 +Mcu.IP0=BOOTPATH +Mcu.IP1=CORTEX_M33_NS +Mcu.IP2=ETH +Mcu.IP3=MEMORYMAP +Mcu.IP4=NVIC +Mcu.IP5=PWR +Mcu.IP6=RCC +Mcu.IP7=RNG +Mcu.IP8=SYS +Mcu.IP9=USART1 +Mcu.IPNb=10 +Mcu.Name=STM32H573IIKxQ +Mcu.Package=UFBGA176 +Mcu.Pin0=PG12 +Mcu.Pin1=PG13 +Mcu.Pin10=PA7 +Mcu.Pin11=VP_CORTEX_M33_NS_VS_Hclk +Mcu.Pin12=VP_PWR_VS_SECSignals +Mcu.Pin13=VP_PWR_VS_LPOM +Mcu.Pin14=VP_RNG_VS_RNG +Mcu.Pin15=VP_SYS_VS_Systick +Mcu.Pin16=VP_BOOTPATH_VS_BOOTPATH +Mcu.Pin17=VP_MEMORYMAP_VS_MEMORYMAP +Mcu.Pin2=PG11 +Mcu.Pin3=PA10 +Mcu.Pin4=PA9 +Mcu.Pin5=PC1 +Mcu.Pin6=PA1 +Mcu.Pin7=PA2 +Mcu.Pin8=PC4 +Mcu.Pin9=PC5 +Mcu.PinsNb=18 +Mcu.ThirdPartyNb=0 +Mcu.UserConstants= +Mcu.UserName=STM32H573IIKxQ +MxCube.Version=6.17.0 +MxDb.Version=DB.6.0.170 +NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.ETH_IRQn=true\:0\:0\:false\:false\:true\:true\:true\:true +NVIC.ForceEnableDMAVector=true +NVIC.HardFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false +NVIC.UsageFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +PA1.Mode=RMII +PA1.Signal=ETH_REF_CLK +PA10.Locked=true +PA10.Mode=Asynchronous +PA10.Signal=USART1_RX +PA2.Mode=RMII +PA2.Signal=ETH_MDIO +PA7.Mode=RMII +PA7.Signal=ETH_CRS_DV +PA9.Locked=true +PA9.Mode=Asynchronous +PA9.Signal=USART1_TX +PC1.Mode=RMII +PC1.Signal=ETH_MDC +PC4.Mode=RMII +PC4.Signal=ETH_RXD0 +PC5.Mode=RMII +PC5.Signal=ETH_RXD1 +PCC.Checker=false +PCC.Display=Plot\: All Steps +PCC.Line=STM32H563/H573 +PCC.MCU=STM32H573IIKxQ +PCC.PartNumber=STM32H573IIKxQ +PCC.Series=STM32H5 +PCC.Temperature=25 +PCC.Vdd=3.0 +PG11.Mode=RMII +PG11.Signal=ETH_TX_EN +PG12.Locked=true +PG12.Mode=RMII +PG12.Signal=ETH_TXD1 +PG13.Mode=RMII +PG13.Signal=ETH_TXD0 +PinOutPanel.CurrentBGAView=Top +PinOutPanel.RotationAngle=0 +ProjectManager.AskForMigrate=true +ProjectManager.BackupPrevious=false +ProjectManager.CompilerLinker=GCC +ProjectManager.CompilerOptimize=6 +ProjectManager.ComputerToolchain=false +ProjectManager.CoupleFile=false +ProjectManager.CustomerFirmwarePackage= +ProjectManager.DefaultFWLocation=true +ProjectManager.DeletePrevious=true +ProjectManager.DeviceId=STM32H573IIKxQ +ProjectManager.FirmwarePackage=STM32Cube FW_H5 V1.6.0 +ProjectManager.FreePins=false +ProjectManager.FreePinsContext= +ProjectManager.HalAssertFull=false +ProjectManager.HeapSize=0x10000 +ProjectManager.KeepUserCode=true +ProjectManager.LastFirmware=true +ProjectManager.LibraryCopy=1 +ProjectManager.MainLocation=Core/Src +ProjectManager.NoMain=false +ProjectManager.PreviousToolchain=STM32CubeIDE +ProjectManager.ProjectBuild=false +ProjectManager.ProjectFileName=h573.ioc +ProjectManager.ProjectName=h573 +ProjectManager.ProjectStructure= +ProjectManager.RegisterCallBack= +ProjectManager.StackSize=0x2000 +ProjectManager.TargetToolchain=CMake +ProjectManager.ToolChainLocation= +ProjectManager.UAScriptAfterPath= +ProjectManager.UAScriptBeforePath= +ProjectManager.UnderRoot=false +ProjectManager.functionlistsort=1-SystemClock_Config-RCC-false-HAL-false,2-MX_GPIO_Init-GPIO-false-HAL-true,3-MX_ETH_Init-ETH-false-HAL-true,4-MX_RNG_Init-RNG-false-HAL-true,5-MX_USART1_UART_Init-USART1-false-HAL-true,0-MX_CORTEX_M33_NS_Init-CORTEX_M33_NS-false-HAL-true,0-MX_PWR_Init-PWR-false-HAL-true +RCC.ADCFreq_Value=250000000 +RCC.AHBFreq_Value=250000000 +RCC.APB1Freq_Value=250000000 +RCC.APB1TimFreq_Value=250000000 +RCC.APB2Freq_Value=250000000 +RCC.APB2TimFreq_Value=250000000 +RCC.APB3Freq_Value=250000000 +RCC.CECFreq_Value=32000 +RCC.CKPERFreq_Value=32000000 +RCC.CRSFreq_Value=48000000 +RCC.CSI_VALUE=4000000 +RCC.CortexFreq_Value=250000000 +RCC.DACFreq_Value=32768 +RCC.EPOD_VALUE=4000000 +RCC.ETHFreq_Value=250000000 +RCC.FCLKCortexFreq_Value=250000000 +RCC.FDCANFreq_Value=25000000 +RCC.FamilyName=M +RCC.HCLKFreq_Value=250000000 +RCC.HSE_VALUE=25000000 +RCC.HSI48_VALUE=48000000 +RCC.HSI_VALUE=64000000 +RCC.I2C1Freq_Value=250000000 +RCC.I2C2Freq_Value=250000000 +RCC.I2C3Freq_Value=250000000 +RCC.I2C4Freq_Value=250000000 +RCC.I3C1Freq_Value=250000000 +RCC.IPParameters=ADCFreq_Value,AHBFreq_Value,APB1Freq_Value,APB1TimFreq_Value,APB2Freq_Value,APB2TimFreq_Value,APB3Freq_Value,CECFreq_Value,CKPERFreq_Value,CRSFreq_Value,CSI_VALUE,CortexFreq_Value,DACFreq_Value,EPOD_VALUE,ETHFreq_Value,FCLKCortexFreq_Value,FDCANFreq_Value,FamilyName,HCLKFreq_Value,HSE_VALUE,HSI48_VALUE,HSI_VALUE,I2C1Freq_Value,I2C2Freq_Value,I2C3Freq_Value,I2C4Freq_Value,I3C1Freq_Value,LPTIM1Freq_Value,LPTIM2Freq_Value,LPTIM3Freq_Value,LPTIM4Freq_Value,LPTIM5Freq_Value,LPTIM6Freq_Value,LPUART1Freq_Value,LSCOPinFreq_Value,LSE_VALUE,LSIRC_VALUE,MCO1PinFreq_Value,MCO2PinFreq_Value,OCTOSPIMFreq_Value,PLL2FRACN,PLL2PoutputFreq_Value,PLL2QoutputFreq_Value,PLL2RoutputFreq_Value,PLL3FRACN,PLL3PoutputFreq_Value,PLL3QoutputFreq_Value,PLL3RoutputFreq_Value,PLLFRACN,PLLN,PLLPoutputFreq_Value,PLLQoutputFreq_Value,PWRFreq_Value,RNGFreq_Value,SAI1Freq_Value,SAI2Freq_Value,SDMMC1Freq_Value,SDMMC2Freq_Value,SPI1Freq_Value,SPI2Freq_Value,SPI3Freq_Value,SPI4Freq_Value,SPI5Freq_Value,SPI6Freq_Value,SYSCLKFreq_VALUE,SYSCLKSource,UART12Freq_Value,UART4Freq_Value,UART5Freq_Value,UART7Freq_Value,UART8Freq_Value,UART9Freq_Value,UCPD1outputFreq_Value,USART10Freq_Value,USART11Freq_Value,USART1Freq_Value,USART2Freq_Value,USART3Freq_Value,USART6Freq_Value,USBFreq_Value,VCOInput2Freq_Value,VCOInput3Freq_Value,VCOInputFreq_Value,VCOOutputFreq_Value,VCOPLL2OutputFreq_Value,VCOPLL3OutputFreq_Value +RCC.LPTIM1Freq_Value=250000000 +RCC.LPTIM2Freq_Value=250000000 +RCC.LPTIM3Freq_Value=250000000 +RCC.LPTIM4Freq_Value=250000000 +RCC.LPTIM5Freq_Value=250000000 +RCC.LPTIM6Freq_Value=250000000 +RCC.LPUART1Freq_Value=250000000 +RCC.LSCOPinFreq_Value=32000 +RCC.LSE_VALUE=32768 +RCC.LSIRC_VALUE=32000 +RCC.MCO1PinFreq_Value=32000000 +RCC.MCO2PinFreq_Value=250000000 +RCC.OCTOSPIMFreq_Value=250000000 +RCC.PLL2FRACN=0 +RCC.PLL2PoutputFreq_Value=258000000 +RCC.PLL2QoutputFreq_Value=258000000 +RCC.PLL2RoutputFreq_Value=258000000 +RCC.PLL3FRACN=0 +RCC.PLL3PoutputFreq_Value=258000000 +RCC.PLL3QoutputFreq_Value=258000000 +RCC.PLL3RoutputFreq_Value=258000000 +RCC.PLLFRACN=0 +RCC.PLLN=125 +RCC.PLLPoutputFreq_Value=250000000 +RCC.PLLQoutputFreq_Value=250000000 +RCC.PWRFreq_Value=250000000 +RCC.RNGFreq_Value=48000000 +RCC.SAI1Freq_Value=258000000 +RCC.SAI2Freq_Value=258000000 +RCC.SDMMC1Freq_Value=250000000 +RCC.SDMMC2Freq_Value=250000000 +RCC.SPI1Freq_Value=250000000 +RCC.SPI2Freq_Value=250000000 +RCC.SPI3Freq_Value=250000000 +RCC.SPI4Freq_Value=250000000 +RCC.SPI5Freq_Value=250000000 +RCC.SPI6Freq_Value=250000000 +RCC.SYSCLKFreq_VALUE=250000000 +RCC.SYSCLKSource=RCC_SYSCLKSOURCE_PLLCLK +RCC.UART12Freq_Value=250000000 +RCC.UART4Freq_Value=250000000 +RCC.UART5Freq_Value=250000000 +RCC.UART7Freq_Value=250000000 +RCC.UART8Freq_Value=250000000 +RCC.UART9Freq_Value=250000000 +RCC.UCPD1outputFreq_Value=8000000 +RCC.USART10Freq_Value=250000000 +RCC.USART11Freq_Value=250000000 +RCC.USART1Freq_Value=250000000 +RCC.USART2Freq_Value=250000000 +RCC.USART3Freq_Value=250000000 +RCC.USART6Freq_Value=250000000 +RCC.USBFreq_Value=48000000 +RCC.VCOInput2Freq_Value=4000000 +RCC.VCOInput3Freq_Value=4000000 +RCC.VCOInputFreq_Value=4000000 +RCC.VCOOutputFreq_Value=500000000 +RCC.VCOPLL2OutputFreq_Value=516000000 +RCC.VCOPLL3OutputFreq_Value=516000000 +USART1.IPParameters=VirtualMode-Asynchronous +USART1.VirtualMode-Asynchronous=VM_ASYNC +VP_BOOTPATH_VS_BOOTPATH.Mode=BP_Activate +VP_BOOTPATH_VS_BOOTPATH.Signal=BOOTPATH_VS_BOOTPATH +VP_CORTEX_M33_NS_VS_Hclk.Mode=Hclk_Mode +VP_CORTEX_M33_NS_VS_Hclk.Signal=CORTEX_M33_NS_VS_Hclk +VP_MEMORYMAP_VS_MEMORYMAP.Mode=CurAppReg +VP_MEMORYMAP_VS_MEMORYMAP.Signal=MEMORYMAP_VS_MEMORYMAP +VP_PWR_VS_LPOM.Mode=PowerOptimisation +VP_PWR_VS_LPOM.Signal=PWR_VS_LPOM +VP_PWR_VS_SECSignals.Mode=Security/Privilege +VP_PWR_VS_SECSignals.Signal=PWR_VS_SECSignals +VP_RNG_VS_RNG.Mode=RNG_Activate +VP_RNG_VS_RNG.Signal=RNG_VS_RNG +VP_SYS_VS_Systick.Mode=SysTick +VP_SYS_VS_Systick.Signal=SYS_VS_Systick +board=custom diff --git a/tutorials/stm32/stm32h573i_dk/minimal/Makefile b/tutorials/stm32/stm32h573i_dk/minimal/Makefile new file mode 100644 index 00000000..cec4bb49 --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/minimal/Makefile @@ -0,0 +1,38 @@ +# Copyright (c) 2026 Cesanta Software Limited +# Environment setup: https://mongoose.ws/docs/getting-started/build-environment/ + +CFLAGS = -W -Wall -Wextra -Wundef -Wshadow -Wdouble-promotion +CFLAGS += -Wformat-truncation -fno-common -Wconversion -Wno-sign-conversion +CFLAGS += -g3 -Os -ffunction-sections -fdata-sections +CFLAGS += -I. -Icmsis_core/CMSIS/Core/Include -Icmsis_h5/Include +CFLAGS += -mcpu=cortex-m33 -mthumb -mfpu=fpv5-sp-d16 -mfloat-abi=hard $(CFLAGS_EXTRA) +LDFLAGS ?= -Tlink.ld -nostdlib -nostartfiles --specs nosys.specs -lc -lgcc -Wl,--gc-sections -Wl,-Map=$@.map + +SOURCES = main.c hal.c +SOURCES += cmsis_h5/Source/Templates/gcc/startup_stm32h573xx.s + +TOP = ../../../.. +CFLAGS += -I$(TOP) +SOURCES += $(TOP)/mongoose.c + +all: firmware.bin + +firmware.elf: cmsis_core cmsis_h5 hal.h link.ld Makefile $(SOURCES) + arm-none-eabi-gcc $(SOURCES) $(CFLAGS) $(CFLAGS_EXTRA) $(LDFLAGS) -o $@ + +firmware.bin: firmware.elf + arm-none-eabi-objcopy -O binary $< $@ + @echo + @echo "To flash, run 'make flash', or use STM32CubeProgrammer" + +flash: firmware.bin + STM32_Programmer_CLI -c port=swd -e all -w firmware.elf -hardRst + +cmsis_core: + git clone -q -c advice.detachedHead=false --depth 1 -b 5.9.0 https://github.com/ARM-software/CMSIS_5 $@ + +cmsis_h5: + git clone -q -c advice.detachedHead=false --depth 1 -b v1.2.0 https://github.com/STMicroelectronics/cmsis_device_h5 $@ + +clean: + rm -rf firmware.* diff --git a/tutorials/stm32/stm32h573i_dk/minimal/hal.c b/tutorials/stm32/stm32h573i_dk/minimal/hal.c new file mode 100644 index 00000000..6dc65c9f --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/minimal/hal.c @@ -0,0 +1,125 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "hal.h" + +bool hal_timer_expired(volatile uint64_t *t, uint64_t period, uint64_t now) { + uint64_t diff = now - *t; // Wrap-safe elapsed time since last expiry + if (period == 0) return false; // Avoid division by zero + if (diff < period) return false; // Period has not elapsed yet + *t += (diff / period) * period; // Preserve cadence, skip missed periods + return true; +} + +static volatile uint64_t s_ticks; // Milliseconds since boot +void SysTick_Handler(void) { // SyStick IRQ handler, triggered every 1ms + s_ticks++; +} + +uint64_t hal_get_tick(void) { + return s_ticks; +}; + +uint32_t SystemCoreClock = SYS_FREQUENCY; +void SystemInit(void) { // Called automatically by startup code + hal_system_init(); // Enable FPU + hal_clock_init(); + SysTick_Config(SystemCoreClock / 1000); // Sys tick every 1ms +} + +void ExitRun0Mode(void) { +} + +struct stat; +__attribute__((weak)) int _fstat(int fd, struct stat *st) { + (void) fd, (void) st; + return -1; +} + +extern unsigned char _end[]; // End of data section, start of heap. See link.ld +static unsigned char *s_current_heap_end = _end; + +size_t hal_ram_used(void) { + return (size_t) (s_current_heap_end - _end); +} + +size_t hal_ram_free(void) { + unsigned char endofstack; + return (size_t) (&endofstack - s_current_heap_end); +} + +void *_sbrk(int incr) { + unsigned char *prev_heap; + unsigned char *heap_end = (unsigned char *) ((size_t) &heap_end - 256); + prev_heap = s_current_heap_end; + // Check how much space we got from the heap end to the stack end + if (s_current_heap_end + incr > heap_end) return (void *) -1; + s_current_heap_end += incr; + return prev_heap; +} + +__attribute__((weak)) int _open(const char *path) { + (void) path; + return -1; +} + +__attribute__((weak)) int _close(int fd) { + (void) fd; + return -1; +} + +__attribute__((weak)) int _isatty(int fd) { + (void) fd; + return 1; +} + +__attribute__((weak)) int _lseek(int fd, int ptr, int dir) { + (void) fd, (void) ptr, (void) dir; + return 0; +} + +__attribute__((weak)) void _exit(int status) { + (void) status; + for (;;) asm volatile("BKPT #0"); +} + +__attribute__((weak)) void _kill(int pid, int sig) { + (void) pid, (void) sig; +} + +__attribute__((weak)) int _getpid(void) { + return -1; +} + +__attribute__((weak)) int _write(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _read(int fd, char *ptr, int len) { + (void) fd, (void) ptr, (void) len; + return -1; +} + +__attribute__((weak)) int _link(const char *a, const char *b) { + (void) a, (void) b; + return -1; +} + +__attribute__((weak)) int _unlink(const char *a) { + (void) a; + return -1; +} + +__attribute__((weak)) int _stat(const char *path, struct stat *st) { + (void) path, (void) st; + return -1; +} + +__attribute__((weak)) int mkdir(const char *path, int mode) { + (void) path, (void) mode; + return -1; +} + +__attribute__((weak)) void _init(void) { +} diff --git a/tutorials/stm32/stm32h573i_dk/minimal/hal.h b/tutorials/stm32/stm32h573i_dk/minimal/hal.h new file mode 100644 index 00000000..a8df86e6 --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/minimal/hal.h @@ -0,0 +1,206 @@ +// Copyright (c) 2026 Cesanta Software Limited +// https://www.st.com/resource/en/reference_manual/dm00031020-stm32f405-415-stm32f407-417-stm32f427-437-and-stm32f429-439-advanced-arm-based-32-bit-mcus-stmicroelectronics.pdf +// https://www.st.com/resource/en/datasheet/stm32f429zi.pdf + +#pragma once + +#include + +#include +#include +#include +#include + + +extern bool hal_timer_expired(volatile uint64_t *t, uint64_t prd, uint64_t now); +extern uint64_t hal_get_tick(void); +extern size_t hal_ram_free(void); +extern size_t hal_ram_used(void); +extern uint32_t SystemCoreClock; + +#define HAL_ETH_PINS \ + PIN('A', 1), /* ETH_REF_CLK */ \ + PIN('A', 2), /* ETH_MDIO */ \ + PIN('A', 7), /* ETH_CRS_DV */ \ + PIN('C', 1), /* ETH_MDC */ \ + PIN('C', 4), /* ETH_RXD0 */ \ + PIN('C', 5), /* ETH_RXD1 */ \ + PIN('G', 11), /* ETH_TX_EN */ \ + PIN('G', 12), /* ETH_TXD1 */ \ + PIN('G', 13) /* ETH_TXD0 */ + +#define BIT(x) (1UL << (x)) +#define CLRSET(reg, clear, set) ((reg) = ((reg) & ~(clear)) | (set)) +#define PIN(bank, num) ((((bank) - 'A') << 8) | (num)) +#define PINNO(pin) (pin & 255) +#define PINBANK(pin) (pin >> 8) + +void hal_init(void); +size_t hal_ram_free(void); +size_t hal_ram_used(void); + +// System clock (11.4, Figure 48; 11.4.5, Figure 51; 11.4.8 +// SYS_FREQUENCY <= 250 MHz; (SYS_FREQUENCY / HPRE) ; hclk = SYS_FREQUENCY +// APB clocks <= 250 MHz. Configure flash latency (WS) in accordance to hclk +// freq (7.3.4, Table 37) +enum { + HAL_HPRE = 7, // register value, divisor value = BIT(value - 7) = / 1 + HAL_PPRE1 = 4, // register values, divisor value = BIT(value - 3) = / 2 + HAL_PPRE2 = 4, + HAL_PPRE3 = 4, +}; +// Make sure your chip package uses the internal LDO, otherwise set PLL1_N = 200 +enum { PLL1_HSI = 64, PLL1_M = 32, PLL1_N = 250, PLL1_P = 2 }; +#define SYS_FREQUENCY \ + ((PLL1_HSI * PLL1_N / PLL1_M / PLL1_P / (BIT(HAL_HPRE - 7))) * 1000000) +#define AHB_FREQUENCY SYS_FREQUENCY +#define APB2_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_PPRE2 - 3))) +#define APB1_FREQUENCY (AHB_FREQUENCY / (BIT(HAL_PPRE1 - 3))) + +static inline void spin(volatile uint32_t n) { + while (n--) (void) 0; +} + +enum { HAL_GPIO_MODE_INPUT, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_MODE_AF, HAL_GPIO_MODE_ANALOG }; +enum { HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_OTYPE_OPEN_DRAIN }; +enum { HAL_GPIO_SPEED_LOW, HAL_GPIO_SPEED_MEDIUM, HAL_GPIO_SPEED_HIGH, HAL_GPIO_SPEED_INSANE }; +enum { HAL_GPIO_PULL_NONE, HAL_GPIO_PULL_UP, HAL_GPIO_PULL_DOWN }; + +#define HAL_GPIO(N) ((GPIO_TypeDef *) ((GPIOA_BASE_NS) + 0x400 * (N))) + +static GPIO_TypeDef *hal_gpio_bank(uint16_t pin) { + return HAL_GPIO(PINBANK(pin)); +} +static inline void hal_gpio_toggle(uint16_t pin) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint32_t mask = BIT(PINNO(pin)); + gpio->BSRR = mask << (gpio->ODR & mask ? 16 : 0); +} +static inline int hal_gpio_read(uint16_t pin) { + return hal_gpio_bank(pin)->IDR & BIT(PINNO(pin)) ? 1 : 0; +} +static inline void hal_gpio_write(uint16_t pin, bool val) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + gpio->BSRR = BIT(PINNO(pin)) << (val ? 0 : 16); +} +static inline void hal_gpio_init(uint16_t pin, uint8_t mode, uint8_t type, + uint8_t speed, uint8_t pull, uint8_t af) { + GPIO_TypeDef *gpio = hal_gpio_bank(pin); + uint8_t n = (uint8_t) (PINNO(pin)); + RCC->AHB2ENR |= BIT(PINBANK(pin)); // Enable GPIO clock + CLRSET(gpio->OTYPER, 1UL << n, ((uint32_t) type) << n); + CLRSET(gpio->OSPEEDR, 3UL << (n * 2), ((uint32_t) speed) << (n * 2)); + CLRSET(gpio->PUPDR, 3UL << (n * 2), ((uint32_t) pull) << (n * 2)); + CLRSET(gpio->AFR[n >> 3], 15UL << ((n & 7) * 4), + ((uint32_t) af) << ((n & 7) * 4)); + CLRSET(gpio->MODER, 3UL << (n * 2), ((uint32_t) mode) << (n * 2)); +} +static inline void hal_gpio_input(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_INPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} +static inline void hal_gpio_output(uint16_t pin) { + hal_gpio_init(pin, HAL_GPIO_MODE_OUTPUT, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, + HAL_GPIO_PULL_NONE, 0); +} + +static inline bool hal_uart_init(USART_TypeDef *uart, unsigned long baud) { + uint8_t af = 7; // Alternate function + uint16_t rx = 0, tx = 0; // pins + uint32_t freq = 0; // Bus frequency. UART1 is on APB2, rest on APB1 + + if (uart == USART1) { + freq = APB2_FREQUENCY, RCC->APB2ENR |= RCC_APB2ENR_USART1EN; + tx = PIN('A', 9), rx = PIN('A', 10); + } else if (uart == USART2) { + freq = APB1_FREQUENCY, RCC->APB1LENR |= RCC_APB1LENR_USART2EN; + tx = PIN('A', 2), rx = PIN('A', 3); + } else if (uart == USART3) { + freq = APB1_FREQUENCY, RCC->APB1LENR |= RCC_APB1LENR_USART3EN; + tx = PIN('D', 8), rx = PIN('D', 9); + } else { + return false; + } + hal_gpio_init(tx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + hal_gpio_init(rx, HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_HIGH, 0, af); + uart->CR1 = 0; // Disable UART + uart->BRR = freq / baud; // Set baud rate + uart->CR1 = USART_CR1_RE | USART_CR1_TE; // Set mode to TX & RX + uart->CR1 |= USART_CR1_UE; // Enable UART + return true; +} +static inline void hal_uart_write_byte(USART_TypeDef *uart, uint8_t byte) { + uart->TDR = byte; + while ((uart->ISR & BIT(7)) == 0) spin(1); +} +static inline void hal_uart_write_buf(USART_TypeDef *uart, char *buf, size_t len) { + while (len-- > 0) hal_uart_write_byte(uart, *(uint8_t *) buf++); +} +static inline int hal_uart_read_ready(USART_TypeDef *uart) { + return uart->ISR & BIT(5); // If RXNE bit is set, data is ready +} +static inline uint8_t hal_uart_read_byte(USART_TypeDef *uart) { + return (uint8_t) (uart->RDR & 255); +} + +static inline void hal_rng_init(void) { + RCC->CCIPR5 |= RCC_CCIPR5_RNGSEL_0; // RNG clock source pll1_q_ck + RCC->AHB2ENR |= RCC_AHB2ENR_RNGEN; // Enable RNG clock + RNG->CR |= RNG_CR_RNGEN; // Enable RNG +} +static inline uint32_t hal_rng_read(void) { + while ((RNG->SR & RNG_SR_DRDY) == 0) spin(1); + return RNG->DR; +} + +static inline bool hal_ldo_is_on(void) { + return (PWR->SCCR & PWR_SCCR_LDOEN) == PWR_SCCR_LDOEN; +} + +// Hw pull-ups on PHY RXD0,1,DV to enable autonegotiation +static inline void hal_ethernet_init(void) { + // Initialise Ethernet. Enable MAC GPIO pins, see UM3115 section 10.7 + uint16_t pins[] = {HAL_ETH_PINS}; + for (size_t i = 0; i < sizeof(pins) / sizeof(pins[0]); i++) { + hal_gpio_init(pins[i], HAL_GPIO_MODE_AF, HAL_GPIO_OTYPE_PUSH_PULL, HAL_GPIO_SPEED_INSANE, + HAL_GPIO_PULL_NONE, 11); // 11 is the Ethernet function + } + NVIC_EnableIRQ(ETH_IRQn); // Setup Ethernet IRQ handler + RCC->APB3ENR |= RCC_APB3ENR_SBSEN; // Enable SBS clock + CLRSET(SBS->PMCR, SBS_PMCR_ETH_SEL_PHY, SBS_PMCR_ETH_SEL_PHY_2); // RMII + RCC->AHB1ENR |= RCC_AHB1ENR_ETHEN | RCC_AHB1ENR_ETHRXEN | RCC_AHB1ENR_ETHTXEN; +} + +static inline void hal_system_init(void) { + SCB->CPACR |= ((3UL << 20U) | (3UL << 22U)); // Enable FPU + __DSB(); + __ISB(); +} + +static inline void hal_clock_init(void) { + // Set flash latency. RM0481, section 7.11.1, section 7.3.4 table 37 + CLRSET(FLASH->ACR, (FLASH_ACR_WRHIGHFREQ_Msk | FLASH_ACR_LATENCY_Msk), + FLASH_ACR_LATENCY_5WS | FLASH_ACR_WRHIGHFREQ_1); + + if (hal_ldo_is_on()) { + PWR->VOSCR = PWR_VOSCR_VOS_0 | PWR_VOSCR_VOS_1; // Select VOS0 + } else { + PWR->VOSCR = PWR_VOSCR_VOS_1; // Select VOS1 + } + uint32_t f = PWR->VOSCR; // fake read to wait for bus clocking + while ((PWR->VOSSR & PWR_VOSSR_ACTVOSRDY) == 0) spin(1); + (void) f; + RCC->CR = RCC_CR_HSION; // Clear HSI clock divisor + while ((RCC->CR & RCC_CR_HSIRDY) == 0) spin(1); // Wait until done + RCC->CFGR2 = (HAL_PPRE3 << 12) | (HAL_PPRE2 << 8) | (HAL_PPRE1 << 4) | (HAL_HPRE << 0); + RCC->PLL1DIVR = + ((PLL1_P - 1) << 9) | ((PLL1_N - 1) << 0); // Set PLL1_P PLL1_N + // Enable P and Q divider outputs; set PLL1_M, select HSI as source, + // !PLL1VCOSEL, PLL1RGE=0 + RCC->PLL1CFGR = + RCC_PLL1CFGR_PLL1QEN | RCC_PLL1CFGR_PLL1PEN | (PLL1_M << 8) | (1 << 0); + RCC->CR |= RCC_CR_PLL1ON; // Enable PLL1 + while ((RCC->CR & RCC_CR_PLL1RDY) == 0) spin(1); // Wait until done + RCC->CFGR1 |= (3 << 0); // Set clock source to PLL1 + while ((RCC->CFGR1 & (7 << 3)) != (3 << 3)) spin(1); // Wait until done +} diff --git a/tutorials/stm32/stm32h573i_dk/minimal/link.ld b/tutorials/stm32/stm32h573i_dk/minimal/link.ld new file mode 100644 index 00000000..feca4517 --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/minimal/link.ld @@ -0,0 +1,30 @@ +ENTRY(Reset_Handler); +MEMORY { + flash(rx) : ORIGIN = 0x08000000, LENGTH = 2048k + sram(rwx) : ORIGIN = 0x20000000, LENGTH = 640k +} +_estack = ORIGIN(sram) + LENGTH(sram); /* End of RAM. stack points here */ + +SECTIONS { + .vectors : { KEEP(*(.isr_vector)) } > flash + .text : { *(.text* .text.*) } > flash + .rodata : { *(.rodata*) } > flash + + .data : { + _sdata = .; + *(.first_data) + *(.ram) + *(.data SORT(.data.*)) + _edata = .; + } > sram AT > flash + _sidata = LOADADDR(.data); + + .bss : { + _sbss = .; + *(.bss SORT(.bss.*) COMMON) + _ebss = .; + } > sram + + . = ALIGN(8); + _end = .; +} diff --git a/tutorials/stm32/stm32h573i_dk/minimal/main.c b/tutorials/stm32/stm32h573i_dk/minimal/main.c new file mode 100644 index 00000000..d1a6bd7f --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/minimal/main.c @@ -0,0 +1,70 @@ +// Copyright (c) 2026 Cesanta Software Limited +// All rights reserved + +#include "mongoose.h" +#include "hal.h" + +#define DEBUG_UART USART1 +#define LED1 PIN('I', 9) // On-board LED pin (green) +#define LED2 PIN('I', 8) // On-board LED pin (orange) +#define LED3 PIN('F', 1) // On-board LED pin (red) + +// Redirect stdout debug output to UART +int _write(int fd, char *ptr, int len) { + if (fd == 1 || fd == 2) hal_uart_write_buf(DEBUG_UART, ptr, (size_t) len); + return len; +} + +static void blink_task(void) { + static uint64_t blink_timer = 0; + if (hal_timer_expired(&blink_timer, 500, hal_get_tick())) { + hal_gpio_toggle(LED1); + } +} + +uint64_t mg_millis(void) { + return hal_get_tick(); +} + +bool mg_random(void *buf, size_t len) { + for (size_t n = 0; n < len; n += sizeof(uint32_t)) { + uint32_t r = hal_rng_read(); + memcpy((char *) buf + n, &r, n + sizeof(r) > len ? len - n : sizeof(r)); + } + return true; +} + +static void http_ev_handler(struct mg_connection *c, int ev, void *ev_data) { + if (ev == MG_EV_HTTP_MSG) { // New HTTP request received + struct mg_http_message *hm = (struct mg_http_message *) ev_data; + if (mg_match(hm->uri, mg_str("/api/tick"), NULL)) { + mg_http_reply(c, 200, "", "{%m:%llu}\n", MG_ESC("tick"), hal_get_tick()); + } else { + mg_http_reply(c, 200, "", "Hi from Mongoose, tick %llu\n", + hal_get_tick()); + } + } +} + +int main(void) { + hal_clock_init(); + hal_rng_init(); + hal_gpio_output(LED1); + hal_gpio_output(LED2); + hal_gpio_output(LED3); + hal_uart_init(DEBUG_UART, 115200); + + hal_ethernet_init(); + + // Start a minimal web server + struct mg_mgr mgr; + mg_mgr_init(&mgr); + mg_http_listen(&mgr, "http://0.0.0.0", http_ev_handler, NULL); + + for (;;) { + mg_mgr_poll(&mgr, 0); + blink_task(); + } + + return 0; +} diff --git a/tutorials/stm32/stm32h573i_dk/minimal/mongoose_config.h b/tutorials/stm32/stm32h573i_dk/minimal/mongoose_config.h new file mode 100644 index 00000000..f5f73ea1 --- /dev/null +++ b/tutorials/stm32/stm32h573i_dk/minimal/mongoose_config.h @@ -0,0 +1,23 @@ +#pragma once + +#define MG_ARCH MG_ARCH_ARMGCC +#define MG_TLS MG_TLS_BUILTIN + +#define MG_ENABLE_TCPIP 1 +#define MG_ENABLE_CUSTOM_MILLIS 1 +#define MG_ENABLE_CUSTOM_RANDOM 1 +#define MG_ENABLE_DRIVER_STM32H 1 +#define MG_OTA MG_OTA_STM32H5 + +// Construct MAC address from the MCU unique ID. It is defined in the +// ST CMSIS header as UID_BASE +#define MGUID ((uint32_t *) 0x1FFF7A10) // Unique 96-bit chip ID +#define MG_SET_MAC_ADDRESS(mac) \ + do { \ + mac[0] = 2; \ + mac[1] = MGUID[0] & 255; \ + mac[2] = (MGUID[0] >> 10) & 255; \ + mac[3] = (MGUID[0] >> 19) & 255; \ + mac[4] = MGUID[1] & 255; \ + mac[5] = MGUID[2] & 255; \ + } while (0)