接着上一篇(實驗一)高級定時器應用。
實驗二: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 <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),頻率相應地發生了改變: