Note06_Key按键驱动_中断方式

本文深入解析Exynos 4412芯片的中断处理机制,包括硬件中断配置、寄存器设置、中断号与中断源对应、中断处理流程及驱动程序设计。探讨了按键中断初始化、中断注册与注销过程,以及中断处理函数的实现。

 

 

  • KEY 硬件原理图

如下图所示,按键共有4个,K1——K4,对应的中断号为 XEINT26——29(并非与硬件实际的中断号对应),其对应的GPIO 引脚为GPX3_2——5。

 

  • KEY 相关寄存器

1)GPX3CON:GPIO PIN 模式设置寄存器

GPX3_2,对应的中断模式需设置为0xF,GPX3_2——5 ==》EXT_INT43[5——2]

......

2)EXT_INT43_CON中断触发方式设置寄存器

EXT_INT43[5——2],中断触发方式,设置为0x4,上下边沿都触发;

......

......

从如下图,可知EXT_INT43,对应的中断ID 为64.

 

  • 中断简介

1)中断源分类1:

中断包含内部中断和外部中断:内部中断:如若中断由控制器或者SOC 上的完整的设备产生的;比如 触摸屏、串口等中断方式;

外部中断:即只连接到GPIO 引脚上,中断信号通过SOC引脚从SOC外部得到的中断,没有其他外部的设备类控制其中断的发生,只能通过设置SFR 来配置中断模式、触发方式等;4412外部中断共计32个:

外部中断

中断ID

中断号

中断 0—15

48——63

EINT[0:15]

中断16—31

64

EINT[16:31]

2)中断源分类2:

共享外部中断:公共的外接中断,中断源ID号: SPI[127:0]

私有中断:私有外设中断 中断源ID号: PPI[15:0]

软中断:由软件设置的中断源 中断源ID号: SGI[15:0]

3)中断处理流程:

首先保存断点 ==》查找中断入口函数 ==》中断处理 ==》返回断点;

  1. 中断源触发中断;
  2. 首先确认GIC是否接收到中断;然后确认是否将中断转给CPU;
  3. 首先确认CPU是否接收到中断;然后确认接受的中断号的权限及优先级;

3)exynos4412中断简介:

中断控制器 gic 作用是进行中断时间的接收和转发;其资源共160个中断源(软中断16个、私有16个、外部128个),而CPU只识别中断号,所以中断处理的第一步就是获取硬件触发的中断源,并转化为CPU可识别的中断号,该过程由gic完成。

4)中断体系结构

略;待补充后补全;

【遗留】exynos 4412芯片如何将中断和中断号对应

  • 中断资源注册函数

1)编写驱动中断模块是,利用内核的如下函数接口注册中断:

int request_irq(

    unsigned int irq,     //中断号

    irq_handler_t handler,  //中断处理函数

    unsigned long flags,     //中断注册的触发方式

    const char *name,    //中断的名字

    void *dev        //给中断处理函数传入的参数

);

2)中断参数解释:

irq: //中断号

1> 对于内部中断号

         arch/arm/mach-exynos/include/mach/irqs.h

    2> 对于外部中断对应的中断号的获取

        a. IRQ_EINT(eint);  

        b. 在知道gpio编号的情况下,可以通过gpio的编号,得到对应注册的中断号

           gpio_to_irq(gpionum); //通过gpio编号返回中断号

handler: //中断处理函数

typedef irqreturn_t (*irq_handler_t)(int,  void *); 

中断处理函数的形参:(发生中断的中断号, 注册中断时为其传入的参数)

中断处理函数返回值:

    IRQ_NONE   //中断处理失败

    IRQ_HANDLED //中断成功处理

flags: //中断注册的触发方式

#define IRQF_TRIGGER_RISING  0x00000001

#define IRQF_TRIGGER_FALLING 0x00000002

#define IRQF_TRIGGER_HIGH    0x00000004

#define IRQF_TRIGGER_LOW     0x00000008

#define IRQF_SHARED      0x00000080

name:  //中断的名字

dev: //中断处理函数传入的参数

中断发生后,由内核调用处理函数时传给处理函数的第二实参

  • 驱动程序代码

1)按键中断初始化:

 5 // KEY 中断初始化: 中断号、按键名、按键计数

  6 struct millkey{

  7     int irqnum;

  8     char *name;

  9     int keycnt;

 10 }keys[] = {

 11     { IRQ_EINT(26), "KEY1", 0 },

 12     { IRQ_EINT(27), "KEY2", 0 },

 13     { IRQ_EINT(28), "KEY3", 0 },

 14     { IRQ_EINT(29), "KEY4", 0 },

 15 };

2)注册中断函数:

 71 /*

 72 **  driver init call func  

 73 */

 74 static int __init demo_init(void)

 75 {

 76     return register_keys();

 77 }

 

 30 /*

 31 **  register key irq devices  

 32 */

 33 static int register_keys(void)

 34 {

 35     int i;

 36     int ret;

 37 

 38     for (i = 0; i < ARRAY_SIZE(keys); ++i) {

 39         ret = request_irq(

 40             keys[i].irqnum,

 41             do_key_handler,

 42             IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,

 43             keys[i].name,

 44             &keys[i]    // irq handler's arg

 45         );

 46 

 47         if (ret < 0) {

 48             goto error0;

 49         }

 50     }

 51 

 52     return 0;

 53 

 54 error0:

 55     while (i--) {

 56         free_irq(keys[i].irqnum, &keys[i]);

 57     }

 58 

 59     return ret;

 60 }

3)中断处理函数:

 17 static irqreturn_t do_key_handler(int irqnum, void *data)

 18 {

 19     struct millkey *pdev = data;

 20 

 21     printk();

 22     pdev->keycnt++;

 23 

 24     printk("%s is %s, keycnt = %d\n", pdev->name,

 25         pdev->keycnt%2?"press down":"release up", pdev->keycnt);

 26 

 27     return IRQ_HANDLED;

 28 }

 

4)中断注销函数:

 62 static void unregister_keys(void)

 63 {

 64     int i;

 65 

 66     for (i = 0; i < ARRAY_SIZE(keys); ++i) {

 67         free_irq(keys[i].irqnum, &keys[i]);

 68     }

 69 }

 70 

 

 79 module_init(demo_init);

 80 

 81 static void __exit demo_exit(void)

 82 {

 83     unregister_keys();

 84 }

 

  • 测试程序代码

中断是由硬件触发,故无应用层的测试程序;

  • 测试结果

1插入驱动模块

[root@millet0923_interrupt]# 

[root@millet0923_interrupt]# insmod demo.ko 

[root@millet0923_interrupt]# 

[root@millet0923_interrupt]# 

[root@millet0923_interrupt]# 

[root@millet0923_interrupt]# find / -name KEY*

/proc/irq/445/KEY4

/proc/irq/444/KEY3

/proc/irq/443/KEY2

/proc/irq/442/KEY1

[root@millet0923_interrupt]# 

[root@millet0923_interrupt]# 

2)查看当前已注册的中断,及其发生的情况

[root@millet0926_03_interrupt]# cat /proc/interrupts

           CPU0       CPU1

28:      63591        472       GIC  MCT 

67:          0          0       GIC  dma-pl330.0

68:          0          0       GIC  dma-pl330.1

75:          0          0       GIC  abc4412-wdt

......

......

442:          0          0  exynos-eint  KEY1                                         

443:          0          0  exynos-eint  KEY2

444:          0          0  exynos-eint  KEY3

445:          0          0  exynos-eint  KEY4

3)按下按键,触发中断,可看到内核打印的日志;

   中断设置双边沿触发,因按键有抖动,故实际按下1次,但弹道多次触发打印

[root@millet0923_interrupt]#

[root@millet0923_interrupt]#

[root@millet0923_interrupt]# [ 7945.490000] KEY1 is release up

[ 7945.625000] KEY1 is press down

[ 7945.625000] KEY1 is release up

[ 7946.225000] KEY2 is release up

[ 7946.350000] KEY2 is press down

[ 7946.940000] KEY3 is release up

[ 7947.110000] KEY3 is press down

[ 7947.665000] KEY4 is press down

[ 7947.665000] KEY4 is release up

[ 7947.665000] KEY4 is press down

[ 7947.665000] KEY4 is release up

[ 7947.790000] KEY4 is press down

[root@millet0923_interrupt]#

 

  • 总结:

  • 遗留问题

 ret = request_irq(

 40             keys[i].irqnum,

 41             do_key_handler,

 42             IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,

 43             keys[i].name,

 44             &keys[i]    // irq handler's arg

 45         );

1)如上中断注册函数,中断号 keys[i].irqnum 是如何和内核内核注册的硬件中断ID联系起来的?可参考如下文档,待分析;

https://blog.youkuaiyun.com/zqixiao_09/article/details/50926677

https://blog.youkuaiyun.com/zqixiao_09/article/details/50908125?locationNum=5&fps=1

https://blog.youkuaiyun.com/zqixiao_09/article/details/50916212

https://www.cnblogs.com/wang_yb/archive/2013/04/19/3030345.html《linux 内核设计与实现阅读笔记

2)代码没有消抖,故设置双边沿触发时,会检测到多次中断,增加消抖处理机制;

定时器定时检测消抖、delay(ms)死等延时消抖、中断电平检测消抖? 分析优缺点;

  1. 若应用程序需根据按键功能实现需求,则它如何知晓哪个按键被按下了呢?

 

 

 

 

 

 

 

 

 

 

 

 

 

#include <stdio.h> #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "driver/gpio.h" #include "driver/ledc.h" #include "esp_log.h" // 引脚定义 #define TTP229_SCL_GPIO 18 #define TTP229_SDO_GPIO 19 #define BUZZER_GPIO 21 // 音符频率定义(更丰富,接近钢琴音域) #define NOTE_C4 262 #define NOTE_CS4 277 #define NOTE_D4 294 #define NOTE_DS4 311 #define NOTE_E4 330 #define NOTE_F4 349 #define NOTE_FS4 370 #define NOTE_G4 392 #define NOTE_GS4 415 #define NOTE_A4 440 #define NOTE_AS4 466 #define NOTE_B4 494 #define NOTE_C5 523 #define NOTE_D5 587 #define NOTE_E5 659 #define NOTE_F5 698 static const char *TAG = "Electronic_Piano"; // 音键映射顺序:物理按键 TP3 TP2 TP1 TP0 TP15 TP14 TP13 TP12 TP8 TP9 TP10 TP11 TP4 TP5 TP6 TP7 // 对应索引: 3 2 1 0 15 14 13 12 8 9 10 11 4 5 6 7 // 我们希望这个顺序对应一组连续上升的音符(比如从 D4 到 F5) const int key_sequence_map[16] = {3, 2, 1, 0, 15, 14, 13, 12, 8, 9, 10, 11, 4, 5, 6, 7}; // 指定每个位置对应的音符(共16个,音阶上行,富有音乐感) const int note_sequence[16] = { NOTE_D4, // 位置0 -> 键3 NOTE_E4, // 位置1 -> 键2 NOTE_F4, // 位置2 -> 键1 NOTE_F5, // 位置3 -> 键0 NOTE_G4, // 位置4 -> 键15 NOTE_GS4, // 位置5 -> 键14 NOTE_A4, // 位置6 -> 键13 NOTE_AS4, // 位置7 -> 键12 NOTE_B4, // 位置8 -> 键8 NOTE_C5, // 位置9 -> 键9 NOTE_CS4, // 位置10 -> 键10 NOTE_D5, // 位置11 -> 键11 NOTE_E5, // 位置12 -> 键4 NOTE_FS4, // 位置13 -> 键5 NOTE_G4, // 位置14 -> 键6 NOTE_A4 // 位置15 -> 键7 }; // 当前播放状态 int current_playing_key = -1; // 初始化 LEDC PWM 用于蜂鸣器(更高分辨率提升音质) void buzzer_init() { ledc_timer_config_t ledc_timer = { .speed_mode = LEDC_LOW_SPEED_MODE, .duty_resolution = LEDC_TIMER_13_BIT, // 更高精度占空比 .timer_num = LEDC_TIMER_1, .freq_hz = 5000, // 起始频率不影响 .clk_cfg = LEDC_AUTO_CLK }; ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer)); ledc_channel_config_t ledc_channel = { .gpio_num = BUZZER_GPIO, .speed_mode = LEDC_LOW_SPEED_MODE, .channel = LEDC_CHANNEL_0, .intr_type = LEDC_INTR_DISABLE, .timer_sel = LEDC_TIMER_1, .duty = 4096, // ~50% 占空比 (8192/2) .hpoint = 0 }; ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel)); // 关闭初始声音 ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); } // 播放指定频率,立即生效 void play_tone(uint32_t frequency) { if (frequency == 0) { ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); // 立刻停止 } else { ledc_set_freq(LEDC_LOW_SPEED_MODE, LEDC_TIMER_1, frequency); ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 4096); ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); } } // 微秒级延时(用于精确控制通信) void delay_us(uint32_t us) { uint32_t start = esp_timer_get_time(); while (((esp_timer_get_time() - start)) < us) { asm volatile("nop"); } } // 读取 TTP229 的 16 位数据(主动触发时钟) uint16_t ttp229_read_keys() { uint16_t data = 0; gpio_set_level(TTP229_SCL_GPIO, 0); delay_us(10); for (int i = 0; i < 16; i++) { gpio_set_level(TTP229_SCL_GPIO, 1); delay_us(3); // 快速采样(符合手册 15.6ms 唤醒响应) int bit = gpio_get_level(TTP229_SDO_GPIO); data |= (bit << i); gpio_set_level(TTP229_SCL_GPIO, 0); delay_us(3); } return data; } // 初始化 TTP229 GPIO void ttp229_init() { gpio_config_t io_conf = {}; // SCL 输出 io_conf.intr_type = GPIO_INTR_DISABLE; io_conf.mode = GPIO_MODE_OUTPUT; io_conf.pin_bit_mask = (1ULL << TTP229_SCL_GPIO); gpio_config(&io_conf); // SDO 输入(TTP229 输出) io_conf.mode = GPIO_MODE_INPUT; io_conf.pull_up_en = false; io_conf.pull_down_en = false; io_conf.pin_bit_mask = (1ULL << TTP229_SDO_GPIO); gpio_config(&io_conf); gpio_set_level(TTP229_SCL_GPIO, 0); } // 查找当前按下的是哪一个键(按自定义顺序优先) int find_mapped_key_index(uint16_t data) { for (int pos = 0; pos < 16; pos++) { int physical_key = key_sequence_map[pos]; // 获取该位置对应的物理键号 if (data & (1 << physical_key)) { return pos; // 返回映射后的“位置编号” } } return -1; } // 主任务 void piano_task(void *arg) { ttp229_init(); buzzer_init(); ESP_LOGI(TAG, "High-Sensitivity Electronic Piano Started!"); while (1) { uint16_t key_data = ttp229_read_keys(); int mapped_key = find_mapped_key_index(key_data); // 状态变化处理 if (mapped_key != current_playing_key) { if (mapped_key == -1) { // 所有键释放,立即停音 play_tone(0); current_playing_key = -1; ESP_LOGD(TAG, "Sound stopped."); } else { uint32_t freq = note_sequence[mapped_key]; play_tone(freq); current_playing_key = mapped_key; ESP_LOGI(TAG, "Play tone: %d Hz (mapped key %d)", freq, mapped_key); } } // 极低延迟消抖 vTaskDelay(pdMS_TO_TICKS(10)); } } void app_main(void) { xTaskCreate(piano_task, "piano_task", 8192, NULL, 7, NULL); } 分析这段代码每一个模块的逻辑,解释它是如何与硬件建立联系的,详细且通俗
最新发布
10-08
#include <reg51.h> // 定义音符频率对应的定时器初值 #define NOTE_C4 65536 - 1911 #define NOTE_D4 65536 - 1703 #define NOTE_E4 65536 - 1517 #define NOTE_F4 65536 - 1432 #define NOTE_G4 65536 - 1276 #define NOTE_A4 65536 - 1136 #define NOTE_B4 65536 - 1012 // 定义按键引脚 sbit KEY_SWITCH1 = P1^0; sbit KEY_SWITCH2 = P1^1; sbit KEY_START_PAUSE = P1^2; // 定义蜂鸣器引脚 sbit BUZZER = P2^0; // 定义歌曲数据,每个元素包含音符频率初值和节拍时长 unsigned int song[] = { NOTE_C4, 200, // 音符 C4,节拍时长 200ms NOTE_D4, 200, // 音符 D4,节拍时长 200ms NOTE_E4, 200, // 音符 E4,节拍时长 200ms NOTE_F4, 200, // 音符 F4,节拍时长 200ms NOTE_G4, 200, // 音符 G4,节拍时长 200ms NOTE_A4, 200, // 音符 A4,节拍时长 200ms NOTE_B4, 200 // 音符 B4,节拍时长 200ms }; // 歌曲长度 unsigned char song_length = sizeof(song) / sizeof(song[0]) / 2; // 当前播放的音符索引 unsigned char current_note = 0; // 定时器0初始化函数 void Timer0_Init() { TMOD |= 0x01; // 设置定时器0为模式1 TH0 = (unsigned char)(NOTE_C4 >> 8); TL0 = (unsigned char)NOTE_C4; ET0 = 1; // 使能定时器0中断 EA = 1; // 全局中断使能 TR0 = 0; // 先不启动定时器 } // 定时器0中断服务函数 void Timer0_ISR() interrupt 1 { TH0 = (unsigned char)(song[current_note * 2] >> 8); TL0 = (unsigned char)song[current_note * 2]; BUZZER = ~BUZZER; // 反转蜂鸣器引脚电平 } // 延时函数,单位:ms void delay_ms(unsigned int ms) { unsigned int i, j; for (i = 0; i < ms; i++) { for (j = 0; j < 123; j++); } } // 主函数 void main() { Timer0_Init(); while (1) { // 检测按键状态并进行相应操作 if (KEY_START_PAUSE == 0) { TR0 = ~TR0; // 启动或暂停定时器 while (KEY_START_PAUSE == 0); // 消抖 } // 播放歌曲 if (TR0) { if (current_note < song_length) { // 设置当前音符的定时器初值 TH0 = (unsigned char)(song[current_note * 2] >> 8); TL0 = (unsigned char)song[current_note * 2]; // 播放当前音符 TR0 = 1; delay_ms(song[current_note * 2 + 1]); TR0 = 0; // 移动到下一个音符 current_note++; } else { // 歌曲播放完毕,从头开始 current_note = 0; } } } } 让歌曲中的蜂鸣器响
09-23
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值