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 驱动 + 组件 + 应用"分层结构。本页覆盖的两类外设是消费类嵌入式产品(遥控器、玩具、家电面板)中最常用的能力:
- NOR Flash:通过 SPI 接口挂接片外 Flash,用于存放配置、字库、资源或用户数据。驱动在
sdk/cpu/cd09/norflash.c中实现,采用"设备-分区"模型,把一块 Flash 逻辑切分为多个命名分区,并通过 SPI 字节/ DMA 接口访问,同时提供互斥锁保护并发访问。 - 红外编解码:基于 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),各符号定义如下:
| 符号 | 时间(单位) | 实际时长 | 含义 |
|---|---|---|---|
| 引导码 HEAD | 16 + 8 | 13.5ms | 帧起始,高 9ms + 低 4.5ms |
| 逻辑 1 | 3 + 1 | 2.25ms | 高 1.69ms + 低 0.56ms |
| 逻辑 0 | 1 + 1 | 1.125ms | 高 0.56ms + 低 0.56ms |
| 重复码 REPEAT | 16 + 4 | 11.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 | 宏 | 4 | Flash 最大分区数,超出后 norflash_new_part 报错 |
_norflash.spi_num | int | -1 | SPI 控制器编号,-1 表示未初始化 |
_norflash.spi_cs_io | u8 | 由初始化设置 | 片选 GPIO,可任意选择 IO |
_norflash.spi_r_width | u8 | 由初始化设置 | SPI 读位宽(标准/多线) |
TCFG_IRKEY_ENABLE | 编译宏 | 0/未定义 | 使能 irkey.c 按键适配层 |
NEC_UNIT_TIME | 宏 | 563 | NEC 基本单位时间(μs) |
NEC_TIME_DEVIATION | 宏 | 10 | 脉宽判定允许偏差(%) |
NEC_TIMEOUT_MS | 宏 | 150 | 解码帧超时(ms),超时视为帧结束 |
IR_ENCODER_MALLOC_ENABLE | 宏 | 0 | 编码器上下文分配方式:0=静态,1=malloc |
irkey_scan_para.scan_time | u16 | 110 | 红外按键扫描周期(ms) |
irkey_scan_para.long_time | u8 | 6 | 长按判定所需的连续扫描次数 |
irkey_scan_para.hold_time | u8 | 9 | HOLD 判定次数(长按后继续按住) |
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,可用于定位码表不匹配问题。
扩展点
- 新增 Flash 分区:调用
norflash_new_part(name, addr, size)注册新分区,之后按分区名读写即可;注意总量不超过MAX_NORFLASH_PART_NUM且不与既有分区重叠。 - 更换遥控器码表:修改
IRTabFF00(或新增协议表并替换ir_get_key_value),无需改动解码器。 - 适配其他红外协议:以
ir_decoder.c为模板,替换时序常量(如 RC5 的 889μs、Sony 的 600μs)与符号分类逻辑;编码器同理调整常量与状态机。 - 按键行为定制:调整
irkey_scan_para的scan_time / long_time / hold_time / click_delay_time可改变长按、连击判定节奏。 - 设备框架接入:每个 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(见外设驱动目录其他条目)