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信号。且结果一致。
    如有不严谨的地方,请批评指正!!!
    第一次发帖子!!!!

    作者:多看多学多CV

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 HAL库 PPM信号及解析

    发表回复