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 <stdio.h>

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轉接口,給開發板上電。

在PC端口串口助手上配置好參數,打開串口,每隔1秒就會收到開發板發過來的信息,如果沒有收到,請檢查線路連接是否正常:

通過串口發送命令>>55 aa 02 01 32 (將佔空比設置爲50%),要注意以16進制發送,可觀察到佔空比發生了改變:

通過串口發送命令>>55 aa 03 02 00 0a(頻率設置爲10Hz),頻率相應地發生了改變:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章