你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

经验分享 | 利用TIMER定时和捕获功能实现编解码演示

[复制链接]
STMCU小助手 发布时间:2026-1-27 15:46

有时我们可能需要做些定制性的数字通信编码,自然也对应着相应的解码工作。假设有这样的需求,发送过程中的0位或1位对应于下图所示的电信号,即1个位的总传输时间是300us,其中,如果高电平持续时间为100us,则对应发送数据的0值位,如果高电平持续时间为200us,则对应发送数据的1值位。

image.png

基于上面约定,假设要发送0xaa编码就对应着下面8个位信号,如果把每个位信号进行等分,则对应着24段高低电平组成的脉冲。

image.png

今天,我们就利用这个做法,即将每个bit信号等分成3段,利用TIMER定时功能和DMA传输,周期性地依序将每个bit信号借助GPIO脚逐段发送出去,每一段持续的时间都一样,这里就是100us,电平或高或低。

下面的演示操作使用STM32g4芯片来完成。TIM3工作在单纯的计数定时模式,每100us产生更新事件,触发DMA传输,将内存数据写到GPIOB的BSRR寄存器,改写PB0的电平以实现编码输出。TIM4的CH1工作在输入捕获模式,将PB0的输出信号通过连线接到捕获脚PA11,基于DMA方式进行数据的捕获,捕获完成后进行解码。 image.png

下面要发送的编码是3个字节,0号字节固定是0xaa,1号字节是命令字节,2号字节是命令字节补码。按照上面的约定,3个字节对应24bit,每bit对应3小段电平,意味着每发送一次完整命令,需发送72个数据出去,最终体现在PB0电平的High/Low变化。

image.png

在TIM4的捕获端呢,捕获单元针对上下沿都进行捕获,这样每一次命令过来,它捕获到的数据个数是48个,因为对于发送端过来的每个0值脉冲或1值脉冲,它都需且只需捕获两次。注意,发送端是按段发送,但接收端则是按位捕获的。

image.png

捕获到1次完整的命令编码后,进行解码,从中获取高电平的长短信息,最终还原出3个命令字节。因为命令字节里有特征字节和补码字节,都可以作为校验。其实,在解码环节我还做了出错信息反馈。

下面是有关存发送命令、发送编码数据、捕获数据、解码还原命令的几个数组。

image.png

前面说过了,发送命令并非直接发送,而是先将命令字节的每个位换成3小段所对应的数据后才发送的。这里就有个转换,用到的函数是下面这个: image.png

TIM4捕获到的数据,还得经过数据解码,还原初始命令,使用下面函数。

image.png

下面是上述截图涉及到的代码,放源码于此供参考。

#define ERROR_VALUE 3      //接收异常时的返回值,演示时未加理睬#define High (0x00000001)  //用于对PB0输出高#define LOW (0x00010000)      //用于对PB0输出低#define COMMAND_SIZE 3   //命令的字节长度#define BYTE_SIZE 8       //BYTE的bit大小#define NUM_BIT_PULSE 3     //每个BIT对应3个编码输出#define NEW_ARRAY_SIZE (COMMAND_SIZE * BYTE_SIZE * NUM_BIT_PULSE) //每次命令要发送的数据个数#define TX_LENGTH    NEW_ARRAY_SIZE   //每次发送命令时,DMA要传输的数据个数#define CAPT_LENGTH (COMMAND_SIZE * BYTE_SIZE*2) //TIM4-CHI每次捕获到数据个数#define RESULT_SIZE (COMMAND_SIZE)              //最终获取到的命令长度,应等于源命令长度#define NarrowPulse (120)  //0脉冲对应的高电平上限,可以根据情况灵活调整#define WidePulse (180)    //1脉冲对应的高电平下限,可以根据情况灵活调整
uint8_t OriginData[]={0xaa,00,00} ;   //原始命令字节数组,3个字节长度uint32_t TXArray[NEW_ARRAY_SIZE];    // 发送命令时,DMA需要的源数组uint32_t CapData[CAPT_LENGTH];       // TIM4做捕获时存放数据的数组uint8_t RXResult[RESULT_SIZE] = {0}; // 存放最终获取的命令数组//根据3字节原始命令转换出要发送的数组TXArray[]void GenerateNewData(uint8_t*  SrcData ); //用于将捕获数据还原出最初命令字节,可以根据特征字节、补码、返回数据判断是否接收异常uint8_t ProcessData(uint32_t source[], uint8_t result[], uint16_t array_size, uint16_t result_size);

基本的实现过程和原理就介绍到这里。下面进入编程、验证环节。

先使用STM32CubeMx进行配置。下面是TIM3的配置,计数频率为1MHz,溢出周期为100us.其更新中断用来触发DMA,将编码数据传输到GPIOB->BSRR寄存器。

image.png

image.png

下面是有关TIM4的配置,其功能主要是实施数据捕获,捕获完成后由软件进行解码。 image.png

image.png

创建工程后,添加必要的用户代码。基于STM32HAL库函数组织代码。

int main(void){   HAL_Init();  SystemClock_Config();  MX_GPIO_Init();  MX_DMA_Init();  MX_TIM3_Init();  MX_TIM4_Init();  /* USER CODE BEGIN 2 */  OriginData[1]=0x12;              //初始命令字,1个字节单位  __HAL_TIM_CLEAR_FLAG(&htim3, TIM_FLAG_UPDATE);  __HAL_TIM_ENABLE_DMA(&htim3, TIM_DMA_UPDATE);    while (1)  {    __HAL_TIM_DISABLE(&htim3);   TIM3->CNT=0;
    HAL_TIM_IC_Start_DMA(&htim4,  TIM_CHANNEL_1, (uint32_t *)CapData,  CAPT_LENGTH);//通过TIM4-CH1捕获通道基于DMA捕获数据   //OriginData[0]=0xaa;  //该字节固定为0xaa特征数据     OriginData[1]++;       //原命令字节
    OriginData[2]=~OriginData[1]+1;  //命令字节的补码      GenerateNewData(&OriginData[0]);  //根据3字节原始命令换成要发送的3*8*3即72个数据,用于DMA改写PB0输出高电平低
    HAL_DMA_Start_IT(&hdma_tim3_up, (uint32_t)&TXArray[0], (uint32_t)&GPIOB->BSRR,  TX_LENGTH); //发送72个数据   __HAL_TIM_ENABLE(&htim3);
    HAL_Delay(1000);         }    }

在捕获完成中断里做数据的解码,并做下次接收的准备。

void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim){
 ProcessData(CapData,  RXResult,  CAPT_LENGTH,  RESULT_SIZE) ;
 HAL_TIM_IC_Start_DMA(&htim4,  TIM_CHANNEL_1, (uint32_t *)CapData,  CAPT_LENGTH);}

有关命令转换的数据编码的参考实现函数:

void GenerateNewData(uint8_t* SrcData )  {
    // 初始化新数组索引    uint8_t  newIndex = 0;    // 遍历原始数组的每一个字节    for (uint8_t byteIndex = 0; byteIndex < COMMAND_SIZE; byteIndex++) {        // 遍历每个字节的每一位        for (uint8_t bitIndex = 0; bitIndex < 8; bitIndex++) {            // 检查当前位            if (SrcData[byteIndex] & (1 << bitIndex)) {                // 如果该位为1                TXArray[newIndex++] = High;                TXArray[newIndex++] = High;                TXArray[newIndex++] = LOW;            } else {                // 如果该位为0                TXArray[newIndex++] = High;                TXArray[newIndex++] = LOW;                TXArray[newIndex++] = LOW;            }        }    }}

有关解码的参考实现函数:

uint8_t ProcessData(uint32_t source[], uint8_t result[], uint16_t array_size, uint16_t result_size) {    // 遍历数组中的每16个数据(8对数据)    for (uint8_t j = 0; j < result_size; j++) {        for (uint8_t i = 0; i < 8; i++) {            uint8_t even_index = 16 * j + 2 * i;            uint8_t odd_index = 16 * j + 2 * i + 1;            uint32_t even_value = source[even_index];            uint32_t odd_value = source[odd_index];            // 比较奇数号元素和偶数号元素的值            if (odd_value <= even_value) {                // 如果奇数号元素的值不大于偶数号元素的值                odd_value += 500; // 先将奇数号元素的值加500            }            // 进行比较和判断            if (odd_value > even_value + 180) {                // 如果奇数号元素的值比偶数号元素的值大于180                result[j] |= (1 << i); // 将result的对应位写1            } else if (odd_value > even_value + 80 && odd_value <= even_value + 120) {                // 如果奇数号元素的值比偶数号元素的值大80但不超过120                result[j] &= ~(1 << i); // 将result的对应位写0            } else {                // 其他情况,返回错误值                return ERROR_VALUE;            }        }    }    return 0; // 成功返回0}

我这里测试时是周期性地发送命令,编码发送、捕获接收、还原命令字节。

下面截图中OriginData就是每次发送的3个命令字节,RxResult就是基于接收数据解码的命令字节。从下面任意截图可以看出接收的命令跟原始命令一致。当然对于具体应用中来说,校验特征字节和命令互补关系即可。 image.png

下面截图中TxArray就是基于TIM3更新事件DMA发送的数据,其实就两个值,即0x00010000和0x00000001,对应PB0是输出低还是输出高。

image.png

下面截图中CapData就是基于TIM4捕获事件的DMA传输数据,数据正常来讲都该落在0~499之间。

image.png

上面主要演示了利用STM32 TIMER更新事件结合GPIO实现编码输出,基于TIMER捕获功能进行解码的过程。当然,我们还可以直接使用TIMER的PWM输出功能,而无须使用GPIO翻转来实现上面功能,今天就不展开了,后面找机会再聊。

image.png
收藏 评论0 发布时间:2026-1-27 15:46

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版