STM32 HAL库 PPM信号及解析

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高电平持续时间。因此,外部中断触发方式的解析思路如下:

  1. 利用上升沿触发外部中断(IO口默认下拉电平)
  2. 利用一个ms级定时器来记录相邻2次外部中断的触发时间间隔,即某个通道PWM的高电平持续时间,并在外部中断回调函数中,对记录的时间间隔进行判断:如果小于等于2ms,说明是某个通道的PWM高电平持续时间;反之即是同步时间,此时,说明PPM信号一个周期已经结束。
STM32CubeMX配置
  1. SYS配置 2

  2. RCC配置 3

  3. 时钟树配置
    4

  4. 外部中断IO口配置
    选择一个IO口配置为GPIO_EXTI模式
    5
    打开NVIC,使能IO口对应的外部中断线,如图:
    6
    打开GPIO,设置外部中断IO口为外部中断上升沿触发,下拉模式
    7

  5. 定时器配置
    选用TIM4作计数,根据ST官方手册总线架构可知,TIM4挂载在APB1时钟线上,时钟树配置APB1为72MHz。
    8

打开Timers,选择TIM4,勾选internal Clock,预分频系数设置为:72-1,重装载计数器周期设置为65535(最大值)
9

至此,基本配置已结束(工程名称、路径等基本选项省略),创建工程并打开。

代码实现
  1. 先进行一次编译,确保项目无误
    10

  2. 打开工程所在文件,创建BSP文件夹,并创建PPM.c和PPM.h
    11

  3. 将以下代码放至对应的文件

  • 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 */
}
  1. 接收机接线
    12

  2. 编译烧录代码,进入debug
    13

通过debug可以发现,PPM信号中,每个通道的最大值为2000,也就是2000us=2ms,与最开始分析的单路信号最长2000us一致。

定时器输入捕获方式

该方法与外部中断上升沿触发相似,都是通过识别PPM信号的上升沿进行解析。如对于定时器输入捕获不太了解的,可以理解为该功能是外部中断边沿触发和计数器的结合体。 话不多说,配置如下:

  1. 选择TIM1的CH1通道为输入捕获模式,时钟源为中心时钟源。预分频系数72-1,us级计数。捕获方式为上升沿捕获
    14

  2. 使能TIM1 capture compare interrupt中断
    此处不需要使能定时器更新中断,因为TIM1是us级计数,计数最大值为65535,即65535us=65.535ms。远远超出了PPM信号的一个周期(20ms),不会出现计数器溢出的情况。至于其他应用场景,根据实际情况而定。
    15

  3. 打开GPIO,选择TIM的IO口,将TIM1CH1通道IO口设置为下拉输入,保证低电平稳定
    16

至此,配置结束。生成工程并打开,编译一次,确保项目无误。
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 */
}
  1. 接收机接线
    17

  2. 编译烧录代码,进入debug
    18

通过debug发现,两种方法均可解析PPM信号。且结果一致。
如有不严谨的地方,请批评指正!!!
第一次发帖子!!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值