【四轴】利用PWM捕获解析接收机信号

在学习这部分之间,建议大家先看之前这篇博客,里面包含对PWM一些重要概念的基本介绍。

【四轴】利用PWM输出驱动无刷电机-CSDN博客

1. 基本原理

1.1 PWM是什么

这一部分可以看我之前的博客,已经对PWM有了基本的介绍。

1.2 什么叫捕获PWM波,这跟解析接收机信号有什么关系

1.2.1 单片机、接收机、遥控器之间的关系

对于我使用的Microzone的6C基础遥控器和Radiolink R7EH接收机来说,它们二者与单片机之间有如下的联系:

关于PPM波,可以简单认为其是一个周期内集成了多个PWM波。

图源遥控器说明文档

可以这样简单认为,遥控器发送的是一束PPM波信号,里面集成了多个通道的PWM波信号,而接收机接收到遥控器发的PPM后,会将该PPM拆分成多个通道单独的类似于PWM波的信号,我们的单片机只需要接收其中1-4通道的信号并进行捕获解析就可以了。

1.2.2 PWM捕获

简单来说,PWM捕获,就是指我们接收到一束PWM波后,分析出其一个周期内的占空比。所以,问题的关键在于如何计算出占空比。

在之前的文章中,我们介绍过与PWM有关的两个寄存器值,CCR和ARR,其中ARR是重装载值,代表一个周期内计数器能达到的最大值;CCR则可以理解为计数器的值,它与ARR的比值就是占空比。

因此,如果我们能做到对于接收到的一个周期的PWM波,算出其CCR和ARR,就自然而然地可以得出其占空比。

1.2.3 如何算CCR

同PWM输出类似,PWM捕获,同样可以依靠定时器实现,并且还要使用定时器中断功能。下面我将阐述中断的作用。

对于这样一束波,我们可以看到脉宽时间与周期的比值,实际就是占空比。而进入脉宽时间时,必定会有一个从低电平到高点平跳变的过程,其有个专门的名词叫做”上升沿“,同理,当脉宽时间结束时,也会相应的有个”下降沿“。STM32的定时器,恰恰为我们提供了三种中断触发方式:上升沿触发、下降沿触发、上升沿和下降沿都触发。

试想,如果我们初始设置为上升沿触发,那么对于捕获到的PWM波,当检测到其刚进入脉宽时间时,会进入中断,这时我们在中断内获取此时的CCR值,再将中断触发方式设置为下降沿触发,那么当检测到脉宽时间结束时,又会进入中断,此时我们再获得此时的CCR值,与之间的相减,不就得到了脉宽时间内CCR的变化量了吗,这个值比上ARR,就是占空比。

2. STM32CUBEMX配置

配置可参考如下视频:

定时器-输入捕获(频率、占空比测量)_哔哩哔哩_bilibili

下图是我的配置:

3. 实现代码

3.1 源文件

#include "Receiver.h"
#include "tim.h"
#include "MySerial.h"

// 数据存储
static uint32_t risingEdgeTime[CHANNEL_COUNT] = {0};  // 存储每个通道的上升沿捕获时间
static uint32_t fallingEdgeTime[CHANNEL_COUNT] = {0}; // 存储每个通道的下降沿捕获时间
static uint8_t isRisingEdge[CHANNEL_COUNT] = {1};     // 每个通道的标志位:1表示检测上升沿,0表示检测下降沿
static uint32_t pwmWidth[CHANNEL_COUNT] = {0};        // 存储每个通道的脉宽(单位:计数值)
static float pwmMapVal[CHANNEL_COUNT] = {0};          // 存储每个通道映射到控制值的结果(0.0 到 1.0)

// 函数声明
static uint32_t CalculatePWMWidth(uint32_t risingEdge, uint32_t fallingEdge, uint32_t period);
static void MapPWMWidthToValue(uint32_t width, uint32_t channelIndex);
static uint32_t GetChannelIndex(TIM_HandleTypeDef *htim);

/**
 * @brief 计算脉宽
 * @param risingEdge 上升沿捕获的计数值
 * @param fallingEdge 下降沿捕获的计数值
 * @param period 定时器的自动重装载值(ARR)
 * @return 脉宽值(单位:计数值)
 * 
 * 该函数根据上升沿和下降沿时间点计算脉宽(高电平时间)。
 * 如果发生计数器溢出,考虑溢出的补偿周期。
 */
static uint32_t CalculatePWMWidth(uint32_t risingEdge, uint32_t fallingEdge, uint32_t period) {
    if (fallingEdge >= risingEdge) {
        return fallingEdge - risingEdge;  // 没有溢出,直接计算差值
    } else {
        return (period - risingEdge) + fallingEdge; // 溢出时补偿
    }
}

/**
 * @brief 映射脉宽到控制值
 * @param width 脉宽值(单位:计数值)
 * @param channelIndex 通道索引
 * 
 * 根据不同通道的范围(MIN_MOTORVAL、MAX_MOTORVAL)将脉宽值映射到 0.0 到 1.0 的范围。
 * 特定通道的映射范围通过 `channelIndex` 确定。
 */
static void MapPWMWidthToValue(uint32_t width, uint32_t channelIndex) {
    float MIN_MOTORVAL, MAX_MOTORVAL, SUB_MOTORVAL;

    // 根据通道索引选择不同的映射范围
    if (channelIndex == CHANNEL3_INDEX) {
        MIN_MOTORVAL = MIN_MOTORVAL3;
        MAX_MOTORVAL = MAX_MOTORVAL3;
        SUB_MOTORVAL = SUB_MOTORVAL3;
    } else if (channelIndex == CHANNEL2_INDEX) {
        MIN_MOTORVAL = MIN_MOTORVAL2;
        MAX_MOTORVAL = MAX_MOTORVAL2;
        SUB_MOTORVAL = SUB_MOTORVAL2;
    } else {
        MIN_MOTORVAL = MIN_MOTORVAL14;
        MAX_MOTORVAL = MAX_MOTORVAL14;
        SUB_MOTORVAL = SUB_MOTORVAL14;
    }

    // 限制脉宽在有效范围内
    if (width < MIN_MOTORVAL) width = MIN_MOTORVAL;
    if (width > MAX_MOTORVAL) width = MAX_MOTORVAL;

    // 映射值计算
    pwmMapVal[channelIndex] = ((float)(width - MIN_MOTORVAL)) / SUB_MOTORVAL;
}

/**
 * @brief 获取当前通道索引
 * @param htim 定时器句柄
 * @return 通道索引(0 ~ CHANNEL_COUNT-1),或 INVALID_CHANNEL 表示无效通道
 * 
 * 根据定时器通道,返回对应的通道索引。该索引用于索引捕获数据的数组。
 */
static uint32_t GetChannelIndex(TIM_HandleTypeDef *htim) {
    switch (htim->Channel) {
        case HAL_TIM_ACTIVE_CHANNEL_1: return CHANNEL1_INDEX; // 通道1
        case HAL_TIM_ACTIVE_CHANNEL_2: return CHANNEL2_INDEX; // 通道2
        case HAL_TIM_ACTIVE_CHANNEL_3: return CHANNEL3_INDEX; // 通道3
        case HAL_TIM_ACTIVE_CHANNEL_4: return CHANNEL4_INDEX; // 通道4
        default: return INVALID_CHANNEL;  // 无效通道
    }
}

/**
 * @brief 定时器输入捕获中断回调函数
 * @param htim 定时器句柄
 * 
 * 该函数在定时器捕获事件发生时触发。
 * 它根据当前通道索引读取捕获值,计算脉宽,并更新映射值。
 * 上升沿和下降沿捕获交替进行。
 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) {
    if (htim->Instance == TIM4) {  // 检查是否为 TIM4
        uint32_t channelIndex = GetChannelIndex(htim);  // 获取通道索引
        if (channelIndex == INVALID_CHANNEL) return;    // 无效通道直接返回

        // 读取捕获值
        uint32_t capturedValue = HAL_TIM_ReadCapturedValue(htim, channelIndex * 4); // 修正参数传递错误

        if (isRisingEdge[channelIndex]) {  // 上升沿捕获
            risingEdgeTime[channelIndex] = capturedValue;  
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, channelIndex * 4, TIM_INPUTCHANNELPOLARITY_FALLING); // 切换到下降沿
        } else {  // 下降沿捕获
            fallingEdgeTime[channelIndex] = capturedValue;
            pwmWidth[channelIndex] = CalculatePWMWidth(risingEdgeTime[channelIndex], fallingEdgeTime[channelIndex], TIM4->ARR); // 计算脉宽
            MapPWMWidthToValue(pwmWidth[channelIndex], channelIndex); // 映射脉宽到控制值
            __HAL_TIM_SET_CAPTUREPOLARITY(htim, channelIndex * 4, TIM_INPUTCHANNELPOLARITY_RISING); // 切换回上升沿
        }
        isRisingEdge[channelIndex] = !isRisingEdge[channelIndex]; // 切换边沿标志位
    }
}

/**
 * @brief 接收机初始化
 * 
 * 启动定时器捕获中断,用于捕获 4 个通道的信号。
 */
void Receiver_Init(void) {
    HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_1); // 启动通道1中断
    HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_2); // 启动通道2中断
    HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_3); // 启动通道3中断
    HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_4); // 启动通道4中断
}

/**
 * @brief 映射值接口
 * 
 * @return 返回对应通道的映射值
 */
float Receiver_GetMappedValue(uint32_t channelIndex) 
{
		return pwmMapVal[channelIndex];
}

3.2 头文件

#ifndef __RECEIVER_H
#define __RECEIVER_H

#include "main.h"

// 宏定义
#define CHANNEL_COUNT 4          // 通道数量
#define CHANNEL1_INDEX 0         // 通道 1 索引
#define CHANNEL2_INDEX 1         // 通道 2 索引
#define CHANNEL3_INDEX 2         // 通道 3 索引
#define CHANNEL4_INDEX 3         // 通道 4 索引
#define INVALID_CHANNEL 255      // 无效通道标志

#define MAX_MOTORVAL14 1750        // 14通道最大脉宽值
#define MIN_MOTORVAL14 1250        // 14通道最小脉宽值
#define MAX_MOTORVAL3 2000        // 3最大脉宽值
#define MIN_MOTORVAL3 1000        // 3最小脉宽值
#define MAX_MOTORVAL2 1750        // 2最大脉宽值
#define MIN_MOTORVAL2 1500        // 2最小脉宽值

#define SUB_MOTORVAL14 500        // 14通道脉宽范围的
#define SUB_MOTORVAL3 1000        // 3通道脉宽范围
#define SUB_MOTORVAL2 250        // 2通道脉宽范围

void Receiver_Init(void);        //初始化函数
float Receiver_GetMappedValue(uint32_t channelIndex); //返回映射值,方便外层调用
#endif // __RECEIVER_H

一些解释

在我的代码中,有一个函数叫做MapPWMWidthToValue,其作用是将脉宽值映射到0-1的范围。

为了实现这个函数,我手动记录了各个通道对应的遥控器拨杆拨到最小、中间、最大时的脉宽值,通道与拨杆的对应关系见下图:

因此才有了以下宏定义:

#define MAX_MOTORVAL14 1750        // 14通道最大脉宽值
#define MIN_MOTORVAL14 1250        // 14通道最小脉宽值
#define MAX_MOTORVAL3 2000        // 3最大脉宽值
#define MIN_MOTORVAL3 1000        // 3最小脉宽值
#define MAX_MOTORVAL2 1750        // 2最大脉宽值
#define MIN_MOTORVAL2 1500        // 2最小脉宽值

参考链接

嵌入式学习笔记——PWM与输入捕获(上)_pwm捕获输入-CSDN博客

Microzone的6C基础款遥控器介绍说明 - 基础 LEVEL1 diy飞机模航

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/925864.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

HTTP 1

文章目录 1.2个简单的预备知识域名统一资源定位符 URL完整的URL 2. http请求和响应 格式画出来&#xff0c;两个工具见一见http请求/响应的样子3. 写一个最简单的httpserver&#xff0c;用浏览器直接测试recvsend处理请求stringstream 字符串分割工具wget 4. 读http报文的细节 …

【mac】mac自动定时开关机和其他常用命令,管理电源设置的工具pmset

一、操作步骤 1、打开终端 2、pmset 是用于管理电源设置的强大工具&#xff0c;我们将使用这个命令 &#xff08;1&#xff09;查询当前任务 pmset -g sched查看到我当前的设置是 唤醒电源开启在 工作日的每天早上8点半 上班时不用手动开机了 &#xff08;2&#xff09;删…

瀚高创库建表pgsql

1.瀚高下载地址&#xff1a; 下载 (highgo.com)https://www.highgo.com/down_main.html 2.瀚高linux安装 上传deb文件到ubuntu系统中 执行 dpkg -i hgdb-see-4.5.8-fe4791c.x86_64.deb 命令安装数据库 安装完成后&#xff0c;会在/opt 目录下生成安装目录 数据库安装完毕后…

力扣--LCR 150.彩灯装饰记录II

题目 代码 if(root null){ return new ArrayList<>(); } Queue<TreeNode> queue new LinkedList<>();List<List<Integer>> res new ArrayList<>();queue.add(root);while(!queue.isEmpty()){int k queue.size();List<Integer> …

ubuntu24.04安装Kubernetes1.31.0(k8s1.30.0)高可用集群

ubuntu24.04安装Kubernetes1.30.0(kubernetes1.30.0)高可用集群 一、总体概览 目前最新版的K8S版本应该是1.31.0,我们安装的是第二新的版本1.30.0,因为有大神XiaoHH Superme指路,所以基本上没踩坑,很顺利就搭建完成了。所有的机器都采用的最新版Ubuntu-Server-24.04长期支…

由于导包而引发的错误

今天在调试时发现删除功能无论如何都无法实现&#xff0c;于是调试找到了mapper层的错误但不知道为什么报错。以下是报错信息。 Caused by: org.apache.ibatis.binding.BindingException: Parameter userIds not found. Available parameters are [arg0, collection, list]at o…

结构体,枚举,联合知识点笔记总结

结构体&#xff1a; 1.之前我们知道&#xff0c;数组&#xff1a;一些值的结合&#xff0c;类型是相同的 结构&#xff1a;是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量 struct s {int a; --|-->结构体成员int b; --}; 注意…

HTML飞舞的爱心(完整代码)

写在前面 HTML语言实现飞舞的爱心完整代码。 完整代码 <!DOCTYPE html> <html lang="en"><head><meta charset="UTF-8"><title>飞舞爱心</title><style>* {margin: 0;padding: 0;}html,body {overflow: hidd…

数据结构--数组

目录 1 定义 1.1 数组内存结构 1.2二维数组 2 练习 2.1 将数组内两个区间内有序元素合并 2.2 leetcode88. 合并两个有序数组 3 缓存与局部性原理 1 定义 1.1 数组内存结构 1 2 3 5 6 给数组添加元素时&#xff0c;应将原来添加位置的元素和之后的元素进行复制 System…

【接口自动化测试】一文从3000字从0到1详解接口测试用例设计

接口自动化测试是软件测试中的一种重要手段&#xff0c;它能有效提高测试效率和测试覆盖率。在进行接口自动化测试之前&#xff0c;首先需要进行接口测试用例的设计。本文将从0到1详细且规范的介绍接口测试用例设计的过程&#xff0c;帮助读者快速掌握这一技能。 一、了解接口…

麒麟系统x86安装达梦数据库

一、安装准备前工作 操作系统&#xff1a;银河麒麟V10&#xff0c;CPU&#xff1a; x86_64 架构 下载地址&#xff0c;麒麟官网&#xff1a;https://www.kylinos.cn/ 数据库&#xff1a;dm8_20220915_x86_kylin10_64 下载地址&#xff0c;达梦数据库官网&#xff1a;https://…

个人博客接入github issue风格的评论,utteranc,gitment

在做个人博客的时候&#xff0c;如果你需要评论功能&#xff0c;但是又不想构建用户体系和评论模块&#xff0c;那么可以直接使用github的issue提供的接口&#xff0c;对应的开源项目有utteranc和gitment&#xff0c;尤其是前者。 它们的原理是一样的&#xff1a;在博客文章下…

6.算法移植第六篇 YOLOV5/rknn生成可执行文件部署在RK3568上

接上一篇文章best-sim.rknn模型生成好后&#xff0c;我们要将其转换成可执行文件运行在RK3568上&#xff0c;这一步需要在rknpu上进行&#xff0c;在强调一遍&#xff01;&#xff01;rknpu的作用是可以直接生成在开发板上运行的程序 退出上一步的docker环境 exit1.复制best-…

PyMOL操作手册

PyMOL 操作手册 The man will be silent, the woman will be tears. – itwangyang ​ 翻译整理&#xff1a;itwangyanng 2024 年 11月 29 日 目录 初识 PyMOL… 5 0.1 安装 PyMOL… 5 0.1.1 Windows 系统开源版 PyMOL 的安装… 5 0.1.2 教育版 PyMOL 的下载安装……

HarmonyOS Next 模拟器安装与探索

HarmonyOS 5 也发布了有一段时间了&#xff0c;不知道大家实际使用的时候有没有发现一些惊喜。当然随着HarmonyOS 5的更新也带来了很多新特性&#xff0c;尤其是 HarmonyOS Next 模拟器。今天&#xff0c;我们就来探索一下这个模拟器&#xff0c;看看它能给我们的开发过程带来什…

Flink 从入门到实战

Flink中的批和流 批处理的特点是有界、持久、大量&#xff0c;非常适合需要访问全部记录才能完成的计算工作&#xff0c;一般用于离线统计。 流处理的特点是无界、实时, 无需针对整个数据集执行操作&#xff0c;而是对通过系统 传输的每个数据项执行操作&#xff0c;一般用于实…

HarmonyOS 5.0应用开发——列表(List)

【高心星出品】 文章目录 列表&#xff08;List&#xff09;列表介绍列表布局设置主轴方向设置交叉轴方向 列表填充分组列表填充 滚动条位置设置滚动位置滚到监听 列表项侧滑 列表&#xff08;List&#xff09; 列表介绍 列表作为一种容器&#xff0c;会自动按其滚动方向排列…

RBF神经网络预测结合NSGAII多目标优化

目录 效果一览基本介绍程序设计参考资料 效果一览 基本介绍 RBF神经网络预测结合NSGAII多目标优化 rbf神经网络预测结合nsga2多目标优化 题外话&#xff1a; 多目标优化是指在优化问题中同时考虑多个目标函数的优化过程。在多目标优化中&#xff0c;通常存在多个冲突的目标&am…

HTTPTomcatServlet

今日目标: 了解JavaWeb开发的技术栈理解HTTP协议和HTTP请求与响应数据的格式掌握Tomcat的使用掌握在IDEA中使用Tomcat插件理解Servlet的执行流程和生命周期掌握Servlet的使用和相关配置1,Web概述 1.1 Web和JavaWeb的概念 Web是全球广域网,也称为万维网(www),能够通过浏览…

理解Linux的select、poll 和 epoll:从原理到应用场景

I/O 多路复用并不是什么新东西&#xff0c;select 早在 1983 年就出现了&#xff0c;poll 在 1997 年&#xff0c;epoll 是 2002 年的产物。面试题总爱问“多路复用多厉害&#xff1f;”其实它就是把轮询的锅甩给了操作系统&#xff0c;而操作系统不过是用 CPU 指令帮你完成事件…