当前位置: 首页 > news >正文

CubeMx笔记 --pwm输出+输入捕获

文章目录

    • 1、PWM制作呼吸灯
      • 1.1、cubemx的设置
      • 1.2、keil设置
    • 2、上升沿捕获
      • 2.1、控制寄存器
      • 2.2、事件生成寄存器
      • 2.3、从模式控制寄存器
      • 2.4、CubeMx设置
      • 2.5、捕获上升沿
    • 3、捕获高电平时间
      • 3.1、位运算
      • 3.2、思路
      • 3.3、代码
      • 3.4、关于自动重装载值
    • 4、捕获方波的周期,占空比
      • 4.1、思路
      • 4.2、代码

所用芯片:stm32f429
本文实现了(包含cubemx的配置)
1、利用PWM制作呼吸灯:定时器3通道4 (1.2)
2、定时器捕获按键的高电平时间:定时器5通道1(3.2)
3、定时器捕获方波的周期和占空比:定时器5通道1(4.2)
参考:
正点原子:STM32F429开发指南-HAL库版本_V1.1
自动重装载值

1、PWM制作呼吸灯

1.1、cubemx的设置

在这里插入图片描述

  • pwm:
    脉冲宽度调制,是英文“Pulse Width Modulation” 的缩写,简称脉宽调制
  • 占空比:
    在一个脉冲周期,通电时间相对于总时间所占的比例 占空比 = Pulse(脉冲时长)/Counter Period(重装载值)
  • 脉冲周期
    T= (89+1)( 499+1))/90Mhz=500us 90Mhz是定时器对应的时钟频率
  • 将tim3的通道4复用到PB1

在这里插入图片描述

在这里插入图片描述

1.2、keil设置

其他代码用cubemx自动生成的就行

uint8_t chang = 0;
uint32_t pulse = 0;//设置占空比
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);//启动pwm输出

while (1)
{
	if(change == 0)
	{
		pulse++;
		if(pulse >= 300)
			{
				change = 1;
			}
	}
	else
	{
		pulse--;
		if(pulse <= 1)
			{
				change = 0;
			}
	}
	
	HAL_Delay(10);//控制自加的速度
	TIM3->CCR4 = pulse;//设置寄存器 通道4
}

2、上升沿捕获

2.1、控制寄存器

TIMx_CR1 控制寄存器 1
TIMx control register 1
在这里插入图片描述

  • 位 9:8 CKD:
    时钟分频 (Clock division)
    此位域指示定时器时钟 (CK_INT) 频率与数字滤波器所使用的采样时钟( ETR、TIx)之间的
    分频比,
    00: tDTS = tCK_INT
    01: tDTS = 2 × tCK_INT
    10: tDTS = 4 × tCK_INT
    11:保留
  • 位 7 ARPE:
    自动重载预装载使能 (Auto-reload preload enable)
    0: TIMx_ARR 寄存器不进行缓冲
    1: TIMx_ARR 寄存器进行缓冲

TIMx 控制寄存器 2 (TIMx_CR2)
在这里插入图片描述

  • 位 6:4 MMS:主模式选择
    (Master mode selection)
    这些位可选择主模式下将要发送到从定时器以实现同步的信息 (TRGO)。这些位的组合如下:
    000: 复位––TIMx_EGR 寄存器中的 UG 位用作触发输出 (TRGO)。如果复位由触发输入
    生成(从模式控制器配置为复位模式),则 TRGO 上的信号相比实际复位会有延迟。
    001: 使能––计数器使能信号 (CNT_EN) 用作触发输出 (TRGO)。该触发输出可用于同时
    启动多个定时器,或者控制在一段时间内使能从定时器。计数器使能信号可由CEN控制位产
    生。当配置为门控模式时,也可由触发输入产生。
    当计数器使能信号由触发输入控制时, TRGO 上会存在延迟,选择主/从模式时除外(请参见
    TIMx_SMCR 寄存器中 MSM 位的说明)。
    010: 更新––选择更新事件作为触发输出 (TRGO)。例如,主定时器可用作从定时器的预分
    频器。
    011: 比较脉冲––一旦发生输入捕获或比较匹配事件,当 CC1IF 被置 1 时(即使已为高电
    平),触发输出都会发送一个正脉冲 (TRGO)。 (TRGO)
    100: 比较––OC1REF 信号用作触发输出 (TRGO)
    101: 比较––OC2REF 信号用作触发输出 (TRGO)
    110: 比较––OC3REF 信号用作触发输出 (TRGO)
    111: 比较––OC4REF 信号用作触发输出 (TRGO)

2.2、事件生成寄存器

TIMx event generation register
在这里插入图片描述

  • 位 0 UG:
    更新生成 (Update generation)
    该位可通过软件置 1,并由硬件自动清零。
    0:不执行任何操作
    1:重新初始化计数器并生成寄存器更新事件。
    请注意,预分频器计数器也将清零(但预分频比不受影响)。如果选择中心对齐模式或 DIR=0(递增计数),计数器将清零;如果 DIR=1(递减计数),计数器将使用自动重载值 (TIMx_ARR)。
  • 计数器使能信号 (CNT_EN)

2.3、从模式控制寄存器

TIMx_SMCR
TIMx slave mode control register
在这里插入图片描述

  • 位 7 MSM:
    主/从模式 (Master/Slave mode)
    0:不执行任何操作
    1:当前定时器的触发输入事件( TRGI)的动作被推迟,以使当前定时器与其从定时器实现
    完美同步(通过 TRGO)。此设置适用于单个外部事件对多个定时器进行同步的情况。

2.4、CubeMx设置

其他的看上面的寄存器
在这里插入图片描述

2.5、捕获上升沿

当检测到PA0上升沿就触发中断
定时器5
在这里插入图片描述

  • 开启中断

在这里插入图片描述

  • 配置PA0

在这里插入图片描述
在这里插入图片描述
用cubemx生成代码后
KEY_UP的初始化在HAL_TIM_Base_MspInit里面

  • 开启中断和捕获中断
HAL_TIM_Base_Start_IT(&htim5);
HAL_TIM_IC_Start_IT(&htim5, TIM_CHANNEL_1);

重写捕获中断函数

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);//点灯
}

当按下key_up bs0会被点亮

3、捕获高电平时间

cubemx的配置和2.5一样需要重写两个中断函数

3.1、位运算

在这里插入图片描述

GPIOA->ODR &= 0XFF0F; //将第 4-7 位清 0
GPIOA->ODR |= 1 << 5;//将 ODR 寄存器的第 5 位设置为 1
GPIOA->ODR &= (uint16_t)~(1<<3);//将第 3 位置 0

3.2、思路

  • 计数器
    TIM5 每刷新一个周期 计数器 TIM5_CNT就+1
    如:TIM5的频率为 1Mhz 则每过1us TIM5_CNT++;
  • 大概的流程
    在这里插入图片描述

3.3、代码

下面是正点原子官方的例程,和我上面的逻辑有点出入,但实际执行效果差不多
注释我改了一部分,方便自己理解,但捕获高电平其实不用这么麻烦

//可以写在主函数,也可以在time.c
/*
	sta当前的捕获状态: 
	[14]0x4000 =1表示捕获到了上升沿 现在待测量是高电平
	[15]0x8000 =1表示捕获到了下降沿 现在待测量是低电平,此时第6位依然是1
	[0~13] 用于记录计数器溢出次数

	val记录计数器的值
*/
uint16_t tim5_ch1_cap_sta = 0;
uint32_t tim5_ch1_cap_val;

//定时器捕获中断回调函数
//第1次捕获的是上升沿
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim5)
		{
			HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);//点亮或关闭DS1
			
			if((tim5_ch1_cap_sta&0x8000) == 0)
				{
					if((tim5_ch1_cap_sta&0x4000))//捕获到了下降沿
						{
							tim5_ch1_cap_sta |= 0x8000;
							//获取当前计数器的值
							tim5_ch1_cap_val=HAL_TIM_ReadCapturedValue(&htim5,TIM_CHANNEL_1);
							//清除之前的沿触发捕获模式
							TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1);
							//设置为上升沿触发
							TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);
						}
					else//捕获到了上升沿
						{
							/*重启定时器5*/
							//关闭
							__HAL_TIM_DISABLE(&htim5);  
							//计数器清0
							__HAL_TIM_SET_COUNTER(&htim5,0);
							//清除之前的沿触发捕获模式
							TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1);
							//定时器5通道1设置为下降沿捕获
							TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);
							//使能定时器5
							__HAL_TIM_ENABLE(&htim5);
							
							tim5_ch1_cap_sta = 0;
							tim5_ch1_cap_val = 0;
							tim5_ch1_cap_sta |= 0x4000;
						}
				}
		}
}

//计数器溢出中断回调函数(TIM5_CNT 只有16位)
//计数器记录的是自己的执行了多少个周期 如:1Mhz 1us计数器+1
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim5)
		{
			if((tim5_ch1_cap_sta&0x8000) == 0)
				{
					if((tim5_ch1_cap_sta&0x4000))
						{
							if((tim5_ch1_cap_sta&0x3fff) == 0x3fff)//记录溢出次数过多(高电平太长)
								{
									tim5_ch1_cap_sta |= 0x8000;//强行表示高电平结束
									tim5_ch1_cap_val &= 0xffffffff;
								}
							else
								{
									tim5_ch1_cap_sta++;
								}
						}
				}
		}
}

/*主函数测试部分代码*/
long long temp;
while (1)
{
	if((tim5_ch1_cap_sta&0x8000))//确认当前为低电平
		{
			u1_printf("Capture ok\r\n");
			temp = tim5_ch1_cap_sta&0x3fff;
			temp = temp*65535;
			temp = temp + tim5_ch1_cap_val;
			
			u1_printf("%.2lf s\r\n",(double)temp/(1000*1000));
			HAL_Delay(1000);
			break;
		}
}
u1_printf("done\r\n");

按住PA0几秒钟再松开
有个0.1s左右的误差,拿手机测的
在这里插入图片描述
定时器5用的这个时钟,只需要把预分频(PSC)设置为89就可以实现1us 计数器+1
在这里插入图片描述

3.4、关于自动重装载值

自动重载寄存器 ARR 用来存放与计数器 CNT 比较的值,如果两个值相等,
对于高级定时器,就递减重复计数器,当重复计数器减为零时就产生更新或中断。
如果没有使用到重复计数器时,就直接产生更新和中断。
对于基本定时器和通用定时器,也就产生更新和中断。

在这里插入图片描述
Counter Period的值也就是计数器 CNT 的值
这个后面还有个括号中(AutoReload Register …),其实也就是设置自动重载寄存器的值 ARR。
下面还有个auto-reload preload (自动重装载预装载),一般就直接Disable
都装载好了,而且一般也不轻易改变这个值,所以我也用不到auto-reload preload。

貌似改变自动重装载值不会影响2.6的结果

4、捕获方波的周期,占空比

只是自己写着玩,并没有真的拿方波测试过

4.1、思路

在这里插入图片描述

4.2、代码

配置那些和2.4一样 依旧定时器5 通道1
主要是两个中断处理函数中写逻辑

//定时器沿变化捕获中断
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim);
//定时器计数器溢出中断
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);

具体的过程注释写得很详细

/*
	tim5_ch1_cap_sta:16位
	把变量当寄存器用表示当前的捕获状态:
	[12]0x1000 表示开启第一个上升沿捕获处理 1 0000 0000 0000
	[13]0x2000 表示捕获到了一个周期 10 0000 0000 0000
	[14]0x4000 表示捕获到了上升沿 开启下降沿捕获处理 100 0000 0000
	[15]0x8000 表示捕获到了下降沿 开启上升沿捕获处理 1000 00000 0000 0000 
	[0~11] 用于记录计数器溢出次数 0000 0000 0000 - 1111 1111 1111
*/
uint16_t tim5_ch1_cap_sta = 0;
uint32_t tim5_ch1_cap_val;//记录捕获周期结束时计数器的值
uint32_t tim5_ch1_cap_hoverflow;//记录高电平溢出次数
uint32_t tim5_ch1_cap_hval = 0;//记录高电平结束时计数器的值

//定时器捕获中断回调函数
//第1次捕获的是上升沿
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim5)
		{
			//HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin);//绿灯
			if(tim5_ch1_cap_sta&0x4000)//捕获到了下降沿,高电平结束
				{
					tim5_ch1_cap_sta &= ~0x4000;//关闭下降沿捕获处理
					//获取高电平时间
					tim5_ch1_cap_hoverflow = (tim5_ch1_cap_sta&0xfff);
					tim5_ch1_cap_hval=HAL_TIM_ReadCapturedValue(&htim5,TIM_CHANNEL_1);
					
					//清除之前的沿触发捕获模式
					TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1);
					//设置为上升沿触发
					TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_RISING);
					
					tim5_ch1_cap_sta |= 0x8000;
				}
			else if(tim5_ch1_cap_sta&0x8000)//第2个上升沿,捕获结束
				{
					tim5_ch1_cap_sta &= ~0x8000;
				    //获取计数器的值
					tim5_ch1_cap_val=HAL_TIM_ReadCapturedValue(&htim5,TIM_CHANNEL_1);
					tim5_ch1_cap_sta|= 0x2000;//表示已经捕获到一个周期
				}
			//第1个上升沿,捕获开始
			else if(tim5_ch1_cap_sta&0x1000)
				{
					//初始化
					tim5_ch1_cap_sta = 0;
					tim5_ch1_cap_val = 0;
					tim5_ch1_cap_hval = 0;

					/*重启定时器5*/
					//失能定时器5
					__HAL_TIM_DISABLE(&htim5);  
					//计数器清0
					__HAL_TIM_SET_COUNTER(&htim5,0);
					
					TIM_RESET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1);
					TIM_SET_CAPTUREPOLARITY(&htim5,TIM_CHANNEL_1,TIM_ICPOLARITY_FALLING);
					
					//使能定时器5
					__HAL_TIM_ENABLE(&htim5);
					
					tim5_ch1_cap_sta |= 0x4000;
				}
		}
}

//计数器溢出中断回调函数(TIM5_CNT 只有16位)
//计数器记录的是自己的执行了多少个周期 如:1Mhz 1us计数器+1
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == &htim5)
		{
			if((tim5_ch1_cap_sta&0x2000) == 0)
				{
					if((tim5_ch1_cap_sta&0xfff) == 0xfff)//记录溢出次数过多
						{
							tim5_ch1_cap_sta &= 0x2000;//强行关闭捕获
							tim5_ch1_cap_val &= 0xffffffff;
						}
					else
						{
							tim5_ch1_cap_sta++;
						}
				}
		}
}
  • 测试
long long period;//周期
long long duty_ratio;//占空比
tim5_ch1_cap_sta |= 0x1000;//开启捕获
while (1)
{
	if(tim5_ch1_cap_sta&0x2000)
	{
		u1_printf("capture ok\r\n");
		
		period =tim5_ch1_cap_sta&0xfff;
		period = period*65535;
		period = period + tim5_ch1_cap_val;
		
		u1_printf("周期:%.2lf s\r\n",((double)(period/1000))/1000);
		
		duty_ratio = tim5_ch1_cap_hoverflow&0xfff;
		duty_ratio = duty_ratio*65535;
		duty_ratio = duty_ratio + tim5_ch1_cap_hval;
		
		u1_printf("占空比:%.2lf %%\r\n",(double)(100.0*duty_ratio/period));
		break;
	}
}

在这里插入图片描述

理论上这个可以捕获小于1Mhz的方波,我按按键来模拟方波测试没问题

相关文章:

  • 轻松玩转树莓派Pico之一、新手上路
  • 目前我国网络安全人才市场状况
  • Redis源码解读之用RedisAe实现一个简单的HTTP服务器
  • 【极简python】第一章 print与变量
  • HAL库与Cubemx\rt-thread Nano系列教程-01-新建HAL工程及移植RT-Nano到Alios Developer Kit
  • 论文阅读_知识蒸馏_MobileBERT
  • No2.搭建基本的资源端解析token(资源服务端)
  • Vue入门【四】-- 事件机制与双向数据绑定
  • 小型超市管理系统的设计与实现 毕业设计-附源码011136
  • R语言缺失时间序列的填充:补齐时间序列数据中所有缺失的时间索引、使用na.locf函数将缺失值NA替换为前序时刻最近的值
  • 26.STM32 SPI通信接口
  • [JS] node.js 入门
  • 卸载mysq并重新安装教程
  • 合并后 ETH 的供应变化以及是否会出现通缩
  • 装饰器模式【Java设计模式】
  • 【node学习】协程
  • 【刷算法】求1+2+3+...+n
  • E-HPC支持多队列管理和自动伸缩
  • ES2017异步函数现已正式可用
  • java B2B2C 源码多租户电子商城系统-Kafka基本使用介绍
  • mysql外键的使用
  • Phpstorm怎样批量删除空行?
  • Redis字符串类型内部编码剖析
  • vue-cli3搭建项目
  • vue从入门到进阶:计算属性computed与侦听器watch(三)
  • 复杂数据处理
  • 工作手记之html2canvas使用概述
  • 基于axios的vue插件,让http请求更简单
  • 基于遗传算法的优化问题求解
  • 区块链分支循环
  • 使用Envoy 作Sidecar Proxy的微服务模式-4.Prometheus的指标收集
  • 我的面试准备过程--容器(更新中)
  • 在 Chrome DevTools 中调试 JavaScript 入门
  • ​ArcGIS Pro 如何批量删除字段
  • ​虚拟化系列介绍(十)
  • ​学习一下,什么是预包装食品?​
  • (cos^2 X)的定积分,求积分 ∫sin^2(x) dx
  • (紀錄)[ASP.NET MVC][jQuery]-2 純手工打造屬於自己的 jQuery GridView (含完整程式碼下載)...
  • (三维重建学习)已有位姿放入colmap和3D Gaussian Splatting训练
  • (转)Android学习笔记 --- android任务栈和启动模式
  • (转载)Linux网络编程入门
  • .aanva
  • .gitignore文件—git忽略文件
  • .Net 4.0并行库实用性演练
  • .net core 实现redis分片_基于 Redis 的分布式任务调度框架 earth-frost
  • .NET 命令行参数包含应用程序路径吗?
  • .NET 中创建支持集合初始化器的类型
  • .net快速开发框架源码分享
  • .net通用权限框架B/S (三)--MODEL层(2)
  • @DataRedisTest测试redis从未如此丝滑
  • @RestControllerAdvice异常统一处理类失效原因
  • [ C++ ] STL priority_queue(优先级队列)使用及其底层模拟实现,容器适配器,deque(双端队列)原理了解
  • [C#]C# OpenVINO部署yolov8图像分类模型
  • [iOS]把16进制(#871f78)颜色转换UIColor
  • [Java]深入剖析常见排序