背景知识
主频main frequency" 或 "clock frequency.
指微控制器或其他数字电路的时钟振荡器的频率。在 CH32V307 或其他微控制器中,主频起到以下作用:
指令执行速度的决定因素:主频确定了微控制器每秒执行多少指令周期,从而影响整体性能和反应速度。
定时器和外设的时基:许多外设和定时器依赖于主频来确定其工作频率。例如,PWM 输出、ADC 转换速率等。
功耗管理:主频越高,微控制器的功耗通常也越高。因此,在对性能要求较低的应用中,可能会降低主频以节省能源。
系统同步:主频为整个系统提供了一个共同的时钟基准,有助于各个模块和外设之间的同步协作。
晶振oscillator。
external oscillator就是外部晶振。
晶振使用机械振动晶体,产生稳定频率震荡。晶振可以提供精确的时钟信号。
external crystal oscillator 是外晶振,精确度高,成本高,稳定性好
internal crystal oscillator 是内晶振,精确度稍低,成本低,易受温度,和电源电压影响
HSI_VALUE
外部晶振启动的timeout,通常需要稍微加大
GPIO Init
因为要点亮LED01,所以同样需要初始化GPIO
/********************************************************************
* 函 数 名 : GPIO_INIT
* 函数功能 : 初始化 GPIO
* 输 入 : 无
* 输 出 : 无
********************************************************************/
void GPIO_INIT(void)
{
GPIO_InitTypeDef GPIO_InitStructure={0}; //GPIO的结构体定义
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE); // 在STM32对于外设的使用里面,每使用到一个外设,都要打开其对应的外设时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;// 规定输出在 pin 11(LED01)
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;// 输出模式:推挽输出(简单说时一种可以输出高低电平的模式)
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_2MHz; // 引脚的输出频率影响着引脚对于数据传输速度的快慢,以及影响芯片的功耗
GPIO_Init(GPIOE, &GPIO_InitStructure);
}
GPIO_InitTypeDef 是一个GPIO结构体,用于存储 GPIO(通用输入/输出)初始化的相关设置。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE); 开启高速APB时钟,每使用一个外设,都要打开其对应的外设时钟
Interrupt Init
/********************************************************************
* 函 数 名 : Interrupt_Init
* 函数功能 : 初始化定时器中断
* 输 入 : 无
* 输 出 : 无
********************************************************************/
void Interrupt_Init(void)
{
NVIC_InitTypeDef NVIC_InitStructure={0};
NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn ;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //子优先级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
首先定义了一个空的定时器中断结构体
使用TIM6中断源,定义抢占优先级,子优先级,启用中断
TIM6 定时器 Init
这是这个任务的核心
/********************************************************************
* 函 数 名 : TIM6_Init
* 函数功能 : 初始化 定时器 TIM6
* 输 入 : arr:自动重装值,psc 预分频系数
* 输 出 : 无
********************************************************************/
void TIM6_Init( u16 arr, u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd( RCC_APB1Periph_TIM6, ENABLE );
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Down;
TIM_TimeBaseInit( TIM6, &TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);
TIM_ARRPreloadConfig( TIM6, ENABLE );
TIM_Cmd( TIM6, ENABLE );
}
这个init方法接收两个参数:定时器溢出值arr(类似while<100的100),预分频值psc(降低定时器频率,不然按照定时器自身的频率,会非常快就完成一次溢出。预分频器会将核心时钟速度除以(预分频值 + 1)来得到定时器的计数速度。例如,如果核心时钟是72MHz,预分频值是71(注意+1的存在),那么定时器的计数速度就会是 1MHz。) t i m e f r e q u e n c y = c l o c k f r e q u e n c y p s c + 1 time frequency = \frac{clock frequency}{psc + 1} timefrequency=psc+1clockfrequency
RCC_APB1PeriphClockCmd enable 对应时钟
定义arr,psc
设置clock division,设置向下计数模式
使用上面的设置初始化
enable 中断,当溢出时产生中断
enable 自动重新加载,溢出后立刻刷新arr
enable tim6,开始计数
终端服务程序函数
/********************************************************************
* 函 数 名 : TIM6_IRQHandler
* 函数功能 : 中断服务程序的函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
void TIM6_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));
volatile uint16_t LED_Status = 0; // 中断里使用的变量加 volatile 可当成全局变量
void TIM6_IRQHandler(void)
{
TIM_ClearFlag(TIM6, TIM_FLAG_Update);//清除标志位
LED_Status = !LED_Status ; // 将 LED 状态值取反
GPIO_WriteBit(GPIOE, GPIO_Pin_11, LED_Status); // 配置 PE11 (即 LED1) 状态
}
首先定义函数TIM6_IRQHandler并生命这是个快速中断
定义LED_Status 变量初始值为0
函数体中的功能如注释所示
Main 函数
/********************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*********************************************************************/
int main(void)
{
GPIO_INIT(); // 初始化 GPIO
TIM6_Init( 5000-1, 14400-1 ); // 初始化定时器,让 LED 1 秒闪烁一次,我们需要让定时器 0.5 秒溢出,要计数 `144M * 0.5 = 72M` 个时钟周期,而定时器只有16位,这是不够的。需要用到预分频器,设分频系数为 14400,可以得到 10KHz 的定时器时钟,这样设置计数值 5000 就可以做到 0.5 ms 定时。
Interrupt_Init();//初始化定时器中断
while(1); // 死循环
}
初始化,并一直循环
注意 TIM6_Init( 5000-1, 14400-1 ),用上面的计时器频率公式计算下。
在system_ch32v30x.c我们可以看到定义的sysclk频率是 #define SYSCLK_FREQ_72MHz 72000000
arr是5000-1,psc是14400-1
那我们自定定时器的freq = 72000000 / (14400 -1 +1) = 5000
也就是5000次/s,和arr值匹配
程序调用逻辑
main()
|-- GPIO_INIT()
|-- TIM6_Init()
|-- Interrupt_Init()
|-- while(1);
|-- [Interrupt Occurs]
|-- TIM6_IRQHandler()
- 程序启动,进入
main()
函数 - GPIO初始化 (
GPIO_INIT()
)- 在这一步,GPIOE的Pin 11和Pin 12被配置为输出模式。这两个引脚通常用于连接LED。
- 定时器TIM6初始化 (
TIM6_Init()
)- 初始化TIM6定时器,设置其计数模式、预分频和溢出值(自动重装值)。预分频和溢出值的设定依赖于您想要的定时周期。
- 中断初始化 (
Interrupt_Init()
)- 配置NVIC(Nested Vectored Interrupt Controller,嵌套向量中断控制器)以允许TIM6中断,并设置其优先级。
- 进入无限循环 (
while(1);
)- 主程序进入一个无限循环,等待中断事件的发生。
- 中断服务例程 (
TIM6_IRQHandler()
)- 当TIM6定时器达到预设的计数值并产生一个"更新"或"溢出"事件时,这个中断服务例程(ISR)会被调用。
- ISR会清除TIM6的更新标志,以便下一个中断。
- ISR也会改变LED(连接在GPIOE的Pin 11)的状态。