<style> table { word-break: normal !important; } </style> ###### tags: `Embedded` # STM32F4 開發紀錄 使用 ALIENTEK正點原子 探索者 STM32F407 開發板 ![](https://i.imgur.com/L6rqbjc.jpg) CPU: Cortex-M4 (ARMv7-M 架構) FLASH: 1Mbytes RAM: 128 KB ## 開發環境 市面上常見用來開發 Cortex-M 的 IDE 整理在下表 | IDE 名稱 | Pros | Cons | | -------- | -------- | -------- | | Keil µVision | 適合新手,不需要太多環境的設置就能開始寫程式,算是最簡單能夠上手的 IDE | 此為授權軟體,免費版限制程式大小最大為 32 KB,超過則需要付費。 <br> 另外不知道為何,我用 STM32CubeMX 產生的程式碼跟我設定的有誤,在 STM32CubeIDE 就沒這問題 | | STM32CubeIDE | 專門用來寫 STM32 的 IDE,各種功能都整合得很好,因為是基於 Eclipse,所以有用過 Eclipse 大概會覺得很親切 | 沒什麼缺點,硬要說的話就是設定比 µVision 多一點,autocomplete 不像 µVision 能夠自動觸發 | | GNU MCU Eclipse| 完全開源的 IDE | 多年前用過覺得設定相當複雜,不但要了解檔案樹,還要懂 openocd、linker 等,看了一個月可能都還不會用 | :::warning 使用 Keil µVision 如果超過 32KB,則會出現以下錯誤 error: L6050U: The code size of this image (66612 bytes) exceeds the maximum allowed for this version of the linker. ::: ## STM32CubeIDE 環境架設 先開啟一個 Project,並試著燒錄程式到板子上。我的 Debugger 叫 fireDAP,所以使用 openocd,在 **Run Configuration ➔ 調試器 (debugger) ➔ 調試探頭** 中選擇 ST-Link (OpenOCD),並且在底下的 **Configuration Script** 中選擇 User Defined,接著我們要修改 openocd 的腳本 在 xxx.cfg 的檔案中修改 source 跟 transport 這兩行,根據你使用的 debugger 會有所不同 ``` source [find interface/cmsis-dap.cfg] transport select "swd" ``` 按一下 Run 的箭頭應該就可以燒錄了 ### 設定 Semihosting [Semihosting](http://wiki.csie.ncku.edu.tw/embedded/Lab7) 可以讓 Target 跟主機用 debugger 來做溝通,所以就不用另外用一個 UART 做 debug。具體來說,可以在板子上呼叫 printf,IDE 的 Console 就可以看到結果,或者是把除錯資料輸出到一個檔案中,方便之後做 debug。 以下這篇說明得很清楚,照做就可以了,exclude 那一欄要注意路徑,在我的 Project 裡要是設定 **`Src/syscalls.c`** https://shawnhymel.com/1840/how-to-use-semihosting-with-stm32/ 到目前為止基本的設定都做好了 > 在不需要 Debug 的時候應該要註解掉所有 printf 及相關輸出的操作,否則之後在燒錄的時候就不能正常運作,程式會卡在 printf 裡。 試試看點亮板子上的 LED 確認一切都能正常運作 ```c int main(void) { ... while (1) { HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9); HAL_Delay(250); } } ``` ### GPIO port output speed register (GPIOx_OSPEEDR) 這個暫存器可以控制 slew rate,也就是電壓從 low 到 high 或是從 high 到 low 所需要的時間,STM32F407 提供四種速度 - 00: Low speed - 01: Medium speed - 10: High speed - 11: Very high speed 參考 http://fastbitlab.com/gpio-output-speed-register-applicability/ ### GPIO Features From *STM32F4-Technical-Training* - Up to 140 multifunction bi-directional I/O ports available on 176 pin package - Almost standard I/Os are 5V tolerant - GPIO connected to AHB bus: max toggling frequency = fAHB/2 = 84 MHz ## GPIO 外部中斷 參考 http://wiki.csie.ncku.edu.tw/embedded/GPIO#stm32f4xx-gpio%E7%89%B9%E6%80%A7 先來試試最簡單的外部中斷,剛好板子上有按鈕,就直接拿來用,硬體電路圖如下 ![](https://i.imgur.com/gMluaMB.png) 使用 KEY2,這個按鈕最後連接到 PE2 腳位,沒有消除彈跳的硬體,下面三個按鈕都連到接地,所以之後要在 STM32 晶片裡設定 PULL-UP。 開啟外部中斷有幾個步驟 1. 在 STM32CudeMX 中腳位設定為 GPIO_EXTI,因為我們是用 PULL-UP,所以使用 External Interrupt Mode with Falling edge trigger detection ![](https://i.imgur.com/sYaZmJv.png) 2. (==重要!==) 到 NVIC 裡把對應的 EXIT line interrupt 的 Enabled 框框打勾,以 PE2 腳位來說就是對應 line2 ![](https://i.imgur.com/MGYpaD7.png) 3. 實作自己的 `HAL_GPIO_EXTI_Callback` Prototype 為 ```c void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) ``` > Q. STM32F4 明明不同的 EXTI line 都有對應的外部中斷函數,為什麼不同 line 的 callback 都是使用 `HAL_GPIO_EXTI_Callback`? > ```c > void EXTI0_IRQHandler(); > void EXTI1_IRQHandler(); > void EXTI2_IRQHandler(); > void EXTI3_IRQHandler(); > void EXTI4_IRQHandler(); > void EXTI9_5_IRQHandler(); > void EXTI15_10_IRQHandler(); > ``` > A. HAL 函數庫對以上 IRQ 做封裝,他們最後都會呼叫使用者定義的 `HAL_GPIO_EXTI_Callback`,使用者必須檢查 GPIO_Pin 來得知這是哪一條 line 的外部中斷 簡單跑一下試試看,順便檢查是否有按鍵彈跳會重複觸發外部中斷 ```c void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) { static int time = 0; printf("activate %d\n", time++); } ``` 輸出結果,果不其然按鍵有彈跳,當我按一下按鍵會同時印出好幾行 ``` target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000894 msp: 0x20020000, semihosting configuring PLL Info : High speed (adapter speed 8000) may be limited by adapter firmware. Info : Padding image section 0 at 0x08000188 with 8 bytes target halted due to debug-request, current mode: Thread xPSR: 0x01000000 pc: 0x08000894 msp: 0x20020000, semihosting activate 0 activate 1 activate 2 activate 3 activate 4 activate 5 activate 6 activate 7 activate 8 activate 9 activate 10 activate 11 ``` ## PWM 參考 http://wiki.csie.ncku.edu.tw/embedded/PWM 試試看 STM32F4 的 PWM 功能,嘗試讓板子上的 LED 變成呼吸燈。 PWM 功能有兩個重要的暫存器,ARR (autoreload register) 和 CRR,Tim14 只有一個通道所以只有一個 CCR 暫存器叫 CCR1 ![](https://i.imgur.com/lgO60QU.png) 使用以下設定 Counter Settings: - Prescaler: 這會設定內部時鐘的除頻數值,TIM14 對應 APB1 Timer,最大頻率是 84Mhz,這裡我設定 APB1 Timer clock 為 16 Mhz,所以 TIM14 是以 16Mhz/16 = 1Mhz 的頻率在計數 - Counter Mode: Up mode,計時器往上計數 - Counter Period: 也就是 ARR 數值,設定計時器的溢位數值,==這裡才決定了 PWM 的頻率 1Mhz / 1000 = 1khz== PWM Generation Channel 1: - PWM Mode 1 - Channel 1 is active as long as TIMx_CNT < TIMx_CCR1 else inactive,也就是計時器數值小於 CCR1 時是 active - Pulse: CCR1 數值,控制 PWM 的 duty cycle - CH Polarity: Low,代表 active 時輸出低電平 因為開發版上的 LED 是低電平亮,所以 Polarity 設定 Low,讓 CCR1 數值 (duty cycle) 越大時 LED 越亮,反之越暗。 以下盡量只列出使用者程式碼的部分 ```c TIM_HandleTypeDef htim14; int main(void) { /* USER CODE BEGIN 1 */ uint8_t dir = 1; uint16_t led0pwmval = 0; ... MX_TIM14_Init(); HAL_TIM_PWM_Start(&htim14, TIM_CHANNEL_1); led0pwmval = htim14.Instance->CCR1; while (1) { HAL_Delay(2); if (dir) led0pwmval++; else led0pwmval--; if (led0pwmval >= htim14.Instance->ARR) dir = 0; if (led0pwmval == 0) dir = 1; htim14.Instance->CCR1 = led0pwmval; } ``` ## Ethernet ### Active PHY Address > This is an identifier that tells you which of the physical PHYs were used to interface to the network. The numbers range from 0 - 31 and change, depending on whether or not you specified a specific PHY or if you let the driver select the default (which varies from card to card). PHY 外部晶片在初始化時,需要指定 PHY Address (非實體記憶體),數值介於 0 到 31 之間,因為一個 MAC 介面最多可以控制 32 個 PHY,這張板子的 PHY 設定在 0,用以下的程式碼設定。 ```c heth.Init.PhyAddress = LAN8720_PHY_ADDRESS; ``` ### 與 EtherCAT 驅動器串聯 我使用開源軟體叫做 [SOEM](https://github.com/OpenEtherCATsociety/SOEM/),此專案提供輕量級的 EtherCAT 函式庫,由於官方並沒有支援 stm32 架構,因此必須動手修改程式碼,由於 SOEM 軟體架構做了很明確的分層,所要修改的程式碼並不多,要修改檔案的目錄如下 - **osal**: 大概是 OS application layer,此目錄要利用目標硬體實作 `gettimeofday` 和 `osal_usleep`,使用 timer 就能很簡單的做出 `osal_usleep`,而 `gettimeofday` 大概需要 RTC,但我是寫死一個起始數值,從開機後累加時間 - **oshw**: 這部分必須實作 Ethernet 的初始化,傳送以及接收訊框,可使用 `HAL_ETH_*` 系列函式實作。 搞定上面兩個目錄 SOEM 就能移植到 stm32 的板子上了,如果遇到問題有很大的機率是 Ethernet 初始化沒設定好。 > 我遇到的問題是使用 auto negotiation 無法直接連接,板子和驅動器之間必須接上 switch 才能正常互通,把 auto negotiation 關掉再調整一些 timing 就成功連上驅動器了。 stm32f4 移植參考的專案: https://os.mbed.com/users/EasyCAT/code/SOEM//file/543d6784d4cc/oshw/nicdrv.cpp/ nicdrv.c 會是需要修改最多的檔案,如果是使用 STM32cubeIDE 設定 ETH,ETH 初始化對應的程式碼會自動產生,所以就不用另外寫了。 (但還是要==注意 auto negotiation==,STM32cubeIDE 似乎沒有處理 disable 的情況) :::spoiler HAL_ETH_Init 修改的部分 ```diff HAL_StatusTypeDef HAL_ETH_Init(ETH_HandleTypeDef *heth) { uint32_t tmpreg1 = 0U, phyreg = 0U; uint32_t hclk = 60000000U; uint32_t tickstart = 0U; uint32_t err = ETH_SUCCESS; /* Check the ETH peripheral state */ if(heth == NULL) { return HAL_ERROR; } /* Check parameters */ assert_param(IS_ETH_AUTONEGOTIATION(heth->Init.AutoNegotiation)); assert_param(IS_ETH_RX_MODE(heth->Init.RxMode)); assert_param(IS_ETH_CHECKSUM_MODE(heth->Init.ChecksumMode)); assert_param(IS_ETH_MEDIA_INTERFACE(heth->Init.MediaInterface)); if(heth->State == HAL_ETH_STATE_RESET) { /* Allocate lock resource and initialize it */ heth->Lock = HAL_UNLOCKED; #if (USE_HAL_ETH_REGISTER_CALLBACKS == 1) ETH_InitCallbacksToDefault(heth); if(heth->MspInitCallback == NULL) { /* Init the low level hardware : GPIO, CLOCK, NVIC. */ heth->MspInitCallback = HAL_ETH_MspInit; } heth->MspInitCallback(heth); #else /* Init the low level hardware : GPIO, CLOCK, NVIC. */ HAL_ETH_MspInit(heth); #endif /* USE_HAL_ETH_REGISTER_CALLBACKS */ } /* Enable SYSCFG Clock */ __HAL_RCC_SYSCFG_CLK_ENABLE(); /* Select MII or RMII Mode*/ SYSCFG->PMC &= ~(SYSCFG_PMC_MII_RMII_SEL); SYSCFG->PMC |= (uint32_t)heth->Init.MediaInterface; /* Ethernet Software reset */ /* Set the SWR bit: resets all MAC subsystem internal registers and logic */ /* After reset all the registers holds their respective reset values */ (heth->Instance)->DMABMR |= ETH_DMABMR_SR; /* Get tick */ tickstart = HAL_GetTick(); /* Wait for software reset */ while (((heth->Instance)->DMABMR & ETH_DMABMR_SR) != (uint32_t)RESET) { /* Check for the Timeout */ if((HAL_GetTick() - tickstart ) > ETH_TIMEOUT_SWRESET) { heth->State= HAL_ETH_STATE_TIMEOUT; /* Process Unlocked */ __HAL_UNLOCK(heth); /* Note: The SWR is not performed if the ETH_RX_CLK or the ETH_TX_CLK are not available, please check your external PHY or the IO configuration */ return HAL_TIMEOUT; } } /*-------------------------------- MAC Initialization ----------------------*/ /* Get the ETHERNET MACMIIAR value */ tmpreg1 = (heth->Instance)->MACMIIAR; /* Clear CSR Clock Range CR[2:0] bits */ tmpreg1 &= ETH_MACMIIAR_CR_MASK; /* Get hclk frequency value */ hclk = HAL_RCC_GetHCLKFreq(); /* Set CR bits depending on hclk value */ if((hclk >= 20000000U)&&(hclk < 35000000U)) { /* CSR Clock Range between 20-35 MHz */ tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_Div16; } else if((hclk >= 35000000U)&&(hclk < 60000000U)) { /* CSR Clock Range between 35-60 MHz */ tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_Div26; } else if((hclk >= 60000000U)&&(hclk < 100000000U)) { /* CSR Clock Range between 60-100 MHz */ tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_Div42; } else if((hclk >= 100000000U)&&(hclk < 150000000U)) { /* CSR Clock Range between 100-150 MHz */ tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_Div62; } else /* ((hclk >= 150000000)&&(hclk <= 183000000)) */ { /* CSR Clock Range between 150-183 MHz */ tmpreg1 |= (uint32_t)ETH_MACMIIAR_CR_Div102; } /* Write to ETHERNET MAC MIIAR: Configure the ETHERNET CSR Clock Range */ (heth->Instance)->MACMIIAR = (uint32_t)tmpreg1; /*-------------------- PHY initialization and configuration ----------------*/ /* Put the PHY in reset mode */ if((HAL_ETH_WritePHYRegister(heth, PHY_BCR, PHY_RESET)) != HAL_OK) { /* In case of write timeout */ err = ETH_ERROR; /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); /* Set the ETH peripheral state to READY */ heth->State = HAL_ETH_STATE_READY; /* Return HAL_ERROR */ return HAL_ERROR; } /* Delay to assure PHY reset */ HAL_Delay(PHY_RESET_DELAY); if((heth->Init).AutoNegotiation != ETH_AUTONEGOTIATION_DISABLE) { /* Get tick */ tickstart = HAL_GetTick(); /* We wait for linked status */ do { HAL_ETH_ReadPHYRegister(heth, PHY_BSR, &phyreg); /* Check for the Timeout */ if((HAL_GetTick() - tickstart ) > ETH_TIMEOUT_LINKED_STATE) { /* In case of write timeout */ err = ETH_ERROR; /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); heth->State= HAL_ETH_STATE_READY; /* Process Unlocked */ __HAL_UNLOCK(heth); return HAL_TIMEOUT; } } while (((phyreg & PHY_LINKED_STATUS) != PHY_LINKED_STATUS)); /* Enable Auto-Negotiation */ if((HAL_ETH_WritePHYRegister(heth, PHY_BCR, PHY_AUTONEGOTIATION)) != HAL_OK) { /* In case of write timeout */ err = ETH_ERROR; /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); /* Set the ETH peripheral state to READY */ heth->State = HAL_ETH_STATE_READY; /* Return HAL_ERROR */ return HAL_ERROR; } /* Get tick */ tickstart = HAL_GetTick(); /* Wait until the auto-negotiation will be completed */ do { HAL_ETH_ReadPHYRegister(heth, PHY_BSR, &phyreg); /* Check for the Timeout */ if((HAL_GetTick() - tickstart ) > ETH_TIMEOUT_AUTONEGO_COMPLETED) { /* In case of write timeout */ err = ETH_ERROR; /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); heth->State= HAL_ETH_STATE_READY; /* Process Unlocked */ __HAL_UNLOCK(heth); return HAL_TIMEOUT; } } while (((phyreg & PHY_AUTONEGO_COMPLETE) != PHY_AUTONEGO_COMPLETE)); /* Read the result of the auto-negotiation */ if((HAL_ETH_ReadPHYRegister(heth, PHY_SR, &phyreg)) != HAL_OK) { /* In case of write timeout */ err = ETH_ERROR; /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); /* Set the ETH peripheral state to READY */ heth->State = HAL_ETH_STATE_READY; /* Return HAL_ERROR */ return HAL_ERROR; } /* Configure the MAC with the Duplex Mode fixed by the auto-negotiation process */ if((phyreg & PHY_DUPLEX_STATUS) != (uint32_t)RESET) { /* Set Ethernet duplex mode to Full-duplex following the auto-negotiation */ (heth->Init).DuplexMode = ETH_MODE_FULLDUPLEX; } else { /* Set Ethernet duplex mode to Half-duplex following the auto-negotiation */ (heth->Init).DuplexMode = ETH_MODE_HALFDUPLEX; } /* Configure the MAC with the speed fixed by the auto-negotiation process */ if((phyreg & PHY_SPEED_STATUS) == PHY_SPEED_STATUS) { /* Set Ethernet speed to 10M following the auto-negotiation */ (heth->Init).Speed = ETH_SPEED_10M; } else { /* Set Ethernet speed to 100M following the auto-negotiation */ (heth->Init).Speed = ETH_SPEED_100M; } } else /* AutoNegotiation Disable */ { /* Check parameters */ assert_param(IS_ETH_SPEED(heth->Init.Speed)); assert_param(IS_ETH_DUPLEX_MODE(heth->Init.DuplexMode)); + /* Check link is up */ + uint32_t reg; + do { + HAL_Delay(20); + HAL_ETH_ReadPHYRegister(heth, 1, &reg); + } while (!(reg & (1 << 2))); /* Set MAC Speed and Duplex Mode */ if(HAL_ETH_WritePHYRegister(heth, PHY_BCR, ((uint16_t)((heth->Init).DuplexMode >> 3U) | (uint16_t)((heth->Init).Speed >> 1U))) != HAL_OK) { /* In case of write timeout */ err = ETH_ERROR; /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); /* Set the ETH peripheral state to READY */ heth->State = HAL_ETH_STATE_READY; /* Return HAL_ERROR */ return HAL_ERROR; } /* Delay to assure PHY configuration */ HAL_Delay(PHY_CONFIG_DELAY); } /* Config MAC and DMA */ ETH_MACDMAConfig(heth, err); /* Set ETH HAL State to Ready */ heth->State= HAL_ETH_STATE_READY; /* Return function status */ return HAL_OK; } ``` ::: ## EtherCAT PDO 分為兩種,在規格書 (CiA402) 中稱作 Transmit-PDOs (TPDOs) and Receive-PDOs (RPDOs),但台達手冊稱作 TxPDO 和 RxPDO。 TxPDO 和 RxPDO 是以從站的角度來看,自從站發出的訊息是 TxPDO,接收的訊息叫 RxPDO | | Index Start from | | -------- | -------- | | TxPDO | 0x1A00 | | RxPDO | 0x1600 | TxPDO0 index: 0x1A00 TxPDO1 index: 0x1A01 以此推類... ### Mapping Layout > The mappings are held in the sub-indexes and are encoded as 32-bit unsigned integers > 參考: https://stackoverflow.com/questions/27132341/how-to-setup-pdo-mapping ``` 16-bit 8-bit 8-bit +--------+--------+--------+--------+ | index |subindex| size | +--------+--------+--------+--------+ `-> size in bits of the parameter ``` 還未修改 PDO Mapping 之前驅動器的設定如下所示,注意 SM2 和 SM3 兩列輸出,他們分別對應 RxPDO 和 TxPDO。 ```shell $ sudo ./test/linux/slaveinfo/slaveinfo enp4s0 -map SOEM (Simple Open EtherCAT Master) Slaveinfo Starting slaveinfo ec_init on enp4s0 succeeded. 1 slaves found and configured. Calculated workcounter 3 Slave:1 Name:? M:000001dd I:00006010 Output size: 48bits Input size: 48bits State: 4 Delay: 0[ns] Has DC: 1 DCParentport:0 Activeports:1.0.0.0 Configured address: 1001 Man: 000001dd ID: 00006010 Rev: 00030000 SM0 A:1000 L: 128 F:00010036 Type:1 SM1 A:10c0 L: 128 F:00010032 Type:2 SM2 A:1180 L: 6 F:00010024 Type:3 SM3 A:1480 L: 6 F:00010000 Type:4 FMMU0 Ls:00000000 Ll: 6 Lsb:0 Leb:7 Ps:1180 Psb:0 Ty:02 Act:01 FMMU1 Ls:00000006 Ll: 6 Lsb:0 Leb:7 Ps:1480 Psb:0 Ty:01 Act:01 FMMUfunc 0:1 1:2 2:3 3:0 MBX length wr: 128 rd: 128 MBX protocols : 04 CoE details: 0f FoE details: 00 EoE details: 00 SoE details: 00 Ebus current: 0[mA] only LRD/LWR:0 PDO mapping according to CoE : SM2 outputs addr b index: sub bitl data_type name [0x0000.0] 0x6040:0x00 0x10 UNSIGNED16 [0x0002.0] 0x607A:0x00 0x20 INTEGER32 SM3 inputs addr b index: sub bitl data_type name [0x0006.0] 0x6041:0x00 0x10 UNSIGNED16 [0x0008.0] 0x6064:0x00 0x20 INTEGER32 End slaveinfo, close socket End program ``` 修改 PDO Mapping 之後預期可見 SM2 和 SM3 的輸出改變 ```shell $ sudo ./test/linux/slaveinfo/slaveinfo enp4s0 -map SOEM (Simple Open EtherCAT Master) Slaveinfo Starting slaveinfo ec_init on enp4s0 succeeded. 1 slaves found and configured. Calculated workcounter 3 Slave:1 Name:? M:000001dd I:00006010 Output size: 112bits Input size: 176bits State: 4 Delay: 0[ns] Has DC: 1 DCParentport:0 Activeports:1.0.0.0 Configured address: 1001 Man: 000001dd ID: 00006010 Rev: 00030000 SM0 A:1000 L: 128 F:00010036 Type:1 SM1 A:10c0 L: 128 F:00010032 Type:2 SM2 A:1180 L: 14 F:00010024 Type:3 SM3 A:1480 L: 22 F:00010000 Type:4 FMMU0 Ls:00000000 Ll: 14 Lsb:0 Leb:7 Ps:1180 Psb:0 Ty:02 Act:01 FMMU1 Ls:0000000e Ll: 22 Lsb:0 Leb:7 Ps:1480 Psb:0 Ty:01 Act:01 FMMUfunc 0:1 1:2 2:3 3:0 MBX length wr: 128 rd: 128 MBX protocols : 04 CoE details: 0f FoE details: 00 EoE details: 00 SoE details: 00 Ebus current: 0[mA] only LRD/LWR:0 PDO mapping according to CoE : SM2 outputs addr b index: sub bitl data_type name [0x0000.0] 0x6040:0x00 0x10 UNSIGNED16 [0x0002.0] 0x607A:0x00 0x20 INTEGER32 [0x0006.0] 0x60FF:0x00 0x20 INTEGER32 [0x000A.0] 0x6071:0x00 0x10 INTEGER16 [0x000C.0] 0x60B8:0x00 0x10 UNSIGNED16 SM3 inputs addr b index: sub bitl data_type name [0x000E.0] 0x6041:0x00 0x10 UNSIGNED16 [0x0010.0] 0x6064:0x00 0x20 INTEGER32 [0x0014.0] 0x606C:0x00 0x20 INTEGER32 [0x0018.0] 0x6077:0x00 0x10 INTEGER16 [0x001A.0] 0x60B9:0x00 0x10 UNSIGNED16 [0x001C.0] 0x60BA:0x00 0x20 INTEGER32 [0x0020.0] 0x60FD:0x00 0x20 UNSIGNED32 End slaveinfo, close socket End program ``` ## TFT-LCD 我用的是以下這片觸控螢幕 | LCD 驅動晶片 | 電容觸控驅動晶片 | 解析度 | | -------- | -------- | -------- | | SSD1963 | FT5206 | 800*480 | ![](https://i.imgur.com/ZcwxJuG.jpg) 實際使用 ![](https://i.imgur.com/t0tmI66.jpg) :::danger :warning: 注意如果初始化後出現畫面閃爍,或是印出雜訊,極有可能是 FSMC 的 timing 沒有設定好,很大可能是因為設得太小導致的! 可嘗試使用以下設定: ```c Timing.AddressSetupTime = 1 * 4; Timing.AddressHoldTime = 15 * 2; Timing.DataSetupTime = 2 * 4; Timing.BusTurnAroundDuration = 15 * 2; Timing.CLKDivision = 16 * 2; Timing.DataLatency = 17 * 2; Timing.AccessMode = FSMC_ACCESS_MODE_A; ``` ::: > 參考: > https://arm-stm.blogspot.com/2016/12/ssd1963-init-collection.html > https://www.youtube.com/watch?v=dQoy4wQpcTI&ab_channel=EEbyKarl > https://support.touchgfx.com/zh-TW/docs/development/touchgfx-hal-development/touchgfx-generator