Chinaunix首页 | 论坛 | 博客
  • 博客访问: 493843
  • 博文数量: 223
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 2145
  • 用 户 组: 普通用户
  • 注册时间: 2014-03-01 10:23
个人简介

该坚持的时候坚持,该妥协的时候妥协,该放弃的时候放弃

文章分类

全部博文(223)

文章存档

2017年(56)

2016年(118)

2015年(3)

2014年(46)

我的朋友

分类: 嵌入式

2016-10-03 22:43:11

一、2440按键中断编程
1. 程序结构优化
将main.c中的mmu_init分开成独立文件
  1. /*
  2.  * 用于段描述符的一些宏定义
  3.  */
  4. #define MMU_DOMAIN (0<<5) /*属于哪个域*/
  5. #define MMU_FULL_ACCESS (3<<10) /*访问权限*/
  6. #define MMU_SPECIAL (1<<4) /*必须使1*/
  7. #define MMU_CACHEABLE (1<<3) /*使能cache*/
  8. #define MMU_BUFFERABLE (1<<2) /*使能buffer*/
  9. #define MMU_SECTION (2<<0) /*段描述符*/

  10. #define MMU_SECDESC (MMU_SECTION|MMU_SPECIAL|MMU_DOMAIN|MMU_FULL_ACCESS)
  11. #define MMU_SECDESC_WB (MMU_SECTION|MMU_SPECIAL|MMU_DOMAIN|MMU_FULL_ACCESS|MMU_CACHEABLE|MMU_BUFFERABLE)

  12. void create_page_table()
  13. {
  14.     //设置ttb的起始位置
  15.     unsigned long *ttb = (unsigned long *)0x30000000;
  16.     //定义虚拟地址、物理地址
  17.     unsigned long vaddr, paddr;
  18.     //定义虚拟地址起始地址、物理起始地址
  19.     vaddr = 0xA0000000;
  20.     paddr = 0x56000000;
  21.     /* ttb加上虚拟地址高12位所指的地址就是PA的基地址位置,物理地址取前12位,依次设置后面的值(见AMR9核手册)
  22.      * 然后我们就建立的从vaddr的地址对应于paddr
  23.      */

  24.     *(ttb + (vaddr>>20) ) = (paddr & 0xfff00000)|MMU_SECDESC;

  25.     // 上面是对GPIO的映射,这里是对内存的映射
  26.     vaddr = 0x30000000;
  27.     paddr = 0x30000000;
  28.     //循环了64M的内存空间
  29.     while(vaddr < 0x34000000)
  30.     {
  31.         //建立内存映射表
  32.         *(ttb + (vaddr>>20)) = (paddr & 0xfff00000)|MMU_SECDESC_WB;
  33.         //加上1M,这个1M使手册中MVA[19:0]的大小
  34.         vaddr += 0x100000;
  35.         paddr += 0x100000;
  36.     }
  37. }

  38. void mmu_enable()
  39. {
  40.     __asm__(
  41.     /*设置TTB*/
  42.     "ldr r0, =0x30000000\n"
  43.     "mcr p15, 0, r0, c2, c0, 0\n"
  44.     /*不进行权限检查*/
  45.     "mvn r0, #0 \n"
  46.     "mcr p15, 0, r0, c3, c0, 0\n"
  47.     /*使能mmu*/
  48.     "mrc p15, 0, r0, c1, c0, 0\n"
  49.     "orr r0, r0, #0x0001\n"
  50.     "mcr p15, 0, r0, c1, c0, 0\n"
  51.     :
  52.     :
  53.     );
  54. }

  55. void mmu_init()
  56. {
  57.     //建立页表、写入ttb
  58.     create_page_table();
  59.     //使能mmu
  60.     mmu_enable();
  61. }
同时将led提取成led.c
  1. #define GPBCON (volatile unsigned long*) 0x56000010            //这里设置成了0x56000010
  2. #define GPBDAT (volatile unsigned long*) 0x56000014            //这里设置成了0x56000014

  3. void led_init()
  4. {
  5.     *(GPBCON) = 0x15400;                                       //对控制寄存器初始化
  6. }

  7. void led_on()
  8. {
  9.     *(GPBDAT) = 0x6bf;                                         //点亮led1和led3
  10. }

  11. void led_off()
  12. {
  13.     *(GPBDAT) = 0x7ff;                                         //熄灭所有led
  14. }
Makefile中增加led.o和mmu.o

  1. #ifdef MMU_ON
  2.     mmu_init();                    //在main.c中关闭mmu
  3. #endif
2. 按键初始化
从原理图中可知,EINT0、EINT1、EINT2、EINT4是GPIOF口
         
  1. #define GPFCON (volatile unsigned long*) 0X56000050
  2. #define GPFDAT (volatile unsigned long*) 0X56000054
  3. //开启
  4. #define GPF0_msk (3<<(0*2))
  5. #define GPF1_msk (3<<(1*2))
  6. #define GPF2_msk (3<<(2*2))
  7. #define GPF4_msk (3<<(4*2))

  8. #define GPF0_int (0x2<<(0*2))
  9. #define GPF1_int (0x2<<(1*2))
  10. #define GPF2_int (0x2<<(2*2))
  11. #define GPF4_int (0x2<<(4*2))

  12. void button_init()
  13. {
  14.     *(GPFCON) &= ~(GPF0_msk|GPF1_msk|GPF2_msk|GPF4_msk);            //先全部清零
  15.     *(GPFCON) |= (GPF0_int|GPF1_int|GPF2_int|GPF4_int);             //设置为中断口
  16. }

3. 初始化中断控制器
1.设置中断掩码控制器
2.开启CPSR中的中断
参考中断处理流程分析,所以要设置Mask寄存器(0X4A000008),但是mask中EINT4-7是一起的,所以还有一个EINTMASK寄存器细分4-7位的中断。设置0就可以开启中断位,设置1为屏蔽中断。
  1. #define INTMSK (volatile unsigned long*) 0x4A000008
  2. #define EINTMSK (volatile unsigned long*) 0x560000A4

  3. void init_irq()
  4. {
  5.     //取消屏蔽
  6.     *(EINTMSK) &= (~(1<<4));                    必须先设置
  7.     *(INTMSK) &= (~(1<<0)) & (~(1<<1)) & (~(1<<2)) & (~(1<<4));
  8.     //操作cpsr寄存器,I位
  9.     __asm__(
  10.     "mrs r0, cpsr\n"                        读取cpsr中的内容
  11.     "bic r0, r0, #0x80\n"                   bic设置第7位是0,开启irq
  12.     "msr cpsr_c, r0\n"                      写进cpsr_controlbit
  13.     :
  14.     :
  15.     );
  16. }
芯片手册中写如果不开启I位,产生中断时也会强制置1。但是我取消了开启中断位,并没有实现中断。

4. 中断处理
1.中断程序总入口设置(start.S):
  1. irq:
  2.     sub lr, lr, #4 /*保存环境*/                    这里不是很懂
  3.     stmfd {r0-r12, lr}                            保存环境
  4.     bl handle_int                                 跳转到中断处理函数
  5.     ldmfd {r0-r12, pc}^                           恢复环境,^表示把spsr恢复到cpsr
关于这里为什么lr需要减4:
    ARM处理器使用流水线来增加处理器指令流的速度,这样可使几个操作同时进行,并使处理与存储器系统之间的操作更加流畅,连续,能提供0.9MIPS/MHZ的指令执行速度。
    PC代表程序计数器,流水线使用三个阶段,因此指令分为三个阶段执行:1.取指(从存储器装载一条指令);2.译码(识别将要被执行的指令);3.执行(处理指令并将结果写回寄存器)。而R15(PC)总是指向“正在取指”的指令,而不是指向“正在执行”的指令或正在“译码”的指令。一般来说,人们习惯性约定将“正在执行的指令作为参考点”,称之为当前第一条指令,因此PC总是指向第三条指令。当ARM状态时,每条指令为4字节长,所以PC始终指向该指令地址加8字节的地址,即:PC值=当前程序执行位置+8;
   ARM指令是三级流水线,取指,译指,执行时同时执行的,现在PC指向的是正在取指的地址,那么cpu正在译指的指令地址是PC-4(假设在ARM状态下,一个指令占4个字节),cpu正在执行的指令地址是PC-8,也就是说PC所指向的地址和现在所执行的指令地址相差8。
   当突然发生中断的时候,保存的是PC的地址即lr的内容
   这样你就知道了,如果返回的时候返回PC,那么中间就有一个指令没有执行,所以用SUB pc lr-irq #4。

参考:


2.中断处理函数(interrupt.c)
  1. #define SRCPND (volatile unsigned long*) 0x4A000000
  2. #define INTPND (volatile unsigned long*) 0x4A000010
  3. #define INTOFFSET (volatile unsigned long*) 0x4A000014

  4. #define EINTPEND (volatile unsigned long*) 0x560000A8


  5. void handle_int()
  6. {
  7.     //判断产生中断的中断源
  8.     unsigned long value = *(INTOFFSET);                    //读取寄存器中的值,用于判断哪个中断发生了

  9.     //根据中断源,执行不同的中断处理
  10.     switch(value)
  11.     {
  12.         case 1:            //k1
  13.             led_on();
  14.             break;

  15.         case 4:            //k2
  16.             led_on();
  17.             break;

  18.         case 2:            //k3
  19.             led_off();
  20.             break;

  21.         case 0:            //k4
  22.             led_off();
  23.             break;

  24.         default:
  25.             break;
  26.     }
  27.     //中断清除
  28.     if(value == 4)
  29.     {
  30.         *(EINTPEND) = 1<<4;                              //4号中断和4-7中在一起,要特殊处理
  31.     }
  32.     *(SRCPND) = 1<<value;                                //清除SRCPND中寄存器中断,需要写入1才能清0寄存器。清空中断源标志
  33.     *(INTPND) = 1<<value;                                //清除INTPND中寄存器的值,同样需要写入1才能清0。表明已经处理
  34. }
5.最后遗漏
完成上面后依然没有实现中断控制。
原因:初始化栈中的SP是在svc模式下的。但是irq模式下的sp指针没有实现初始化。所以需要初始化IRQ模式下的svc
  1. init_stack:
  2.     msr cpsr_c, #0xd2
  3.     ldr sp, =0x33000000 //初始化R13_irq
  4.     msr cpsr_c, #0xd3
  5.     ldr sp, =0x34000000        //初始化R13_SVC
  6.     mov pc, lr
二、6410按键中断编程
主要部分还是interrupt.c
  1. /*
  2.  * interrupt.c
  3.  *
  4.  * Created on: 2016-10-2
  5.  * Author: root
  6.  */

  7. #define EINT0CON0 (volatile unsigned long*) 0x7F008900
  8. #define EINT0CON1 (volatile unsigned long*) 0x7F008904
  9. #define EINT0MASK (volatile unsigned long*) 0x7F008920

  10. #define VIC0INTENABLE (volatile unsigned long*) 0x71200010
  11. #define VIC1INTENABLE (volatile unsigned long*) 0x71300010
  12. #define EINT0_VICADDR (volatile unsigned long*) 0x71200100
  13. #define EINT20_VICADDR (volatile unsigned long*) 0x71300104
  14. #define EINT0PEND (volatile unsigned long*) 0x7F008924
  15. #define VIC0ADDRESS (volatile unsigned long*) 0x71200F00
  16. #define VIC1ADDRESS (volatile unsigned long*) 0x71300F00

  17. void key1_isr()
  18. {
  19.     //1.保存环境
  20.     __asm__(
  21.     "sub lr, lr, #4\n"
  22.     "stmfd sp!, {r0-r12,lr}\n"
  23.     :
  24.     :
  25.     );
  26.     //2.中断处理
  27.     led_on();
  28.     //3.清除中断
  29.     *(EINT0PEND) = ~(0x0);
  30.     *(VIC0ADDRESS) = 0;
  31.     *(VIC1ADDRESS) = 0;
  32.     //4.恢复环境
  33.     __asm__(
  34.     "ltmfd sp!, {r0-r12,pc}^\n"
  35.     :
  36.     :
  37.     );
  38. }

  39. void key8_isr()
  40. {
  41.     //1.保存环境
  42.     __asm__(
  43.     "sub lr, lr, #4\n"
  44.     "stmfd sp!, {r0-r12,lr}\n"
  45.     :
  46.     :
  47.     );
  48.     //2.中断处理
  49.     led_off();
  50.     //3.清除中断
  51.     *(EINT0PEND) = ~(0x0);
  52.     *(VIC0ADDRESS) = 0;
  53.     *(VIC1ADDRESS) = 0;
  54.     //4.恢复环境
  55.     __asm__(
  56.     "ltmfd sp!, {r0-r12,pc}^\n"
  57.     :
  58.     :
  59.     );
  60. }

  61. void init_irq()
  62. {
  63.     //配置按键下降沿
  64.     *(EINT0CON0) = 0b010;                            设置EINT0和EINT1为下降沿触发
  65.     *(EINT0CON1) = 0b010<<8;                         设置EINT21, 20为下降沿触发

  66.     *(EINT0MASK) = 0;                                关闭EINT0屏蔽寄存器

  67.     *(VIC0INTENABLE) |= 0b1;                         使能中断
  68.     *(VIC1INTENABLE) |= 0b10;                        从INTERRUPT SOURCE表中了解到External interrupt 20 ~ 27在VIC1第2位。

  69.     *(EINT0_VICADDR) = key1_isr;                     设置中断向量地址,中断0是0x71200100寄存器中,写入EIN0地址
  70.     *(EINT1_VICADDR) = key8_isr;                     和使能类似,在EINT1中第二个0x71300104寄存器中,写入EINT20的地址

  71.     __asm__(
  72.     "mcr p15,0,r0,c1,c0,0\n"                         使能向量中断功能,210不需要
  73.     "orr r0, r0, #(1<<24)\n"
  74.     "mcr p15,0,r0,c1,c0,0\n"

  75.     "mrs r0, cpsr\n"                                 同样是开启总中断的使能
  76.     "bic r0, r0, #0x80\n"
  77.     "msr cpsr_c, r0\n"
  78.     :
  79.     :
  80.     );

  81. }

SRCPND:中断发生时,处理完后置1清除标志。0-32代表不同的中断类型
INTMSK:中断屏蔽位
INTPND:SRCPND说明了有什么中断被触发了,INTPND说明了CPU即将或已经在对某一个中断进行处理。
INTOFFSET:寄存器的功能则很简单,它的作用只是用于表明哪个中断正在被处理。
SUBSRCPND:有效的只有0—14位,SRCPND的细分类。

阅读(702) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~