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

news2024/11/30 6:44:26

在学习这部分之间,建议大家先看之前这篇博客,里面包含对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飞机模航

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

洛谷 P1162 填涂颜色 C语言 bfs

题目&#xff1a; https://www.luogu.com.cn/problem/P1162 由数字 0 组成的方阵中&#xff0c;有一任意形状的由数字 1 构成的闭合圈。现要求把闭合圈内的所有空间都填写成 22。例如&#xff1a;66的方阵&#xff08;n6&#xff09;&#xff0c;涂色前和涂色后的方阵如下&am…

38 基于单片机的宠物喂食(ESP8266、红外、电机)

目录 一、主要功能 二、硬件资源 三、程序编程 四、实现现象 一、主要功能 基于STC89C52单片机&#xff0c;采用L298N驱动连接P2.3和P2.4口进行电机驱动&#xff0c; 然后串口连接P3.0和P3.1模拟ESP8266&#xff0c; 红外传感器连接ADC0832数模转换器连接单片机的P1.0~P1.…

霍夫变换:原理剖析与 OpenCV 应用实例

简介&#xff1a;本文围绕霍夫变换相关内容展开&#xff0c;先是讲解霍夫变换基本原理&#xff0c;包含从 xy 坐标系到 kb 坐标系及极坐标系的映射等。接着介绍了 cv2.HoughLines、cv2.HoughLinesP 概率霍夫变换、cv2.HoughCircles 霍夫圆变换的函数用法、参数含义、与常规霍夫…

【Debug】hexo-github令牌认证 Support for password authentication was removed

title: 【Debug】hexo-github令牌认证 date: 2024-07-19 14:40:54 categories: bug解决日记 description: “Support for password authentication was removed on August 13, 2021.” cover: https://pic.imgdb.cn/item/669b38ebd9c307b7e9f3e5e0.jpg 第一章 第一篇博客记录一…

JVM 性能调优 -- JVM常用调优工具【jps、jstack、jmap、jstats 命令】

前言&#xff1a; 前面我们分析怎么去预估系统资源&#xff0c;怎么去设置 JVM 参数以及怎么去看 GC 日志&#xff0c;本篇我们分享一些常用的 JVM 调优工具&#xff0c;我们在进行 JVM 调优的时候&#xff0c;通常需要借助一些工具来对系统的进行相关分析&#xff0c;从而确定…

net9 abp vnext 多语言通过数据库动态管理

通过数据库加载实现动态管理&#xff0c;用户可以自己修改界面显示的文本&#xff0c;满足国际化需求 如图所示,前端使用tdesign vnext 新建表TSYS_Localization与TSYS_LocalizationDetail 国旗图标下载网址flag-icons: Free Country Flags in SVG 在Shared下创建下图3个文件 …

Vue:使用 KeepAlive 缓存切换掉的 component

一、内置特殊元素 不是组件 <component>、<slot> 和 <template> 具有类似组件的特性&#xff0c;也是模板语法的一部分。但它们并非真正的组件&#xff0c;同时在模板编译期间会被编译掉。因此&#xff0c;它们通常在模板中用小写字母书写。 1.1 <compone…

Spring中每次访问数据库都要创建SqlSession吗?

一、SqlSession是什么二、源码分析1&#xff09;mybatis获取Mapper流程2&#xff09;Spring创建Mapper接口的代理对象流程3&#xff09;MapperFactoryBean#getObject调用时机4&#xff09;SqlSessionTemplate创建流程5&#xff09;SqlSessionInterceptor拦截逻辑6&#xff09;开…

【数据结构】填空集

基本术语 顺序队列在实现的时候&#xff0c;通常将数组看成是一个首尾相连的循环队列&#xff0c;这样做的目的是为避免产生&#xff08;溢出&#xff09;现象 数组q[M]&#xff08;M等于6&#xff09;存储一个循环队&#xff0c;first和last分别指向首尾指针。已知first2,la…

【趣味升级版】斗破苍穹修炼文字游戏HTML,CSS,JS

目录 图片展示 开始游戏 手动升级&#xff08;满100%即可升级&#xff09; 升级完成&#xff0c;即可解锁打怪模式 新增功能说明&#xff1a; 如何操作&#xff1a; 完整代码 实现一个简单的斗破苍穹修炼文字游戏&#xff0c;你可以使用HTML、CSS和JavaScript结合来构建…

【在Linux世界中追寻伟大的One Piece】多线程(三)

目录 1 -> Linux线程同步 1.1 -> 条件变量 1.2 -> 同步概念与竞态条件 1.3 -> 条件变量函数 1.4 -> 为什么pthread_cond_wait需要互斥量 1.5 -> 条件变量使用规范 2 -> 生产者消费者模型 2.1 -> 为什么要使用生产者消费者模型 2.2 -> 生产…

AI数据分析工具(一)

Looker Studio&#xff08;谷歌&#xff09;-免费 优点 免费使用&#xff1a;对于中小型企业和个人用户来说&#xff0c;没有任何费用压力&#xff0c;可以免费享受到数据可视化和报表创建的功能。与Google服务集成&#xff1a;特别适合使用Google产品生态的企业&#xff0c;…

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

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

springboot 配置跨域访问

什么是 CORS&#xff1f; CORS&#xff0c;全称是“跨源资源共享”&#xff08;Cross-Origin Resource Sharing&#xff09;&#xff0c;是一种Web应用程序的安全机制&#xff0c;用于控制不同源的资源之间的交互。 在Web应用程序中&#xff0c;CORS定义了一种机制&#xff0…

探索Python WebSocket新境界:picows库揭秘

文章目录 探索Python WebSocket新境界&#xff1a;picows库揭秘第一部分&#xff1a;背景介绍第二部分&#xff1a;picows库概述第三部分&#xff1a;安装picows库第四部分&#xff1a;简单库函数使用方法第五部分&#xff1a;场景应用第六部分&#xff1a;常见Bug及解决方案第…

【Linux】-学习笔记06

第二章、时间同步服务器 2.1时间同步服务器的使用 2.1.1系统时区时间的管理 timedatectl set-time "2024-02-13 10:41:55" ##设定系统时间 timedatectl list-timezones ##显示系统的所有时区 timedatectl set-timezone "Asia/Shangh…

Mac使用charles抓包

一、官网下载安装 二、配置Help--->SSL Proxying 有证书选择全部信任即可 三、设置系统代理&#xff0c;mac每次重启都需要选择&#xff0c;否则会没有数据 四、设置端口&#xff08;如果无法获取https&#xff09; 五、手机链接&#xff0c;从网页下载证书保存到手机&…

3d扫描建模产品开发-三维扫描检测蓝光检测

现当下&#xff0c;汽车制造、航空航天&#xff0c;还是消费电子、医疗器械&#xff0c;三维扫描检测与蓝光技术正以前所未有的精度和效率&#xff0c;推动着产品从概念到实物的快速转化。 三维扫描技术&#xff0c;简而言之&#xff0c;就是通过激光、结构光&#xff08;如蓝…

Hive中的基本数据类型和表的类型

Hive支持关系数据库中大多数据基本数据类型&#xff0c;同时还支持三种复杂类型。 示例&#xff1a; Hive表 创建表 – 直接建表法 create table t_page_view ( page_id bigint comment ‘页面ID’, page_name string comment ‘页面名称’, page_url string comment ‘页面…

Python PDF转JPG图片小工具

Python PDF转JPG图片小工具 1.简介 将单个pdf装换成jpg格式图片 Tip: 1、软件窗口默认最前端&#xff0c;不支持调整窗口大小&#xff1b; 2、可通过按钮选择PDF文件&#xff0c;也可以直接拖拽文件到窗口&#xff1b; 3、转换质量有5个档位&#xff0c;&#xff08;0.25&a…