裸机 程序 编写诀窍
2020-05-23 23:59
465 查看
1.如何向GPIO、UART等寄存器内写数据?(只需要定义少量地址情况下)
向寄存器写数据,我们要知道寄存器的地址,知道了地址后,又如何将数据写入地址中
[code]/* * 定义GPIO1相关寄存器地址 */ #define GPIO1_DR *((volatile unsigned int *)0X0209C000) #define GPIO1_GDIR *((volatile unsigned int *)0X0209C004) #define GPIO1_PSR *((volatile unsigned int *)0X0209C008) /* 初始化LED */ void led_init(void) { /* GPIO初始化 */ GPIO1_GDIR = 0x8; /* 设置为输出 */ GPIO1_DR = 0X0; /* 打开LED灯 */ }
定义GPIO1_DR寄存器时,使用了两个星号*,volatile unsigned int * 是定义0x0209c000为unsigned int类型的指针,最前面的*号是取地址里的值,类似于*p=num;就是将num值放在p的地址中。
2.如果地址很多,且寄存器地址是连续的,如何定义更加?
[code]/* * GPIO寄存器结构体 */ typedef struct { volatile unsigned int DR; volatile unsigned int GDIR; volatile unsigned int PSR; volatile unsigned int ICR1; volatile unsigned int ICR2; volatile unsigned int IMR; volatile unsigned int ISR; volatile unsigned int EDGE_SEL; }GPIO_Type; #define GPIO1_BASE (0x0209C000) #define GPIO2_BASE (0x020A0000) #define GPIO3_BASE (0x020A4000) #define GPIO4_BASE (0x020A8000) #define GPIO5_BASE (0x020AC000) #define GPIO1 ((GPIO_Type *)GPIO1_BASE) #define GPIO2 ((GPIO_Type *)GPIO2_BASE) #define GPIO3 ((GPIO_Type *)GPIO3_BASE) #define GPIO4 ((GPIO_Type *)GPIO4_BASE) #define GPIO5 ((GPIO_Type *)GPIO5_BASE)
因为一组寄存器包括很多,但是他们的地址都是连续的,比如和GPIO1有关的寄存器地址都是连续的,这时候可以定义GPIO1的基地址,剩下的寄存器用结构体表示。然后将GPIO1的基地址设置结构体类型的指针(注意这时候只有一个*号,和上面的不一样)。
调用时这么调用:
[code]void led_init(void) { /* GPIO初始化 */ GPIO1->GDIR = 0x8; /* 设置为输出 */ GPIO1->DR = 0X0; /* 打开LED灯 */ }
这也是为什么定义地址时只用了一个星号,因为 指针变量名->成员名 等价于 (*指针变量名).成员名 采用“->”符号就相当于取地址里的值了。
3.关于汇编语言的例子
[code].global _start _start: ldr pc, =Reset_Handler /* 复位中断服务函数 */ ldr pc, =Undefined_Handler /* 未定义指令中断服务函数 */ ldr pc, =SVC_Handler /* SVC */ ldr pc, =PreAbort_Handler /* 预取终止*/ ldr pc, =DataAbort_Handler /* 数据终止 */ ldr pc, =NotUsed_Handler /* 未使用*/ ldr pc, =IRQ_Handler /* IRQ中断*/ ldr pc, =FIQ_Handler /* FIQ中断 */ /* 复位中断服务函数 */ Reset_Handler: cpsid i /* 关闭IRQ */ /* 关闭I,D Cache和MMU * 修改SCTLR寄存器,采用读-改-写的方式 */ MRC p15, 0, r0, c1, c0, 0 /* 读取SCTLR寄存器的数据到r0寄存器里面*/ bic r0, r0, #(1 << 12) /* 关闭I Cache */ bic r0, r0, #(1 << 11) /* 关闭分支预测 */ bic r0, r0, #(1 << 2) /* 关闭D Cache*/ bic r0, r0, #(1 << 1) /* 关闭对齐 */ bic r0, r0, #(1 << 0) /* 关闭MMU */ MCR p15, 0, r0, c1, c0, 0 /* 将R0寄存器里面的数据写入到SCTLR里面*/ #if 0 /* 设置中断向量偏移 */ ldr r0, =0x87800000 dsb isb /*数据存储器隔离、指令存储器隔离 强迫CPU等待它之前的指令执行完毕。 为了执行效率,cpu可能不会立马执行一条指令,这条语句强制执行完再执行后面的语句*/ MCR p15,0,r0,c12,c0,0 /* 设置VBAR寄存器=0X87800000 */ dsb isb #endif .global _bss_start _bss_start: .word __bss_start .global _bss_end _bss_end: .word __bss_end /*清除BSS段*/ ldr r0, _bss_start ldr r1, _bss_end mov r2, #0 bss_loop: stmia r0!, {r2} cmp r0, r1 /* 比较R0和R1里面的值 */ ble bss_loop /*如果r0地址小于等于r1,继续清除bss段*/ /* 设置处理器进入IRQ模式 */ mrs r0, cpsr /* 读取cpsr到r0*/ bic r0, r0, #0x1f /* 清除cpsr的bit4-0*/ orr r0, r0, #0x12 /* 使用IRQ模式*/ msr cpsr, r0 /* 将r0写入到cpsr*/ ldr sp, =0x80600000 /* 设置IRQ模式下的sp*/ /* 设置处理器进入SYS模式 */ mrs r0, cpsr /* 读取cpsr到r0*/ bic r0, r0, #0x1f /* 清除cpsr的bit4-0*/ orr r0, r0, #0x1f /* 使用SYS模式*/ msr cpsr, r0 /* 将r0写入到cpsr*/ ldr sp, =0x80400000 /* 设置SYS模式下的sp*/ /* 未使用 */ NotUsed_Handler: ldr r0, =NotUsed_Handler bx r0
4.关于中断函数的编写框架
[code]/* 定义中断处理函数 */ typedef void (*system_irq_handler_t)(unsigned int gicciar, void *param); /* 中断处理函数结构体 */ typedef struct _sys_irq_handle { system_irq_handler_t irqHandler; /* 中断处理函数 */ void *userParam; /* 中断处理函数的参数 */ }sys_irq_handle_t; /* 中断处理函数表 */ static sys_irq_handle_t irqTable[NUMBER_OF_INT_VECTORS]; /* 初始化中断处理函数表 */ void system_irqtable_init(void) { unsigned int i = 0; irqNesting = 0; for(i = 0; i < NUMBER_OF_INT_VECTORS; i++ ) { irqTable[i].irqHandler = default_irqhandler; irqTable[i].userParam = NULL; } } /* 注册中断处理函数 */ void system_register_irqhandler(IRQn_Type irq, system_irq_handler_t handler, void *userParam) { irqTable[irq].irqHandler = handler; irqTable[irq].userParam = userParam; } /* 具体的中断处理函数,汇编中IRQ_Handler会调用此函数 */ void system_irqhandler(unsigned int gicciar) { uint32_t intNum = gicciar; irqNesting++; /* 根据中断ID号,读取中断处理函数,然后执行 */ irqTable[intNum].irqHandler(intNum, irqTable[intNum].userParam); irqNesting--; } /* 默认中断处理函数 */ void default_irqhandler(unsigned int gicciar, void *userParam) { while(1) { } } 比如说在GPIO初始化时,如果用到GPIO中断,调用下面的函数注册中断就行了,第二个参数是GPIO的中断服务函数 system_register_irqhandler(GPIO1_Combined_16_31_IRQn,(system_irq_handler_t)gpio1_irqhand, NULL);
亮点在于:①因为GIC有几百个中断,不可能挨个去定义中断处理函数;所以将一个数组定义为中断处理函数结构体类型。这样就可以通过for循环来初始化;
②中断处理函数结构体里成员变量是函数,通过定义函数指针来实现。
③在汇编的IRQ中断里通过传递中断号,确定应该执行哪一个中断处理函数。(之前初始化时已经注册过中断处理函数了)。
④在初始化中注册中断处理函数,这样可以确定中断处理函数是哪一个(特别注意注册中断处理函数时参数是指针传递,而不是值传递,值传递的话只是局部)。
相关文章推荐
- 在ubuntu下使用StarterWare为BeagleBone-Black编写裸机程序并在CCS中用Jlink调试
- mini2440按键裸机程序编写运行
- SPI总线 的使用 和 裸机程序编写
- Mkefile文件编写及点亮两个led的mini2440裸机程序
- 基于TX2440开发板在ADS1.2中编写LED的驱动(GPIO的使用)裸机程序
- (一)keil4 MDK 开发环境下编写裸机程序 (参考杨铸 北航) (开发板只需要连接JLNK 就行了)
- 编写led裸机程序并烧写到ARM开发板
- am335x在ubuntu下使用StarterWare编写裸机程序并在CCS中用Jlink调试
- am335x在ubuntu下使用StarterWare编写裸机程序并在CCS中用Jlink调试
- S3C2440 windows下使用jlink gdbserver,arm-none-eabi-gdb进行裸机程序编写调试
- 如何将在Eclipse中编写的java程序发布成.exe的可执行文件
- [5.18]编写一个学生和教师数据输入和显示程序,学生数据有编号、姓名、班号、和成绩,教师数据有编号 姓名、职称和部门。要求将编号、姓名输入和显示设计成一个类person,并作为学生数据操作类stud
- C#编写的托盘程序 不关程序不关机的解决方法
- [Web基础]使用SpringMVC编写web程序的自我约定方法论
- 智能蛇(C语言)程序编写要点
- 如何给自己编写的程序隐蔽的设置开机启动
- 程序设计的理念是从写测试程序开始,然后编写可以通过测试的程序
- MFC屏幕录制程序编写
- 模仿JavaAppArguments.java示例,编写一个程序,此程序从命令行接收多个数字,求和之后输出结果。