###### 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 ¬osans_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)