当前位置: 首页 > news >正文

009.中断实践之实现按键测试|千篇笔记实现嵌入式全栈/裸机篇

1. 要做什么?

MINI2440上有6个按键,我们要做一个测试程序,当按下按键的时候,串口打印对应的按键按下的消息。

要包含中断的知识,就要用中断的方法去实现,

为了使用中断实现,我们需要搭建一个完整的中断流程框架(中断异常的现场保护、返回地址计算、中断分发、基础操作API实现等)

2. 具体思路

测试程序和中断框架的两条流程设计:

代码框图:

3. 怎么实现?

3.1 框架层面

3.1.1 irq_handler.c

创建common/irq_handler.c文件,

实现irq框架初始化,

/// @brief 中断控制器初始化 /// 设置IRQ模式的SP, 禁用IRQ/FIQ void irq_handler_init() { unsigned long cpsr; __asm__ volatile ( // 读取cpsr "mrs %0, cpsr\n\t" // 禁用IRQ/FIQ, 切换至IRQ模式 "msr cpsr_c, #0b11010010\n\t" // 设置IRQ模式的SP "ldr sp, =0x40001000\n\t" // 恢复原来模式和原始中断状态 "msr cpsr, %0\n\t" : "=&r" (cpsr) : : "sp", "cc", "memory" ); // 清除可能的中断挂起 SUBSRCPND = SUBSRCPND; SRCPND = SRCPND; INTPND = INTPND; // 屏蔽所有中断 INTMSK = 0xFFFFFFFF; INTSUBMSK = 0xFFFFFFFF; }

实现irq处理函数,作用是中断分发,

// 默认中断处理函数 (弱实现,用户可重写) void __attribute__((weak)) irq_eint0_handler(void) { } void __attribute__((weak)) irq_eint1_handler(void) { } void __attribute__((weak)) irq_eint2_handler(void) { } void __attribute__((weak)) irq_eint3_handler(void) { } void __attribute__((weak)) irq_eint4_7_handler(void) { } void __attribute__((weak)) irq_eint8_23_handler(void) { } // ...略... // 中断处理函数表 static irq_handler_t irq_handlers[32] = { [IRQ_EINT0] = irq_eint0_handler, [IRQ_EINT1] = irq_eint1_handler, [IRQ_EINT2] = irq_eint2_handler, [IRQ_EINT3] = irq_eint3_handler, [IRQ_EINT4_7] = irq_eint4_7_handler, [IRQ_EINT8_23] = irq_eint8_23_handler, // ...略... }; /// @brief IRQ中断处理函数 /// 中断分发 void __irq_handler() { // 获取中断源偏移 uint32_t offset = INTOFFSET; if (offset >= 32 || NULL == irq_handlers[offset]) return ; // 清除对应的中断标志位 SRCPND = (1 << offset); INTPND = (1 << offset); // 分发 irq_handlers[offset](); }

我们使用了weak函数技巧,实现了所有中断处理函数的存根,然后应用层根据需要,覆盖实现对应的处理函数~

还要实现一些公共的API,

/// @brief CPU中断使能控制 void irq_enable(bool enable) { unsigned long old_cpsr = disable_irq_save(); if (!enable) old_cpsr |= (1 << 7); else old_cpsr &= ~(1 << 7); restore_irq_mask(old_cpsr); } /// @brief 恢复IRQ Mask void restore_irq_mask(unsigned long old_cpsr) { __asm__ volatile ( "msr cpsr, %0\n\t" : : "r" (old_cpsr) : "cc", "memory" ); } /// @brief 禁用IRQ并保存当前cpsr unsigned long disable_irq_save() { unsigned long old_cpsr; __asm__ volatile ( /* 读取cpsr */ "mrs %0, cpsr\n\t" /* 禁止IRQ */ "orr r1, %0, #(1 << 7)\n\t" "msr cpsr_c, r1\n\t" : "=&r" (old_cpsr) : // 这里损坏部声明了r1, 表示我弄脏了r1, 这样编译器会将r1自动压栈保护 : "cc", "memory", "r1" ); return old_cpsr; } /// @brief 中断源使能控制 void irq_src_enable(irq_src_t src, bool enable) { unsigned long _save = disable_irq_save(); SRCPND = (1 << src); INTPND = (1 << src); if (enable) { INTMSK &= ~(1 << src); } else { INTMSK |= (1 << src); } restore_irq_mask(_save); }

我们用了大量的的内联汇编技巧,可以参考章节:

007.GNU C内联汇编杂谈|千篇笔记实现嵌入式全栈/裸机篇

3.1.2 start.s

回到common/start.s,进行修改,

注释掉原来的irq: b ., 重新实现irq段,

并且设置cpsr确保处于SVC模式,然后调整SVC模式下的栈顶,为IRQ模式预留512字节栈空间,

@irq: b . fiq: b . @ irq现场保护和恢复 irq: @ 修正返回地址: @ ARM9流水线取指 (Fetch) -> 译码 (Decode) -> 执行 (Execute) -> 访存 (Memory) -> 回写 (Write) @ LR(异常触发时PC) = 触发点PC + 8 @ 那么目标返回点PC = 触发点PC + 4 = (LR - 8) + 4 = LR - 4 sub lr, lr, #4 @ 压栈保护通用寄存器和lr stmdb sp!, {r0-r12, lr} @ 跳转至C分发函数 bl __irq_handler @ 恢复现场退出中断 @ ^表示将SPSR>CPSR, 实现模式切换 ldmia sp!, {r0-r12, pc}^ .section .text .global _start _start: @ 关闭看门狗 ldr r0, =WTCON ldr r1, =0 str r1, [r0] @ 关中断且确保处于SVC模式 msr cpsr_c, 0b11010011 @ 设置栈顶(给IRQ SP预留512空间) ldr sp, =0x40000E00

3.2 应用方面

看原理图,

为了与常规轮询方式对比,我们选择两路按键使用轮询的方式,四路按键使用外部中断的方式,

/** * @file key.c * @brief 按键测试 * 部分采用轮询, 部分采用中断 * @pins: * KEY1(轮询): EINT8/GPG0 * KEY2(轮询): EINT11/GPG3 * KEY3(中断): EINT13/GPG5 * KEY4(中断): EINT14/GPG6 * KEY5(中断): EINT15/GPG7 * KEY6(中断): EINT19/GPG11 */

新建key/main.c,

实现main函数,用于配置和逻辑处理,

int main() { // 配置为INPUT GPIO_SET_MODE(GPIOGCON, 0, 0b00, 2); GPIO_SET_MODE(GPIOGCON, 3, 0b00, 2); // 配置为EINT GPIO_SET_MODE(GPIOGCON, 5, 0b10, 2); GPIO_SET_MODE(GPIOGCON, 6, 0b10, 2); GPIO_SET_MODE(GPIOGCON, 7, 0b10, 2); GPIO_SET_MODE(GPIOGCON, 11, 0b10, 2); // 上拉 GPIO_SET_PULLUP(GPIOGUP, 0, 0); GPIO_SET_PULLUP(GPIOGUP, 3, 0); GPIO_SET_PULLUP(GPIOGUP, 5, 0); GPIO_SET_PULLUP(GPIOGUP, 6, 0); GPIO_SET_PULLUP(GPIOGUP, 7, 0); GPIO_SET_PULLUP(GPIOGUP, 11, 0); // 外部中断配置 EINT13/14/15/19 // 下降沿触发 EXTINT1 &= ~((7 << 20) | (7 << 24) | (7 << 28)); EXTINT2 &= ~(7 << 12); EXTINT1 |= (0b010 << 20) | (0b010 << 24) | (0b010 << 28); EXTINT2 |= (0b010 << 12); // 外部中断屏蔽 // 屏蔽其它所有外部中断 EINTMASK = ~((1 << 13) | (1 << 14) | (1 << 15) | (1 << 19)); // 使能EINT8_23中断 irq_src_enable(IRQ_EINT8_23, true); // 使能CPU中断 irq_enable(true); bool _key1_st_old = false; bool _key2_st_old = false; int count_for_key1 = 0; int count_for_key2 = 0; while (1) { // 10ms周期 easy_delay_ms(10); count_ref ++; if (event_key3) { uart0_printf("KEY3: pressed\n"); event_key3 = false; } if (event_key4) { uart0_printf("KEY4: pressed\n"); event_key4 = false; } if (event_key5) { uart0_printf("KEY5: pressed\n"); event_key5 = false; } if (event_key6) { uart0_printf("KEY6: pressed\n"); event_key6 = false; } // 轮询检测KEY1/KEY2 // 100ms去抖 bool key1_st = GPIO_GET_VALUE(GPIOGDAT, 0) ? true : false; bool key2_st = GPIO_GET_VALUE(GPIOGDAT, 3) ? true : false; if (key1_st != _key1_st_old) { if (++count_for_key1 > DEBOUNCE_COUNT) { if (!(_key1_st_old = key1_st)) uart0_printf("KEY1: pressed\n"); count_for_key1 = 0; } } else { count_for_key1 = 0; } if (key2_st != _key2_st_old) { if (++count_for_key2 > DEBOUNCE_COUNT) { if (!(_key2_st_old = key2_st)) uart0_printf("KEY2: pressed\n"); count_for_key2 = 0; } } else { count_for_key2 = 0; } } return 0; }

然后,

覆盖实现中断源处理函数,就是通过外部中断挂起寄存器来判断是哪里按键触发,

///@brief EINT8_23 中断处理函数 void irq_eint8_23_handler(void) { unsigned long _pend = EINTPEND; EINTPEND = _pend; // 清除中断标志 if (_pend & (1 << 13)) { if ((count_ref - count_for_key3) > DEBOUNCE_COUNT_EINT) { event_key3 = true; count_for_key3 = count_ref; } } if (_pend & (1 << 14)) { if ((count_ref - count_for_key4) > DEBOUNCE_COUNT_EINT) { event_key4 = true; count_for_key4 = count_ref; } } if (_pend & (1 << 15)) { if ((count_ref - count_for_key5) > DEBOUNCE_COUNT_EINT) { event_key5 = true; count_for_key5 = count_ref; } } if (_pend & (1 << 19)) { if ((count_ref - count_for_key6) > DEBOUNCE_COUNT_EINT) { event_key6 = true; count_for_key6 = count_ref; } } }

完整的代码,见当前TAG:9-isr-key

4. 效果

编译,make key

生成key.bin,烧录,运行,

依次按下所有按键,

http://www.jsqmd.com/news/603533/

相关文章:

  • 保姆级教程:手把手教你用LIOSAM跑通自己的数据集(含常见报错解决)
  • 卡证检测矫正模型C语言接口封装:为嵌入式设备提供轻量级调用
  • Carrada雷达数据集GooGle Colab在线运行
  • 全网爆火的大模型AI知识库,保姆级教程来了
  • win 安装openclaw (docker镜像模式),解决18789访问失败问题
  • 3D Face HRN开源镜像价值:免训练、免标注、免3D扫描设备的平民化方案
  • 肿瘤患者如何预防口腔黏膜炎发生?速舒提供科学护理方案 - 资讯焦点
  • 从FAISS到Milvus:一个AI工程师的向量数据库技术栈演进史与踩坑实录
  • COMSOL仿真揭示石墨烯临界耦合光吸收特性:费米能级调控下的光学性能研究
  • 塞尔达传说存档定制指南:打造个性化游戏体验
  • 2026.4 紫题金了
  • CTC语音唤醒模型与数据结构优化实战
  • 嵌入式C++编译时间缩短82%的实战路径(仅限前500名工程师掌握的增量构建秘钥)
  • 想点奶茶外卖,沪上阿姨鲜果茶值得点吗?美团周末五折福利帮你解锁高性价比答案 - 资讯焦点
  • 告别电脑噪音烦恼:用FanControl 264版实现完美风扇控制
  • EdB Prepare Carefully个性化定制指南:打造你的理想RimWorld开局
  • Steam Deck终极模拟器配置指南:EmuDeck一键安装30+经典游戏机
  • lesson70:jQuery Ajax完全指南:从基础到4.0新特性及现代替代优秀的方案引言:jQuery Ajax的时代价值与演进 - Leone
  • AI将取代80%的测试工作?我持反对意见
  • 3分钟搞定Axure RP中文界面:告别英文困扰,专注原型设计
  • 【2026实测】Syncthing下载安装教程:Syncthing文件同步工具使用全攻略 - xiema
  • 实战指南:基于快马生成集成openclaw的爬虫项目,安装即用
  • 从音频到DDR:一文搞懂PCB设计中“包地”、“类差分”和“真差分”走线到底怎么用
  • 跨平台媒体传输新选择:Go2TV 3分钟入门指南
  • Python实战:海康工业相机主动取流(getoneframetimeout)图像数据解析与OpenCV实时显示优化
  • 2026 ICPC Asia Pacific Championship - E. Parallel Sums
  • [Windows] EchoTrace v3.1.0 W信聊天记录导出、分析与年度报告生成工具
  • 拒绝盲目跟风!2026高口碑主治医师机构红榜揭秘,看完再选不踩雷 - 医考机构品牌测评专家
  • JBoltAI框架4.2版本更新:Java开发者的AI新利器
  • 从‘听不清’到‘听得准’:深入FunASR的VAD模型,教你调参优化语音识别在嘈杂环境下的表现