本文开发环境:
- MCU型号:STM32F103C8T6
- IDE环境: MDK 5.27
- 代码生成工具:STM32CubeMx 5.6.1
- HAL库版本:STM32Cube_FW_F1_V1.8.0
本文内容:
- STM32 使用 定时器捕获功能捕获红外时序
- 解码 hx1838 时序
附件:
红外遥控器 + 红外接收头 :
测试过程请注意遥控和接收头的距离不要过远,本文遥控在1m以上会有不稳定现象,实际操作可以使用示波器或逻辑分析仪捕获波形,保证接收头收到的遥控数据是完整的。
一、接收头的滤波输出
hx1838 红外接收头自带了滤波的功能,本文使用的接收头中,当接收到38Khz的PWM 时,输出低电平,否则输出高电平:
由于接收头的滤波功能,所以程序只需要检测高低电平的时长既可。
二、NEC编码
本文使用的 红外遥控器采用了NEC编码规则:
- NEC 载波频率为 38Khz
- 引导码:9ms 高电平 + 4.5ms 低电平
- 1 码 :0.56 ms 高电平 + 0.56 ms 低电平
- 0 码 : 0.56ms 高电平 + 1.68 ms 低电平
- 结束码 :0.56ms 高电平
- 数据帧格式:引导码 + 地址 + 地址反码 + 键值 + 键值反码 + 结束码
- 重复帧格式:9ms 高电平 + 2.25ms低电平 + 结束位 + 结束码
- 高位在前,即首先收到的是高位的数据
注:本文使用的接收头,电平极性与协议相反。所以,当捕获到一个 9ms 低电平 + 4.5ms 高电平时,即收到一个引导码。
三、时序图
这是逻辑分析仪捕获到当遥控上按键 [1] 被按下一次的时序:
根据 NEC 的编码格式,可以实际看出这一帧数据的含义:
由于红外接收头输出的极性相反,所以高电平宽的波形为1码,高电平窄的波形为0码。
四、解码
只需要获取每个波形高低电平时长,通过然后将对应的数据置1或清0,最后读取整个数据即可。本文将时序的解码分为三个部分:
- 时序捕获
这部分程序负责捕获高低电平的之间变化,并记录其持续的值记录到数组中,初始化时,将定时器通道配置为下降沿捕获,当捕获到一个下降沿,就将捕获极性改为上升沿,并记录此时时间,下一次捕获时,说明获得了一个低电平,将两次时间作差,既可以得到低电平的时间。
- 时序解码
这部分程序负责读取时序捕获的值,并根据时长判断码值,最后合并成数据,如读取数组第一个值和第二个值分为为 9ms 和 4.5 ms,那么将认为捕获到一个引导码,读取到一个0.56ms低电平和0.56ms高电平,则认为读取到一个 0 码。
- 执行程序
这部分根据解析到的键值执行相应的代码
五、STM32CubeMx 输入捕获配置
这里只列出关键配置,关于 Mx 配置定时器捕获原理及完整的示例,详见:Mx 配置定时器 的输入捕获部分
- 本文将 TIM1 的 CH1 设置为输入捕获通道:
- 并打开了定时器溢出中断和捕获中断:
将定时器配置为72分频,即计数频率为 1M (TIM1 时钟源为 72M),可以捕获us级别的时间间隔。
六、程序
程序主要分为4个部分:
调试语句
波形捕获
波形解码
程序执行
使能捕获
空闲状态:本文使用变量sta_idle来标志空闲状态,空闲状态只能是没有数据正在捕获中。
1. 调试部分
当 宏定义 RX_DBG_EN 为 1 时,RX_DGB 等效于 printf ,可以正常的打印数据,当 RX_DBG_EN 为0 时 RX_DGB 为空语句,程序不打印数据。
#define RX_DBG_EN 0
#if RX_DBG_EN
#define RX_DBG(format, ...) printf(format, ##__VA_ARGS__)
#else
#define RX_DBG(format, ...) ;
#endif
这样,可通过RX_DBG_EN 宏,来控制程序是否打印调试信息。
本文示例工程中,宏被关闭,如果需要查看调试信息,将值改为1即可。由于打印数据较多,有一定概率打印不完整,一般重复3-4次即可获取一次完整数据。打开调试宏请避免使用连按功能。
2. 波形捕获
本文设置了TIM1的CH1为捕获通道,波形的捕获需要2个回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
极性捕获函数用来捕获 IO 口的电平跳变,并记录当时计时的值
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
因为定时器溢出以后,计数会清0(本文配置决定),所以当捕获的电平在计数周期末时比如(9999),下一个电平来时候,可能捕获的值为550,所以计算时间差需要 550 + 10000(1次溢出)- 9999
2.1 捕获中断回调函数
rx_rcv_init() 是 HAL_TIM_IC_CaptureCallback() 子函数:
/* 电平捕获中断回调 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static uint16_t tmp_cnt_l,tmp_cnt_h;
if(TIM1 == htim->Instance)
{
switch(cap_pol) //根据极性标志位判断捕获是低电平还是高电平
{
/* 捕获到下降沿 */
case 0:
tmp_cnt_l = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1); //记录当前时刻
TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1); //复位极性配置
TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); //改变极性
cap_pol = 1; //极性标志位改为上升沿
if(sta_idle) //如果当前为空闲状态,空闲捕获到的时序,为第一个下降沿
{
rx_rcv_init();
break; //返回
}
rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_l - tmp_cnt_h; //与上次捕获的计时作差,记录值
tim_udt_cnt = 0; //溢出次数清0
RX_DBG("(%2d)%4d us:Hrn",cap_pulse_cnt,rx_frame[cap_pulse_cnt]); //DBG:打印捕获到的电平及其时长
cap_pulse_cnt++; //计数++
break;
/* 捕获到上升沿 */
case 1:
tmp_cnt_h = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1);
TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1);
TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
cap_pol = 0;
if(sta_idle)
{
rx_rcv_init();
break;
}
rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_h - tmp_cnt_l;
tim_udt_cnt = 0;
RX_DBG("(%2d)%4d us:Lrn",cap_pulse_cnt,rx_frame[cap_pulse_cnt]);
cap_pulse_cnt++;
break;
default:
break;
}
}
}
第38行:这里需要判断一次,是否为第一个边缘,如果是就执行初始化相关内容,然后退出,第一个边缘是没有上一次边缘可以与之作差的,此后的每一个边缘触发,都是一个电平时长的捕获。
cap_pulse_cnt,是方波个数的统计,也用来做方波数组的下标值,因为他们存在一一对应的关系。
tim_udt_cnt在非空闲状态定时器溢出一次,将累加1,每一次捕获到电平,计算时长以后,都需要将变量清零。所以,当这个值在一定未被清零时候,说明一定时间没有数据产生,可借助它来判断MCU是否进入空闲状态。
2.2 定时器1 溢出回调函数
/* 溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(TIM1 == htim->Instance)
{
if(sta_idle) //空闲状态,不作任何处理
{
return;
}
tim_udt_cnt++; //溢出一次
if(tim_udt_cnt == 3) //溢出3次
{
tim_udt_cnt = 0; //溢出次数清零
sta_idle = 1; //这是为空闲状态
cap_frame = 1; //标记捕获到新的数据
}
}
}
当 tim_udt_cnt 变量累计到3时候,证明至少20ms,至多30ms未捕获到新的电平,固判断为空闲状态
3. 时序解码
波形捕获部分,已经将捕获到的电平宽度一一保存在数组rx_frame中,所以这部分程序需要分析数组的各个值。这部分需要一个数据结构:
#define RX_SEQ_NUM 33
struct {
uint16_t src_data[RX_SEQ_NUM*2];
uint16_t repet_cnt;
union{
uint32_t rev;
struct
{
uint32_t key_val_n:8;
uint32_t key_val :8;
uint32_t addr_n :8;
uint32_t addr :8;
}_rev;
}data;
}rx;
RX_SEQ_NUM :定义了波形(1高电平+1低电平的组合)的个数,此处不考虑结束码,所以是 33 个 波形,
src_data :用存储捕获到的值,由于一个波形有一个高电平和一个低电平,所以数组的长度需要 RX_SEQ_NUM * 2
data:是一个共同体,其中变量 rev 和 结构体_rev 处于同一内存中,程序可以直接操作rev,达到改变 结构体_rev 成员值的方法。
uint8_t appro(int num1,int num2)
{
return (abs(num1-num2) < 300);
}
uint8_t hx1838_data_decode(void)
{
memcpy(rx.src_data,rx_frame,RX_SEQ_NUM*4);
memset(rx_frame,0x00,RX_SEQ_NUM*4);
RX_DBG("========= rx.src[] =================rn");
for(uint8_t i = 0;i<=(RX_SEQ_NUM*2);i++)
{
RX_DBG("[%d]%drn",i,rx.src_data
);
}
RX_DBG("========= rx.rec =================rn");
if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],4500)) //#1. 检测前导码
{
uint8_t tmp_idx = 0;
rx.repet_cnt = 0; //新按键开始,按键重复个数清0
for(uint8_t i = 2;i<(RX_SEQ_NUM*2);i++) //#2. 检测数据
{
if(!appro(rx.src_data,560))
{
RX_DBG("%d,err:%d != 560rn",i,rx.src_data);
return 0;
}
i++;
if(appro(rx.src_data,1680))
{
rx.data.rev |= (0x80000000 >> tmp_idx); //第 tmp_idx 为置1
tmp_idx++;
}
else if(appro(rx.src_data,560))
{
rx.data.rev &= ~(0x80000000 >> tmp_idx); //第 tmp_idx 位清0
tmp_idx++;
}
else
{
RX_DBG("%d,err:%d != 560||1680rn",i,rx.src_data[i+1]);
return 0;
}
}
}
else if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],2250) && appro(rx.src_data[2],560))
{
rx.repet_cnt++;
return 2;
}
else
{
RX_DBG("前导码检测错误rn");
return 0;
}
return 1;
}
第3,4行:拷贝捕获电平的数组到新数组中,并将捕获电平的数组清零,由于数组元素为2个字节所以长度需要 x2,即为波形个数的4倍。
注意到,由于捕获到的时序是有误差的,所以程序不能直接判断值是否相等,所以appro(int num1,int num2)用来判断两个值是否接近,只需要接近标准值即可,本文设置的范围为 ± 300 us。
程序首先是判断前2个值,若为 9500us 和 4500 us,就是前导码,可以进一步判断后续的值,若前3个值是 9500us + 2250us + 560us 那就是重复码,rx.repet_cnt ++累加1,若都不是,则数据出错。
4. 执行部分
当解码程序将捕获的电平解析以后,就可以根据键值来执行对应的程序了:
void hx1838_proc(uint8_t res)
{
if(res == 0)
{
printf("error rn");
return;
}
if(res == 2)
{
printf("repet(%d)rn",rx.repet_cnt);
return;
}
switch(rx.data._rev.key_val)
{
case 162:
printf("detected code [%s] rn","1");
break;
case 98:
printf("detected code [%s] rn","2");
break;
case 226:
printf("detected code [%s] rn","3");
break;
case 34:
printf("detected code [%s] rn","4");
break;
case 2:
printf("detected code [%s] rn","5");
break;
case 194:
printf("detected code [%s] rn","6");
break;
case 224:
printf("detected code [%s] rn","7");
break;
case 168:
printf("detected code [%s] rn","8");
break;
case 144:
printf("detected code [%s] rn","9");
break;
case 152:
printf("detected code [%s] rn","0");
break;
case 104:
printf("detected code [%s] rn","*");
break;
case 176:
printf("detected code [%s] rn","#");
break;
case 24:
printf("detected code [%s] rn","↑");
break;
case 16:
printf("detected code [%s] rn","←");
break;
case 74:
printf("detected code [%s] rn","↓");
break;
case 90:
printf("detected code [%s] rn","→");
break;
case 56:
printf("detected code [%s] rn","OK");
break;
default:
printf("detected unknow code rn");
break;
}
}
程序直接判断了rx.data._rev.key_val的值,这是因为在对rev操作时,已经改变了它的值,这体现了共同体的便利性,否则需要程序进行拆解。这里执行程序比较简单,只是简单把值打印出来。其中,参数为 解码函数的返回值,0表示数据错误,1表示新的按键值,2表示重复码。
5. 使能捕获
最后需要注意,HAL库默认是不开启捕获的,所以需要用户使能:
void hx1838_cap_start(void)
{
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_1);
}
hx1838_cap_start()分别打开了溢出中断和捕获中断
6. 测试
void HX1838_demo(void)
{
hx1838_cap_start(); //使能捕获
while(1)
{
if(cap_frame) //如果捕获到新的数据
{
hx1838_proc(hx1838_data_decode()); //处理解码后的数据
cap_frame = 0; //标记未捕获到数据
}
};
}
上文代码写在新建立文件HX1838.c中,在main函数调用需要声明外部函数:
int main(void)
{
...
HAL_Init();
...
while (1)
{
... ...
extern void HX1838_demo(void);
HX1838_demo();
}
/* USER CODE END 3 */
}
本文使用ST-LINk的调试打印功能,运行程序后MDK的调试窗口中可以观察到数据情况:
本文开发环境:
- MCU型号:STM32F103C8T6
- IDE环境: MDK 5.27
- 代码生成工具:STM32CubeMx 5.6.1
- HAL库版本:STM32Cube_FW_F1_V1.8.0
本文内容:
- STM32 使用 定时器捕获功能捕获红外时序
- 解码 hx1838 时序
附件:
红外遥控器 + 红外接收头 :
测试过程请注意遥控和接收头的距离不要过远,本文遥控在1m以上会有不稳定现象,实际操作可以使用示波器或逻辑分析仪捕获波形,保证接收头收到的遥控数据是完整的。
一、接收头的滤波输出
hx1838 红外接收头自带了滤波的功能,本文使用的接收头中,当接收到38Khz的PWM 时,输出低电平,否则输出高电平:
由于接收头的滤波功能,所以程序只需要检测高低电平的时长既可。
二、NEC编码
本文使用的 红外遥控器采用了NEC编码规则:
- NEC 载波频率为 38Khz
- 引导码:9ms 高电平 + 4.5ms 低电平
- 1 码 :0.56 ms 高电平 + 0.56 ms 低电平
- 0 码 : 0.56ms 高电平 + 1.68 ms 低电平
- 结束码 :0.56ms 高电平
- 数据帧格式:引导码 + 地址 + 地址反码 + 键值 + 键值反码 + 结束码
- 重复帧格式:9ms 高电平 + 2.25ms低电平 + 结束位 + 结束码
- 高位在前,即首先收到的是高位的数据
注:本文使用的接收头,电平极性与协议相反。所以,当捕获到一个 9ms 低电平 + 4.5ms 高电平时,即收到一个引导码。
三、时序图
这是逻辑分析仪捕获到当遥控上按键 [1] 被按下一次的时序:
根据 NEC 的编码格式,可以实际看出这一帧数据的含义:
由于红外接收头输出的极性相反,所以高电平宽的波形为1码,高电平窄的波形为0码。
四、解码
只需要获取每个波形高低电平时长,通过然后将对应的数据置1或清0,最后读取整个数据即可。本文将时序的解码分为三个部分:
- 时序捕获
这部分程序负责捕获高低电平的之间变化,并记录其持续的值记录到数组中,初始化时,将定时器通道配置为下降沿捕获,当捕获到一个下降沿,就将捕获极性改为上升沿,并记录此时时间,下一次捕获时,说明获得了一个低电平,将两次时间作差,既可以得到低电平的时间。
- 时序解码
这部分程序负责读取时序捕获的值,并根据时长判断码值,最后合并成数据,如读取数组第一个值和第二个值分为为 9ms 和 4.5 ms,那么将认为捕获到一个引导码,读取到一个0.56ms低电平和0.56ms高电平,则认为读取到一个 0 码。
- 执行程序
这部分根据解析到的键值执行相应的代码
五、STM32CubeMx 输入捕获配置
这里只列出关键配置,关于 Mx 配置定时器捕获原理及完整的示例,详见:Mx 配置定时器 的输入捕获部分
- 本文将 TIM1 的 CH1 设置为输入捕获通道:
- 并打开了定时器溢出中断和捕获中断:
将定时器配置为72分频,即计数频率为 1M (TIM1 时钟源为 72M),可以捕获us级别的时间间隔。
六、程序
程序主要分为4个部分:
调试语句
波形捕获
波形解码
程序执行
使能捕获
空闲状态:本文使用变量sta_idle来标志空闲状态,空闲状态只能是没有数据正在捕获中。
1. 调试部分
当 宏定义 RX_DBG_EN 为 1 时,RX_DGB 等效于 printf ,可以正常的打印数据,当 RX_DBG_EN 为0 时 RX_DGB 为空语句,程序不打印数据。
#define RX_DBG_EN 0
#if RX_DBG_EN
#define RX_DBG(format, ...) printf(format, ##__VA_ARGS__)
#else
#define RX_DBG(format, ...) ;
#endif
这样,可通过RX_DBG_EN 宏,来控制程序是否打印调试信息。
本文示例工程中,宏被关闭,如果需要查看调试信息,将值改为1即可。由于打印数据较多,有一定概率打印不完整,一般重复3-4次即可获取一次完整数据。打开调试宏请避免使用连按功能。
2. 波形捕获
本文设置了TIM1的CH1为捕获通道,波形的捕获需要2个回调函数:
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
极性捕获函数用来捕获 IO 口的电平跳变,并记录当时计时的值
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
因为定时器溢出以后,计数会清0(本文配置决定),所以当捕获的电平在计数周期末时比如(9999),下一个电平来时候,可能捕获的值为550,所以计算时间差需要 550 + 10000(1次溢出)- 9999
2.1 捕获中断回调函数
rx_rcv_init() 是 HAL_TIM_IC_CaptureCallback() 子函数:
/* 电平捕获中断回调 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
static uint16_t tmp_cnt_l,tmp_cnt_h;
if(TIM1 == htim->Instance)
{
switch(cap_pol) //根据极性标志位判断捕获是低电平还是高电平
{
/* 捕获到下降沿 */
case 0:
tmp_cnt_l = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1); //记录当前时刻
TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1); //复位极性配置
TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); //改变极性
cap_pol = 1; //极性标志位改为上升沿
if(sta_idle) //如果当前为空闲状态,空闲捕获到的时序,为第一个下降沿
{
rx_rcv_init();
break; //返回
}
rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_l - tmp_cnt_h; //与上次捕获的计时作差,记录值
tim_udt_cnt = 0; //溢出次数清0
RX_DBG("(%2d)%4d us:Hrn",cap_pulse_cnt,rx_frame[cap_pulse_cnt]); //DBG:打印捕获到的电平及其时长
cap_pulse_cnt++; //计数++
break;
/* 捕获到上升沿 */
case 1:
tmp_cnt_h = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1);
TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1);
TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);
cap_pol = 0;
if(sta_idle)
{
rx_rcv_init();
break;
}
rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_h - tmp_cnt_l;
tim_udt_cnt = 0;
RX_DBG("(%2d)%4d us:Lrn",cap_pulse_cnt,rx_frame[cap_pulse_cnt]);
cap_pulse_cnt++;
break;
default:
break;
}
}
}
第38行:这里需要判断一次,是否为第一个边缘,如果是就执行初始化相关内容,然后退出,第一个边缘是没有上一次边缘可以与之作差的,此后的每一个边缘触发,都是一个电平时长的捕获。
cap_pulse_cnt,是方波个数的统计,也用来做方波数组的下标值,因为他们存在一一对应的关系。
tim_udt_cnt在非空闲状态定时器溢出一次,将累加1,每一次捕获到电平,计算时长以后,都需要将变量清零。所以,当这个值在一定未被清零时候,说明一定时间没有数据产生,可借助它来判断MCU是否进入空闲状态。
2.2 定时器1 溢出回调函数
/* 溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(TIM1 == htim->Instance)
{
if(sta_idle) //空闲状态,不作任何处理
{
return;
}
tim_udt_cnt++; //溢出一次
if(tim_udt_cnt == 3) //溢出3次
{
tim_udt_cnt = 0; //溢出次数清零
sta_idle = 1; //这是为空闲状态
cap_frame = 1; //标记捕获到新的数据
}
}
}
当 tim_udt_cnt 变量累计到3时候,证明至少20ms,至多30ms未捕获到新的电平,固判断为空闲状态
3. 时序解码
波形捕获部分,已经将捕获到的电平宽度一一保存在数组rx_frame中,所以这部分程序需要分析数组的各个值。这部分需要一个数据结构:
#define RX_SEQ_NUM 33
struct {
uint16_t src_data[RX_SEQ_NUM*2];
uint16_t repet_cnt;
union{
uint32_t rev;
struct
{
uint32_t key_val_n:8;
uint32_t key_val :8;
uint32_t addr_n :8;
uint32_t addr :8;
}_rev;
}data;
}rx;
RX_SEQ_NUM :定义了波形(1高电平+1低电平的组合)的个数,此处不考虑结束码,所以是 33 个 波形,
src_data :用存储捕获到的值,由于一个波形有一个高电平和一个低电平,所以数组的长度需要 RX_SEQ_NUM * 2
data:是一个共同体,其中变量 rev 和 结构体_rev 处于同一内存中,程序可以直接操作rev,达到改变 结构体_rev 成员值的方法。
uint8_t appro(int num1,int num2)
{
return (abs(num1-num2) < 300);
}
uint8_t hx1838_data_decode(void)
{
memcpy(rx.src_data,rx_frame,RX_SEQ_NUM*4);
memset(rx_frame,0x00,RX_SEQ_NUM*4);
RX_DBG("========= rx.src[] =================rn");
for(uint8_t i = 0;i<=(RX_SEQ_NUM*2);i++)
{
RX_DBG("[%d]%drn",i,rx.src_data
);
}
RX_DBG("========= rx.rec =================rn");
if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],4500)) //#1. 检测前导码
{
uint8_t tmp_idx = 0;
rx.repet_cnt = 0; //新按键开始,按键重复个数清0
for(uint8_t i = 2;i<(RX_SEQ_NUM*2);i++) //#2. 检测数据
{
if(!appro(rx.src_data,560))
{
RX_DBG("%d,err:%d != 560rn",i,rx.src_data);
return 0;
}
i++;
if(appro(rx.src_data,1680))
{
rx.data.rev |= (0x80000000 >> tmp_idx); //第 tmp_idx 为置1
tmp_idx++;
}
else if(appro(rx.src_data,560))
{
rx.data.rev &= ~(0x80000000 >> tmp_idx); //第 tmp_idx 位清0
tmp_idx++;
}
else
{
RX_DBG("%d,err:%d != 560||1680rn",i,rx.src_data[i+1]);
return 0;
}
}
}
else if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],2250) && appro(rx.src_data[2],560))
{
rx.repet_cnt++;
return 2;
}
else
{
RX_DBG("前导码检测错误rn");
return 0;
}
return 1;
}
第3,4行:拷贝捕获电平的数组到新数组中,并将捕获电平的数组清零,由于数组元素为2个字节所以长度需要 x2,即为波形个数的4倍。
注意到,由于捕获到的时序是有误差的,所以程序不能直接判断值是否相等,所以appro(int num1,int num2)用来判断两个值是否接近,只需要接近标准值即可,本文设置的范围为 ± 300 us。
程序首先是判断前2个值,若为 9500us 和 4500 us,就是前导码,可以进一步判断后续的值,若前3个值是 9500us + 2250us + 560us 那就是重复码,rx.repet_cnt ++累加1,若都不是,则数据出错。
4. 执行部分
当解码程序将捕获的电平解析以后,就可以根据键值来执行对应的程序了:
void hx1838_proc(uint8_t res)
{
if(res == 0)
{
printf("error rn");
return;
}
if(res == 2)
{
printf("repet(%d)rn",rx.repet_cnt);
return;
}
switch(rx.data._rev.key_val)
{
case 162:
printf("detected code [%s] rn","1");
break;
case 98:
printf("detected code [%s] rn","2");
break;
case 226:
printf("detected code [%s] rn","3");
break;
case 34:
printf("detected code [%s] rn","4");
break;
case 2:
printf("detected code [%s] rn","5");
break;
case 194:
printf("detected code [%s] rn","6");
break;
case 224:
printf("detected code [%s] rn","7");
break;
case 168:
printf("detected code [%s] rn","8");
break;
case 144:
printf("detected code [%s] rn","9");
break;
case 152:
printf("detected code [%s] rn","0");
break;
case 104:
printf("detected code [%s] rn","*");
break;
case 176:
printf("detected code [%s] rn","#");
break;
case 24:
printf("detected code [%s] rn","↑");
break;
case 16:
printf("detected code [%s] rn","←");
break;
case 74:
printf("detected code [%s] rn","↓");
break;
case 90:
printf("detected code [%s] rn","→");
break;
case 56:
printf("detected code [%s] rn","OK");
break;
default:
printf("detected unknow code rn");
break;
}
}
程序直接判断了rx.data._rev.key_val的值,这是因为在对rev操作时,已经改变了它的值,这体现了共同体的便利性,否则需要程序进行拆解。这里执行程序比较简单,只是简单把值打印出来。其中,参数为 解码函数的返回值,0表示数据错误,1表示新的按键值,2表示重复码。
5. 使能捕获
最后需要注意,HAL库默认是不开启捕获的,所以需要用户使能:
void hx1838_cap_start(void)
{
HAL_TIM_Base_Start_IT(&htim1);
HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_1);
}
hx1838_cap_start()分别打开了溢出中断和捕获中断
6. 测试
void HX1838_demo(void)
{
hx1838_cap_start(); //使能捕获
while(1)
{
if(cap_frame) //如果捕获到新的数据
{
hx1838_proc(hx1838_data_decode()); //处理解码后的数据
cap_frame = 0; //标记未捕获到数据
}
};
}
上文代码写在新建立文件HX1838.c中,在main函数调用需要声明外部函数:
int main(void)
{
...
HAL_Init();
...
while (1)
{
... ...
extern void HX1838_demo(void);
HX1838_demo();
}
/* USER CODE END 3 */
}
本文使用ST-LINk的调试打印功能,运行程序后MDK的调试窗口中可以观察到数据情况:
举报