###### tags: `Embedded` # NUCLEO-H755ZI-Q 開發紀錄 [TOC] CPU: STM32H755ZI (Cortex(R) M7+M4) Flash: 2MBytes RAM: 1MB With L1 cache ![](https://i.imgur.com/dumYKfE.jpg) ## Architecture <!-- ![](https://i.imgur.com/e34dOLJ.png) --> ![](https://i.imgur.com/LQeYXSY.png) ### Performance Implication STM32H755 的架構十分複雜,我只針對我的應用紀錄可能的效能問題,詳細可參考 *AN4891* 和 *RM0399*。 當 M7 核心要存取位在 D2 Domain 的 SRAM1 時,他必須經過 **D1-to-D2 AHB bus** (上圖藍底標註位置),D1-to-D2 bus 是 D2 AHB bus 的其中一個 bus master,當 M7 核心和 M4 核心同時要存取 SRAM1 時,AHB bus 必須做出仲裁,仲裁演算法是使用 round robin,所以有可能 M7 先存取 SRAM1,也有可能 M4 先存取。 以下為手冊的描述 > #### AHB bus matrices in D2 and D3 domains > The AHB bus matrices in D2 and D3 domains ensure and arbitrate concurrent accesses from multiple masters to multiple slaves. This allows efficient simultaneous operation of high-speed peripherals. > The arbitration uses a **round-robin algorithm**. > > #### Cortex®-M4 D-bus >The Cortex®-M4 CPU uses the 32-bit D-bus, **via AHB bus matrix**, for literal load and debug access to memories containing code or data and mapped on addresses below 0x20000000: the internal SRAM1, SRAM2, SRAM3, and the internal Flash memory. > > [name=RM0399] 因此當 M4 在存取同為 D2 Domain 的 SRAM1 時,如果沒有 M7 同時存取 SRAM1,那此時速度會是最快的。當 M7 同時也要存取 SRAM1 時,M4 就會感受到存取速度變慢,因為有一部分的存取時間被 M7 (更準確說是 D1-to-D2 AHB bus) 搶佔了! ## MPU 參考 *AN4838* 因為 Cortex-M7 有 cache,因此設定適當的 MPU 參數十分重要,尤其是當使用: 1. FMC 2. Inter Core Communication 此時須確保兩個核心做記憶體區間是不被快取的 ## Bootloader 參考 *AN2606* ## MDMA 嘗試用 MDMA 寫入用 FMC 驅動的 LCD-TFT,~~但目前並不成功~~,已成功,關鍵是使用 repeated block transfer - 確認執行 `HAL_MDMA_Start_IT` 後 `HAL_MDMA_IRQHandler` 有被觸發 - 確認使用者註冊的 callback 函式 `MDMA_XferCpltCallback` 有被呼叫 ~~- 預期螢幕上會顯示黑色區塊,但未成功 <---- 現在在這裡 :cry:~~ [time=Mon, Sep 6, 2021 9:59 PM] - 使用 **repeated block transfer**,將 RAM_D1 的資料傳輸到 FMC,關鍵是每一個 block 不可超過 64KB (65536 byte),可參考我在 ST Community 詢問的 [MDMA 問題](https://community.st.com/s/question/0D53W000023XrPTSA0/problem-using-memtomem-dma-from-sram-to-fmc-on-stm32h755) ```c hmdma_mdma_channel40_sw_0.Init.Request = MDMA_REQUEST_SW; ``` ```c lcd.c extern MDMA_HandleTypeDef hmdma_mdma_channel40_sw_0; static void MDMA_XferCpltCallback(MDMA_HandleTypeDef *handle) { printf("hello %d\n", handle->State); } void StartlcdTask_MDMA(void *argument) { static uint16_t color_p[100 - 50 + 1][200 - 100 + 1] = {0}; lcd_init(); lcd_fill_screen(0x4243); HAL_MDMA_RegisterCallback(&hmdma_mdma_channel40_sw_0, HAL_MDMA_XFER_CPLT_CB_ID, MDMA_XferCpltCallback); lcd_prepare_to_write(100, 50, 200, 100); HAL_MDMA_Start_IT(&hmdma_mdma_channel40_sw_0, color_p, TFT_RAM, (200 - 100 + 1) * 2, (100 - 50 + 1)); while (1) { HAL_Delay(10); } } ``` 依據 STM32Cube Expansion Package for STM32H7 Series MDMA (AN5001) >### MDMA with external SDRAM >In order to move the image on the LCD, it is displayed and managed by MDMA as a regular memory-to-memory word copy. The start address of the SDRAM is configured in the LCD-TFT display controller and hardware decoded with the flexible memory controller (FMC). Each line is composed of pixels. Each pixel is 4 bytes in this case. Lines are not contiguous in the memory area. For this reason, a line is a block and there is an offset to go to the next line. A 320 × 240 regular image represents 240 blocks, each block being composed of 320 words. The first pixel of the screen (top-left corner) corresponds to the start address (the lowest address value) of the external memory. The screen has a resolution of 640 × 480 allowing the image to be moved inside the entire screen. ## TIM ### N-Pulse Generation 如何產生 N 個 pulse?答案是使用 Timer 的 One Pulse Mode 搭配 Repetition Counter (RCR) Repetition Counter 的值 + 1 就等於產生的 Pulse 數 首先呼叫以下函式啟用 One Pulse Mode (以 TIM15 為例) ```c HAL_TIM_OnePulse_Start(&htim15, TIM_CHANNEL_1); ``` 事實上 One Pulse Mode 多半為硬體觸發,但我想寫在軟體裡,因此使用以下函式觸發一次 One Pulse Mode,等到 repetition counter underflow 時會自動停止 Timer。 ```c __HAL_TIM_ENABLE(&htim15); ``` :::warning ~~`HAL_TIM_OnePulse_Start` 只適用於 Channel 1 和 Channel 2,但其他 Channel 也可使用 One Pulse Mode,這時候需用 `HAL_TIM_PWM_Start`。~~ ``` HAL_TIM_PWM_Start(&htim8, TIM_CHANNEL_3); ``` 使用 RCR 控制輸出 N pulses 時,建議直接操作 TIM 暫存器而不是使用 HAL 函式庫,因為可以避免不必要的 Bug 產生 可參考 AN4776 > [time=Sat, Feb 17, 2024 2:15 AM] ::: #### 其他設定 ![](https://i.imgur.com/yVpAQto.png) ![](https://i.imgur.com/qkXiikJ.png) - ==PWM mode 2==: TIMx_CNT < TIMx_CCR1 時通道 1 為 inactive,否則為 active - CH Polarity High: active 定義為高電平 - Pulse: Duty Cycle 下圖為範例,大方波週期為 2 Sec,小方波週期為 0.9 sec,當大方波為 rising edge 時觸發小方波 兩個方波 duty cycle 都為 50% ![](https://i.imgur.com/9Ft60pM.jpg) ### ==Trigger Another Timer== 如果要用的 TIM 沒有提供 RCR Register (如 TIM2~5),如何產生 N 個 Pulse 呢?答案是使用拿有 RCR Register 的 TIM 來觸發 TIM2~5。 下圖為 TIMx internal trigger connection,Slave TIM 為我們想觸發的 TIM,而 ITRx 為觸發源的 TIM,這張表格列出了哪些 TIM 可以串接再一起 ![](https://i.imgur.com/Ez9SgJQ.png) 由兩個重要的暫存器控制 - TS[4:0]: Trigger selection - SMS[3:0]: Slave mode selection - 0110: Trigger Mode - The counter starts at a ==rising edge== of the trigger TRGI (but it is not reset). Only the start of the counter is controlled ### Motor Control https://www.aliexpress.com/item/1005002927091186.html ### Reference - auto-reload preload: https://www.cnblogs.com/birdBull/p/15469791.html - https://www.digikey.tw/en/maker/projects/getting-started-with-stm32-timers-and-timer-interrupts/d08e6493cefa486fb1e79c43c0b08cc6 - https://www.youtube.com/watch?v=QMAgD9SS5_E&ab_channel=STMicroelectronics ## I2C https://www.ti.com/lit/an/slva704/slva704.pdf ## LVGL 中文字體 ![](https://i.imgur.com/xljFbs5.jpg) 原本 lvgl 預設支援的中文字體小於一千個 (中日韓文共一千字),遇到出現頻率較少的字就無法顯示,例如 "疫苗" 兩個字在 lvgl 就無法顯示出來,而且字體大小只有 8, 16 兩種選擇,加上字體本身也不好看,所以必須要自訂字體。 lvgl 的官網就提供工具可以產生所需要的 bitmap https://lvgl.io/tools/fontconverter 選擇你要的字體,我選擇 Noto Sans TC,==Name== 設定為 notosans_24,大小為 24,Bpp 設定 2bit-per-pixel,最後一欄 ==Symbols== 則是最重要的,要填入支援的字 我使用 github 上有人分享的常用中文字,總共 3760 個字,應付一般應用已經綽綽有餘了。 https://github.com/kaienfr/Font/blob/master/learnfiles/chinese%E7%AE%80%E7%B9%81%E5%B8%B8%E7%94%A8%E5%AD%97%E8%A1%A8.txt 所有可印出的 ascii 字元: https://gist.github.com/alexlauerman/8569945 而產生的 .c 檔怎麼使用呢,如果使用 STM32CudeIDE 可以丟入 Core/Src 中,並修改 lv_conf.h 的內容: ```c /*Optionally declare custom fonts here. *You can use these fonts as default font too and they will be available *globally. E.g. #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(my_font_1) *LV_FONT_DECLARE(my_font_2)*/ #define LV_FONT_CUSTOM_DECLARE LV_FONT_DECLARE(notosans_24); /*Always set a default font*/ #define LV_FONT_DEFAULT &notosans_24 ``` 重新編譯就能使用了 :::danger :warning: 中文字體庫會佔 flash 不少空間,在我的板子上就用掉了大約 700KBytes。 ::: 這時候可能有些 awesome font 定義的 icon 不能使用 可以用這個 ttf https://github.com/lvgl/lvgl/files/6082668/Font.Awesome.5.Free.for.LVGL.zip ## LVGL PC Simulator on **Windows** ### :arrow_right: **請直接參考這篇: [lvgl 使用記錄](https://hackmd.io/@st9540808/ByxcMmx_s)** 用了一下 lvgl **強力推薦**的 code block 開發環境真的覺得有夠難用的,不但在設定編輯器字體直接給我當掉,而且還沒辦法看到 console 輸出,一氣之下決定改用 Eclipse。 首先有幾個步驟 - 先到 [lv_sim_eclipse_sdl](https://github.com/lvgl/lv_sim_eclipse_sdl) 把專案 clone 下來 - 安裝 Eclipse,我是選 Eclipse IDE 2021‑06,安裝時選 C/C++ development - 安裝 mingw,我選擇 [MSYS2](**https://**) - 原本 mingw installer 到 2013 就沒更新了,而且只支援 gcc 6,MSYS2 支援到 gcc 10 - 用 MSYS2 要安裝額外的套件也很方便,簡單輸入個命令就可以了 - 用 `pacman -S <package name>` 可查看此套件所有支援的硬體架構 :heavy_check_mark: 很方便 - 可參考以下安裝方法 ```shell pacman -S mingw-w64-x86_64-gcc pacman -S mingw-w64-x86_64-libpng pacman -S mingw-w64-x86_64-SDL2 ``` - 安裝 MSYS2 把路徑加到 PATH 環境變數,在我的機器上是以下路徑 ``` C:\msys64\mingw64\bin ``` - 開啟 Eclipse,把剛剛 clone 下來的專案匯入到 Eclipse 中 - Project ➔ Properties ➔ C/C++ Build ➔ Tool Chain Editor 中的 Current toolchain 選擇 MingGW GCC - 按下 :hammer: 按鈕組建專案,這時候應該會遇到 lv_drivers/display/fbdev.c 編譯錯誤,簡單把它從專案排除 (右鍵 Resource Configurations ➔ Exclude from Build) 就可以了 :::info 可略過這步 - 編譯完成後按下 Run 箭頭符號,這時候應該會跟你說找不到 lv_sim_eclipse_sdl,因為本專案是給 Linux 使用的,在 Windows 會變成 lv_sim_eclipse_sdl.exe。 - 首先 refresh 這個專案應該就能看到 lv_sim_eclipse_sdl.exe - 修改 pc_simulator.launch 第五行 ```xml=5 <stringAttribute key="org.eclipse.cdt.launch.PROGRAM_NAME" value="Debug/lv_sim_eclipse_sdl.exe"/> ``` 參考: https://github.com/lvgl/lv_sim_eclipse_sdl/issues/97 ::: :warning: Eclipse 內建的 console 輸出好像會卡在緩衝區,直到程式結束才印出來,因為我需要看即時的 console 的輸出,所以我都是在**資料夾直接點擊執行**。 執行結果 ![](https://i.imgur.com/zLEk8Bg.png) :warning: 另外如果遇到中文沒辦法正常顯示,需要把 Encoding 改成 UTF-8 ## TIM :::info 先看 - [General-purpose timer cookbook for STM32 microcontrollers]( https://www.st.com/resource/en/application_note/dm00236305-generalpurpose-timer-cookbook-for-stm32-microcontrollers-stmicroelectronics.pdf) - [STM32 cross-series timer overview](https://www.st.com/resource/en/application_note/an4013-stm32-crossseries-timer-overview-stmicroelectronics.pdf) ::: 好難用 參考: - https://micromouseonline.com/2016/02/03/tim3-arr-regular-interrupts-stm32f4/ - https://www.cnblogs.com/milton/p/15028413.html ### Encoder Mode ![](https://i.imgur.com/8dmFf7m.jpg) 我需要分辨 A 相和 B 相是誰領先 90 度,就必須用 Timer Encoder Mode 解決。 參考: - http://elastic-notes.blogspot.com/p/cubemx-stm32-encoder-interface.html - https://deepbluembedded.com/stm32-timer-encoder-mode-stm32-rotary-encoder-interfacing/ - https://blog.csdn.net/qq_17525633/article/details/116719309 > [time=Sun, Mar 6, 2022 5:29 PM] ## Inter Core Communication 如何在兩個核心之間 (M7 & M4) 溝通呢 (AKA 異核通訊)?最簡單的方式是使用 Shared Memory 模型,我們可直接拿 CMSIS RTOSv2 提供的 Message Queue 來用。 ### Native Message Queue - 優點:使用簡單,能快速上手 - 缺點:因為是使用輪詢檢查 Queue 中是否有資料,會造成 CPU 資源浪費 :warning: 要先設定適當的 MPU 參數,否則可能會拿到非預期的資料 ![](https://i.imgur.com/no9waIa.png) 將 Message Queue 的緩衝區位址定義在 RAM_D3 中 (D3 Domain),這必須要修改 linker script (.ld 檔)。 來看看怎麼修改 ld 檔囉,首先開啟 CM7 和 CM4 的 STM32H755ZITX_FLASH.ld,在檔案最下面加入這幾行 ```diff /DISCARD/ : { libc.a ( * ) libm.a ( * ) libgcc.a ( * ) } + .RAM_D3 : + { + . = ALIGN(4); + KEEP(*(.RAM_D3)) + KEEP(*(.RAM_D3*)) + } >RAM_D3 ``` M4 預設沒有定義 RAM_D3,因此要額外定義 ```diff MEMORY { FLASH (rx) : ORIGIN = 0x08100000, LENGTH = 1024K RAM (xrw) : ORIGIN = 0x10000000, LENGTH = 288K + RAM_D3 (xrw) : ORIGIN = 0x38000000, LENGTH = 64K } ``` Common\msgqueue.h ```c #ifndef INC_MSGQUEUE_H_ #define INC_MSGQUEUE_H_ extern uint32_t cb_mem[40]; extern uint32_t mq_mem[2]; extern osMessageQueueAttr_t queue_attr; #endif /* INC_MSGQUEUE_H_ */ ``` Common\msgqueue.c ```c #include "cmsis_os2.h" #include "FreeRTOS.h" #include "queue.h" #include "msgqueue.h" /* put buffer inside D3 Domain (SRAM4) */ uint32_t cb_mem[40] __attribute__((section(".RAM_D3.cb"))); uint32_t mq_mem[80] __attribute__((section(".RAM_D3.mq"))); osMessageQueueAttr_t queue_attr = { .name = "led_queue", .attr_bits = 0, .cb_mem = cb_mem, .cb_size = sizeof(cb_mem), .mq_mem = mq_mem, .mq_size = sizeof(mq_mem), }; ``` #### Application Code 以下示範如何在兩個核心之間交換資料,CM7負責傳資料而CM4負責接收,使用的是 cmsis os (FreeRTOS) 的 Message Queue,傳送資料的大小及格式需自行定義,在這裡為 `hmi_write_rq_t` 的結構體。 CM7 ```c #include "cmsis_os2.h" #include "msgqueue.h" osMessageQueueId_t msgq_hmi_write_rq; hmi_write_rq_t write_rq; void Startm7Task(void *argument) { write_rq.dm_start = 38; write_rq.length = 2; msgq_hmi_write_rq = osMessageQueueNew(10, sizeof(hmi_write_rq_t), &queue_attr); while (1) { write_rq.dm_start++; osMessageQueuePut(msgq_hmi_write_rq, &write_rq, 0, osWaitForever); osDelay(50); } } ``` CM4 ```c osMessageQueueId_t msgq_hmi_write_rq; hmi_write_rq_t write_rq; void Startm4Task(void *argument) { static_assert(sizeof(hmi_write_rq_t) == 8, "sizeof(hmi_write_rq_t) != 8"); osStatus_t ret; msgq_hmi_write_rq = osMessageQueueNew(10, sizeof(hmi_write_rq_t), &queue_attr); while (1) { ret = osMessageQueueGet(msgq_hmi_write_rq, &write_rq, NULL, 0); if (ret == osOK) { debug_print("[%s] dm_start: %u, length: %u", __func__, write_rq.dm_start, write_rq.length); } osDelay(1); } } ``` 比較好的做法是當沒有資料時處理器能進入低功耗模式,當有資料時觸發中斷從啟動處理器並拿資料。 實作上可使用 ==Hardware Semaphore== (HSEM) ### HSEM 此為比較進階的功能,使用方法待補... ### Linker Scripts References - https://stackoverflow.com/questions/38239722/how-to-place-a-symbol-at-certain-address-via-the-linker-script - http://www.math.utah.edu/docs/info/ld_3.html#SEC16 ## EEPROM 使用別人已寫好的程式碼 https://github.com/nimaltd/ee24 ### Reference - [AT24C04](https://ww1.microchip.com/downloads/en/DeviceDoc/doc0180.pdf) - [AT24C256](https://ww1.microchip.com/downloads/en/DeviceDoc/AT24C128C-AT24C256C-Data-Sheet-DS20006270B.pdf) - tutorial: https://www.youtube.com/watch?v=-tV2pPXZ4VM&ab_channel=ControllersTech - 範例程式 https://controllerstech.com/eeprom-and-stm32/ - https://www.cnblogs.com/firege/p/9372029.html ## EtherCat Master 使用 SOEM 開源軟體實作 > [time=Fri, Dec 17, 2021 8:01 PM] 重新啟動 :1234: 檢查 ethernet link 是否上線 ```c // Check Link Status do { osDelay(50); HAL_ETH_ReadPHYRegister(&heth, 0, 1, &val); } while (!(val & (1 << 2))); ``` ## AWS Iot Core 來玩玩 AWS 雲端囉,首先要先能傳資料到 AWS IoT Core,才能進行接下來的工作 連線到 Iot Core,首先有幾部份要確認 - 在 AWS 上 - 確認 certificate 啟用 - 確認有適當的 policy - 在本機或板子上 - 把 private key, certifcate 寫入板子 - 把 endpoint 寫入板子 - 確認網路連線 https://docs.aws.amazon.com/freertos/latest/userguide/freertos-prereqs.html ``` StartDefaultTask() -> DEMO_RUNNER_RunDemos() runDemoTask() -> ``` <!-- ```javascript= const collection = "塔塔加管理中心" var AWS = require('aws-sdk'); const dynamo = new AWS.DynamoDB.DocumentClient(); exports.handler = async (event) => { // TODO implement var now = new Date().toLocaleString('zh-TW', { year:'numeric', month:'2-digit', day:'2-digit', hour:'2-digit', minute:'2-digit', second:'2-digit', hour12: false }); const params = { TableName: "ESUN_Tataka_Traffic", Item: { "site": collection, "timestamp": now, } }; dynamo.put(params, function(err, data) { if (err) { console.error("Unable to add device. Error JSON:", JSON.stringify(err, null, 2)); return {"message": "error"} } else { console.log(data) console.log("Data saved:", JSON.stringify(params, null, 2)); return {"message": "Item created in DB"} } }); }; ``` - aws demo clock rate? tim6/tim7 tim2~5 APB1 ```c if (determined) { big_iteration++; small_iteration++; } else { big_state = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_6); small_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_15); if (big_state == GPIO_PIN_RESET && big_iteration == 0) { // start counting HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); big_iteration = 1; } if (small_state == GPIO_PIN_RESET && small_iteration == 0) { // start counting HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin); small_iteration = 1; } // 大型車 if (big_state == GPIO_PIN_RESET && small_state == GPIO_PIN_RESET) { large_car++; determined = true; } // 中型車 if (big_state == GPIO_PIN_RESET && small_state == GPIO_PIN_SET && big_iteration > 5) { medium_car++; determined = true; } // 機車 if (big_iteration == 0 && small_state == GPIO_PIN_SET) { motorbike++; determined = true; } } // Check if we need to reset if (big_iteration > 60 || small_iteration > 30) { big_iteration = 0; small_iteration = 0; IotLogInfo("{\"機車\": %d, \"中小型車\": %d, \"大型車\": %d}", motorbike, medium_car, large_car); determined = false; } ``` ```c // full iteration: 1.5sec, small iteration: 0.75sec while (1) { if (small_iteration) { small_iteration++; } else { small_state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_15); if (small_state == GPIO_PIN_RESET) { small_iteration = 1; HAL_GPIO_TogglePin(LD3_GPIO_Port, LD3_Pin); } } if (big_iteration) { big_iteration++; } else { big_state = HAL_GPIO_ReadPin(GPIOC, GPIO_PIN_6); if (big_state == GPIO_PIN_RESET) { // start counting HAL_GPIO_TogglePin(LD2_GPIO_Port, LD2_Pin); big_iteration = 1; } } if (small_iteration > 30) { small_iteration = 0; } vTaskDelay(pdMS_TO_TICKS(25UL)); } ``` ```c while (1) { if (xSemaphoreTake(CountingSem, 50)) { if (LARGE_IS_ON) { vTaskDelay(pdMS_TO_TICKS(50UL)); if (LARGE_IS_ON) { xTaskCreate(buttom_large_triggered, "LARGE BOTTON", 1500, NULL, 3, &th[th_idx]); th_idx = (th_idx + 1) % 3; } else xSemaphoreGive(CountingSem); } else xSemaphoreGive(CountingSem); } if (iter++ > 1) { iter = 0; IotLogInfo("Semaphore: %d", uxSemaphoreGetCount(CountingSem)); } vTaskDelay(pdMS_TO_TICKS(50UL)); } ``` --> ## STM32 Modbus 使用 https://github.com/alejoseb/Modbus-STM32-HAL-FreeRTOS 這個函式庫,此函式庫支援 STM32,且能用 RS485 作為底層通訊。STM32H7 的 UART 已提供 hardware RS485 DE 控制腳位,所以不需要自行用 GPIO 控制讀或寫,啟用方式如下圖。 ![](https://i.imgur.com/ug9KBiJ.png) 接著根據驅動器參數設定 baud rate, parity bit, 等等,就能使用函式庫發送 modbus 封包,modbus 初始化方式如下: ```c modbusHandler_t ModbusH; uint16_t ModbusDATA[8]; ModbusH.uModbusType = MB_MASTER; ModbusH.port = &huart4; ModbusH.u8id = 0; ModbusH.u16timeOut = 1000; ModbusH.EN_Port = NULL; ModbusH.u16regs = ModbusDATA; ModbusH.u16regsize = sizeof(ModbusDATA) / sizeof(ModbusDATA[0]); ModbusH.xTypeHW = USART_HW; ModbusInit(&ModbusH); ModbusStart(&ModbusH); ``` - `EN_Port`: 因為使用 hardware RS485 DE,所以此設定為 `NULL` - `u16regs`: 使用要自行分配一段記憶體區間,當要發送或接收,都會把資料寫進這裡 ### 讀取一個暫存器 來試試看用 modbus 讀暫存器囉 ```c modbus_t telegram; telegram.u8id = 1; telegram.u8fct = 3; telegram.u16RegAdd = 0x0; telegram.u16CoilsNo = 1; telegram.u16reg = ModbusDATA; ModbusQuery(&ModbusH, telegram); u32NotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (u32NotificationValue != ERR_OK_QUERY) { HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET); } ``` - `u8id`: slave number - `u8fct`: function code, 3 代表讀 - `u16RegAdd`: register number - `u16CoilsNo`: number of register to read or write ### 寫入暫存器 來寫入暫存器囉,試試看寫入編號為 5 的暫存器,寫入的值為 45。 ```c modbus_t telegram; telegram.u8id = 1; telegram.u8fct = 16; telegram.u16RegAdd = 0x5; telegram.u16CoilsNo = 1; telegram.u16reg = ModbusDATA; ModbusDATA[0] = 45; ModbusQuery(&ModbusH, telegram); u32NotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY); if (u32NotificationValue != ERR_OK_QUERY) { HAL_GPIO_WritePin(LD2_GPIO_Port, LD2_Pin, GPIO_PIN_SET); } ``` ### References - https://www.ni.com/zh-tw/innovations/white-papers/14/the-modbus-protocol-in-depth.html - https://github.com/alejoseb/Modbus-STM32-HAL-FreeRTOS - https://electronics.stackexchange.com/questions/384306/stm32f4-how-are-preemption-priorities-and-sub-priorities-used - https://vkinngworld.blogspot.com/2016/04/plcmodbus.html ## FreeRTOS > Low priority numbers denote low priority tasks. The idle task has priority zero (tskIDLE_PRIORITY). Timer 的 callback 能不能設定優先權?根據 FreeRTOS 上關於 [Software Timers](https://www.freertos.org/Configuring-a-real-time-RTOS-application-to-use-software-timers.html) 的敘述,`configTIMER_TASK_PRIORITY` 可設定優先值 ## Cortex-M7 Priority 紀錄一下心得 ![](https://i.imgur.com/2V6FRXj.png) :::warning 上圖的 Priority 為 ST 定義的,實際 Exception Number 是從 1 開始,有 15 個是給 Built in Exceptions (0x0000_0004 到 0x0000_003c+4,共 60 個 byte) ::: - Exception Number: Cortex-M 為每個 exception 都定義了一個數值,這個數值無法被修改,==Exception Number 結合 Priority/Sub-Priority 的數值共同決定這個 exception 的優先權==,Priority 較高的 exception 會先執行,如果兩個 exception 的 Priority 數值相等,則 exception 數值較低的會先執行 - Priority/Sub-Priority: 每個 exception 都有一個大小為 4bit 的 Priority/Sub-Priority 值,預設是 Priority 佔 4bit 而 Priority 佔 0bit,但這是可以調整的 參考: https://interrupt.memfault.com/blog/arm-cortex-m-exceptions-and-nvic ## WIFI/BLE 使用介面為 AT Command https://docs.espressif.com/projects/esp-at/en/latest/AT_Command_Set/Basic_AT_Commands.html#cmd-uartc ## 中斷延遲 開啟 FPU 有可能增加中斷的延遲時間,因為系統需要把 FPU 暫存器的值存到 stack 上,因此我設計一項實驗看我從觸發中斷,到中斷實際執行所花的時間 直接看結論,設定 M4 Clock 為 240MHz,在我的板子上 (NUCLEO-H755ZI-Q),測出的中斷延遲如下表 | Floating-point ABI | Compiler Optimization | latency (ns) | | -------- | -------- | -------- | | -mfloat-abi=hard | -O0 | ~300ns | | -mfloat-abi=softfp | -O0 | ~300ns | | -mfloat-abi=hard | -O1 | ~160ns | `-O1` 的延遲約為 39 個 Clock Cycle 實驗方式如下,我用 LD1 的 GPIO 當作訊號源打到 EXTI0_IRQn,當 LD1 訊號為正緣或負緣則會觸發中斷 (EXTI0_IRQHandler),因此看 LD1 和 INTR_CH2 的時間差即可知道中斷延遲 ```c void delay_us(uint16_t steps) { // 1/240 microseconds per step __HAL_TIM_SET_COUNTER(&htim6,0); // set the counter value a 0 while (__HAL_TIM_GET_COUNTER(&htim6) < steps); // wait for the counter to reach the us input in the parameter } while (1) { HAL_GPIO_TogglePin(LD1_GPIO_Port, LD1_Pin); delay_us(500); } ... void EXTI0_IRQHandler(void) { /* USER CODE BEGIN EXTI0_IRQn 0 */ // HAL_GPIO_WritePin(LD1_GPIO_Port, LD1_Pin, GPIO_PIN_RESET); HAL_GPIO_TogglePin(INTR_CH2_GPIO_Port, INTR_CH2_Pin); /* USER CODE END EXTI0_IRQn 0 */ HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_0); /* USER CODE BEGIN EXTI0_IRQn 1 */ /* USER CODE END EXTI0_IRQn 1 */ } ``` ### `-O0` 的實驗結果 ![](https://i.imgur.com/mwXCYwA.jpg) ### `-O1` 的實驗結果 ![](https://i.imgur.com/X495f2v.jpg)