一:基础知识
1-1:硬件部分
1-1-1:红外线
红外线是波长介于微波和可见光之间的电磁波,波长在 760 纳米到 1 毫米之间,是波形比红光长的非可见光。自然界中的一切物体,只要它的温度高于绝对零度(-273)就存在分子和原子的无规则运动,其表面就会不停的辐射红外线。当然了,虽然都是辐射红外线,但是不同的物体辐射的强度是不一样的,而我们正是利用了这一点把红外技术应用到我们的实际开发中。
1-1-2:红外发射管
红外发射管很常用,在我们的遥控器上都可以看到,它类似发光二极管,但是它发射出来的是红外光,是我们肉眼所看不到的。我们学过发光二极管的亮度会随着电流的增大而增加,同样的道理,红外发射管发射红外线的强度也会随着电流的增大而增强
1-1-3:红外接收管
红外接收管内部是一个具有红外光敏感特征的 PN 节,属于光敏二极管,但是它只对红外光有反应。无红外光时,光敏管不导通,有红外光时,光敏管导通形成光电流,并且在一定范围内电流随着红外光的强度的增强而增大
1-2:红外遥控器发射
1-2-1:概念
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机和手机系统中。
随着家用电器、视听产品的普及,红外线遥控器已被广泛使用在各种类型的家电产品上(如遥控开关、智能开关等)。其具有体积小、抗干扰能力强、功耗低、功能强、成本低等特点,在工业设备中也得到广泛应用。一般而言,一个通用的红外遥控系统由发射和接收两大部分组成
1-2-2:基本原理
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为 38KHz,这是由发射端所使用的 455KHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取 12,所以455KHz÷12≈37.9KHz≈38KHz。
通常的红外遥控器是将遥控信号(二进制脉冲码)调制在 38KHz 的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
二进制脉冲码的形式有多种,其中最为常用的是 PWM 码(脉冲宽度调制码)和 PPM 码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率,我们才可以选取一体化红外接收头和制定解码方案。
红外遥控的发射电路是采用红外发光二极管来发出经过调制的红外光波;红外接收电路由红外接收二极管、三极管或硅光电池组成,它们将红外发射器发射的红外光转换为相应的电信号,再送后置放大器。
发射端:一般由指令键(或操作杆)、指令编码系统、调制电路、驱动电路、发射电路等几部分组成。当按下指令键或推动操作杆时,指令编码电路产生所需的指令编码信号,指令编码信号对载波进行调制,再由驱动电路进行功率放大后由发射电路向外发射经调制定的指令编码信号。
接收端:一般由接收电路、放大电路、调制电路、指令译码电路、驱动电路、执行电路(机构)等几部分组成。接收电路将发射器发出的已调制的编码指令信号接收下来,并进行放大后送解调电路,解调电路将已调制的指令编码信号解调出来,即还原为编码信号。指令译码器将编码指令信号进行译码,最后由驱动电路来驱动执行电路实现各种指令的操作控制。
1-3:编码格式
现有的红外遥控包括两种方式: PWM(脉冲宽度调制) 和 PPM(脉冲位置调制)
PWM(脉冲宽度调制) :以发射红外载波的占空比代表“0” 和“1”。为了节省能量,一般情况下, 发射红外载波的时间固定,通过改变不发射载波的时间来改变占空比。 例如常用的电视遥控器,使用 NEC upd6121, 其“0”为载波发射 0.56ms,不发射 0.56ms;其“1”为载波发射 0.56ms,不发射 1.68ms; 此外,为了解码的方便,还有引导码, upd6121 的引导码为载波发射 9ms,不发射 4.5ms。upd6121 总共的编码长度为 108ms
PPM(脉冲位置调制) : 以发射载波的位置表示“0”和“1”。从发射载波到不发射载波为“0”,从不发射载波到发射载波为“1” 。其发射载波和不发射载波的时间相同,都为 0.68ms,也就是每位的时间是固定的。
本文示例程序使用PWM调制信号
1-4:NEC编码
红外遥控器的编码格式通常有两种格式: NEC 和 RC5,本文介绍NEC编码格式。
1-4-1:NEC 编码格式特征
① 使用 38 kHz 载波频率
② 引导码间隔是 9 ms + 4.5 ms
③ 使用 16 位客户代码
④ 使用 8 位数据代码和 8 位取反
1-4-2:NEC 编码时序图
NEC 协议通过脉冲串之间的时间间隔来实现信号的调制(英文简写 PWM)。逻辑“0”是由 0.56ms 的 38KHZ 载波和 0.56ms 的无载波间隔组成;逻辑“1”是由 0.56ms 的 38KHZ 载波和 1.685ms 的无载波间隔组成;结束位是 0.56ms 的38K 载波
二:HAL库程序实现
2-1:程序实现思路
通过定时器的输入捕获功能,因为NEC编码的0和1高电平时间是一样的,通过信号下降沿与上升沿时间的差值也就是低电平时间就可以得到对应编码格式为0 or 1 的时间位,本程序以STM32F03C8T6 经8分频后1us进行计数为例,根据NEC编码的特征,计数值560左右为 0 ,1685左右为1。建立一个数组作为缓冲区,得到33位数据就表明接收完成(指令为32位 1同步码头+8地址码+8地址反码+8控制码+8控制反码),接收完成后继续查找同步码直到再次按下红外遥控器按键。
由于每个按键能得到的32位不同的指令码,只要得到对应的编码就能判断哪个按键被按下,在确保编码唯一性的前提下可以对得到的编码进行变化方便对数据进行处理
2-2:CubeMX 配置
2-2-1:时钟配置
2-2-2:定时器设置
本文以TIM1 的channel4为例,设置为输入捕获模式,如上文所述,分频系数设置为8,以1us为周期进行计数,并开启捕获比较中断和更新事件中断。
2-2-3:串口配置
开启USART1方便后续通过串口查看数据
2-3:详细代码编写
2-3-1:模块化编程
分别建立对应的.c 和 .h 文件
2-3-2:参数定义
uint32_t upcount; //记录更新事件产生的次数
uint32_t valueUp; //捕获到上升沿时刻的值
uint32_t valueDown; //捕获到下降沿时刻的值
uint8_t isUpCapture=1; //判断是否为上升沿捕获,初始需要捕获上升沿,给1
uint16_t PluseWidth; //脉宽(上升沿捕获和下降沿捕获时间的差值)
uint16_t buffer[128]={0};//接收数据缓冲区
uint16_t bufferID=0; //数据存放的位置
uint8_t rcvFlag=0; //接收成功标志位
char printfbuff[128]={0};
char num[4]={0};
2-3-3:初始化函数
开启对应的2个中断就行就行
void InfraredRemote_Init(void)
{
HAL_TIM_Base_Start_IT(&htim1); //定时器更新时,产出中断
HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_4); //开启输入捕获通道中断
}
2-3-4:两个中断回调函数的编写
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) //更新中断回调函数
{
upcount++;
}
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim) //输入捕获中断回调函数
{
if(isUpCapture) //如果是上升沿捕获
{
valueUp=HAL_TIM_ReadCapturedValue(htim ,TIM_CHANNEL_4); //读取捕获上升沿时刻的计数器值
isUpCapture=0;
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_4, TIM_INPUTCHANNELPOLARITY_FALLING); //设置为下降沿捕获
upcount=0; //清零更新计数器
}
else //如果是下降沿捕获
{
valueDown=HAL_TIM_ReadCapturedValue(htim,TIM_CHANNEL_4);
isUpCapture=1;
__HAL_TIM_SET_CAPTUREPOLARITY(htim,TIM_CHANNEL_4,TIM_INPUTCHANNELPOLARITY_RISING);
PluseWidth=valueDown+upcount*65535 -valueUp ; //upcount*65535 防止计数器溢出 单位是1us
//同步码 (低电平9ms,高电平4.5ms)
if(PluseWidth>4400 && PluseWidth<4600)
{
bufferID=0;
buffer[bufferID++]=PluseWidth;
}
else if (bufferID>0) //收到了同步码
{
buffer[bufferID++] = PluseWidth;
if (bufferID>32) //如果接收到了32位(指令为32位 1同步码头+8地址码+8地址反码+8控制码+8控制反码)
{
rcvFlag=1;
bufferID=0; //继续查找同步码
}
}
}
}
2-3-5:打印得到的33位编码
uint16_t InfraredRemote_Get_KeyNum2(void)
{
if (rcvFlag==1)
{
for(int i=0;i<33;i++)
{
sprintf(printfbuff,"%u ",buffer[i]);
HAL_UART_Transmit(&huart1,(uint8_t *)printfbuff,strlen(printfbuff),HAL_MAX_DELAY);
}
HAL_UART_Transmit(&huart1,(uint8_t *)"\r\n",2,HAL_MAX_DELAY);
rcvFlag=0;
}
return (uint16_t)num[2];
}
得到的串口编码数据如下
第一位为4.5ms的引导码,后面32位控制码分别是8地址码+8地址反码+8控制码+8控制反码
2-3-6:简化得到的NEC编码
按8位分割,将buffer中的数据转换为4字节数字存储在num中
void bitbuffer_to_num(char num[]) //将buffer中的数据转换为4字节数字存储在num中
{
num[0]=0;
num[1]=0;
num[2]=0;
num[3]=0;
for(int i=0;i<32;i++) //按8位分割
{
if (buffer[i+1] <1000) //二进制为为0
{
num[i/8] = num[i/8]<<1;
}
else //二进制为为1
{
num[i/8] = num[i/8]<<1;
num[i/8] |= 0x01;
}
}
}
2-3-7:输出转化后的的编码
uint16_t InfraredRemote_Get_KeyNum(void)
{
if (rcvFlag==1)
{
for(int i=0;i<4;i++)
{
bitbuffer_to_num(num);
sprintf(printfbuff,"%02x ",num[i]);
HAL_UART_Transmit(&huart1,(uint8_t *)printfbuff,strlen(printfbuff),HAL_MAX_DELAY);
// sprintf(printfbuff,"%u ",buffer[i]);
// HAL_UART_Transmit(z&huart1,(uint8_t *)printfbuff,strlen(printfbuff),HAL_MAX_DELAY);
}
// HAL_UART_Transmit(&huart1,(uint8_t *)"\r\n",2,HAL_MAX_DELAY);
rcvFlag=0;
}
return (uint16_t)num[2];
}
打印出的数据如下
我使用的红外遥控键位分布如下:
为了方便使用,我们把字符型键码转化为整型
分别按下每个按键得到对应的键码并进行声明
#define IR_1 0xa2
#define IR_2 0x62
#define IR_3 0xe2
#define IR_4 0x22
#define IR_5 0x02
#define IR_6 0xc2
#define IR_7 0xe0
#define IR_8 0xa8
#define IR_9 0x90
#define IR_Xing 0x68
#define IR_0 0x98
#define IR_Jing 0xb0
#define IR_UP 0x18
#define IR_Left 0x10
#define IR_Right 0x5a
#define IR_Down 0x4a
#define IR_OK 0x38
2-3-8:.h文件的编写
#ifndef __InfraredRemote_H__
#define __InfraredRemote_H__
#define IR_1 0xa2
#define IR_2 0x62
#define IR_3 0xe2
#define IR_4 0x22
#define IR_5 0x02
#define IR_6 0xc2
#define IR_7 0xe0
#define IR_8 0xa8
#define IR_9 0x90
#define IR_Xing 0x68
#define IR_0 0x98
#define IR_Jing 0xb0
#define IR_UP 0x18
#define IR_Left 0x10
#define IR_Right 0x5a
#define IR_Down 0x4a
#define IR_OK 0x38
void InfraredRemote_Init(void);
void bitbuffer_to_num(char num[]);
uint16_t InfraredRemote_Get_KeyNum(void);
#endif
2-3-9:红外遥控模块在main函数中的使用举例
实验现象:按下OK键码,板载LED点亮,按下其他键值熄灭
/* 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 "string.h"
#include "InfraredRemote.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 */
/* 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_TIM1_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
InfraredRemote_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
HAL_UART_Transmit(&huart1,(uint8_t *)"start!!\r\n",9,HAL_MAX_DELAY);
HAL_TIM_Base_Start_IT(&htim1); //定时器更新时,产出中断
HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_4); //开启输入捕获通道中断
while (1)
{
uint16_t key_num=InfraredRemote_Get_KeyNum();
if (key_num==IR_OK)
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,0);
}
else
{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,1);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* 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_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.HSICalibrationValue = RCC_HSICALIBRATION_DEFAULT;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
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_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0) != 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 */
三:代码开源
完整的KEIL程序和Cube MX文件开源