【STM32HAL—–红外遥控】
1. 基本介绍
1.1. 红外线
红外线是波长介于微波和可见光之间的电磁波,波长在 760 纳米到 1 毫米之间,是波形比红光长的非可见光。自然界中的一切物体,只要它的温度高于绝对零度(-273)就存在分子和原子的无规则运动,其表面就会不停的辐射红外线。当然了,虽然都是辐射红外线,但是不同的物体辐射的强度是不一样的,而我们正是利用了这一点把红外技术应用到我们的实际开发中。
1.2. 红外发射管
红外发射管很常用,在我们的遥控器上都可以看到,它类似发光二极管,但是它发射出来的是红外光,是我们肉眼所看不到的。我们学过发光二极管的亮度会随着电流的增大而增加,同样的道理,红外发射管发射红外线的强度也会随着电流的增大而增强。
1.3. 红外接收管
红外接收管内部是一个具有红外光敏感特征的 PN 节,属于光敏二极管,但是它只对红外光有反应。无红外光时,光敏管不导通,有红外光时,光敏管导通形成光电流,并且在一定范围内电流随着红外光的强度的增强而增大。
2. 红外遥控器发射
2.1. 红外遥控器原理
红外遥控是一种非接触,无线控制技术。具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等特点。红外遥控系统一般有红外发射装置和红外接收设备两大部分组成。红外遥控的原理就是利用红外线进行通讯,比如生活中常用的电视遥控器,空调遥控器等,大多都是红外通讯实现的遥控功能。
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为38kHz,这是由发射端所使用的455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取12,所以为38kHz。也有一些遥控系统采用36kHz、40 kHz、56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码) 调制在38KHz的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
3. 二进制脉冲编码
二进制脉冲码的形式有多种,其中最为常用的是NEC Protocol 的PWM码(脉冲宽度调制)和 Philips RC-5 Protoco 的 PPM码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率,才可以选取一体化红外接收头和制定解码方案。这里针对NEC编码形式做一个详细介绍。NEC编码形式有以下特点:
3.1. NEC码的位定义
一个脉冲对应 560us 的连续载波,一个逻辑1传输需要 2.25ms(560us脉冲+1680us 低电平) ,一个逻辑 0的传输需要 1.125ms (560us脉冲+560us 低电平)。而红外接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为: 逻辑 1应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以通过计算高电平时间判断接收到的数据是0还是1。NEC码位定义时序图如下:
特别标注一下,上图逻辑0和逻辑1的时序图对应的是接收端的时序图。
3.2. NEC遥控指令的数据格式
NEC遥控指令的数据格式为:引导码、地址码、地址反码、控制码控制反码。引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成地址码、地址反码、控制码、控制反码均是8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性 (可用于校验)。数据格式如下:
NEC遥控指令的数据帧由以下几个部分组成:
- 引导码(Leader Code)
- 地址码(Address Code)
- 地址码反码(Address Inverted Code)
- 命令码(Command Code)
- 命令码反码(Command Inverted Code)
3.2.1. 引导码(Leader Code)
3.2.2. 地址码(Address Code)
3.2.3. 地址码反码(Address Inverted Code)
3.2.4. 命令码(Command Code)
3.2.5. 命令码反码(Command Inverted Code)
3.2.6. 重复码(Repeat Code)
当用户长时间按住遥控器的某个按键时,NEC协议会发送重复码,而不是重复发送完整的数据帧。重复码的格式如下:
3.3. 波形示例
假设一个数据帧的地址码为0x17(00010111),命令码为0x25(00100101),其波形图如下:
- 引导码:9ms低电平 + 4.5ms高电平
- 地址码:00010111
- 地址码反码:11101000
- 命令码:00100101
- 命令码反码:11011010
- 结束码:560μs低电平
3.4. 重复码波形示例
当用户长时间按住某个按键时,重复码的波形如下:
- 低电平:9ms
- 高电平:2.25ms
- 低电平:560μs
- 高电平:97.94ms
3.5. 解码过程
在接收端,通过检测信号的上升沿和下降沿来判断脉冲的宽度和间隔时间,从而解码出逻辑0和逻辑1。具体步骤如下:
- 检测引导码:
- 接收数据:
- 校验数据:
- 处理重复码:
总结:NEC协议通过特定的时间序列和脉冲宽度来表示逻辑0和逻辑1,从而实现数据的编码和解码。了解这些位定义和数据帧格式对于设计和实现红外遥控系统非常重要。
红外发射装置只需要按键按下即可产生红外信号,我们只需要针对红外接收设备编写程序即可。上面介绍了,红外接收设备在收到脉冲的时候为低电平,在没有脉冲的时候为高电平。根据“0”和“1”的时序图可知,我们只需要监测红外接收设备的数据输出引脚的高电平持续时间就可以判断接收到的是“0”还是“1”。
另外,没有按键按下时,也就是没有发红外信号,没有脉冲,红外接收设备的数据输出引脚一直为高电平。只有接收到脉冲时,说明有按键按下,此时红外接收设备的数据输出引脚为低电平。因此,可以利用外部中断的下降沿出发来判断是否有按键按下,在中断中测量高电平持续时间来判断接收到的是“0”还是“1”。
4. 红外遥控程序设计
4.1. STM32CubeMX软件配置
4.1.1. 定时器4配置
4.2. 程序编写
#include "ir.h"
ir_t ir; // 创建红外结构体对象
/// @brief 红外通信初始化
/// @param
void Ir_init(void)
{
HAL_TIM_Base_Start_IT(&htim4);
HAL_TIM_IC_Start_IT(&htim4, TIM_CHANNEL_4);
}
/// @brief 红外接收信号转换
/// @param num
void ir_rece(char num[])
{
for (int i = 0; i < 32;i++)
{
if(ir.buffer[i]<1200)
{
num[i / 8] = num[i / 8] << 1;
}
else
{
num[i / 8] = num[i / 8] << 1;
num[i / 8] |= 0x01;
}
}
}
/// @brief Period elapsed callback in non-blocking mode
/// @param htim
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
ir.upcount++;
}
/// @brief Input Capture callback in non-blocking mode
/// @param htim
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{
if(ir.isup_flag)
{
ir.isup_flag = 0;
ir.upcount = 0;
ir.valueup = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_4);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_4, TIM_ICPOLARITY_FALLING);
}
else
{
ir.isup_flag = 1;
ir.valuedown = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_4);
__HAL_TIM_SET_CAPTUREPOLARITY(htim, TIM_CHANNEL_4, TIM_ICPOLARITY_RISING);
ir.weigh = ir.valuedown + (ir.upcount*65536) - ir.valueup;
if(ir.weigh>4400 && ir.weigh<4600)
{
ir.buffid = 0;
ir.buffer[ir.buffid++]=ir.weigh;
}
else if(ir.buffid > 0)
{
ir.buffer[ir.buffid++]=ir.weigh;
if(ir.buffid > 32)
{
ir.recflag = 1;
ir.buffid = 0;
}
}
}
}
#ifndef __IR_H__
#define __IR_H__
#include "tim.h"
/* 红外参数结构体 */
//***************************************************************************************************
typedef struct
{
uint32_t upcount; // 定时器中断溢出次数
uint16_t valueup; // 上升沿计数
uint16_t valuedown; // 下降沿计数
uint8_t isup_flag; // 上升沿标志
uint32_t weigh; // 脉宽值
uint16_t buffer[40]; // 数据缓冲区
uint16_t buffid; // 缓冲区ID
uint8_t recflag; // 接收标志
char num[4]; // 转换为十六进制后的数据包
}ir_t;
extern ir_t ir;
//***************************************************************************************************
/* 红外函数声明 */
//***************************************************************************************************
void Ir_init(void);
void ir_rece(char num[]);
//***************************************************************************************************
#endif
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2024 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "tim.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "ir.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch, FILE* f)
{
HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 1000);
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
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_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("init ok\r\n");
Ir_init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(ir.recflag)
{
for (int i = 0; i < 4; i++)
{
ir_rece(ir.num);
printf("0x%02x ",ir.num[i]);
}
switch (ir.num[2])
{
case 0xd1:printf("开机");break;
case 0xf1:printf("主界面");break;
case 0x91:printf("TEST");break;
case 0x81:printf("音量+");break;
case 0xcc:printf("音量-");break;
case 0xe1:printf("返回");break;
case 0xf0:printf("上一个");break;
case 0xd4:printf("播放");break;
case 0xc8:printf("下一个");break;
case 0xb4:printf("0");break;
case 0xd8:printf("C");break;
case 0x98:printf("1");break;
case 0x8c:printf("2");break;
case 0xbd:printf("3");break;
case 0x88:printf("4");break;
case 0x9c:printf("5");break;
case 0xad:printf("6");break;
case 0xa1:printf("7");break;
case 0xa5:printf("8");break;
case 0xa9:printf("9");break;
}
printf("\r\n");
ir.recflag = 0;
}
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
作者:半夜吃早餐