Keil MDK STM32系列
- Keil MDK STM32系列(一) 基於標准外設庫SPL的STM32F103開發
- Keil MDK STM32系列(二) 基於標准外設庫SPL的STM32F401開發
- Keil MDK STM32系列(三) 基於標准外設庫SPL的STM32F407開發
- Keil MDK STM32系列(四) 基於抽象外設庫HAL的STM32F401開發
- Keil MDK STM32系列(五) 使用STM32CubeMX創建項目基礎結構
- Keil MDK STM32系列(六) 基於HAL的ADC模數轉換
- Keil MDK STM32系列(七) 基於HAL的PWM和定時器
- Keil MDK STM32系列(八) 基於HAL的PWM和定時器輸出音頻
- Keil MDK STM32系列(九) 基於HAL和FatFs的FAT格式SD卡TF卡讀寫
概述
從前面介紹的STM32開發可以感受到, 雖然SPL對於純寄存器方式開發已經是很大的進步, SPL將大部分寄存器配置做了很好的封裝, 配置項簡單易讀, 但是外設與輸出腳的映射關系, 配置項的數量, 配置之間的關聯都使得配置難度並未降低, 在項目啟動階段依然要通過不斷查閱MCU的用戶手冊去修改方案, 各種情況下的代碼例程直接借鑒容易出錯, 需要反復嘗試, 往往在調通外設這一步就已經耗費了開發人員的大量時間.
所以ST將開發庫的重心遷移到了HAL上, 配合HAL的就是STM32CubeMX這個圖形化配置工具. 通過圖形化界面, 通過預設的邏輯輔助生成代碼模板. 這樣可以將底層寄存器和外設的復雜定義和邏輯包裝在工具界面之下, 避免開發人員進行重復的學習和試錯, 節約開發人員的時間精力. 這也是市場上其他主流MCU的技術配套發展方向.
下面介紹STM32CubeMX的安裝和使用
下載安裝STM32CubeMX
安裝后, Alt+U
打開Embedded Software Package Manager, 在里面找到STM32F4, 勾選最高的那個版本, 當前是1.26.2, 點擊下面的Install Now. 這個需要比較長時間.
對於1.26.2這個版本, 不要去嘗試From Local, 因為這個版本的zip文件有兩個, 一個1.26.0, 一個1.26.2的patch, 這兩個包都下載到本地后, 嘗試了無數次, From Local都會報錯. 最后還是點Install Now, 讓它從網絡下載安裝的.
配置項目
使用STM32CubeMX快速創建項目代碼, 主要就是以下幾步:
- MCU選擇
- System Core -> SYS 配置A13, A14 (必須)
- System Core -> RCC 配置時鍾源 (必須)
- Clock Configuration -> 配置系統時鍾 (必須)
- 配置各種外設
1. 選擇MCU型號
點擊主界面的ACCESS TO MCU SELECTOR
, 會打開型號選擇, 在左邊勾選Arm Cortex-M4, 在右邊選對應的型號, 然后點右上角的Start Project
2. PIN腳設置
上一步點擊后, 就會進入Pinout & Configuration
界面, 在左側的列表中, 默認有改動的是System Core下的NVIC, 默認勾選的是旁邊的SYS.
- System Core -> SYS, Debug 選擇 Serial Wire, 這時候會看到圖上的PA13和PA14被高亮, GPIO也會產生相應的配置. 這一步比較重要, 如果不配置, 編譯好的固件寫入MCU后, 再啟動就連不上STLink了.
- System Core -> RCC, HSE和LSE, 都選擇Crystal/Ceramic Resonator, 啟用外部的高速和低速振盪源, 這時候GPIO中, PC14, PC15, PH0, PH1 會自動產生對應的配置
- Timers -> RTC 如果要啟用外部時鍾源, 勾選 Activate Clock Source, 這樣在下一步的時鍾配置中就可以選擇內部或者外部時鍾源
- Connectivity -> USART1, Mode 選擇 Asynchronous, 其他默認, 在 System Core -> GPIO 下可以看到又增加了對應USART1的配置, PA9和PA10. 如果要選擇PB6和PB7, 需要在右側的Pinout圖上取消PA9和PA10后, 在PB6, PB7上設置, 再到左側菜單中選擇
- 看項目需要, 可以繼續選擇SPI, ADC等
3. 時鍾設置
點擊上方的導航進入 Clock Configuration
界面. 這里很直觀地展示了HSE, LSE, HSI, LSI 這些時鍾源與系統和外設的關聯.
- 如果使用默認的HSI, 時鍾就是16MHz
- 如果使用外部的HSE, 時鍾就是25MHz(看具體的板子), 可以直接連到SYSCLK, 也可以通過PLL, 在HCLK處填入84MHz后回車, 軟件會自動計算出前面部分正確的分頻和倍頻數字. SYSCLK的頻率需要配置到MDK項目配置中的Xtal
4. 配置項目名稱和路徑
Toolchain/IDE 要選擇 MDK-ARM, 版本選V5
5. 生成項目代碼
點擊右上角的GENERATE CODE
就會生成代碼, 代碼可以直接用Keil MDK打開
外設配置步驟
配置定時器
通過TIMx配置
- 勾選 Internal Clock
- 配置下方參數
- Counter Settings
- Prescaler: 0
- Counter Mode: Up
- Counter Period: 1999 這里和Perscaler組合, 實現(0+1)(1999+1)個時鍾的周期
- Internal Clock Division (CKD): No division
- auto-reload preload: Disable
- Trigger Output (TRGO) Parameters
- Master/Slave Mode (MSM bit): Disable
- Trigger Envent Selection: Reset
- NVIC Settings
- TIM3 global interrupt: Enable
對應代碼變化
stm32f4xx_hal_conf.h 啟用TIM模塊
#define HAL_TIM_MODULE_ENABLED
main.c
static void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 1999;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
stm32f4xx_hal_msp.c 這里會一起處理其他的TIM實例
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
if(htim_base->Instance==TIM2)
{
// ...
}
else if(htim_base->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
}
}
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef* htim_base)
{
if(htim_base->Instance==TIM2)
{
//...
}
else if(htim_base->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_DISABLE();
HAL_NVIC_DisableIRQ(TIM3_IRQn);
}
}
stm32f4xx_it.h 增加對應的定時中斷處理
void TIM3_IRQHandler(void);
stm32f4xx_it.c
/**
* @brief This function handles TIM3 global interrupt.
*/
void TIM3_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim3);
}