第五讲——定时器PWM输出+DS3115舵机控制
文章目录
前言
本文是STM32LL库编程系列第五讲,主要分享如何使用定时器输出PWM,本文使用的芯片型号是STM32F407
编程逻辑:手机APP发送数据——>控制板蓝牙接收数据——>DMA传输数据置内存——>到内存提取需要数据——>经过换算转化成PWM占空比——>控制舵机
说明一下,每次遇到新配置我都会详细说明,如果直接个答案,没解析的都是往期文章解析过的,有需要可以考古。我每次工程会开一下搭配外设,比如串口、DMA,如果不需要自行省略。
一、什么是舵机?
舵机是一种电机装置,用来控制机械装置的方向和位置。它通常由电机、传动装置和位置反馈装置组成,可以通过控制信号来精确地控制其位置和角度。舵机通常用来控制舵面、方向盘、摆动臂等部件的运动,从而实现精确的定位和控制。舵机的工作原理类似于普通的电机,但其精度和稳定性更高,适用于需要精确控制的场合。舵机主要分为模拟舵机和数字舵机。
1.模拟舵机和数字舵机的区别
在电路上:数字舵机的控制电路比模拟舵机多了微处理器和晶振,电路设计也更加复杂。
信号驱动上:数字舵机只需发送1次PWM信号就能保持在规定的某个位置,会自动按设定速度转动要控制角度。而模拟舵机是需要多次发送PWM信号才能够保持在规定的位置上,要实现对舵机的控制,还要按照规定的要求速度进行转动。
工作模式:数字舵机分辨率更高,能产生更大的固定力量。模拟舵机是脉冲动力,这种动力实际上每秒传递50次,被调制成开/关脉冲的最大电压,并产生小段小段的动力。
总之数字舵机各方面优于模拟舵机(浅显理解)
二、什么是PWM?
PWM(Pulse Width Modulation,脉宽调制)是一种常用的调制技术,通过改变信号的脉冲宽度来实现对模拟信号的模拟。PWM信号由一个固定频率的周期性脉冲组成,每个周期内包含一个高电平(通常称为"on"状态)和一个低电平(通常称为"off"状态)。通过改变高电平的持续时间(即脉冲宽度),可以实现对信号的调制。
1.PWM输出原理
图 14.1.1 就是一个简单的 PWM 原理示意图。图中,我们假定定时器工作在向上计数 PWM模式,且当 CNT<CCRx 时,输出 0,当 CNT>=CCRx 时输出 1。那么就可以得到如上的 PWM示意图:当 CNT 值小于 CCRx 的时候,IO 输出低电平(0),当 CNT 值大于等于 CCRx 的时候,IO 输出高电平(1),当 CNT 达到 ARR 值的时候,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 输出的占空比,改变 ARR 的值,就可以改变 PWM 输出的频率,这就是 PWM 输出的原理。
根据PWM模式、输出极性、计数方向,共有8种PWM输出状态,如下图所示:
二、使用CubeMX建立工程
这里只说明关于定时器部分的配置介绍,其他外设配置说明见往期文章
首先选择时钟和PWM输出通道,这里用的是内部时钟,选择TIM2的CH2、CH3、CH4输出PWM。
设置参数
TIM2时钟频率为84MHz,预分频设置为84,从而 f(CK_INT) =1MHz。(84-1是因为重装载时会加1)
选择向上计数
计数重装载值设置为5000,则一个周期耗时5000/1000000=5ms,频率为200Hz,DS3115舵机的控制频率为50Hz~330Hz,频率越高舵机响应速度越快,位置控制精度也会更高。同样热量与功耗也会更高。故200Hz足够。
CKD是的定时器时钟 (CK_INT) 频率与数字滤波器所使用的采样时钟 (TIx) 之间的分频比,这里不分频。(上一章有详细解释)
使能计数自动重装载
三个通道PWM输出设置一致
设置PWM模式1
Pulse是用来设置初始占空比,占空比=Pulse/ARR,这里初始为0
Fast mode 顾名思义为快速模式,适合非常高频率PWM开启,这里不需要
输出极性为高
接着设置好
UART5(用于蓝牙数据收发);
UART5_RX的DMA(用于搬运数据,注意开启中断);
USART1(用于串口助手显示);
对以上设置有疑问可以参考往期文章
将PWM输出引脚设置下拉,
对于输入引脚,如果没有外部上下拉,就一定要内部上下拉,避免空闲时误触发,具体上拉还是下拉根据需要的空闲状态而定。而输出引脚可以不上下拉,当然,如果有空闲状态,最好拉置空闲状态。
配置LL库输出,至此CubeMX配置结束(这里跳了很多中间步骤,如果结合往期文章还是无法理解,结尾会给出工程下载地址,可以下载查看)
三、keil工程代码编写
这是DS3115舵机控制逻辑,可知占空比=10%对应0度。占空比=50%对应180度
1.dma.c
编写USART3的DMA接收函数(解析见往文)
/* USER CODE BEGIN 2 */
void uart5_DMA_init(void)
{
//DMA接收配置
LL_DMA_SetPeriphAddress(DMA1, LL_DMA_STREAM_0, (uint32_t)&(UART5->DR));//设置外设地址
LL_DMA_SetMemoryAddress(DMA1, LL_DMA_STREAM_0, (uint32_t)uart5RX_buf);//设置内存地址
LL_DMA_SetDataLength(DMA1, LL_DMA_STREAM_0, 6);//设置接受的数据长度
//清除中断标志位
LL_DMA_ClearFlag_TC0(DMA1);
LL_DMA_EnableIT_TC(DMA1,LL_DMA_STREAM_0);
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_0);//使能DMA数据流
LL_USART_EnableDMAReq_RX(UART5);//启用串口DMA接收模式
}
/* USER CODE END 2 */
这里只所以把数据长度设置为6是因为我蓝牙每个数据帧占6个字节。
分别为:起始位、数据位1、数据位2、数据位3、校验位、停止位
于是每次发送一帧数据就会进入TC中断。
2.tim.c
有一个点差点忘强调了,用cubemx使能了定时器用LL库生成工程,定时器配置函数会警告。报错语句如下。
TIM_InitStruct.Prescaler = 84-LL_TIM_IC_FILTER_FDIV1_N2;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 5000-LL_TIM_IC_FILTER_FDIV1_N2;
原因是数据大小不匹配。Prescaler寄存器是32位的,而LL_TIM_IC_FILTER_FDIV1_N2是16位的,本生这种警告不不会导致程序错误。但问题是LL_TIM_IC_FILTER_FDIV1_N2值有错。通过查找发现LL_TIM_IC_FILTER_FDIV1_N2 = 0x10<<20;与1相差甚远,所以大家自行改回1,如下所示:
TIM_InitStruct.Prescaler = 84-1;
TIM_InitStruct.CounterMode = LL_TIM_COUNTERMODE_UP;
TIM_InitStruct.Autoreload = 5000-1;
3.stm32f4xx_it.c
首先定义参数如下
/* USER CODE BEGIN TD */
unsigned char uart5RX_buf[6]={0};
unsigned char DMA1_TC0;
/* USER CODE END TD */
这里只开启了DMA中断,中断服务函数如下所示:
void DMA1_Stream0_IRQHandler(void)
{
/* USER CODE BEGIN DMA1_Stream0_IRQn 0 */
if(LL_DMA_IsActiveFlag_TC0(DMA1))
{
LL_DMA_ClearFlag_TC0(DMA1);
LL_USART_Disable(UART5);
DMA1_TC0 = 1;
}
/* USER CODE END DMA1_Stream0_IRQn 0 */
/* USER CODE BEGIN DMA1_Stream0_IRQn 1 */
/* USER CODE END DMA1_Stream0_IRQn 1 */
}
进入TC中断后,先清除中断标志位、关闭UART5的使能(很重要,后面解释)、标志位置1。
4.mian.c
首先定义参数如下
/* USER CODE BEGIN 1 */
u16 DS3115_pwm1=500,DS3115_pwm2=500,DS3115_pwm3=1010;
/* USER CODE END 1 */
UART5_DMA初始化别忘了
/* USER CODE BEGIN 2 */
uart5_DMA_init();
/* USER CODE END 2 */
while循环函数
while (1)
{
if(DMA1_TC0)
{
DMA1_TC0 = 0;
//进行输入数据与脉宽的换算,设置的uart5RX_buf[x]=0~100对应脉宽500~2500
DS3115_pwm1 = uart5RX_buf[1]*20+500;
DS3115_pwm2 = uart5RX_buf[2]*20+500;
DS3115_pwm3 = uart5RX_buf[3]*20+500;
//设置上下限限制
if(DS3115_pwm1>2500)
DS3115_pwm1 = 2500;
if(DS3115_pwm1<500)
DS3115_pwm1 = 500;
if(DS3115_pwm2>2500)
DS3115_pwm2 = 2500;
if(DS3115_pwm2<500)
DS3115_pwm2 = 500;
if(DS3115_pwm3>2500)
DS3115_pwm3 = 2500;
if(DS3115_pwm3<500)
DS3115_pwm3 = 500;
//打印到串口助手输出
printf("DS3115_pwm1 = %d ",DS3115_pwm1);
printf("DS3115_pwm2 = %d ",DS3115_pwm2);
printf("DS3115_pwm3 = %d\n",DS3115_pwm3);
//将计算好的占空比设置给各通道PWM
LL_TIM_OC_SetCompareCH2(TIM2,DS3115_pwm1);
LL_TIM_OC_SetCompareCH3(TIM2,DS3115_pwm2);
LL_TIM_OC_SetCompareCH4(TIM2,DS3115_pwm3);
//使能UART5和DMA数据流
LL_USART_Enable(UART5);
LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_0);//使能DMA数据流
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
当我发送一个数据到蓝牙,数据存入UART5_DR寄存器,同时DMA会将数据从UART5_DR转移到uart5RX_buf[6]数组中,6个字节的数据都转移结束进入TC中断,从而DMA1_TC0 = 1,进入if函数,将uart5RX_buf数组中的值换算成PWM设置给对应通道同时设置DMA1_TC0 = 0,这样一个控制流程就完成了,等待下次数据到来开启下一个轮回。
当DMA设置的字节个数全部传输完成,会自动失能DMA该通道数据流,所以每次需要使用LL_DMA_EnableStream(DMA1, LL_DMA_STREAM_0);
重新使能,才能开启下一个轮回。如果不对UART5进行同步的使能和失能处理会导致DMA“关机”错误,需要重新复位程序才能“开机”。
当串口的接收寄存器中已经接收到一帧数据,但数据寄存器中的数据没有及时被DMA转移走,而后面的数据又来了,会导致后面的数据无法存入,从而产生了上溢错误(ORE),而一旦产生上溢错误后,就无法再触发 DAM 请求,及时之后再启动 DMA 也不行,无法触发 DMA 请求就无法将数据寄存器内的数据及时转移走,如此陷入“关机”。
也就是说在if函数里,DMA是关闭的,而UART5是开启的,在这段函数运行时间内,如果继续给蓝牙发送数据,就会进入DMA“关机”,故if函数里也要把UART5关闭,这也是为什么在TC中断服务函数里失能UART5的原因。(花了大量时间才找到这个错误,忘珍惜)。
四、效果展示
手机APP《蓝牙调试器》自定义界面如图,这款APP挺好用的。
第五讲定时器PWM输出+DS3115舵机控制
五、工程下载
链接:https://pan.baidu.com/s/188rcXj0lLwKiDcqt22Aomg?pwd=1234
提取码:1234
尾言
时间匆忙,有错误之处还望大家指出,(可能有些错别字,不影响阅读就好)