使用STM32测量脉宽可变的PWM波的脉冲宽度

最近受疫情影响导致我莫得办法出去玩,打游戏一不小心又给打通关了就只能找点东西玩玩了,所以就有了下面这篇文章。。。。。。搞这个东西的时候遇见一些好玩的问题,我写在第6部分,希望能帮到看到这篇小文章的同志们

1.硬件平台:stm32f103zet6,正点原子的精英板
2.使用到的硬件:定时器3,定时器5,串口1,按键
3.描述:使用定时器3产生一个pwm波,占空比可通过串口调试助手调整。把pwm波通过IO和杜邦线输入到定时器5的输入捕获通道以测量pwm波的高电平持续时间,单位为us
4.主要代码

(1).main.c

//1.main.c
#include "stm32f10x.h"
#include "timer.h"
#include "usart.h"
#include "delay.h"
int main(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	uart_init(115200);
	timer3_ch2_pwm_out(999,7199);//定时器2时钟10khz,溢出时间为100ms,pwm波周期为100ms
	TIM_SetCompare2(TIM3,499);//占空比50%,高电平时间为50ms
	timer5_ch1_capture(0XFFFF,71);//定时器3时钟1Mhz,记一个数为1us,溢出时间为65536us
	while(1)
	{
		if(USART_RX_STA&0x8000) 
		{
			USART_RX_STA=0;//请注意这里,当字符串接收完成后(即USART_RX_STA&0x8000=1,在system下的usart.c内有相关代码)请在接收中断函数或main内将USART_RX_STA置零,否则将无法完成下次接收
		}
		//一般情况下在这里都会写个delay_ms(10),但是这里延时之后会导致上面的if来不及执行,就会使得无法完成下次的串口接收
	}
}

(2).timer.c

#include "timer.h"
#include "sys.h"
void timer3_ch2_pwm_out(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitType;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitType;
	TIM_OCInitTypeDef TIM_OCInitType;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_AFIO,ENABLE);
	GPIO_PinRemapConfig(GPIO_FullRemap_TIM3, ENABLE);//部分重映射对应PB5,完全重映射对应PC7,不映射对应PA7,但是PA7并不能生成PWM波,其余两个都可以
	GPIO_InitType.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitType.GPIO_Pin=GPIO_Pin_7;
	GPIO_InitType.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOC,&GPIO_InitType);
	
	TIM_TimeBaseInitType.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitType.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitType.TIM_Period=arr;
	TIM_TimeBaseInitType.TIM_Prescaler=psc;
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitType);
	
	TIM_OCInitType.TIM_OCMode=TIM_OCMode_PWM2;
	TIM_OCInitType.TIM_OCPolarity=TIM_OCPolarity_High;
	TIM_OCInitType.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OC2Init(TIM3,&TIM_OCInitType);
	TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM3,ENABLE);//使占空比的调整在本周期立即生效
	
	TIM_Cmd(TIM3,ENABLE);

}
	
	
	
	
void timer5_ch1_capture(u16 arr,u16 psc)
{
	GPIO_InitTypeDef GPIO_InitType;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseInitType;
	TIM_ICInitTypeDef TIM_ICInitType;
	NVIC_InitTypeDef NVIC_InitType;
	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5,ENABLE);
	//RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	GPIO_InitType.GPIO_Mode=GPIO_Mode_IPD;
	GPIO_InitType.GPIO_Pin=GPIO_Pin_0;
	GPIO_InitType.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitType);
	
	TIM_TimeBaseInitType.TIM_ClockDivision=TIM_CKD_DIV1;
	TIM_TimeBaseInitType.TIM_CounterMode=TIM_CounterMode_Up;
	TIM_TimeBaseInitType.TIM_Period=arr;
	TIM_TimeBaseInitType.TIM_Prescaler=psc;
	TIM_TimeBaseInit(TIM5,&TIM_TimeBaseInitType);
	
	TIM_ICInitType.TIM_Channel=TIM_Channel_1;
	TIM_ICInitType.TIM_ICFilter=0x0;
	TIM_ICInitType.TIM_ICPolarity=TIM_ICPolarity_Rising;
	TIM_ICInitType.TIM_ICPrescaler=TIM_ICPSC_DIV1;
	TIM_ICInitType.TIM_ICSelection=TIM_ICSelection_DirectTI;
	TIM_ICInit(TIM5,&TIM_ICInitType);
	
	NVIC_InitType.NVIC_IRQChannel=TIM5_IRQn;
	NVIC_InitType.NVIC_IRQChannelCmd=ENABLE;
	NVIC_InitType.NVIC_IRQChannelPreemptionPriority=1;
	NVIC_InitType.NVIC_IRQChannelSubPriority=1;
	NVIC_Init(&NVIC_InitType);
	
	TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);
	
	TIM_Cmd(TIM5,ENABLE);

}

u8 timer3_ch2_capture_sta=0;//该变量的位0指示上次是否检测到了高电平,1代表检测到高电平;位1表示是否完成了一次先检测高电平再检测一次低电平的过程,1代表完成了
u8 overFlowTimer=0;
u16 timer3_ch2_capture_val;

void TIM5_IRQHandler()
{
		if(TIM_GetITStatus(TIM5,TIM_IT_Update)==SET)//定时器3溢出
		{
			if(timer3_ch2_capture_sta&0x01)
			{
				if(overFlowTimer>=0xFF)//溢出次数到最大值,强制结束本次高电平维持时间测量
				{
					timer3_ch2_capture_sta|=0x02;//标记本次捕获完成,当然本行可以不要
					timer3_ch2_capture_val=0xffff;
					timer3_ch2_capture_sta=0;
					printf("高电平为%d微秒,到达最大时间范围\n",256*65536+timer3_ch2_capture_val);
				}else
				{
					overFlowTimer++;
				}
			}
		}

	
	
	if(TIM_GetITStatus(TIM5,TIM_IT_CC1)==SET)
	{
		if(timer3_ch2_capture_sta&0x01)//符合条件的话说明上次捕获了高电平,那么这次捕获的一定是低电平
		{
			timer3_ch2_capture_sta|=0x02;//标记本次捕获完成,当然本行可以不要
			timer3_ch2_capture_val=TIM_GetCapture1(TIM5);
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising);
			timer3_ch2_capture_sta=0;//清掉标志位准备开始下一次上升沿和下降沿检测
			printf("高电平为%d微秒\n",overFlowTimer*65536+timer3_ch2_capture_val);
		}else
		{
			//timer3_ch2_capture_sta=0;
			overFlowTimer=0;
			timer3_ch2_capture_val=0;
			TIM_SetCounter(TIM5,0);//以上为清零
			
			timer3_ch2_capture_sta|=0x01;//高电平指示被赋值
			TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //当捕获上升沿后改为捕获下降沿
		}
	}
	TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位,一定不要忘,要不然下次进不了中断
}

(3).usart.c
该部分是在正点原子的源码基础上改动的,改动的地方我都标注了

#include "sys.h"
#include "usart.h"	  
#include "stm32f10x.h"
#include "stdlib.h"//我自己加的
#include "string.h"//我自己加的
////////////////////////////////////////////////////////////////////////////////// 	 
//如果使用ucos,则包括下面的头文件即可.
#if SYSTEM_SUPPORT_OS
#include "includes.h"					//ucos 使用	  
#endif
//////////////////////////////////////////////////////////////////////////////////	 
//本程序只供学习使用,未经作者许可,不得用于其它任何用途
//ALIENTEK STM32开发板
//串口1初始化		   
//正点原子@ALIENTEK
//技术论坛:www.openedv.com
//修改日期:2012/8/18
//版本:V1.5
//版权所有,盗版必究。
//Copyright(C) 广州市星翼电子科技有限公司 2009-2019
//All rights reserved
//********************************************************************************
//V1.3修改说明 
//支持适应不同频率下的串口波特率设置.
//加入了对printf的支持
//增加了串口接收命令功能.
//修正了printf第一个字符丢失的bug
//V1.4修改说明
//1,修改串口初始化IO的bug
//2,修改了USART_RX_STA,使得串口最大接收字节数为2的14次方
//3,增加了USART_REC_LEN,用于定义串口最大允许接收的字节数(不大于2的14次方)
//4,修改了EN_USART1_RX的使能方式
//V1.5修改说明
//1,增加了对UCOSII的支持
////////////////////////////////////////////////////////////////////////////////// 	  
 

//////////////////////////////////////////////////////////////////
//加入以下代码,支持printf函数,而不需要选择use MicroLIB	  
#if 1
#pragma import(__use_no_semihosting)             
//标准库需要的支持函数                 
struct __FILE 
{ 
	int handle; 

}; 

FILE __stdout;       
//定义_sys_exit()以避免使用半主机模式    
_sys_exit(int x) 
{ 
	x = x; 
} 
//重定义fputc函数 
int fputc(int ch, FILE *f)
{      
	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
    USART1->DR = (u8) ch;      
	return ch;
}
#endif 

/*使用microLib的方法*/
 /* 
int fputc(int ch, FILE *f)
{
	USART_SendData(USART1, (uint8_t) ch);

	while (USART_GetFlagStatus(USART1, USART_FLAG_TC) == RESET) {}	
   
    return ch;
}
int GetKey (void)  { 

    while (!(USART1->SR & USART_FLAG_RXNE));

    return ((int)(USART1->DR & 0x1FF));
}
*/
 
#if EN_USART1_RX   //如果使能了接收
//串口1中断服务程序
//注意,读取USARTx->SR能避免莫名其妙的错误   	
u8 USART_RX_BUF[USART_REC_LEN];     //接收缓冲,最大USART_REC_LEN个字节.
//接收状态
//bit15,	接收完成标志
//bit14,	接收到0x0d
//bit13~0,	接收到的有效字节数目
u16 USART_RX_STA=0;       //接收状态标记	  
  
void uart_init(u32 bound){
  //GPIO端口设置
  GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9
   
  //USART1_RX	  GPIOA.10初始化
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
  GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

  //Usart1 NVIC 配置
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置

	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	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); //初始化串口1
  USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
  USART_Cmd(USART1, ENABLE);                    //使能串口1 

}

int temp;//我自己加的,非正点原子源码

void USART1_IRQHandler(void)                	//串口1中断服务程序
	{
	u8 Res;
#if SYSTEM_SUPPORT_OS 		//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntEnter();    
#endif
	if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //接收中断(接收到的数据必须是0x0d 0x0a结尾)
		{
		Res =USART_ReceiveData(USART1);	//读取接收到的数据
		
		if((USART_RX_STA&0x8000)==0)//接收未完成
			{
			if(USART_RX_STA&0x4000)//接收到了0x0d
				{
				if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
					else {
					//以下为我自己加的,非正点原子的源码
					temp=atoi(USART_RX_BUF);
					if(temp>=1&&temp<=999)
					{
						TIM_SetCompare2(TIM3,temp);
						printf("PWM ARR寄存器为999,OC寄存器为%d\n",temp);
					}
					
					USART_RX_STA|=0x8000;	//接收完成了
					memset(USART_RX_BUF,0,sizeof(USART_RX_BUF));
					//以上为我自己加的,非正点原子的源码
					}
				}
			else //还没收到0X0D
				{	
				if(Res==0x0d)USART_RX_STA|=0x4000;
				else
					{
					USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
					USART_RX_STA++;
					if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收	  
					}		 
				}
			}   		 
     } 
#if SYSTEM_SUPPORT_OS 	//如果SYSTEM_SUPPORT_OS为真,则需要支持OS.
	OSIntExit();  											 
#endif
} 
#endif	


MDK5工程结构如下(使用的是正点原子提供的新建工程模板)
在这里插入图片描述
5.测试
程序最初设定的PWM波的周期是100ms,占空比为50%,也就是说理论上高电平持续时间为50000us。初始状态下的测试结果如下(PC7连接PA0):
在这里插入图片描述
与理论值还是很接近的。接下来再使用串口调试助手改变占空比试试看,最开始时CCR2的值是499,这里我设定的能输入的范围是1到999。测试结果如下:
在这里插入图片描述
可以看到当输出比较值被设定为1后高电平几乎就是100ms,与理论很接近。
在这里插入图片描述
如上图,当输出比较寄存器的值被设定为999后高电平脉宽持续时间几乎为0,与理论相符合。

我使用的捕获通道是定时器5的通道1,连接在PA0,恰好PA0上面还连接一个按下为高电平输入的按键,所以按按键也能测试。在前面的测试中考虑了高电平持续时间过长会溢出的问题,所以专门准备了一个变量来记录溢出的次数,根据溢出的次数和捕获通道计时器的值之和能得到准确的持续时间。定时器5的溢出时间是63356us,接下来试试看在有溢出的情况下能否测准:
在这里插入图片描述
图中倒数第二次我一直按下按键不松手,到了大概十六秒多的时候自动结束测量,这是因为记录溢出次数的变量是八位的,定时器的值是16位的,那么最大测量时间就是256*65536+0xffff,理论值是16842752us,与实测数据仅相差1us。如果换个16位的变量记录溢出次数那记录的时间范围将大大延长。大家如果要试验的话可以使用别的定时器或串口,修改起来也是很简单的

6.遇到的一些问题
(1.)当pwm波出不来的时候先检查两遍用的定时器的配置,当找不着问题的时候不要怀疑自己,有可能代码没写错,可能只是硬件上由于IO连接了其他片内外设导致的。就比如我使用的定时器3的通道2,如果不重映射或者不部分重映射的话对应的IO引脚输出不了pwm波。遇见这种情况建议换个通道,或者换个定时器,或者是映射一下试试看,总比看着代码干瞪眼强。
(2.)测时间的时候不要只读取一个捕获通道的计数器值就完事了,如果不溢出的话那还是准确的,如果高电平比较长,那就不准确了。
(3.)定时器5的中断服务函数里一定要手动清除中断标志位,要不然下次就进不去中断了。
(4.)一般我们习惯在main.c的while(1)里放一个延时,但这个延时可能会导致某些代码来不及执行。就比如把我的while(1)里放个delay_ms(10)会导致上面的if语句来不及执行从而导致在第一次接收字符串完成后下次能进接收中断但却无法接收,因为USART_RX_STA没有被清掉(大概是因为定时器5的时钟频率比较高导致的吧)

这些我在注释里也有写,大家看代码的时候希望能注意一下
关于代码有哪里看不懂的话可以留言,如果我看到的话会尽量回复。(我也只是个STM32的半吊子,如果能帮到大家的话还是会尽量帮的)
完整工程:https://download.csdn.net/download/naruhina/12124097

发布了74 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/naruhina/article/details/104108063