/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file nec_infrared.c
* @author Motor Control SDK Team, Yuwell Software XiangenWang
* @brief Voice Recognition Module Initialization Section,
including peripheral initialization and message node insertion, etc.
* @version 1.0
* @changelog version 1.0 初始版本 2025.11.13
- 新增:新建第一个版本的软件,待完善解析命令后的程序执行部分
******************************************************************************
* @attention
*
*
© Copyright (c) 2025 Yuwell Software Danyang.Jiangsu.China.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted, provided that the following conditions are met:
*
* 1. Redistribution of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. Neither the name of Yuwell Software nor the names of other
* contributors to this software may be used to endorse or promote products
* derived from this software without specific written permission.
* 4. This software, including modifications and/or derivative works of this
* software, must execute solely and exclusively on microcontroller or
* microprocessor devices manufactured by or for Yuwell Software.
* 5. Redistribution and use of this software other than as permitted under
* this license is void and will automatically terminate your rights under
* this license.
*
* THIS SOFTWARE IS PROVIDED BY Yuwell Software AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NON-INFRINGEMENT OF THIRD PARTY INTELLECTUAL PROPERTY
* RIGHTS ARE DISCLAIMED TO THE FULLEST EXTENT PERMITTED BY LAW. IN NO EVENT
* SHALL Yuwell Software OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
******************************************************************************
*/
/* USER CODE END Header */
#include "nec_infrared.h"
#include "string.h"
#include "fm33lg0xx_queue.h"
#include "fm33lg0xx_event.h"
#include "ht24lc02_eeprom.h"
/************************** 全局变量定义 **************************/
Infrared infrared_info; // 初始化红外结构体,状态默认为IDLE
/************************** 私有函数声明 **************************/
static uint32_t nec_calc_pulse_width(Infrared* info, uint32_t current_val);
/**
* @brief 红外模块初始化函数
* @param NONE
* @retval NONE
* @note 1. 配置红外接收引脚为输入上拉模式,LPTIM32为1MHz计数频率(64MHz系统时钟分频64)
* 2. 配置LPTIM32输入捕获为双边沿触发,使能捕获和溢出中断
* 3. 配置NVIC中断优先级,使能LPTIM32中断
* @author jarvis
* @date 2025.11.25
*/
void nec_infrared_init(void)
{
FL_LPTIM32_InitTypeDef timInit;
FL_LPTIM32_IC_InitTypeDef timICInit;
FL_GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.pin = NEC_INFRARED_GPIO_PIN;
GPIO_InitStruct.mode = FL_GPIO_MODE_DIGITAL;
GPIO_InitStruct.outputType = FL_GPIO_OUTPUT_PUSHPULL;
GPIO_InitStruct.pull = FL_ENABLE;
GPIO_InitStruct.remapPin = FL_DISABLE;
GPIO_InitStruct.analogSwitch = FL_DISABLE;
FL_GPIO_Init(NEC_INFRARED_GPIO_PORT, &GPIO_InitStruct);
FL_LPTIM32_StructInit(&timInit);
timInit.clockSource = FL_CMU_LPTIM32_CLK_SOURCE_APBCLK; // 系统频率为64Mhz
timInit.mode = FL_LPTIM32_OPERATION_MODE_NORMAL;
timInit.prescalerClockSource = FL_LPTIM32_CLK_SOURCE_INTERNAL;
timInit.prescaler = FL_LPTIM32_PSC_DIV64; // 当前计数频率分频为1Mhz
timInit.autoReload = 300000 - 1; // 最大波长为500ms
timInit.onePulseMode = FL_LPTIM32_ONE_PULSE_MODE_CONTINUOUS;
timInit.triggerEdge = FL_LPTIM32_ETR_TRIGGER_EDGE_BOTH;
timInit.countEdge = FL_LPTIM32_ETR_COUNT_EDGE_FALLING;
FL_LPTIM32_Init(LPTIM32, &timInit);
timICInit.ICSource = FL_LPTIM32_IC1_CAPTURE_SOURCE_GROUP0;
timICInit.ICEdge = FL_LPTIM32_IC_EDGE_FALLING; // 双边缘捕获
FL_LPTIM32_IC_Init(LPTIM32, FL_LPTIM32_CHANNEL_3, &timICInit);
FL_LPTIM32_ClearFlag_CC(LPTIM32, FL_LPTIM32_CHANNEL_3); // 清除标志
FL_LPTIM32_ClearFlag_Update(LPTIM32);
FL_LPTIM32_EnableIT_CC(LPTIM32, FL_LPTIM32_CHANNEL_3); // 中断使能
FL_LPTIM32_EnableIT_Update(LPTIM32);
NVIC_DisableIRQ(LPTIMx_IRQn); // 使能并配置NVIC
NVIC_SetPriority(LPTIMx_IRQn,1); // 设置中断优先级
NVIC_EnableIRQ(LPTIMx_IRQn);
FL_LPTIM32_Enable(LPTIM32); // 使能LPTIM32
infrared_info.ir_state = IR_STATE_IDLE;
infrared_info.pulse_width_index = 0;
infrared_info.code_464_ing = 0;
}
/**
* @brief 计算红外脉冲宽度(处理定时器溢出)
* @param info: 指向红外结构体的指针
* @param current_val: 当前捕获的定时器计数值
* @retval 脉冲宽度(单位:μs,1MHz计数频率)
* @note 1. 若当前值大于等于上一次值,直接相减;否则处理溢出(当前值+500000-上一次值)
* 2. 500000为LPTIM32的自动重装值(最大计数)
* @author jarvis
* @date 2025.12.12
*/
static uint32_t nec_calc_pulse_width(Infrared* info, uint32_t current_val)
{
uint32_t pulse_width = 0;
info->ic_value_last = info->ic_value_current;
info->ic_value_current = current_val;
// 处理定时器溢出(当前值 < 上一次值)
if (info->ic_value_current >= info->ic_value_last)
{
pulse_width = info->ic_value_current - info->ic_value_last;
}
else
{
// 溢出后的值 = 当前值 + (自动重装值 + 1) - 上一次值
pulse_width = (info->ic_value_current + 500000)- info->ic_value_last;
}
return pulse_width;
}
/**
* @brief 解析NEC红外遥控指令并处理对应逻辑
* @note 该函数用于解析NEC协议的红外脉冲宽度数据,提取有效指令数据,
* 并根据不同的指令码执行对应的事件触发和状态管理逻辑
* @param info: 指向红外数据处理结构体的指针,包含脉冲宽度数组、解析后的数据、
* 错误标记、状态标记等信息
* @retval 无
* @attention 1. 仅处理NEC协议的32位数据部分,需确保脉冲宽度数组前33个元素有效
* 2. 解析过程中若检测到无效脉冲宽度,会立即标记解码错误并返回
* 3. 指令处理涉及全局事件组操作,需保证全局事件组的线程安全
*/
static void nec_command_decode(Infrared* info)
{
// 对数据进行解析并将数据下发到消息队列中
uint8_t i = 0;
info->received_data = 0;
info->received_data_index = 0;
MsgQueueItem queue_item;
MsgQueueItem filter_item;
// 解析数组
if((info->pulse_width_array[0] > NEC_START_FALLING_MIN)&&(info->pulse_width_array[0] < NEC_START_FALLING_MAX))
{
for(i = 1;i < 33; i++)
{
if((info->pulse_width_array[i] > NEC_DATA0_FALLING_MIN)&&(info->pulse_width_array[i] < NEC_DATA0_FALLING_MAX))
{
info->received_data_index++;
}else if((info->pulse_width_array[i] > NEC_DATA1_FALLING_MIN)&&(info->pulse_width_array[i] < NEC_DATA1_FALLING_MAX))
{
info->received_data += (1 << info->received_data_index++);
}else{
info->decode_error = true;
info->received_data = 0;
info->received_data_index = 0;
return;
}
}
}
info->decode_error = false;
// vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK);
// OK灯闪烁一下
if(info->received_data == NEC_CODE_NUM_4)
{
if(info->code_464_ing == 2)
{
vOxygenEventGroupSetBits(&global_event, EVENT_464_CLEAR); // 获取到第三个4则下发清零事件
info->code_464_ing = 0;
}else if(info->code_464_ing == 0){
info->code_464_ing = 1; // 标记正在校准中
vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK);
}else{
info->code_464_ing = 0;
}
}
if(info->received_data == NEC_CODE_NUM_6)
{
if(info->code_464_ing == 1)
{
vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK);
info->code_464_ing = 2;
}else{
info->code_464_ing = 0;
}
}
#if PROGREMA_DEBUG_MODE
if(info->received_data == NEC_CODE_TIMER)
{
vOxygenEventGroupSetBits(&global_event, EVENT_OK_BLINK);
// 接收到定时按键时不进行任何操作
queue_item.type = MSG_TYPE_HOUR_SAVE;
queue_item.data.hour_meter.hour = 0;
modify_or_add_queue_node_by_type(&global_queue, MSG_TYPE_HOUR_SAVE, queue_item);
queue_item.type = MSG_TYPE_MINUTE_SAVE;
queue_item.data.hour_meter.minute = 0;
modify_or_add_queue_node_by_type(&global_queue, MSG_TYPE_MINUTE_SAVE, queue_item);
ht24lc02_word_write(7200, INDEX_E4_ELEC_HIGH);
ht24lc02_word_write(20, INDEX_E1_PRESS_LOW);
ht24lc02_word_write(280, INDEX_E2_PRESS_HIGH);
ht24lc02_word_write(180, INDEX_E7_220V_LOW);
filter_item.data.filter_server.last_clean_filter_hours = 0;
filter_item.data.filter_server.last_replace_filter_hours = 0;
filter_item.data.filter_server.clean_filter_time_need_stored = true;
filter_item.data.filter_server.replace_filter_time_need_stored = true;
modify_or_add_queue_node_by_type(&global_queue, MSG_TYPE_FILTER_TIME_SAVE, filter_item);
}
#endif
}
/**
* @brief 红外遥控的捕获中断处理函数
* @param info: 指向红外结构体的指针
* @param Cap_Val: 捕获的定时器计数值
* @retval NONE
* @note 1. 计算脉冲宽度,检测NEC协议的起始位、重复帧、数据位、长按位
* 2. 数据位分为0和1,累计8位为1字节,4字节为一帧数据,完成后校验
* 3. 入参为NULL时直接返回
* @author jarvis
* @date 2025.11.25
*/
static void user_infrared_IRQHandle(Infrared* info, uint32_t Cap_Val)
{
if (info == NULL)
{
return;
}
info->pulse_width = nec_calc_pulse_width(info, Cap_Val); // 计算脉冲宽度
if((info->pulse_width >= NEC_START_FALLING_MIN)&&(info->pulse_width <= NEC_START_FALLING_MAX))
{
memset(info->pulse_width_array, 0, sizeof(info->pulse_width_array));
info->pulse_width_index = 0;
info->ir_state = IR_STATE_WAIT_DATA_END;
}
info->pulse_width_array[info->pulse_width_index++] = info->pulse_width;
info->clear_delay = 0;
if(info->pulse_width_index >= 33)
{
info->pulse_width_index = 0;
info->ir_state = IR_STATE_IDLE;
info->ok_count++;
// 对接收到的数据进行解析
nec_command_decode(info);
}
}
/**
* @brief 红外遥控的溢出中断处理函数
* @param info: 指向红外结构体的指针
* @retval NONE
* @note 1. 处理无信号时的脏数据清理,重置信号标记
* 2. 处理通信超时(计数到10时清空超时标志和通信值)
* 3. 入参为NULL时直接返回
* @author jarvis
* @date 2025.11.25
*/
static void user_infrared_Updata_IRQHandle(Infrared* info)
{
if (info == NULL)
{
return;
}
// 两种情况下重新刷新内容 1.红外处于IDLE模式下 2.处于IR_STATE_WAIT_DATA_END模式超过两个周期(100ms)
if((info->ir_state == IR_STATE_IDLE)&&(info->pulse_width_array[0] != 0))
{
memset(info->pulse_width_array, 0, sizeof(info->pulse_width_array));
info->pulse_width_index = 0;
}
if(info->ir_state == IR_STATE_WAIT_DATA_END)
{
if(info->clear_delay >= 2)
{
memset(info->pulse_width_array, 0, sizeof(info->pulse_width_array));
info->pulse_width_index = 0;
info->clear_delay = 0;
info->ir_state = IR_STATE_IDLE;
}else{
info->clear_delay++;
}
}
}
/**
* @brief LPTIM32中断服务函数
* @param NONE
* @retval NONE
* @note 1. 处理捕获中断和溢出中断,分别调用对应的红外处理函数
* 2. 先清除中断标志,再处理数据,避免中断嵌套导致的问题
* @author jarvis
* @date 2025.11.25
*/
void LPTIM_IRQHandler(void)
{
uint32_t Cap_Val;
if(FL_LPTIM32_IsActiveFlag_CC(LPTIM32, FL_LPTIM32_CHANNEL_3))
{
FL_LPTIM32_ClearFlag_CC(LPTIM32, FL_LPTIM32_CHANNEL_3);
Cap_Val = FL_LPTIM32_ReadCaptureCH3(LPTIM32);
user_infrared_IRQHandle(&infrared_info, Cap_Val); // 调用红外解码函数
}
if(FL_LPTIM32_IsActiveFlag_Update(LPTIM32))
{
FL_LPTIM32_ClearFlag_Update(LPTIM32);
user_infrared_Updata_IRQHandle(&infrared_info); // 调用溢出处理函数
}
}