STM32 HAL库 PPM信号及解析
什么是PPM信号
PPM(Pulse Position Modulation,脉冲位置调制,又称脉位调制)。波形图如下:
如上图,PPM信号标准刷新率为50HZ(周期为20ms)。PPM信号把多路PWM信号调制到一路通道上,发送到接收机后再由接收机还原成多路PWM从各个通道输出。
注意:各个通道的高电平信号是一个紧挨着一个的,而不是每个通道固定分配2ms的时间。
由于单路信号最长是2000us,周期20ms,所以理论上可以容纳10路。而由于需要进行同步,实际上遥控器最多只能容纳9路信号。
接收机输出的每帧信号(20ms)里,理论上最后必然有至少2ms的时间里,所有的通道都输出低电平,单片机解码时就是利用这一点来判断一帧信号结束的。
STM32解析PPM信号
通过观察PPM信号的波形图,不难发现,相邻两个上升沿的时间差(除同步时间:至少2ms低电平),就对应单个通道PWM的高电平持续时间。由此,解析PPM信号可以采用以下两种方式:
外部中断触发方式
通过对PPM信号的初步分析得出:上升沿之间为单通道PWM高电平持续时间。因此,外部中断触发方式的解析思路如下:
- 利用上升沿触发外部中断(IO口默认下拉电平)
- 利用一个ms级定时器来记录相邻2次外部中断的触发时间间隔,即某个通道PWM的高电平持续时间,并在外部中断回调函数中,对记录的时间间隔进行判断:如果小于等于2ms,说明是某个通道的PWM高电平持续时间;反之即是同步时间,此时,说明PPM信号一个周期已经结束。
STM32CubeMX配置
-
SYS配置
-
RCC配置
-
时钟树配置
-
外部中断IO口配置
选择一个IO口配置为GPIO_EXTI模式
打开NVIC,使能IO口对应的外部中断线,如图:
打开GPIO,设置外部中断IO口为外部中断上升沿触发,下拉模式,
-
定时器配置
选用TIM4作计数,根据ST官方手册总线架构可知,TIM4挂载在APB1时钟线上,时钟树配置APB1为72MHz。
打开Timers,选择TIM4,勾选internal Clock,预分频系数设置为:72-1,重装载计数器周期设置为65535(最大值)
至此,基本配置已结束(工程名称、路径等基本选项省略),创建工程并打开。
代码实现
-
先进行一次编译,确保项目无误
-
打开工程所在文件,创建BSP文件夹,并创建PPM.c和PPM.h
-
将以下代码放至对应的文件
- PPM.c
#include "ppm.h"
#include "main.h"
#include "stdio.h"
uint16_t PPM_Sample_Cnt = 0; // 通道
uint8_t PPM_Chn_Max = 8; // 最大通道数
uint32_t PPM_Time = 0; // 获取通道时间
uint16_t PPM_Okay = 0; // 下一次解析状态
uint16_t PPM_Databuf[8] = {0}; // 所有通道的数组
// PPM解析中断回调函数
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if (GPIO_Pin == GPIO_PIN_3) // 判断是否为接收器产生的中断,设置为PA3
{
PPM_Time = TIM4->CNT; // 获取定时器计数值
TIM4->CNT = 0; // 计数器归零
if (PPM_Okay == 1) // 判断是否是新的一轮解析
{
PPM_Sample_Cnt++; // 通道数+1
PPM_Databuf[PPM_Sample_Cnt - 1] = PPM_Time; // 把每一个通道的数值存入数组
if (PPM_Sample_Cnt >= PPM_Chn_Max) // 判断是否超过额定通道数
PPM_Okay = 0;
}
if (PPM_Time >= 2050) // 长时间无上升沿即无通道数据,进入下一轮解析
{
PPM_Okay = 1;
PPM_Sample_Cnt = 0;
}
}
}
- PPM.h
#ifndef __PPM_H__
#define __PPM_H__
#include "gpio.h"
#include "tim.h"
extern uint16_t PPM_Databuf[8]; // 所有通道的数组
#endif /* __PPM_H__*/
- main.c
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM4_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start(&htim4);//以计数器模式开启TIM4
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
-
接收机接线
-
编译烧录代码,进入debug
通过debug可以发现,PPM信号中,每个通道的最大值为2000,也就是2000us=2ms,与最开始分析的单路信号最长2000us一致。
定时器输入捕获方式
该方法与外部中断上升沿触发相似,都是通过识别PPM信号的上升沿进行解析。如对于定时器输入捕获不太了解的,可以理解为该功能是外部中断边沿触发和计数器的结合体。 话不多说,配置如下:
-
选择TIM1的CH1通道为输入捕获模式,时钟源为中心时钟源。预分频系数72-1,us级计数。捕获方式为上升沿捕获
-
使能TIM1 capture compare interrupt中断
此处不需要使能定时器更新中断,因为TIM1是us级计数,计数最大值为65535,即65535us=65.535ms。远远超出了PPM信号的一个周期(20ms),不会出现计数器溢出的情况。至于其他应用场景,根据实际情况而定。
-
打开GPIO,选择TIM的IO口,将TIM1CH1通道IO口设置为下拉输入,保证低电平稳定
至此,配置结束。生成工程并打开,编译一次,确保项目无误。
4. 将以下代码放至对应的文件
- PPM.c
#include "ppm.h"
#include "main.h"
#include "stdio.h"
uint16_t PPM_Sample_Cnt = 0; // 通道
uint8_t PPM_Chn_Max = 8; // 最大通道数
uint32_t PPM_Time = 0; // 获取通道时间
uint16_t PPM_Okay = 0; // 下一次解析状态
uint16_t PPM_Databuf[8] = {0}; // 所有通道的数组
// // PPM解析中断回调函数
// void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
// {
// if (GPIO_Pin == GPIO_PIN_3) // 判断是否为接收器产生的中断,设置为PA3
// {
// PPM_Time = TIM4->CNT; // 获取定时器计数值
// TIM4->CNT = 0; // 计数器归零
// if (PPM_Okay == 1) // 判断是否是新的一轮解析
// {
// PPM_Sample_Cnt++; // 通道数+1
// PPM_Databuf[PPM_Sample_Cnt - 1] = PPM_Time; // 把每一个通道的数值存入数组
// if (PPM_Sample_Cnt >= PPM_Chn_Max) // 判断是否超过额定通道数
// PPM_Okay = 0;
// }
// if (PPM_Time >= 2050) // 长时间无上升沿即无通道数据,进入下一轮解析
// {
// PPM_Okay = 1;
// PPM_Sample_Cnt = 0;
// }
// }
// }
// TIM1 CH1通道输入捕获中断回调函数
/* USER CODE BEGIN 4 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if (htim->Instance == TIM1)
{
if (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1)
{
PPM_Time = HAL_TIM_ReadCapturedValue(&htim1, TIM_CHANNEL_1); // 获取当前的捕获值
// 基于输入捕获的特性:捕获就是通过检测捕获通道上的边沿信号。在边沿信号发生跳变(比如上升沿/下降沿)的时候,
// 将当前定时器的值(TIMx_CNT)存放到对应的通道的捕获/比较寄存器(TIMx_CCR)里面,完成一次捕获。
__HAL_TIM_SET_COUNTER(&htim1,0);
if (PPM_Okay == 1) // 判断是否是新的一轮解析
{
PPM_Sample_Cnt++; // 通道数+1
PPM_Databuf[PPM_Sample_Cnt - 1] = PPM_Time; // 把每一个通道的数值存入数组
if (PPM_Sample_Cnt >= PPM_Chn_Max) // 判断是否超过额定通道数
PPM_Okay = 0;
}
if (PPM_Time >= 2050) // 长时间无上升沿即无通道数据,进入下一轮解析
{
PPM_Okay = 1;
PPM_Sample_Cnt = 0;
}
}
}
}
/* USER CODE END 4 */
- PPM.h 不变
- main.c
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM4_Init();
MX_TIM1_Init();
/* USER CODE BEGIN 2 */
// HAL_TIM_Base_Start(&htim4);
HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1); // 以中断的形式开启TIM1CH1通道的输入捕获功能
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
-
接收机接线
-
编译烧录代码,进入debug
通过debug发现,两种方法均可解析PPM信号。且结果一致。
如有不严谨的地方,请批评指正!!!
第一次发帖子!!!!