stm32之TIM-高级定时器应用实例二(测量频率和占空比)

接着上一篇(实验一)高级定时器应用。

实验二:PWM输入捕捉实验

实验要求:

高级定时器TIM1接收TIM2产生的PWM,TIM1测量PWM的频率和占空比,并将数据从UART1上发送到上位机,同时上位机通过发送命令改变PWM的占空比和频率。

硬件设计:

       用杜邦线连接TIM2通道一(PA.0)引脚与TIM1通道一(PA.8)引脚。PA.9是USART1的输出引脚,PA.10是USART1的接收引脚,分别接到串口转接板的RXD、TXD。

实验步骤:

  1. 初始化USART1,用于与PC端通信
  2. 初始化通用定时器TIM2和高级定时器TIM1,前者产生PWM,后者捕获PWM,杜邦线桥接。
  3. 初始化SysTick系统滴答计时器,用于任务周期管理
  4. 创建一个数据接收队列,接收来自PC端的串口数据
  5. 根据报文的格式,解析接收队列里面的数据

         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
	
    //捕获通道IC1
    TIM_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 1ms

void 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    0x02

typedef 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转接口,给开发板上电。

stm32之TIM-高级定时器应用实例二(测量频率和占空比)_第1张图片

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

stm32之TIM-高级定时器应用实例二(测量频率和占空比)_第2张图片

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

stm32之TIM-高级定时器应用实例二(测量频率和占空比)_第3张图片

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

stm32之TIM-高级定时器应用实例二(测量频率和占空比)_第4张图片

你可能感兴趣的:(STM32应用实例)