本人是大一的学生,学习了一段时间的stm32,此系列博客为个人的学习笔记,方便个人复习,如有错误或问题,非常非常欢迎大家来大力指正。
简单用文字说一下原理。
如果先要清楚了解建议去b站看一下keysking大佬的教程,很有趣易懂(本视频的部分图也来自keysking视频中的图片,大家如果要学习强烈推荐他的视频)时钟树我认为视频讲解要比图文效果好很多,所以这节课强烈推荐看视频
单片机内的逻辑电路都是由各种与或非门组成,假设有下面那种电路结构 A,B两段同时输送高低电平信号,在理想情况下电平在同一时间到达门进行判断,然后再读入寄存器中。但事实上如果没有边缘触发器(下端写clk那个),门里面的电路非常复杂,等到门运算完,没有经过门的电信号早已到达下一个门,与未知的信号产生运算。寄存器读到的数也不是我们想要的数据。这会导致电路处理的混乱。而单片机内的这种结构非常非常多,按照这样就乱套了。所以我们要做的就是延长寄存器读取的时间,等到上个门的信号到达下一个门运算得到了数据再读取。
所以就有了我们的边缘触发器,他clk端是控制端,d的输入q是输出。时钟源产生,其特性是当低电平转化为高电平时产生的上升沿时,才会输出信号,一个上升沿到另外一个上升沿的时间已经够门运算的电平到达边缘触发器,一个周期之内副作用电平不会对寄存器造成影响。
而片上外设也需要精确的时钟信号,如串口需要精确的比特率,spi,iic等需要时钟线,定时器需要计时,ADC需要采样间隔。
这个是时钟树的基本结构
还有其他的时钟树分支可以在cubeMX看到如css是时钟安全系统,当一个时钟故障可以迅速转换另一个时钟,并产生中断,FCLK是自由运行时钟,当我们需要低功耗的停止模式时,AHB上的HCLK时钟会停止运行,所有的外设也会停止运行,当需要唤醒stm32,需要外部中断,就要用到FCLK。fclk来自AHB的预分频器,还有RTC时钟和MCO
定时器,定时器的时钟信号来自APB时钟线
TIM定时器高级定时器多了:死区时间互补,刹车信号输入等关于电机的功能
定时器是读取,计数脉冲的次数,然后然后根据周期算出时间,实现计时的原理。计算器也可以加入分频器去增加计时器计数的时间。(就是因为计数器是16位,最多有65536位数可以计算,如果按照72mhz,一次计数的时间就太短了。如果2分频,就是将两个周期变为一个,使周期变长,可计算的时间也更长了。)
当计数器和自动重装载寄存器相同时就会触发中断,然后计数器重新加载
上面是定时器的基本逻辑。预分频器和自动重装载寄存器有影子寄存器,当开启影子寄存器机制的时候,真正在工作的是影子寄存器,当预分频器和自动重装载寄存器的数值改变的时候,先储存在原寄存器上,然后等一个周期过后再改变。
一,标准库的实现
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1; //psc预分频器
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;//向上计数器模式
TIM_TimeBaseInitStructure.TIM_Period=10000-1; //ARR自动重装器的初始值
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1; //滤波分频,TIM_CKD_DIV1为不分频
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0; //重复计数器,0为载入一次开启中断,1为载入两次中断,以此类推
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//初始化tim
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//选择中断组
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
步骤为①打开RCC时钟
②用结构体定义时基单元(PSC预分频器,ARR自动重装值,计数方式)
③定义NVIC中断优先级(因为要用到中断)
完成一次计数,就在中断函数执行一遍
void TIM2_IRQHandler(void)
要实现定时,我们可以在中断函数上设置一个num变量,触发函数时自增就行。
二,hal库的实现
按照标准库上的,先开启RCC时钟
开启tim内部时钟,并根据标准库的预分频器和自动重装载寄存器填写数据(auto-reload-preload是开启影子寄存器)
打开tim的NVIC
之后生成代码。
先开启tim
HAL_TIM_Base_Start(&htim3);
现在tim就开启成功了,可以使用_hal_tim_get_counter之类的去读取计数器的值,还有很多函数在库中可以找到。
接下来打开中断
将上面代码改为
HAL_TIM_Base_Start_IT(&htim3);
然后写回调函数(也可以理解成是中断函数)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim==&htim3)//判断一下是否为tim3的中断
{
}
}
然后一次中断就可以执行一次上面的代码
要实现定时,我们可以在中断函数上设置一个num变量,触发函数时自增就行。
三,外部时钟:ETR是外部触发器,就是外部时钟的来源
TI1_ED是双边缘触发。TI1FP1,TI2FP2可调
ETR2:使用的方法和内部时钟是一样的,在HAL库中,将tim的CLOCK SOURCE改为ETR就可以进行计数了。
输入滤波器也是十分重要的,因为外部的时钟源经常会发生抖动,使得传输进来的数据不稳定。所以我们要开启滤波器
首先,滤波器会对传输进来的信号进行检测他的脉宽,当脉宽小于设定值,则判断此信号无效
HAL库的实现:在这里打开clock fiter那里,一般写15就ok了
ETR1:是用到了触发控制器的从模式,只需要在cubeMX里面设置成这样即可,代码无需改动
clock fiter设置变为了trigger fiter设置,用法和功能一样
标准库的实现:
和上面的内部时钟一样
将内部时钟改为外部时钟。因为外部时钟要用到外部设备接GPIO,所以要用到定义GPIO
TIM_InternalClockConfig(TIM2);//改为下面的
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
而外部中断的定义与hal库对应,第一个为通道,第二个为分频,第三个为外部极性选择,第四个为滤波器(clock fiter)
上面也要加个GPIO的定义
RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOC, &GPIO_InitStructure);
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
就🆗了