stm32之TIM-高级定时器应用实例二(测量频率和占空比)
接着上一篇(实验一)高级定时器应用。
实验二:PWM输入捕捉实验
实验要求:
高级定时器TIM1接收TIM2产生的PWM,TIM1测量PWM的频率和占空比,并将数据从UART1上发送到上位机,同时上位机通过发送命令改变PWM的占空比和频率。
硬件设计:
用杜邦线连接TIM2通道一(PA.0)引脚与TIM1通道一(PA.8)引脚。PA.9是USART1的输出引脚,PA.10是USART1的接收引脚,分别接到串口转接板的RXD、TXD。
实验步骤:
- 初始化USART1,用于与PC端通信
- 初始化通用定时器TIM2和高级定时器TIM1,前者产生PWM,后者捕获PWM,杜邦线桥接。
- 初始化SysTick系统滴答计时器,用于任务周期管理
- 创建一个数据接收队列,接收来自PC端的串口数据
- 根据报文的格式,解析接收队列里面的数据
1) H[0x55aa]+LEN[1]+CMD[1]+ARG[1] //设置占空比
example:55 aa 02 01 32 //将占空比设置为50%
2) H[0x55aa]+LEN[1]+CMD[1]+ARG[2] //设置频率
example:55 aa 03 02 00 0a //将频率设置为10Hz
创建USART1.h
#ifndef __USART1_INIT_H__
#define __USART1_INIT_H__#include "stm32f10x.h"
#include int fputc(int ch, FILE *f); //重定向库函数,调用printf时将从串口输出
void USART1_Configuration(void);//打印输出串口初始化#endif
创建USART1.c
#include "USART1.h"void USART1_Configuration(void)//打印输出串口初始化{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;//配置串口1 (USART1) 时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);//配置串口1接收终端的优先级NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0); NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);//配置串口1 发送引脚(PA.09)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure); //配置串口1 接收引脚 (PA.10)GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);//串口1工作模式(USART1 mode)配置 USART_InitStructure.USART_BaudRate = 9600;//一般设置为9600;USART_InitStructure.USART_WordLength = USART_WordLength_8b; //数据位为8个字节USART_InitStructure.USART_StopBits = USART_StopBits_1; //一位停止位USART_InitStructure.USART_Parity = USART_Parity_No ; //无校验位USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不需要流控制USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //接收发送模式USART_Init(USART1, &USART_InitStructure); USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断USART_Cmd(USART1, ENABLE);//使能串口
}int fputc(int ch, FILE *f) //重定向c库里面的fputc到串口,那么使用printf时就能将打印的信息从串口发送出去,在PC上同串口助手接收信息
{//将Printf内容发往串口USART_SendData(USART1, (unsigned char) ch);while( USART_GetFlagStatus(USART1,USART_FLAG_TC)!= SET); return (ch);
}
创建TIM.h
#ifndef __TIM_INIT_H
#define __TIM_INIT_H
#include "stm32f10x.h"void GPIO_Configuration(void);//IO口配置
void TIM1_Configuration(void);//高级定时器配置
void TIM2_Configuration(void);//通用定时器配置#endif
创建TIM.c
#include "TIM.h"void GPIO_Configuration(void)//IO口配置
{GPIO_InitTypeDef GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB|RCC_APB2Periph_GPIOA, ENABLE);//开启GPIOC的外设时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); //开启TIM1时钟GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //设置引脚模式为通用推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_8; //PA.0:TIM2_CH1 波形输出 PA.8:TIM1_CH1 接收波形GPIO_Init(GPIOA, &GPIO_InitStructure); //调用库函数,初始化GPIOC}#define ADVANCE_TIM TIM1
void TIM1_Configuration(void)//高级定时器配置 捕获PWM
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_ICInitTypeDef TIM_ICInitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE); //开启TIM1时钟/*配置定时器捕获中断的优先级*/NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = TIM1_CC_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);TIM_TimeBaseStructure.TIM_Period = 0xFFFF-1; TIM_TimeBaseStructure.TIM_Prescaler =(720-1); //计数器时钟频率为100kHz 72MHz/720=100kHz TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //不需要分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数方式 向上计数TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //重复计数器TIM_TimeBaseInit(ADVANCE_TIM, &TIM_TimeBaseStructure); //调用库函数,初始化TIM1//捕获通道IC1TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; //选择CH1 PA.8 作为输入信号通道TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;TIM_ICInitStructure.TIM_ICFilter = 0x00;TIM_PWMIConfig(ADVANCE_TIM,&TIM_ICInitStructure);//当工作在PWM输入模式时,只需要设置触发信号的那一路即可(用于测量周期)//另外一路(用于测量占空比)会有硬件自动设置//捕获通道IC2/*TIM_ICInitStructure.TIM_Channel = TIM_Channel_2;TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_IndirectTI;TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;TIM_ICInitStructure.TIM_ICFilter = 0x00;TIM_PWMIConfig(ADVANCE_TIM,&TIM_ICInitStructure);*///选择输入捕获的触发信号TIM_SelectInputTrigger(ADVANCE_TIM,TIM_TS_TI1FP1);//选择从模式:复位模式//PWM输入模式时,从模式必须工作在复位模式,当捕获开始时,计数器CNT会被复位TIM_SelectSlaveMode(ADVANCE_TIM,TIM_SlaveMode_Reset);TIM_SelectMasterSlaveMode(ADVANCE_TIM,TIM_MasterSlaveMode_Enable);//使能定时器,计数器开始计数TIM_Cmd(ADVANCE_TIM, ENABLE); //使能捕获中断TIM_ITConfig(ADVANCE_TIM,TIM_IT_CC1,ENABLE);
}void TIM2_Configuration(void)//通用定时器配置
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;TIM_OCInitTypeDef TIM_OCInitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2时钟TIM_TimeBaseStructure.TIM_Period = 1000-1; //从0开始计数 一个周期1000次TIM_TimeBaseStructure.TIM_Prescaler =(3600-1); //定时器时钟频率为20kHz 72MHz/3600=20kHz 注意:这个频率不等于pwm的频率 TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //不需要分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数方式 向上计数TIM_TimeBaseStructure.TIM_RepetitionCounter=0; //不使用重复计数器TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //调用库函数,初始化TIM2//PWM模式配置TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Enable;TIM_OCInitStructure.TIM_Pulse = 300-1;TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;TIM_OCInitStructure.TIM_OCNPolarity = TIM_OCNPolarity_High;TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set;TIM_OCInitStructure.TIM_OCNIdleState = TIM_OCNIdleState_Reset;//初始化输出比较通道一TIM_OC1Init(TIM2,&TIM_OCInitStructure);//使能ARR寄存器预装载TIM_OC1PreloadConfig(TIM2,TIM_OCPreload_Enable);//使能定时器,计数器开始计数TIM_Cmd(TIM2, ENABLE); //主动输出使能TIM_CtrlPWMOutputs(TIM2,ENABLE);
}
创建SystemTick.h
#ifndef __SYSTEMTICK_INIT_H_
#define __SYSTEMTICK_INIT_H_#include "core_cm3.h"
void MsTick_Updata(void);
int If_TimeOut(uint32_t* oldtick,uint32_t diff_tick);
int Tick_Updata(uint32_t* tick);
void MsTick_Updata(void);#endif //__SYSTEMTICK_INIT_H
创建SystemTick.c
#include "SystemTick.h"__IO uint32_t OT_Sys_tick=0; // 系统tick 1msvoid MsTick_Updata(void) // 系统滴答定时器中断里 1ms调用一次
{ OT_Sys_tick++;
}
static uint32_t get_OT_sys_tick(void)
{ return OT_Sys_tick;
}int Tick_Updata(uint32_t* tick)
{ uint32_t ticknow;*tick=get_OT_sys_tick();return 1;
}int If_TimeOut(uint32_t* oldtick,uint32_t diff_tick) // 1--超时 0--没有超时
{uint32_t ticknow;uint32_t diff;ticknow=get_OT_sys_tick();diff=ticknow-(*oldtick);return diff > diff_tick;
}
创建UartComm.h
#ifndef __UARTCOMM__H_
#define __UARTCOMM__H_
#include "core_cm3.h"#define CMD_SETDUTYCYCLE 0x01
#define CMD_SETFREQUENCY 0x02typedef struct{u8 step; //报文解析的步调u8 cmd; //报文的命令u16 datalen; //报文的数据长度u16 reccnt; //已接收的长度u8 argbuf[50]; //报文命令参数保存的缓存
}OrderStruct;u8 OrderParser(OrderStruct* pOS,u8 ch);#endif //__UARTCOMM__H_
创建UartComm.c
#include "UartComm.h"u8 OrderParser(OrderStruct* pOS,u8 ch)
{u8 ret=0;switch(pOS->step){case 0:pOS->step=(ch==0x55)?1:0;break;case 1:pOS->step=(ch==0xaa)?2:0;break;case 2:pOS->datalen=ch;pOS->reccnt=0;pOS->step++;break;case 3:pOS->cmd=ch;pOS->reccnt++;pOS->step++;break;case 4:pOS->argbuf[pOS->reccnt-1]=ch;pOS->reccnt++;if(pOS->reccnt==pOS->datalen){ //接收完成pOS->step=0;ret=1;}break;default:pOS->step=0;break;}return ret;
}
创建Queue.h
#ifndef __QUEUE__H__
#define __QUEUE__H__
#include "core_cm3.h"#define QUE_LEN 10 //队列的大小typedef struct
{u16 in;u16 out;u16 cntMax;u8* pBuf;
}QueueT;/*队列的特点:先进先出,若队列满了,不能再放数据。可循环使用队列的位置*/void QueueCreate(QueueT* thiz,u8* BufAddress,u16 BufSize); //创建一个队列,初始化结构体里面的成员
u16 getDataCount(QueueT* thiz); //获取队列里面有效的数据的大小
u16 getEmptyCount(QueueT* thiz); //获取队列里面还剩余多少空的位置
u8 inQueue(QueueT* thiz,u8 data); //将一个数据放进队列
u8 outQueue(QueueT* thiz); //从队列里面拿一个数据出来#endif //__QUEUE__H__
创建Queue.c
#include "Queue.h"void QueueCreate(QueueT* thiz,u8* BufAddress,u16 BufSize)
{thiz->in=0;thiz->out=0;thiz->cntMax=BufSize;thiz->pBuf=BufAddress;
}u16 getDataCount(QueueT* thiz)
{if (thiz->in >= thiz->out){return (thiz->in - thiz->out);}else{return (thiz->in + thiz->cntMax - thiz->out);}
}u16 getEmptyCount(QueueT* thiz)
{u16 dataCnt;if (thiz->in >= thiz->out){dataCnt=(thiz->in - thiz->out);}else{dataCnt=(thiz->in + thiz->cntMax - thiz->out);} if ((dataCnt+1u) >= thiz->cntMax) {return 0; //队列已满}return (thiz->cntMax-dataCnt-1u);}u8 inQueue(QueueT* thiz,u8 data)
{u16 in;in = thiz->in + 1u;if (in >= thiz->cntMax){in = 0;}if (in == thiz->out){ //队里已满return 0;}thiz->pBuf[thiz->in] = data;thiz->in = in;return 1;
}u8 outQueue(QueueT* thiz)
{u8 data;u16 out;if (thiz->in == thiz->out){ //队列没有数据return 0;}out = thiz->out;data = thiz->pBuf[out];out++;if (out >= thiz->cntMax){out = 0;}thiz->out = out;return data;
}
创建main.c
#include "stm32f10x.h"
#include "USART1.h"
#include "TIM.h"
#include "Queue.h"
#include "SystemTick.h"
#include "UartComm.h"extern int IC1Value,IC2Value;#define RxbufSize 50
QueueT RxQueueEntity; //包含了队列内信息的结构体
u8 databuf[RxbufSize];//队列缓存__IO u8 Setdutycycle; //预要设置的占空比
__IO u16 Setfrequency; //预要设置的频率int main(void)
{ float DutyCycle,Frequency;uint32_t DetectorTick=0; //ms 记录输出频率和占空比的间隔时间OrderStruct OrderStr;USART1_Configuration();//打印输出串口初始化GPIO_Configuration();//IO口配置TIM1_Configuration();//高级定时器配置TIM2_Configuration();//通用定时器配置QueueCreate(&RxQueueEntity,&databuf[0],RxbufSize); //创建串口1的接收队列SysTick_Config(SystemCoreClock/1000); //配置系统滴答定时器 用于任务调度管理while(1){if(If_TimeOut(&DetectorTick,1000)){ //1000ms周期 打印一起高级定时器TIM1 所测量信号的频率和占空比Tick_Updata(&DetectorTick);if(IC1Value!=0){DutyCycle=(float)((IC2Value+1)*100)/(IC1Value+1);Frequency=100000/IC1Value;printf("频率:%0.2fHz\n 占空比:%0.2f%%\n",Frequency,DutyCycle);}}#if 1 //把队列里的数据逐个拿出来 进行命令解析 if(getDataCount(&RxQueueEntity)!=0){if(OrderParser(&OrderStr,outQueue(&RxQueueEntity))!=0){//接收到一条完整的命令,执行命令操作switch(OrderStr.cmd){case CMD_SETDUTYCYCLE: //设置TIM2占空比Setdutycycle=OrderStr.argbuf[0];TIM_SetCompare1(TIM2,Setdutycycle*10-1);printf("Setdutycycle=%d\n",Setdutycycle);break;case CMD_SETFREQUENCY: //设置TIM2频率Setfrequency=OrderStr.argbuf[0]<<8;Setfrequency|=OrderStr.argbuf[1];//TIM_Prescaler=72MHz/(Setfrequency*(TIM_Period+1))-1 其中TIM_Period=1000-1 化简得72000/Setfrequency-1 TIM_PrescalerConfig(TIM2,72000/Setfrequency-1,TIM_PSCReloadMode_Immediate); //配置TIM_Prescaler的值printf("Setfrequency=%d\n",Setfrequency);break;default:break;}}}
#else //直接把队列的数据拿出来 不做命令解析if(getDataCount(&RxQueueEntity)!=0){printf("%c",outQueue(&RxQueueEntity)); }
#endif}
}
最后在stm32f10x_it.c文件里面加入以下内容:
#include "SystemTick.h"
void SysTick_Handler(void) //系统滴答计时器中断,配置为每1ms发生一次中断计数
{MsTick_Updata();
}int IC1Value=0,IC2Value=0;
void TIM1_CC_IRQHandler(void) //将TIM1侧量的值保存起来,在main函数里面计算。中断里的代码尽量精简
{if (TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET){TIM_ClearITPendingBit(TIM1,TIM_IT_CC1);IC1Value=TIM_GetCapture1(TIM1);IC2Value=TIM_GetCapture2(TIM1);}
}#include "Queue.h"
extern QueueT RxQueueEntity;void USART1_IRQHandler(void)//串口收到的数据 先放进队列里面 然后在main函数里面拿出来处理
{ u16 code,Status;if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){ code=USART_ReceiveData(USART1);Status = USART_GetFlagStatus(USART1, USART_FLAG_NE|USART_FLAG_PE|USART_FLAG_NE);if(Status!=RESET){//如果发生错误接忽略接收的数据USART_ClearFlag(USART1,Status);//把错误标志清楚return;}if(getEmptyCount(&RxQueueEntity)!=0){ //判断队列是否有空的位置,若满了就丢弃inQueue(&RxQueueEntity,code); //将接受到的数据放进队列}//printf("%c",code); //将接受到的数据直接返回打印} }
还有一个需要注意的地方,在core_cm3.h里面找到NVIC_EnableIRQ函数,前面加上#include "stm32f10x.h",如下:
#include "stm32f10x.h"
static __INLINE void NVIC_EnableIRQ(IRQn_Type IRQn)
{NVIC->ISER[((uint32_t)(IRQn) >> 5)] = (1 << ((uint32_t)(IRQn) & 0x1F)); /* enable interrupt */
}
如果不加的话编译会有错误。
下载验证:
将所有文件创建好了之后,编译下载到开发板。用杜邦线将PA.0接到PA.8,串口线连接到USB转接口,给开发板上电。

在PC端口串口助手上配置好参数,打开串口,每隔1秒就会收到开发板发过来的信息,如果没有收到,请检查线路连接是否正常:

通过串口发送命令>>55 aa 02 01 32 (将占空比设置为50%),要注意以16进制发送,可观察到占空比发生了改变:

通过串口发送命令>>55 aa 03 02 00 0a(频率设置为10Hz),频率相应地发生了改变:

本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
