个人AT32原创学习笔记,转载需告知作者本人,并注明出处!
总体概述:时钟源的选择与定时器设置
分析源码前,先来简单了解一下,如果需要详细了解AT32内部时钟机制与配置流程及New_Clock_Configuration的安装与使用,请移步:
【雅特力AT32】 时钟配置及New_Clock_Configuration
无论是时钟配置还是定时器,都离不开一个核心的东西–时钟源,他们都有自己默认的时钟源,也可以用户自定义选择,个人建议结合代码编写需结合时钟树和技术手册,这样更容易快速上手。
目录
总体概述:时钟源的选择与定时器设置
时钟源的选择
定时器与计数器计算预分频系数
自动重装载值:
定时器周期计算公式
输出pwm设置占空比源码分析
时钟配置
延时函数
软件定时器
时钟源的选择
个人分析所得,如有纰漏,愿闻指正,详细请查找计数手册。
一定要看技术手册和时钟树,如果想具体研究弄明白!!!
系统时钟配置:1. HICK:48mhz(默认8mhz)2. HEXT:4-25mhz;
延时函数: 1. systick_clock_source_config来选择systemclock(HICK);
2. 默认 systick_clock,即为经分频后到systick外设的时钟;
定时器: 1. 用户自定义,常见:systemclock/(10000-1)
定时器与计数器计算
玩定时器有很多人卡在这里,我也很久没用了,这里复习总结一下。
预分频系数
预分频系数就是将频率分割,比如分频系数是72,则该时钟的频率会变成72MHZ/72=1MHZ,但是在设置的时候要注意,数值应该是72-1。
假定分频系数是72-1,那么频率变成1MHZ,也就意味着STM32在一秒钟会数1M次,即1us数一次(实际上是上升沿/下降沿)。
自动重装载值:
需要定时1ms,由于1ms=1us*1000,那么预装载值就是1000-1,如此类推;
在预分频系数确定的情况下,定时的时长就由预装载值确定了,到了设定的重装载值,计数器溢出;
定时器周期计算公式
T =(arr+1) * (PSC+1) / Tck
其中T为单个周期时间,TCK为时钟频率,PSC为时钟预分频系数,arr为自动重装载值
输出pwm设置占空比
通道数据对应设置即可。
如下图:
占空比 =(499+1)/(999+1)= 50%;
源码分析
时钟配置
自行选择时钟源,时钟配置步骤与New_Clock_Configuration下载使用可移步见文章开头介绍的笔者另一篇文章。
/**
* @brief system clock config program
* @note the system clock is configured as follow:
* system clock (sclk) = (hext * pll_ns)/(pll_ms * pll_fr) / 2
* system clock source = pll (hext)
* - hext = HEXT_VALUE 8MHz
* - sclk = 144000000
* - ahbdiv = 1
* - ahbclk = 144000000
* - apb2div = 1
* - apb2clk = 144000000
* - apb1div = 2
* - apb1clk = 72000000
* - pll_ns = 72
* - pll_ms = 1
* - pll_fr = 1
* @param none
* @retval none
*/
void system_clock_config(void)
{
/* reset crm */
crm_reset(); //复位crm时钟配置
/* config flash psr register */
flash_psr_set(FLASH_WAIT_CYCLE_4); //配置flash PSR寄存器
/* enable pwc periph clock:启用PWC外围时钟 */
crm_periph_clock_enable(CRM_PWC_PERIPH_CLOCK, TRUE); //启用 PWC 外设时钟
/* ensure system clock to highest, set power ldo output voltage to 1.3v */
pwc_ldo_output_voltage_set(PWC_LDO_OUTPUT_1V3); //置 LDO 输出电压为最高 1.3V
crm_clock_source_enable(CRM_CLOCK_SOURCE_HEXT, TRUE); //启用 PLL,并等待 PLL 稳定。
/* wait till hext is ready */
while(crm_hext_stable_wait() == ERROR){}
/* config pll clock resource
common frequency config list: pll source selected hick or hext(8mhz)
_____________________________________________________________________________
| | | | | | | | |
| sysclk | 150 | 144 | 120 | 108 | 96 | 72 | 36 |
|________|_________|_________|_________|_________|_________|_________________|
| | | | | | | | |
|pll_ns | 75 | 72 | 120 | 108 | 96 | 72 | 72 |
| | | | | | | | |
|pll_ms | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| | | | | | | | |
|pll_fr | FR_2 | FR_2 | FR_4 | FR_4 | FR_4 | FR_4 | FR_8 |
|________|_________|_________|_________|_________|_________|________|________|
if pll clock source selects hext with other frequency values, or configure pll to other
frequency values, please use the at32 new clock configuration tool for configuration. */
/* 配置 PLL 时钟资源:选择 HEXT(8MHz) 作为 PLL 的时钟源;
PLL 参数:pll_ns = 72, pll_ms = 1, pll_fr = FR_2
PLL时钟 = (hext * pll_ns) / (pll_ms * pll_fr) / 2 = (8MHz * 72) / (1 * 2) / 2 = 144MHz */
crm_pll_config(CRM_PLL_SOURCE_HEXT, 72, 1, CRM_PLL_FR_2);
//启用 PLL,并等待 PLL 稳定。
/* enable pll */
crm_clock_source_enable(CRM_CLOCK_SOURCE_PLL, TRUE);
/* wait till pll is ready */
while(crm_flag_get(CRM_PLL_STABLE_FLAG) != SET){}
//配置 AHB 时钟分频为 1,APB2 时钟分频为 1,APB1 时钟分频为 2
/* config ahbclk */
crm_ahb_div_set(CRM_AHB_DIV_1); //AHB总线不分频:144HZ
/* config apb2clk, the maximum frequency of APB2 clock is 150 MHz */
crm_apb2_div_set(CRM_APB2_DIV_1); //AHB2不分频
/* config apb1clk, the maximum frequency of APB1 clock is 120 MHz */
crm_apb1_div_set(CRM_APB1_DIV_2); //AHB1由2分频: 72HZ
//启用自动步进模式,选择 PLL 作为系统时钟源
/* enable auto step mode */
crm_auto_step_mode_enable(TRUE);
/* select pll as system clock source */
crm_sysclk_switch(CRM_SCLK_PLL);
/* wait till pll is used as system clock source */
while(crm_sysclk_switch_status_get() != CRM_SCLK_PLL){}//等待系统时钟源切换到 PLL
//关闭自动步进模式,更新系统核心时钟变量。
/* disable auto step mode */
crm_auto_step_mode_enable(FALSE);
/* update system_core_clock global variable */
system_core_clock_update();
}
对应New_Clock_Configuration工具设置,这玩意挺好用,有啥疑问拿代码对照一些挺好,有需要雅特力官网下载即可:
延时函数
如果不使用下文函数选择,默认为ahb总线上的sys tick为时钟源(如上图);
配置SysTick定时器时钟源(延时函数时钟源选择):systick_clock_source_config()
/**
* @brief config systick clock source
* @param source
* this parameter can be one of the following values:
* - SYSTICK_CLOCK_SOURCE_AHBCLK_DIV8
* - SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV
* @retval none
*/
void systick_clock_source_config(systick_clock_source_type source)
{
if(source == SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV)
{
SysTick->CTRL |= SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV;
}
else
{
SysTick->CTRL &= ~(uint32_t)SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV;
}
}
典例
/**
* @brief initialize delay function
* @param none
* @retval none
*/
void delay_init()
{
/* configure systick */
systick_clock_source_config(SYSTICK_CLOCK_SOURCE_AHBCLK_NODIV);
fac_us = system_core_clock / (1000000U); //system_core_clock为 HEXT时钟(系统核心时钟)8Mhz,则fac_us为80hz
fac_ms = fac_us * (1000U);
}
/**
* @brief inserts a delay time.
* @param nus: specifies the delay time length, in microsecond.
* @retval none
*/
void delay_us(uint32_t nus)
{
uint32_t temp = 0;
SysTick->LOAD = (uint32_t)(nus * fac_us); //SysTick定时器将被配置为在装载值为80时进行计数,每计数一个单位的时间为1微秒;
SysTick->VAL = 0x00; //VAL寄存器存储的当前计数值清零,确保计时器从0开始计数,实现精确的延时时间。
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk ; //启用了SysTick定时器,让其开始计时,以完成延时操作
do
{
temp = SysTick->CTRL;
//循环继续的条件为当前定时器计数位为1(temp & 0x01为真),且定时器溢出位为0(!(temp & (1 << 16))为真)
}while((temp & 0x01) && !(temp & (1 << 16))); //判断计数(80)是否溢出(80hz/1us)
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk; //关闭SysTick定时器,CTRL与上使能值的取反值,即清除使能位
SysTick->VAL = 0x00;
}
/**
* @brief inserts a delay time.
* @param nms: specifies the delay time length, in milliseconds.
* @retval none
*/
void delay_ms(uint16_t nms)
{
uint32_t temp = 0;
while(nms)
{
if(nms > STEP_DELAY_MS)
{
SysTick->LOAD = (uint32_t)(STEP_DELAY_MS * fac_ms);
nms -= STEP_DELAY_MS;
}
else
{
SysTick->LOAD = (uint32_t)(nms * fac_ms);
nms = 0;
}
SysTick->VAL = 0x00;
SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
do
{
temp = SysTick->CTRL;
}while((temp & 0x01) && !(temp & (1 << 16)));
SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
SysTick->VAL = 0x00;
}
}
/**
* @brief inserts a delay time.
* @param sec: specifies the delay time, in seconds.
* @retval none
*/
void delay_sec(uint16_t sec)
{
uint16_t index;
for(index = 0; index < sec; index++)
{
delay_ms(500);
delay_ms(500);
}
}
软件定时器
主要展示如何使用软件定时器的配置,典例主要功能如下:
tmr1溢出中断会产生软件触发。
Led2开关表示tmr1溢出中断响应。
Led3和led4切换表示终止第4行中断响应。
/**
**************************************************************************
* @file main.c
* @brief main program
**************************************************************************
展示了如何使用软件触发器退出。
tmr1溢出中断会产生软件触发。
Led2开关表示tmr1溢出中断响应。
Led3和led4切换表示终止第4行中断响应。
**************************************************************************
*/
#include "at32a423_board.h"
#include "at32a423_clock.h"
/** @addtogroup AT32A423_periph_examples
* @{
*/
/** @addtogroup 423_EXINT_exint_software_trigger EXINT_exint_software_trigger
* @{
*/
void exint_line4_config(void); //配置外部中断线4
static void tmr1_config(void);
uint8_t i,num,num1 = 0;
/**
* @brief exint line4 config. configure pa0 in interrupt mode
* @param None
* @retval None
*/
void exint_line4_config(void)
{
exint_init_type exint_init_struct;
crm_periph_clock_enable(CRM_SCFG_PERIPH_CLOCK, TRUE); //启用外设时钟
exint_default_para_init(&exint_init_struct); //初始化外部中断配置结构
exint_init_struct.line_enable = TRUE; //启用外部中断线
exint_init_struct.line_mode = EXINT_LINE_INTERRUPT; //置外部中断模式为中断模式
exint_init_struct.line_select = EXINT_LINE_4; //选择外部中断线4
exint_init_struct.line_polarity = EXINT_TRIGGER_RISING_EDGE; //设置触发极性为上升沿触发
exint_init(&exint_init_struct); //初始化外部中断
exint_flag_clear(EXINT_LINE_4); //清除外部中断标志
nvic_irq_enable(EXINT4_IRQn, 0, 1); //使能外部中断4的 NVIC 中断
}
/**
* @brief tmr1 configuration.
* @param none
* @retval none
* 配置定时器1,使其以1Hz的频率触发溢出中断,并在溢出时执行相应的中断处理程序
*/
static void tmr1_config(void)
{
crm_clocks_freq_type crm_clocks_freq_struct = {0}; //定义并初始化系统时钟频率结构体
/* get system clock */
crm_clocks_freq_get(&crm_clocks_freq_struct); //获取系统时钟频率
crm_periph_clock_enable(CRM_TMR1_PERIPH_CLOCK, TRUE); //启用定时器1的外设时钟
/* (systemclock / (system_core_clock/10000)) / 10000 = 80Hz(1s) */
/* 设置定时器1的基本参数,包括计数器的重载值和预分频器的值,以实现80Hz(1秒)的定时功能 */
tmr_base_init(TMR1, 10000-1, system_core_clock/10000-1); //初始化定时器基本参数 ((uint32_t)8000000) /*!< 高速内部时钟的取值(hz) */
tmr_cnt_dir_set(TMR1, TMR_COUNT_UP); //设置定时器计数方向为向上计数
tmr_clock_source_div_set(TMR1, TMR_CLOCK_DIV1); //设置定时器时钟源分频
tmr_interrupt_enable(TMR1, TMR_OVF_INT, TRUE); //使能定时器1的溢出中断
nvic_irq_enable(TMR1_OVF_TMR10_IRQn, 0, 0); //使能定时器1的 NVIC 中断
}
/**
* @brief tmr1 interrupt handler
* @param none
* @retval none
*/
void TMR1_OVF_TMR10_IRQHandler(void)
{
//检查定时器溢出中断标志位是否被设置:定时中断
if(tmr_interrupt_flag_get(TMR1,TMR_OVF_FLAG) != RESET)
{
//生成exint软件中断事件(触发1s定时器中断):EXINT_LINE_4
at32_led_toggle(LED2);
// printf("1s counter: %d\r\n", num1++);
exint_software_interrupt_event_generate(EXINT_LINE_4);
tmr_flag_clear(TMR1,TMR_OVF_FLAG);
}
}
/**
* @brief exint4 interrupt handler
* @param none
* @retval none
*/
void EXINT4_IRQHandler(void)
{
//检查外部中断标志位是否被设置
if(exint_interrupt_flag_get(EXINT_LINE_4) != RESET)
{
at32_led_toggle(LED3);
at32_led_toggle(LED4);
// if(i++ == 3) //三秒一次中断
// {
// delay_us(5);
// printf("3s counter: %d\r\n", num++);
// i = 0;
// }
exint_flag_clear(EXINT_LINE_4);
}
}
/**
* @brief main function.
* @param none
* @retval none
*/
int main(void)
{
nvic_priority_group_config(NVIC_PRIORITY_GROUP_4);
system_clock_config();
at32_board_init();
/* turn led2/led3/led4 on */
at32_led_off(LED2);
at32_led_off(LED3);
at32_led_off(LED4);
exint_line4_config();
tmr1_config();
tmr_counter_enable(TMR1, TRUE);
while(1)
{
}
}