针对STM32F103RC学习过程中遇到的较为复杂、难以理解的地方,此处对其进行详细分析,以避免学习笔记中容易出错、混淆、看不懂的地方
基于正点原子ALIENTEKmini版,由于其手册对于部分模块的讲解有些模糊,此处记录一些个人理解,便于日后重温
概念
输出比较是对于已有的信号,自定义要求,当计数器满足要求时,便输出有效,否则输出无效(可以是电平,也可以是模拟信号)
PWM是输出比较的特例,通过定时器产生规律的信号,例如三角波、锯齿波、正弦波等,而一般输出比较仅产生矩形波
输入捕获与输出比较对立,是检查输入引脚获取到的信号,通过通用/高级定时器TIMx,对其时间相关的参数进行测定,例如脉宽、频率、周期、占空比等。
主要依靠对输入信号的边沿(上升/下降)进行抓取,通过定时器进行计数,来达到其目的。
除此之外,输入捕获还有自带的滤波降噪等简单的信号处理。
学习目的
了解输入捕获的应用场景,如何配置相关的寄存器,如何对已有的信号进行分析,并得到其信息
接下来以一个小实验为例,实验目标:
1、通过PWM产生一个规律的三角波信号(具体电压值、频率等无严格要求),并通过一个LED展示(由亮到灭,再由灭到亮)
2、通过按键产生电平序列,获得不规则的矩形波
3、对于获得的矩形波,测量其脉宽,并通过串口输出展示,得到按键按下的时间
分析过程
1、PWM原理及设计
PWM脉宽调制,将单一的电平信号,按照其占空比,输出一个模拟电量,可用其设计产生一些规律的信号,例如矩形波、三角波、锯齿波等
其原理可自行百度
PWM输出模式只能由高级定时器TIM1/TIM8产生,配置为PWM模式,我们设置CH1输出
需要配置的寄存器有:
外设时钟使能寄存器RCC_APB2ENR:将输出通道的时钟使能
端口配置寄存器GPIOx_CRH/CRL:输出通道对应的IO口设置为复用输出
捕获/比较模式寄存器TIMx_CCMRn:设置PWM模式、启用预装载
捕获/比较使能寄存器TIMx_CCER:OC1输出使能
捕获比较寄存器TIMx_CCRn:输出比较值
刹车和死区寄存器TIMx_BDTR:主输出使能
自动重装载寄存器TIMx_ARR:设置计数值
预分频器TIMx_PSC:设置预分频
控制寄存器TIMx_CR1:自动重装载预装载允许
PWM频率 = Tclk1 / ((arr+1)*(prer+1)),输出频率应与之接近
占空比γ = TIM1_CCRn / ARR
输出电压U = γ*标准电压Vcc
2、输入捕获原理及设计
对于已有信号,检测其有效起始点(上升/下降沿),启动计时直至有效终止点,获取信号的周期、频率、脉宽、占空比等信息
输入捕获可由通用定时器TIMx产生,配置为输入捕获模式,我们设置CH3输入
需要配置的寄存器有:
外设时钟使能寄存器RCC_APB2ENR:将输出通道的时钟使能、TIMx时钟使能
端口配置寄存器GPIOx_CRH/CRL:输出通道对应的IO口设置为电平输入
捕获/比较模式寄存器TIMx_CCMRn:设置滤波器,映射到TI3
捕获/比较使能寄存器TIMx_CCER:上升/下降沿捕获
DMA/中断使能寄存器TIMx_DIER:允许捕获中断、允许更新中断
捕获比较寄存器TIMx_CCRn:输入捕获值
自动重装载寄存器TIMx_ARR:设置计数值
预分频器TIMx_PSC:设置预分频
控制寄存器TIMx_CR1:使能定时器
由于STM32计数器非常快,所以对于按键来说,按下的时间很容易因为远大于超时值,导致计数器溢出,因此需要对以下情况进行处理,并以此编写输入捕获中断程序
如上图所示,分为五种可能性,因此需要对所有情况进行分析处理
①②为正常的开始和结束,分别需要开始计时直至终止,和停止计时、发送数据并等待下一次开始
③是处于无效的区段,此时可以任凭计数器计时(反正每次开始都会清空一次计数),但是此时不进行任何数据处理或发送
④当有效时间较长时,会导致计数器溢出,此时我们可以通过对溢出计次,然后清空计数器接着计数,直到最后有效电平结束后,对时间进行一个统一折算
⑤当时间非常长时,不仅计数器溢出,而且溢出次数过多,导致存储溢出次数的寄存器都不够用时,我们只能通过扩大寄存器容量,或者再添加一级寄存器,用于存储“溢出次数寄存器”的溢出次数,否则只能进行强制停止,将最大数据发出,并重新等待下一次上升沿
脉宽Pulsewidth = 溢出次数*最大计时+最后一次计时
代码编写
主要用来对比学习,注意哪些寄存器需要配置,如何配置,以及寄存器的配置顺序,仅供参考
1、PWM输出部分
/*
定时器TIM1_CH1 PWM输出初始化
psc 预分频值
arr 计数器值
*/
void TIM1_PWM_Init(u16 arr,u16 psc)
{
RCC->APB2ENR|=1<<11; //使能TIM1时钟
GPIOA->CRH&=0XFFFFFFF0;
GPIOA->CRH|=0X0000000B; //PD2复用推挽输出
TIM1->ARR=arr; //设置计数值
TIM1->PSC=psc; //设置预分频
TIM1->CCMR1|=7<<4; //设置输出比较模式
TIM1->CCMR1|=1<<3;
TIM1->CCER|=1<<0; //输出比较使能
TIM1->BDTR|=1<<15; //主输出使能
TIM1->CR1|=0X0081; //允许自动重装,使能计数器
}
2、输入捕获部分
//定时器TIM2_CH3 输入捕获初始化
void TIM2_Cap_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<0; //使能TIM2时钟
RCC->APB2ENR|=1<<2; //使能PA2时钟
GPIOA->CRL&=0XFFFFF0FF;
GPIOA->CRL|=0X00000800; //TIM2_CH3对应PA2,设置上拉/下拉输入
GPIOA->ODR|=1<<2; //默认上拉
TIM2->ARR=arr; //设置计数器值
TIM2->PSC=psc; //设置预分频
TIM2->CCMR2|=1<<4; //设置滤波器
TIM2->CCMR2|=1<<0; //映射到中断TI3
TIM2->CCER|=1<<8; //下降沿捕获
TIM2->CCER|=1<<9;
TIM2->DIER|=1<<3; //允许捕获中断3
TIM2->DIER|=1<<0; //允许更新中断3
TIM2->CR1|=1<<0; //使能计数器
MY_NVIC_Init(2,0,TIM2_IRQn,2); //中断初始化
}
/*
捕获状态
为了减少变量和调用方便,我们将其假设为一个寄存器使用
[7]:是否完成捕获,0表示未完成,1表示完成
[6]:捕获低电平,0表示未捕获低电平,1表示捕获低电平
[5:0]:捕获高电平后,计数器溢出次数
*/
u8 STATE=0;
//捕获值
u16 VALUE=0;
//输入捕获TIM2中断服务程序
void TIM2_IRQHandler(void)
{
if((STATE&0X80)==0) //未完成,不能发出去
{
if((TIM2->SR&0X01)&&(STATE&0x40)) //低电平有效期间(按键一直按下),而计数器溢出(时间太长了)
{
if((STATE&0X3F)==0X3F) //持续时间非常长,导致溢出计数都不够用了,不能再继续++了,则强行打断
{
STATE|=0X80; //强行完成捕获
VALUE=0XFFFF; //value赋最大值代表最后一次溢出
}
else STATE++;
}
if(TIM2->SR&0X08) //捕获到东西了
{
if(STATE&0X40) //已经捕获过低电平了(说明此次捕获为上升沿)
{
STATE|=0X80; //完成捕获了
VALUE=TIM2->CCR3; //将计数值传递给value
TIM2->CCER|=1<<9; //等待下降沿
}
else if((STATE&0X40)==0) //未捕获过低电平(说明此次捕获为下降沿)
{
VALUE=0; //清空捕获值
STATE=0X40; //已经捕获过低电平了
TIM2->CNT=0; //清空计数器
TIM2->CCER&=~(1<<9); //等待上升沿
}
}
}
TIM2->SR=0; //清空中断标志位,上述任一情况发生均需要清空标志位,如果无情况发生则原本就为0
}
3、主函数部分
extern u8 STATE;
extern u16 VALUE;
int main()
{
u16 led0pwmval=0;
u8 dir=1;
u32 time=0;
Stm32_Clock_Init(9); //一堆初始化
delay_init(72);
uart_init(72,9600);
LED_Init();
KEY_Init();
LED0=1;
LED1=0;
while(WKUP==0); //为了做区别,按键按下则程序正式开始
TIM2_Cap_Init(0XFFFF,71); //以1MHz计数
TIM1_PWM_Init(899,0); //PWM启动
while(1)
{
delay_ms(10);
if(dir)led0pwmval++; //亮暗交替轮回
else led0pwmval--;
if(led0pwmval>300)dir=0;
if(led0pwmval==0)dir=1;
TIM1->CCR1=led0pwmval; //传值给通道CH1,点亮LED0
if(STATE&0X80)
{
time=STATE&0X3F; //溢出时间总和
time*=65536;
time+=VALUE; //按键时长
printf("按键按下的时间为:%d us\r\n",time);
STATE=0; //清空状态及计数
}
}
}
实验结果
在结果之前,需要注意以下事宜:
1、端口:PWM对应CH1,输入捕获对应CH3
2、对于结果显示部分,LED应与CH1相连,按键应与CH3相连,串口连接至电脑
3、按键、LED及串口通信的软硬件设计此处略过,如果有不懂,参照 STM32学习笔记(未更新)
实验结果:LED由亮变暗,再由暗变亮,串口工具显示固定值时间数据
串口展示按键按下的时间,如下图所示,圈出部分为最大计时值,按下时间太长则显示此数值