Embedded
使用 ALIENTEK正點原子 探索者 STM32F407 開發板
CPU: Cortex-M4 (ARMv7-M 架構)
FLASH: 1Mbytes
RAM: 128 KB
市面上常見用來開發 Cortex-M 的 IDE 整理在下表
IDE 名稱 | Pros | Cons |
---|---|---|
Keil µVision | 適合新手,不需要太多環境的設置就能開始寫程式,算是最簡單能夠上手的 IDE | 此為授權軟體,免費版限制程式大小最大為 32 KB,超過則需要付費。 另外不知道為何,我用 STM32CubeMX 產生的程式碼跟我設定的有誤,在 STM32CubeIDE 就沒這問題 |
STM32CubeIDE | 專門用來寫 STM32 的 IDE,各種功能都整合得很好,因為是基於 Eclipse,所以有用過 Eclipse 大概會覺得很親切 | 沒什麼缺點,硬要說的話就是設定比 µVision 多一點,autocomplete 不像 µVision 能夠自動觸發 |
GNU MCU Eclipse | 完全開源的 IDE | 多年前用過覺得設定相當複雜,不但要了解檔案樹,還要懂 openocd、linker 等,看了一個月可能都還不會用 |
使用 Keil µVision 如果超過 32KB,則會出現以下錯誤
error: L6050U: The code size of this image (66612 bytes) exceeds the maximum allowed for this version of the linker.
先開啟一個 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 可以讓 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 確認一切都能正常運作
int main(void)
{
...
while (1) {
HAL_GPIO_TogglePin(GPIOF, GPIO_PIN_9);
HAL_Delay(250);
}
}
這個暫存器可以控制 slew rate,也就是電壓從 low 到 high 或是從 high 到 low 所需要的時間,STM32F407 提供四種速度
參考 http://fastbitlab.com/gpio-output-speed-register-applicability/
From STM32F4-Technical-Training
參考
http://wiki.csie.ncku.edu.tw/embedded/GPIO#stm32f4xx-gpio特性
先來試試最簡單的外部中斷,剛好板子上有按鈕,就直接拿來用,硬體電路圖如下
使用 KEY2,這個按鈕最後連接到 PE2 腳位,沒有消除彈跳的硬體,下面三個按鈕都連到接地,所以之後要在 STM32 晶片裡設定 PULL-UP。
開啟外部中斷有幾個步驟
HAL_GPIO_EXTI_Callback
Prototype 為
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
Q. STM32F4 明明不同的 EXTI line 都有對應的外部中斷函數,為什麼不同 line 的 callback 都是使用
HAL_GPIO_EXTI_Callback
?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 的外部中斷
簡單跑一下試試看,順便檢查是否有按鍵彈跳會重複觸發外部中斷
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
參考
http://wiki.csie.ncku.edu.tw/embedded/PWM
試試看 STM32F4 的 PWM 功能,嘗試讓板子上的 LED 變成呼吸燈。
PWM 功能有兩個重要的暫存器,ARR (autoreload register) 和 CRR,Tim14 只有一個通道所以只有一個 CCR 暫存器叫 CCR1
使用以下設定
Counter Settings:
PWM Generation Channel 1:
因為開發版上的 LED 是低電平亮,所以 Polarity 設定 Low,讓 CCR1 數值 (duty cycle) 越大時 LED 越亮,反之越暗。
以下盡量只列出使用者程式碼的部分
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;
}
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,用以下的程式碼設定。
heth.Init.PhyAddress = LAN8720_PHY_ADDRESS;
我使用開源軟體叫做 SOEM,此專案提供輕量級的 EtherCAT 函式庫,由於官方並沒有支援 stm32 架構,因此必須動手修改程式碼,由於 SOEM 軟體架構做了很明確的分層,所要修改的程式碼並不多,要修改檔案的目錄如下
gettimeofday
和 osal_usleep
,使用 timer 就能很簡單的做出 osal_usleep
,而 gettimeofday
大概需要 RTC,但我是寫死一個起始數值,從開機後累加時間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 的情況)
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, ®);
+ } 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;
}
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
以此推類…
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。
$ 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 的輸出改變
$ 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
我用的是以下這片觸控螢幕
LCD 驅動晶片 | 電容觸控驅動晶片 | 解析度 |
---|---|---|
SSD1963 | FT5206 | 800*480 |
實際使用
可嘗試使用以下設定:
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