Air105 有 1 個 Timer 單元,包含 8 個獨立定時器: Timer0 到 Time7, 8 個定時器中斷源獨立,每個定時器單獨占 1 個中斷源, 使用 PCLK 時鐘頻率作為定時器計時鐘源, 定時器採用向下計數方式. 每個 Timer 單元定時器都支持 PWM 模式, PWM 模式最高... ...
目錄
Air105 的 Timer
定時器
- 1 個 Timer 單元,包含 8 個獨立定時器: Timer0 - Time7
- 8 個定時器中斷源獨立,每個定時器單獨占 1 個中斷源
- 使用 PCLK 時鐘頻率作為定時器計時鐘源
- 定時器採用向下計數方式
定時器的兩種運行模式
- user-defined: 定時器計數值載入TimerNLoadCount寄存器設定值, 使用用戶模式可以產生固定時間的定時器中斷
- free-runing: 定時器計數值會載入其允許的最大值, 即0xFFFFFFFF. 在定時器產生中斷(計數到0)前, 用戶可以再編程或禁止定時器中斷. 使用這個模式, 定時器只產生1次中斷, 中斷產生後計數重置為 0xFFFFFFFF 並向下計數, 但不會再產生中斷.
PWM
- 每個 Timer 單元定時器都支持 PWM 模式
- PWM 模式最高頻率 PCLK/2
- PWM 單次觸發(one shot)功能
定時器相關代碼
以下代碼基於 air105_project https://gitee.com/iosetting/air105_project 的庫函數
定時器模塊結構
在Air105中, 全局只有一個定時器模塊, TIMM0
typedef struct
{
TIM_TypeDef TIM[TIM_NUM];
__I uint32_t TIM_IntStatus;
__I uint32_t TIM_EOI;
__I uint32_t TIM_RawIntStatus;
__I uint32_t TIM_Comp;
__IO uint32_t TIM_ReloadCount[TIM_NUM];
} TIM_Module_TypeDef;
這個 TIMM0 的地址定義在 air105.h 中
#define TIMM0 ((TIM_Module_TypeDef *)TIMM0_BASE)
#define AIR105_PERIPH_BASE (0x40000000UL) /*!< (Peripheral) Base Address */
#define AIR105_APB0_BASE (AIR105_PERIPH_BASE + 0x10000)
#define TIMM0_BASE (AIR105_APB0_BASE + 0x3000)
- 地址 = 0x40000000UL + 0x10000 + 0x3000 = 0x4001 3000
- 範圍 [0x4001_3000, 0x4001_3FFF]
定時器初始化
定時器的初始化只需要兩個參數: TIMx, 周期(時鐘數), 為配合定時器使用, 還需要定義中斷
void Timer_Init(void)
{
TIM_InitTypeDef TIM_InitStruct;
NVIC_InitTypeDef NVIC_InitStructure;
// 開啟定時器的外設時鐘
SYSCTRL_APBPeriphClockCmd(SYSCTRL_APBPeriph_TIMM0, ENABLE);
SYSCTRL_APBPeriphResetCmd(SYSCTRL_APBPeriph_TIMM0, ENABLE);
// 定時器的時鐘是 PCLK, 計數間隔為 1ms 對應的時鐘數
TIM_InitStruct.TIM_Period = SYSCTRL->PCLK_1MS_VAL;
// 使用 定時器0
TIM_InitStruct.TIMx = TIM_0;
// 初始化
TIM_Init(TIMM0, &TIM_InitStruct);
// 開啟定時器0的中斷
TIM_ITConfig(TIMM0, TIM_InitStruct.TIMx, ENABLE);
//NVIC
NVIC_SetPriorityGrouping(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannel = TIM0_0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
// 啟動定時器0
TIM_Cmd(TIMM0, (TIM_NumTypeDef)TIM_0, ENABLE);
}
在庫函數中, 會將模式設為 user-defined, 即自動迴圈, 重覆載入周期並產生中斷.
/**
* @brief Initializes the TIMx Unit peripheral according to the specified parameters.
* @param TIMMx: x can be 0 to select the TIM peripheral
* @param TIM_InitStruct: pointer to a TIM_InitTypeDef structor that contains the configuration information
* @retval None
*/
void TIM_Init(TIM_Module_TypeDef *TIMMx, TIM_InitTypeDef *TIM_InitStruct)
{
TIM_Cmd(TIMMx, TIM_InitStruct->TIMx, DISABLE);
TIMMx->TIM[TIM_InitStruct->TIMx].ControlReg = 0;
TIMMx->TIM[TIM_InitStruct->TIMx].ControlReg |= TIMER_CONTROL_REG_TIMER_MODE;
TIMMx->TIM[TIM_InitStruct->TIMx].ControlReg &= ~TIMER_CONTROL_REG_TIMER_PWM;
TIMMx->TIM[TIM_InitStruct->TIMx].LoadCount = TIM_InitStruct->TIM_Period;
}
定時器中斷處理
Air105對應每個定時器, 各有一個中斷處理函數, 可以查看 startup.air105.s 中的中斷向量定義
TIM0_0_IRQHandler
TIM0_1_IRQHandler
TIM0_2_IRQHandler
TIM0_3_IRQHandler
TIM0_4_IRQHandler
TIM0_5_IRQHandler
TIM0_6_IRQHandler
TIM0_7_IRQHandler
對應 Timer0 的中斷處理, 寫在 air105_it.c. TIM_ClearITPendingBit 和 NVIC_ClearPendingIRQ 是必須調用的, 用於清除中斷
void TIM0_0_IRQHandler(void)
{
TIM_ClearITPendingBit(TIMM0, TIM_0);
NVIC_ClearPendingIRQ(TIM0_0_IRQn);
}
下麵加入處理邏輯的例子, 每秒調用一次 timer_handler(), 註意不要在中斷處理中使用耗時的工作
extern uint32_t timer_count;
extern void timer_handler(void);
void TIM0_0_IRQHandler(void)
{
timer_count++;
if (timer_count >= 1000)
{
timer_count = 0;
timer_handler();
}
TIM_ClearITPendingBit(TIMM0, TIM_0);
NVIC_ClearPendingIRQ(TIM0_0_IRQn);
}
定時器示例代碼
使用Timer0控制板載LED每隔一秒閃爍
https://gitee.com/iosetting/air105_project/tree/master/Demos/Timer/Timer_Blink
Air105 的 PWM
Air105 的8個獨立定時器均可編程產生PWM信號. 當用戶設定TimerNControlReg中PWM比特位為1
後,定時器進入PWM工作模式. 此時 PWM 由 TimerNLoadCount2 和 TimerNLoadCount 寄存器分別控制高電平及低電平周期翻轉輸出.
頻率和占空比設置
- 高電平周期 = (TimerNLoadCount2 + 1) * PCLK_Period
- 低電平周期 = (TimerNLoadCount + 1) * PCLK_Period
PWM 相關代碼
PWM初始化也只需要三個參數 TIMx 和高低電平兩個周期, 兩者之和就是一個PWM周期
typedef struct
{
TIM_NumTypeDef TIMx;
uint32_t TIM_LowLevelPeriod;
uint32_t TIM_HighLevelPeriod;
}TIM_PWMInitTypeDef;
用Timer5初始化
void TimerPWM_Init(void)
{
TIM_PWMInitTypeDef TIM_PWMInitStruct;
SYSCTRL_APBPeriphClockCmd(SYSCTRL_APBPeriph_TIMM0, ENABLE);
SYSCTRL_APBPeriphResetCmd(SYSCTRL_APBPeriph_TIMM0, ENABLE);
//Timer5 -> PWM5
TIM_PWMInitStruct.TIM_HighLevelPeriod = SYSCTRL->PCLK_1MS_VAL;
TIM_PWMInitStruct.TIM_HighLevelPeriod = 0;
TIM_PWMInitStruct.TIMx = TIM_5;
TIM_PWMInit(TIMM0, &TIM_PWMInitStruct);
TIM_Cmd(TIMM0, TIM_5, ENABLE);
}
在初始化PWM的庫函數中, 預設將模式設為 user-defined, 自動迴圈載入周期, 並屏蔽中斷
/**
* @brief Initializes the TIMx PWM Unit peripheral according to the specified parameters.
* @param TIMMx: x can be 0 to select the TIM peripheral
* @param TIM_PWMInitStruct: pointer to a TIM_PWMInitTypeDef structor that contains the configuration information
* @retval None
*/
void TIM_PWMInit(TIM_Module_TypeDef *TIMMx, TIM_PWMInitTypeDef *TIM_PWMInitStruct)
{
TIM_Cmd(TIMMx, TIM_PWMInitStruct->TIMx, DISABLE);
TIMMx->TIM[TIM_PWMInitStruct->TIMx].ControlReg = 0;
TIMMx->TIM[TIM_PWMInitStruct->TIMx].ControlReg |= TIMER_CONTROL_REG_TIMER_MODE;
TIMMx->TIM[TIM_PWMInitStruct->TIMx].ControlReg |= TIMER_CONTROL_REG_TIMER_PWM;
TIMMx->TIM[TIM_PWMInitStruct->TIMx].ControlReg |= TIMER_CONTROL_REG_TIMER_INTERRUPT;
TIMMx->TIM[TIM_PWMInitStruct->TIMx].LoadCount = TIM_PWMInitStruct->TIM_LowLevelPeriod;
TIMMx->TIM_ReloadCount[TIM_PWMInitStruct->TIMx] = TIM_PWMInitStruct->TIM_HighLevelPeriod;
}
將 PB5 功能復用為 PWM5
GPIO_InitTypeDef gpio;
gpio.GPIO_Pin = GPIO_Pin_5;
gpio.GPIO_Mode = GPIO_Mode_Out_PP;
gpio.GPIO_Remap = GPIO_Remap_2;
GPIO_Init(GPIOB, &gpio);
printf("GPIO Init\r\n");
實時調節占空比, 後兩個參數代表PCLK時鐘周期個數
TIM_SetPWMPeriod(TIMM0, TIM_5, period - high_period, high_period);
PWM示例代碼
使用PWM5(Timer5)控制LED產生呼吸燈效果
https://gitee.com/iosetting/air105_project/tree/master/Demos/PWM/PWM_FadeLED
示例接線:
根據 開發板的BOM PCB 查看 https://wiki.luatos.com/_static/bom/Air105.html
示例中使用Timer4, Timer5對應的PWM4和PWM5輸出, 使用的是PB4和PB5, 對應開發板的SP2_MO和SP2_MI, 開發板上的PWM5對應的是PC7, 要註意, 別接錯了.
運行示例, 將兩個LED各自串接一個1-5K的電阻, 分別接GND後接在SP2_MO和SP2_MI上, 就能看到呼吸燈的效果了