杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

NOR Flash 与红外编解码组件

本页面深入解析 AC82N SDK 中的 NOR Flash 外设驱动(norflash.c)与 NEC 红外编解码组件(ir_decoder.c / ir_encoder.c / irkey.c),涵盖分区模型、SPI 访问抽象、NEC 协议时序、捕获中断状态机、按键映射与配置方式。

Purpose and Scope

本文档面向需要在 AC82N 平台上使用片外 NOR Flash 存储或接入红外遥控(发射/接收)的固件开发者,内容以源码为依据,包括:

  • NOR Flash 驱动的分区模型(最多 4 个分区)、SPI 通信抽象、读/写/擦除流程;
  • NEC 协议红外解码器的时序判定算法与 GPTimer 捕获中断处理流程;
  • NEC 协议红外编码器的发送状态机与 PWM/Timer 驱动方式;
  • 红外按键驱动如何把解码命令映射为上层按键事件(IR_00~IR_20);
  • 相关的编译开关、配置参数、故障模式与扩展方式。

属于外设驱动子系统的其他话题(例如普通 GPIO 按键、GPIO IRQ、SFC 启动级 Flash 引导等)不在本页范围,相关能力请参见各自的驱动页面。

Overview

AC82N 是杰理科技面向 MCU 应用推出的 SoC 平台,其 SDK 采用"CPU 驱动 + 组件 + 应用"分层结构。本页覆盖的两类外设是消费类嵌入式产品(遥控器、玩具、家电面板)中最常用的能力:

  1. NOR Flash:通过 SPI 接口挂接片外 Flash,用于存放配置、字库、资源或用户数据。驱动在 sdk/cpu/cd09/norflash.c 中实现,采用"设备-分区"模型,把一块 Flash 逻辑切分为多个命名分区,并通过 SPI 字节/ DMA 接口访问,同时提供互斥锁保护并发访问。
  2. 红外编解码:基于 NEC 协议(38kHz 载波、563μs 基本单位时间),解码器(ir_decoder.c)利用 GPTimer 的输入捕获功能测量下降沿间的脉宽并还原 32 位命令帧;编码器(ir_encoder.c)则利用 GPTimer 的 PWM 输出能力按状态机逐位发送载波脉冲;irkey.c 再把解码得到的命令值映射为应用层按键。

两者都以定时器/中断驱动为核心,具有确定性的时序约束:解码器要求脉宽测量误差在 10% 以内,编码器要求 PWM 时序按 563μs 单位精确切换。理解这些时序参数是正确接入本组件的前提。

Architecture

flowchart TD
    subgraph sg_App["应用层 (App)"]
        Irkey["irkey.c<br/>红外按键驱动"]
        NorDemo["norflash_demo.c<br/>Flash 读写演示"]
    end

    subgraph sg_Periph["外设驱动层 (sdk/cpu)"]
        Norflash["norflash.c<br/>NOR Flash 驱动"]
        IRDecoder["ir_decoder.c<br/>NEC 解码器"]
        IREncoder["ir_encoder.c<br/>NEC 编码器"]
    end

    subgraph sg_HW["硬件抽象层 (CPU 外设)"]
        SPI["SPI 控制器<br/>spi_send/recv_byte、spi_dma_*"]
        GPTimer["GPTimer<br/>捕获 / PWM / 定时"]
        GPIO["GPIO<br/>片选与电平控制"]
    end

    Irkey -->|"ir_decoder_get_command_value()"| IRDecoder
    IRDecoder -->|"gptimer 捕获中断"| GPTimer
    IREncoder -->|"PWM 输出 / 定时中断"| GPTimer
    Norflash -->|"spi_*_byte / spi_dma_*"| SPI
    Norflash -->|"gpio_hw_* 片选控制"| GPIO
    NorDemo --> Norflash

架构说明:

  • norflash.c 位于 CPU 驱动层,直接操作 SPI 与 GPIO 硬件抽象接口。它本身不关心数据内容,只提供"按地址读写、按分区管理"的原始访问能力;上层(如 norflash_demo.c、文件系统、字库模块)通过设备框架或分区名使用它。
  • ir_decoder.c 挂在 GPTimer 捕获中断上,属于纯中断驱动的被动接收端。它只维护一个静态解码上下文(ir_decode_t),应用层通过 ir_decoder_get_command_value() 主动取走最新命令。
  • ir_encoder.c 与解码器对称,属于主动发送端,内部是一个由定时器中断推进的状态机,逐 bit 切换 PWM 输出。
  • irkey.c 把解码器输出的命令字节与遥控器码表(IRTabFF00)对照,生成 IR_00~IR_20 等按键值,接入通用的 key_driver 扫描框架,使上层按键逻辑与红外协议解耦。

这一分层刻意把协议时序(编解码器)与业务语义(按键映射)分离:更换遥控器时只需改码表,更换协议(如 RC5/Sony)时只需替换编解码器而无需改动按键层。

NOR Flash 驱动实现

norflash.c 是整个 Flash 访问能力的核心,整体受编译开关 TCFG_NORFLASH_DEV_ENABLE 控制(#if defined(TCFG_NORFLASH_DEV_ENABLE) && TCFG_NORFLASH_DEV_ENABLE),未定义该宏时驱动整体不参与编译,这保证了不同产品可裁剪外设。

分区模型(Partition Model)

驱动把一整块 Flash 视为由多个命名分区组成的地址空间,最多支持 4 个分区:

#define MAX_NORFLASH_PART_NUM       4

struct norflash_partition {
    const char *name;
    u32 start_addr;
    u32 size;
    struct device device;
};

struct norflash_info {
    u32 flash_id;
    u32 flash_capacity;
    int spi_num;
    int spi_err;
    u8 spi_cs_io;
    u8 spi_r_width;
    u8 part_num;
    u8 open_cnt;
    struct norflash_partition *const part_list;
    struct spi_platform_data *spi_pdata_use;
    SPI_mutex mutex;
    u32 max_end_addr;
};

Source: norflash.c

设计意图: 分区模型把"物理设备"与"逻辑资源"解耦——固件各模块(配置、资源、日志)只需要知道分区的名字,无需关心物理地址,地址与大小的分配集中在创建分区时一次完成。struct device 内嵌在每个分区中,使每个分区都可以作为一个独立的设备节点注册到设备框架(device/device.h),支持 open/read/write/ioctl 等标准操作。全局单例 _norflash(.spi_num = -1 表示 SPI 尚未初始化,.part_list = nor_part)承载所有状态。

分区管理由三个函数完成:

  • norflash_find_part(const char *name):线性遍历 part_list,按名字精确匹配返回分区指针,找不到返回 NULL;
  • norflash_new_part(const char *name, u32 addr, u32 size):在空闲槽位创建分区,memset 清零后写入 name/start_addr/size,并维护 max_end_addr(所有分区最大结束地址)与 part_num;槽位耗尽时打印 log_error("create norflash part fail") 并返回 NULL;
  • norflash_delete_part(const char *name):按名字删除分区,释放槽位供后续创建。

Source: norflash.c

SPI 访问抽象

驱动通过一组宏把对 SPI 控制器和 GPIO 片选的操作统一起来,屏蔽了寄存器细节,也让上层代码可读性更强:

#define spi_cs_init() \
	do { \
		gpio_hw_set_die(IO_PORT_SPILT(_norflash.spi_cs_io), 1); \
		gpio_hw_set_direction(IO_PORT_SPILT(_norflash.spi_cs_io), 0); \
		gpio_hw_write(_norflash.spi_cs_io, 1); \
	} while (0)

#define spi_cs_h()                  gpio_hw_write(_norflash.spi_cs_io, 1)
#define spi_cs_l()                  gpio_hw_write(_norflash.spi_cs_io, 0)
#define spi_read_byte()             spi_recv_byte(_norflash.spi_num, &_norflash.spi_err)
#define spi_write_byte(x)           spi_send_byte(_norflash.spi_num, x)
#define spi_dma_read(x, y)          spi_dma_recv(_norflash.spi_num, x, y)
#define spi_dma_write(x, y)         spi_dma_send(_norflash.spi_num, x, y)
#define spi_set_width(x)            spi_set_bit_mode(_norflash.spi_num, x)

Source: norflash.c

要点:

  • 片选独立于 SPI 控制器:CS 引脚用 GPIO 软件控制(spi_cs_l()/spi_cs_h()),因此可以自由选择任意 IO 作片选,不受硬件 CS 通道限制;spi_cs_init() 会先配置方向与默认高电平(不选中),spi_cs_uninit() 则把引脚恢复为高阻输入并关闭上下拉,便于低功耗。
  • 支持双模式访问:字节模式(spi_send_byte/spi_recv_byte)适合命令与状态轮询,DMA 模式(spi_dma_send/spi_dma_recv)适合大块数据搬运(如读字库),可显著降低 CPU 占用。
  • 错误随路返回:spi_recv_byte 把错误码写入 _norflash.spi_err,驱动可以在事务结束后统一检查,避免每次读取都做错误分支。
  • 位宽可切换:spi_set_width 通过 spi_set_bit_mode 切换(如标准 SPI 与 Dual/Quad 模式),为高速读取预留了扩展能力。

读写擦核心函数

驱动头部分声明的三个核心函数构成完整的数据通路:

int _norflash_read(u32 addr, u8 *buf, u32 len, u8 cache);
int _norflash_eraser(u8 eraser, u32 addr);
static int _norflash_write_pages(u32 addr, u8 *buf, u32 len);
static void _norflash_cache_sync_timer(void *priv);

Source: norflash.c

  • _norflash_read(addr, buf, len, cache):按地址读取 len 字节到 buf。cache 参数启用写缓存同步机制——若前一次写操作的数据还停留在缓存中,读操作前需先把缓存同步到 Flash,保证读到最新数据(_norflash_cache_sync_timer 为缓存同步定时器回调)。
  • _norflash_eraser(eraser, addr):擦除操作,eraser 区分擦除粒度(如扇区/块),返回擦除状态。
  • _norflash_write_pages(addr, buf, len):按页写入。NOR Flash 的页写入是"或逻辑"(只能把 1 写为 0),因此写之前必须先擦除,且页内写入不能跨页,这是驱动必须按页拆分写入的根本原因。上层在使用时必须遵循"擦→写"的顺序约束,驱动本身通过页循环保证单页内地址连续性。

注:norflash.c 的其余部分(设备 open/close/ioctl、SPI 初始化、缓存页管理等)位于文件后续约 870 行中;上面已列出的结构体、宏与函数声明均来自文件头部源码,可直接作为理解整个驱动数据通路的入口。

红外解码器(NEC 协议接收)

ir_decoder.c 实现 NEC 协议解码:38kHz 载波经接收头整形后输出脉冲序列,解码器通过测量下降沿间隔还原数据位。它依赖 GPTimer 的输入捕获功能,在每次下降沿触发中断。

NEC 时序常量与容差

#define NEC_NBITS           32
#define NEC_TIME_DEVIATION  10 //允许10%偏差
#define NEC_UNIT_TIME       563
#define NEC_HEADER_TIME     ((16 + 8) * NEC_UNIT_TIME)
#define NEC_BIT_0_TIME      ((1 + 1) * NEC_UNIT_TIME)
#define NEC_BIT_1_TIME      ((3 + 1) * NEC_UNIT_TIME)
#define NEC_REPEAT_TIME     ((16 + 4) * NEC_UNIT_TIME)
#define NEC_TIMEOUT_MS      150 //150ms

Source: ir_decoder.c

NEC 协议以 563μs 为单位(NEC_UNIT_TIME),各符号定义如下:

符号时间(单位)实际时长含义
引导码 HEAD16 + 813.5ms帧起始,高 9ms + 低 4.5ms
逻辑 13 + 12.25ms高 1.69ms + 低 0.56ms
逻辑 01 + 11.125ms高 0.56ms + 低 0.56ms
重复码 REPEAT16 + 411.25ms长按键重复帧

NEC_TIME_DEVIATION = 10 表示允许 ±10% 的脉宽偏差,这是对廉价遥控器晶振误差与接收头 jitter 的工程折衷——容差过小会导致误判,过大则会混淆相邻符号。

解码上下文与脉宽分类

struct ir_decode_t {
    u32 jiffies; //记录当前系统时间
    u32 ir_data; //存储接收到的红外数据,4*8bit = (cmd_not + cmd +addr_not + addr)
    u8 capture_tid; //用于捕获功能的tiemr_id
    u8 bit_count; //有效bit个数
    u8 command_count; //有效命令个数
};
static struct ir_decode_t _ir_decode;

Source: ir_decoder.c

解码器为单实例静态对象(_ir_decode),天然串行,无需加锁;ir_data 按 NEC 帧格式存放 4 个字节:cmd_not + cmd + addr_not + addr,便于上层直接取用。脉宽判定由三个辅助函数完成:

  • jiffies_timeout_check():比较两次中断间的系统节拍差,若超过 NEC_TIMEOUT_MS(150ms)判定为帧超时。它显式处理了 jiffies 回绕(jiffies_diff < 0 时加 0xffffffff 修正),保证系统长时间运行后仍能正确判定;
  • is_time_within(t, center, deviation):判断实测脉宽 t 是否落在 center ± deviation% 范围内,使用 __builtin_abs 处理有符号差;
  • ir_decoder_pulse_check(us):依次与引导码、bit0、bit1、重复码匹配,返回 NEC_HEAD / NEC_BIT_0 / NEC_BIT_1 / NEC_REPEAT,全部不匹配则返回 NEC_ERROR。

Source: ir_decoder.c

捕获中断处理流程

static void ir_decode_irq(u32 tid, void *arg)
{
    u32 us = gptimer_get_capture_cnt2us(tid);
    //超时或第一个边沿直接退出
    if (jiffies_timeout_check()) {
        return;
    }
    u32 type = ir_decoder_pulse_check(us);

    switch (type) {
    case NEC_HEAD:
        ir_decode->bit_count = 0;
        ir_decode->command_count = 0;
        break;
    case NEC_BIT_1:
        ir_decode->ir_data >>= 1;
        ir_decode->ir_data |= 0x80000000;
        ir_decode->bit_count++;
        ir_decoder_bit_count_check();
        break;
    case NEC_BIT_0:
        ir_decode->ir_data >>= 1;
        ir_decode->bit_count++;
        ir_decoder_bit_count_check();
        break;
    case NEC_REPEAT:
        ir_decoder_bit_count_check();
        break;
    default:
        ...
    }
}

Source: ir_decoder.c

算法核心——移位累积: 每收到一个数据位,先把 ir_data 右移一位,若是逻辑 1 再置位最高位(|= 0x80000000)。NEC 协议先发低位,这种"先移后置"的写法恰好让第一个到达的 bit 最终落在最低位,等效于按到达顺序组装 32 位帧。当 bit_count 达到 NEC_NBITS(32)时 ir_decoder_bit_count_check() 递增 command_count,表示收到一帧完整命令;重复码不改变 ir_data,仅重新计数,实现长按重复语义。

sequenceDiagram
    participant R as 红外接收头
    participant T as GPTimer(捕获)
    participant D as ir_decoder.c
    participant K as irkey.c

    R->>T: 载波脉冲(下降沿)
    T->>D: 捕获中断 ir_decode_irq()
    D->>D: gptimer_get_capture_cnt2us 换算脉宽
    D->>D: jiffies_timeout_check() 帧超时判断
    D->>D: ir_decoder_pulse_check() 脉宽分类
    alt NEC_HEAD
        D->>D: 复位 bit_count / command_count
    else NEC_BIT_1
        D->>D: ir_data >>= 1 并置位 bit31
    else NEC_BIT_0
        D->>D: ir_data >>= 1
    else NEC_REPEAT
        D->>D: 沿用上一帧 ir_data
    end
    Note over D: 32bit 收满 → command_count++
    K->>D: ir_decoder_get_command_value()
    D-->>K: 命令字节 (0x00~0xFF / 0xFF 无效)
    K->>K: 查 IRTabFF00 映射为 IR_xx 按键

超时兜底(关键设计): 若遥控器停止发送或首边沿到来,jiffies_timeout_check() 直接 return,避免把帧间噪声当作数据。150ms 的选择兼顾了两点:长于 NEC 重复码间隔(约 108ms)以保证重复帧能被连续解析,短于普通按键间隔以避免把两帧不同命令粘连。

红外编码器(NEC 协议发送)

ir_encoder.c 与解码器对称,负责按 NEC 协议把 32 位命令帧通过 GPTimer 的 PWM 输出发送出去。其核心是一个定时器中断驱动的状态机。

发送时序常量

#define IR_NEC_PULSE_UNIT   563
#define IR_NEC_BIT_1_H  1
#define IR_NEC_BIT_1_L  3
#define IR_NEC_BIT_0_H  1
#define IR_NEC_BIT_0_L  1
#define IR_NEC_HEAD_H   16
#define IR_NEC_HEAD_L   8
#define IR_NEC_END_H    1
#define IR_NEC_END_L    1
#define IR_NEC_END_REPEAT_L    75//196
#define IR_NEC_REPEAT_H 16
#define IR_NEC_REPEAT_L 4
#define IR_NEC_REPEAT_END_H 1
#define IR_NEC_REPEAT_END_L 175
#define IR_NEC_REPEAT   0x00FF00FF

Source: ir_encoder.c

编码侧与解码侧共用同一套 NEC 时序基元(563μs 单位),但以"高电平持续 N 个单位、低电平持续 M 个单位"的方式描述,例如 bit1 = 高 1 单位 + 低 3 单位、引导码 = 高 16 单位 + 低 8 单位。IR_NEC_REPEAT = 0x00FF00FF 是 NEC 标准重复码的数据模式(地址反码+地址+命令反码+命令)。

发送状态机

enum state : u8 {
    IR_IDLE,
    IR_HEAD,
    IR_DATA,
    IR_END,
    IR_REPEAT,
    IR_REPEAT_END,
};

struct ir_encode_info {
    u32 ir_data; //存储发送的红外数据,4*8bit = (cmd_not + cmd +addr_not + addr)
    enum state state; //状态机,记录当前发送处于哪个阶段
    u8 pwm_tid; //用于pwm功能的timer_id
    u8 timer_tid; //用于timer功能的timer_id
    u8 high_level_count; //高电平持续count个单位时间
    u8 low_level_count; //低电平持续count个单位时间
    u8 repeat_en; //重复码使能标志
    u8 pwm_high_ctrl: 1; //高电平使能标志位,避免重复开关
    u8 pwm_low_ctrl: 1; //低平使能标志位,避免重复开关
    u8 bit_count: 6; //已发送bit个数
};

Source: ir_encoder.c

stateDiagram-v2
    [*] --> IR_IDLE
    IR_IDLE --> IR_HEAD: 发送命令触发
    IR_HEAD --> IR_DATA: 引导码发送完成
    IR_DATA --> IR_END: 32bit 数据发送完成
    IR_END --> IR_IDLE: 结束码完成(无重复)
    IR_END --> IR_REPEAT: repeat_en 开启
    IR_REPEAT --> IR_REPEAT_END: 重复码发送完成
    IR_REPEAT_END --> IR_REPEAT: 继续重复发送
    IR_REPEAT_END --> IR_IDLE: 停止发送

状态机的推进依赖两个定时器资源:pwm_tid 用于产生 38kHz 载波(仅在需要发射时使能 PWM,通过 pwm_high_ctrl/pwm_low_ctrl 两个位标志避免重复开关),timer_tid 用于按 563μs 单位倒计时当前高/低电平段,段结束即切到下一状态。high_level_count/low_level_count 记录当前符号的高低电平单位数,bit_count 记录已发送的数据位数(6 位足够容纳 32)。

设计意图: 把"载波产生"(PWM,硬件持续输出)与"时序编排"(Timer 中断逐段切换)分离,是低 CPU 开销实现红外发射的关键——CPU 只在段边界被唤醒一次,中间 38kHz 载波完全由硬件产生。ir_encode_info 默认静态分配(IR_ENCODER_MALLOC_ENABLE = 0 时使用 static struct ir_encode_info _ir_encode,ir_encoder_malloc() 负责清零初始化),避免在实时发送路径上引入动态内存分配的不确定性;需要时也可通过宏切换为 malloc 分配。

红外按键驱动

irkey.c 把解码器输出的命令字节翻译为应用层按键事件,是解码器与通用按键框架之间的适配层,整体受 TCFG_IRKEY_ENABLE 编译开关控制。

接入按键扫描框架

struct key_driver_para irkey_scan_para = {
    .scan_time 	= 110,            //按键扫描频率, 单位: ms
    .last_key   = NO_KEY,         //上一次get_value按键值, 初始化为NO_KEY;
    .filter_time 	= 0,              //按键消抖延时;
    .long_time 	= 6,              //按键判定长按数量
    .hold_time 	= (6 + 3),        //按键判定HOLD数量
    .click_delay_time = 2,         //按键被抬起后等待连击延时数量
    .key_type	= KEY_DRIVER_TYPE_IR,
    .get_value 	= ir_get_key_value,
};

Source: irkey.c

irkey_scan_para 描述了红外按键的扫描节奏与语义:每 110ms 调用一次 ir_get_key_value() 采集;连续 6 次相同判定为长按(long_time),长按后再 3 次判定为 HOLD(hold_time);key_type = KEY_DRIVER_TYPE_IR 让按键框架把红外输入与矩阵键盘区分开。

命令 → 按键码表映射

const struct ff00_2_keynum IRTabFF00[] = {
    {0x07,  IR_06},
    {0x08,  IR_15},
    {0x09,  IR_08},
    {0x0C,  IR_12},
    {0x0D,  IR_11},
    {0x15,  IR_07},
    {0x15,  IR_09},
    {0x18,  IR_13},
    {0x19,  IR_10},
    {0x1C,  IR_16},
    {0x40,  IR_04},
    {0x42,  IR_18},
    {0x43,  IR_05},
    {0x44,  IR_03},
    {0x45,  IR_00},
    {0x46,  IR_01},
    {0x47,  IR_02},
    {0x4A,  IR_20},
    {0x52,  IR_19},
    {0x5A,  IR_17},
    {0x5E,  IR_14},
};

Source: irkey.c

IRTabFF00 是针对 FF00 协议遥控器的命令值→按键值映射表(命令值取自解码帧的 cmd 字节)。ir_get_key_value() 的执行逻辑为:调用 ir_decoder_get_command_value() 取最新命令——返回 0xff 表示无有效命令(irkey.c 约定 0xff 为无效值)则返回 NO_KEY;否则在表中线性查找,命中返回对应 IR_xx 按键值,未命中返回 0xff(key_value 初值),并打印 recive tkey: 0x%x 便于调试。

Source: irkey.c

扩展点: 更换不同品牌遥控器时,只需要更新 IRTabFF00 码表(或按协议新增一张表并切换 get_value 实现);协议层面若需要支持非 NEC 协议,则替换/扩展 ir_decoder.c 的时序常量与分类函数,irkey.c 无需改动。

配置选项

选项类型默认说明
TCFG_NORFLASH_DEV_ENABLE编译宏0/未定义使能 norflash.c 编译;为 0 时整个 Flash 驱动被裁剪
MAX_NORFLASH_PART_NUM宏4Flash 最大分区数,超出后 norflash_new_part 报错
_norflash.spi_numint-1SPI 控制器编号,-1 表示未初始化
_norflash.spi_cs_iou8由初始化设置片选 GPIO,可任意选择 IO
_norflash.spi_r_widthu8由初始化设置SPI 读位宽(标准/多线)
TCFG_IRKEY_ENABLE编译宏0/未定义使能 irkey.c 按键适配层
NEC_UNIT_TIME宏563NEC 基本单位时间(μs)
NEC_TIME_DEVIATION宏10脉宽判定允许偏差(%)
NEC_TIMEOUT_MS宏150解码帧超时(ms),超时视为帧结束
IR_ENCODER_MALLOC_ENABLE宏0编码器上下文分配方式:0=静态,1=malloc
irkey_scan_para.scan_timeu16110红外按键扫描周期(ms)
irkey_scan_para.long_timeu86长按判定所需的连续扫描次数
irkey_scan_para.hold_timeu89HOLD 判定次数(长按后继续按住)

API 参考

以下签名均取自本组件源码中已观察到的实现/调用点。

NOR Flash 分区管理(norflash.c)

struct norflash_partition *norflash_find_part(const char *name)

  • 描述:按名字查找 Flash 分区。
  • 参数:name — 分区名(字符串)。
  • 返回:命中返回分区指针;未命中返回 NULL。

struct norflash_partition *norflash_new_part(const char *name, u32 addr, u32 size)

  • 描述:创建新分区,更新 max_end_addr 与 part_num。
  • 参数:name — 分区名;addr — 起始地址;size — 分区大小。
  • 返回:成功返回分区指针;槽位已满(>4 个分区)返回 NULL 并打印 log_error。

void norflash_delete_part(const char *name)

  • 描述:按名字删除分区并释放槽位。

底层数据通路(norflash.c,声明于文件头部)

int _norflash_read(u32 addr, u8 *buf, u32 len, u8 cache)

  • 描述:从 Flash 读取 len 字节到 buf。
  • 参数:addr — 读取地址;buf — 输出缓冲;len — 长度;cache — 非 0 时先同步写缓存再读,保证读到最新数据。
  • 返回:0 成功,非 0 失败。

int _norflash_eraser(u8 eraser, u32 addr)

  • 描述:按粒度擦除 Flash。
  • 参数:eraser — 擦除粒度(扇区/块等);addr — 擦除地址。
  • 返回:0 成功,非 0 失败。

static int _norflash_write_pages(u32 addr, u8 *buf, u32 len)

  • 描述:按页写入数据(写前须已擦除,页内不跨页)。
  • 参数:addr — 起始地址;buf — 源数据;len — 长度。
  • 返回:0 成功,非 0 失败。

红外解码器(ir_decoder.c / ir_decoder.h)

u8 ir_decoder_get_command_value(void)(调用点见 irkey.c)

  • 描述:取最近一次完整解码的命令值(NEC 帧 cmd 字节)。
  • 返回:0x00~0xFF 有效命令;0xff 表示无有效命令(上层据此返回 NO_KEY)。

static void ir_decode_irq(u32 tid, void *arg)

  • 描述:GPTimer 捕获中断回调,完成脉宽测量、分类与位累积;属于内部实现,由捕获定时器注册。
  • 参数:tid — 捕获定时器 ID(用于 gptimer_get_capture_cnt2us 换算脉宽);arg — 中断参数。

红外编码器(ir_encoder.c / ir_encoder.h)

static u32 ir_encoder_malloc()

  • 描述:初始化编码上下文(静态或 malloc,由 IR_ENCODER_MALLOC_ENABLE 决定)并清零。
  • 返回:0 成功。

红外按键(irkey.c)

u8 ir_get_key_value(void)

  • 描述:从解码器取命令值并查 IRTabFF00 码表。
  • 返回:IR_00~IR_20 按键值;无有效命令或未命中返回 NO_KEY。

int irkey_init(void)

  • 描述:红外按键初始化,注册 irkey_scan_para 到按键驱动框架。

故障模式、边界情况与并发

  • 帧超时/首边沿:jiffies_timeout_check() 在超时或首边沿直接返回,防止帧间噪声与半帧数据污染 ir_data。系统节拍回绕(jiffies_diff < 0)通过加 0xffffffff 显式修正,保证长期运行的计时正确性。
  • 脉宽不匹配:ir_decoder_pulse_check() 对无法匹配任何符号的脉宽返回 NEC_ERROR,中断走 default 分支丢弃该脉冲,解码器不会因单个异常脉冲崩溃或误锁帧。
  • 劣质遥控器/晶振偏差:NEC_TIME_DEVIATION = 10(±10%)是精度与鲁棒性的平衡点;若产品使用极差晶振的遥控器,需按实际测量调整该常量。
  • 长按语义:NEC 重复码(REPEAT)不修改 ir_data,仅通过 command_count 递增维持"同一命令持续有效",配合 irkey.c 的 long_time/hold_time 实现长按/HOLD 上报,避免重复码被误判为新按键。
  • Flash 写前未擦除:NOR Flash 页写入为或逻辑,驱动不代做擦除,上层必须先 _norflash_eraser 再写,否则数据位无法从 0 恢复为 1——这是最常见的应用层错误。
  • 并发访问:解码器为单静态实例、完全中断驱动,串行安全;Flash 驱动通过 SPI_mutex 互斥保护多任务并发读写,open_cnt 记录打开计数;spi_err 随路记录 SPI 错误供事务后检查。
  • 中断上下文约束:ir_decode_irq 运行在中断上下文,内部只做移位、计数等无阻塞操作,不调用可能睡眠的 API;编码器状态机同样在定时器中断中推进,任何耗时操作都不可放入该路径。
  • 内存分配:编码器默认静态分配(IR_ENCODER_MALLOC_ENABLE = 0),避免发送路径上的堆分配不确定性;Flash 分区表也是静态数组。

性能与运维要点

  • DMA 大块搬运:spi_dma_read/spi_dma_write 支持 DMA 传输,读字库/资源等大块数据时应优先 DMA 路径以降低 CPU 占用;命令与状态轮询用字节模式即可。
  • 写缓存同步:_norflash_read 的 cache 参数与 _norflash_cache_sync_timer 提供"先缓存、后落盘"的延迟写机制,可减少擦写次数、延长 Flash 寿命;使用前需理解其同步语义,避免读回陈旧数据。
  • 中断频率:NEC 解码每个下降沿一次中断,一帧约 68 个沿;编码器每 563μs 段边界一次中断。系统级低功耗设计需考虑这两个定时器在睡眠期间的唤醒行为(ir_encoder.c 引用了 asm/power_interface.h,说明其参与电源管理)。
  • 调试手段:解码器启用 LOG_DEBUG_ENABLE 时打印每次脉宽与类型(us = %d, type = %d)及收满帧数据(ir_data:0x%08x, command_count:%d);irkey.c 打印 recive tkey: 0x%x,可用于定位码表不匹配问题。

扩展点

  1. 新增 Flash 分区:调用 norflash_new_part(name, addr, size) 注册新分区,之后按分区名读写即可;注意总量不超过 MAX_NORFLASH_PART_NUM 且不与既有分区重叠。
  2. 更换遥控器码表:修改 IRTabFF00(或新增协议表并替换 ir_get_key_value),无需改动解码器。
  3. 适配其他红外协议:以 ir_decoder.c 为模板,替换时序常量(如 RC5 的 889μs、Sony 的 600μs)与符号分类逻辑;编码器同理调整常量与状态机。
  4. 按键行为定制:调整 irkey_scan_para 的 scan_time / long_time / hold_time / click_delay_time 可改变长按、连击判定节奏。
  5. 设备框架接入:每个 Flash 分区内嵌 struct device,可注册为标准设备节点,供文件系统或业务模块通过 open/read/write 访问。

相关链接

  • norflash.c 源码
  • norflash_demo.c 演示
  • ir_decoder.c 源码
  • ir_encoder.c 源码
  • irkey.c 源码
  • ir_decoder.h 头文件
  • ir_encoder.h 头文件
  • 相关目录页面:GPIO 按键驱动、GPIO IRQ、SFC 引导级 Flash(见外设驱动目录其他条目)
Prev
段码 LCD 驱动