接着上一篇(实验一)高级定时器应用。
高级定时器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
//捕获通道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),频率相应地发生了改变: