目录

前言

一、获取数据

二、代码实现

1.38Khz的载波频率

2.38kHz PWM载波实现

三、红外协议设计

1. 起始信号

2. 数据帧结构

3. 数据位表示

4. 完整数据发送流程

四、按键中断处理

五、主程序逻辑

七、问题排查

总结



前言

环境:

芯片:STM32F103C8T6

Keil:V5.35.0.2


一、获取数据

因每家空调都有自己的协议,我也没找到我的这款空调的代码定义,所以我采用了最原始的方法,即使用示波器采取空调的原有波形,然后自己发送出去。

这里接收头是淘宝购买的红外通用接收头,将空调设置为26°,然后发送,这边用示波器实时接收代码

根据NEC的红外协议,长时间为1,短时间为0.将获取到的代码进行16进制转换。可以得到以下4端代码:

		uint64_t data1 = 0x482802052;//35
		uint64_t data2 = 0x20008;//32
		uint64_t data3 = 0x482802072;//35
		uint64_t data4 = 0xB;//32

 虽然第二和第四段代码数据较少,但是也需要从左面加0补齐32位。第一和第三则需要补齐35位。

注意:中间有等待的连接时间,约20ms-40ms。

二、代码实现

1.38Khz的载波频率

这里有个偏差,即我们发现接收的频率很低,并不是38Khz,但是却要求是38Khz,原因是发送端必须是38Khz的载波频率,接收端的接收头收到38Khz的频率后,会一直输出高电平,没有38Khz或频率不对则输出低电平,所以接收和输出的频率是不一样的。

2.38kHz PWM载波实现

红外通信需要38kHz载波,通过STM32的TIM2定时器实现:

void PWM_Init(void)
{
    // 启用TIM2和GPIOA时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    
    // 配置PA0为复用推挽输出
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 配置TIM2基础参数
    TIM_InternalClockConfig(TIM2);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;    // ARR
    TIM_TimeBaseInitStructure.TIM_Prescaler = 19 - 1;   // PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    
    // 配置PWM模式
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 50;  // 占空比50%
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    
    TIM_Cmd(TIM2, ENABLE);
}

关键参数计算

  • 系统时钟:72MHz

  • 预分频值(PSC):19-1 → 分频系数19

  • 自动重载值(ARR):100-1

  • 载波频率 = 72MHz / 19 / 100 ≈ 37.89kHz ≈ 38kHz

  • 占空比 = CCR/ARR = 50/100 = 50%

三、红外协议设计

1. 起始信号

void send_start(void)
{
    // 9.6ms高电平(38kHz载波)
    TIM_Cmd(TIM2, ENABLE);
    Delay_us(9600);
    
    // 4.5ms低电平(无载波)
    TIM_Cmd(TIM2, DISABLE);
    Delay_us(4500);
}

2. 数据帧结构

本设计采用自定义协议,包含四个数据段:

uint64_t data1 = 0x482802052;  // 35位
uint64_t data2 = 0x20008;      // 32位
uint64_t data3 = 0x482802072;  // 35位
uint64_t data4 = 0xB;          // 32位

3. 数据位表示

  • 逻辑0:560us载波 + 560us空闲

  • 逻辑1:560us载波 + 1690us空闲

4. 完整数据发送流程

		send_start();//起始信号
//数据1		
		for(int i=35;i>0;i--)
		{
		//高电平560us
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);
		//0低电平560us,1低电平1690us
		TIM_Cmd(TIM2, DISABLE);
			if((data1 >> (i-1)) & 0x01)
			{
				Delay_us(1690);
			}
			else
			{
				Delay_us(560);
			}
		}
		//补位0
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);	
		TIM_Cmd(TIM2, DISABLE);	
		
		
//数据2			
		//20ms高电平
		TIM_Cmd(TIM2, DISABLE);
		Delay_ms(20);//取反设置为20ms低电平
		
		for(int i=32;i>0;i--)
		{
		//高电平560us
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);
		//0低电平560us,1低电平1690us
		TIM_Cmd(TIM2, DISABLE);
			if(((data2 >> (i-1)) & 0x01))
			{
				Delay_us(1690);
			}
			else
			{
				Delay_us(560);
			}
		}
		//补位0
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);
		TIM_Cmd(TIM2, DISABLE);			


		//40ms高电平
		TIM_Cmd(TIM2, DISABLE);
		Delay_ms(40);//取反设置为40ms低电平
		
		//起始信号	
		send_start();//起始信号
//数据3				
		for(int i=35;i>0;i--)
		{
		//高电平560us
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);
		//0低电平560us,1低电平1690us
		TIM_Cmd(TIM2, DISABLE);
			if(((data3 >> (i-1)) & 0x01))
			{
				Delay_us(1690);
			}
			else
			{
				Delay_us(560);
			}
		}
		//补位0
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);	
		TIM_Cmd(TIM2, DISABLE);			
//数据4	
	//20ms高电平	
		TIM_Cmd(TIM2, DISABLE);
		Delay_ms(20);//取反设置为20ms低电平
		
		for(int i=32;i>0;i--)
		{
		//高电平560us
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);
		//0低电平560us,1低电平1690us
		TIM_Cmd(TIM2, DISABLE);
			if(((data4 >> (i-1)) & 0x01))
			{
				Delay_us(1690);
			}
			else
			{
				Delay_us(560);
			}
		}
		TIM_Cmd(TIM2, ENABLE);
		Delay_us(560);
		TIM_Cmd(TIM2, DISABLE);			
}

四、按键中断处理

通过外部中断检测按键动作:

void CountSensor_Init(void)
{
    // 配置GPIOB为输入上拉模式
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_14 | GPIO_Pin_13;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);
    
    // 配置外部中断
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource14);
    EXTI_InitTypeDef EXTI_InitStructure;
    EXTI_InitStructure.EXTI_Line = EXTI_Line14 | EXTI_Line13;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);
    
    // 配置中断优先级
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    NVIC_Init(&NVIC_InitStructure);
}

// 中断服务函数
void EXTI15_10_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line14)) {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_14) == 0) {
            P26_Flag++;  // 26℃按键标志
        }
        EXTI_ClearITPendingBit(EXTI_Line14);
    }
    
    if (EXTI_GetITStatus(EXTI_Line13)) {
        if (GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_13) == 0) {
            P24_Flag++;  // 24℃按键标志
        }
        EXTI_ClearITPendingBit(EXTI_Line13);
    }
}

五、主程序逻辑

在主循环中检测按键标志并发送相应指令:

int main(void)
{
    PWM_Init();            // 初始化PWM
    CountSensor_Init();    // 初始化按键中断
    Delay_Init();          // 初始化延时函数
    
    while(1) {
        if(P26_Flag > 0) {
            send_data_26();  // 发送26℃指令
            P26_Flag = 0;
        }
        
        if(P24_Flag > 0) {
            send_data_24();  // 发送24℃指令
            P24_Flag = 0;
        }
        
    }
}

六、协议分析与优化建议

  1. 协议分析

    • 起始信号:9.6ms载波 + 4.5ms空闲

    • 数据包1:35位数据 + 补位0

    • 间隔:20ms空闲

    • 数据包2:32位数据 + 补位0

    • 长间隔:40ms空闲

    • 重复起始信号

    • 数据包3和数据包4

七、问题排查

  1. 红外无反应

    • 检查38kHz载波是否正常(示波器观察PA0引脚)

    • 确认红外发射管方向正确

    • 测量发射管工作电流(通常10-20mA)

  2. 信号不稳定

    • 优化延时函数精度

    • 增加软件消抖处理

    • 检查电源稳定性

  3. 遥控距离短

    • 增加红外发射管驱动电路(淘宝好多是普通的,需要买1W或3W功率的(需要仔细找找,一般销量不大)我使用的是3W的,在屋内只要不是完全遮挡,几乎任何角度都能接收到。)

    • 使用多颗发射管并联

    • 提高发射功率(注意不要超过器件极限)

 


总结

本文详细介绍了基于STM32的红外空调遥控器实现方案,涵盖了38kHz PWM载波生成、自定义红外协议设计、按键中断处理等关键技术点。本方案稍加修改即可适配不同品牌空调协议,也可扩展为万能红外遥控器。通过理解底层通信原理,相信大家可以进一步探索物联网设备的红外控制技术。

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐