1.如何向GPIO、UART等寄存器内写数据?(只需要定义少量地址情况下)
向寄存器写数据,我们要知道寄存器的地址,知道了地址后,又如何将数据写入地址中
/*
* 定义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.如果地址很多,且寄存器地址是连续的,如何定义更加?
/*
* 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的基地址设置结构体类型的指针(注意这时候只有一个*号,和上面的不一样)。
调用时这么调用:
void led_init(void)
{
/* GPIO初始化 */
GPIO1->GDIR = 0x8; /* 设置为输出 */
GPIO1->DR = 0X0; /* 打开LED灯 */
}
这也是为什么定义地址时只用了一个星号,因为 指针变量名->成员名 等价于 (*指针变量名).成员名 采用“->”符号就相当于取地址里的值了。
3.关于汇编语言的例子
.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.关于中断函数的编写框架
/* 定义中断处理函数 */
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中断里通过传递中断号,确定应该执行哪一个中断处理函数。(之前初始化时已经注册过中断处理函数了)。
④在初始化中注册中断处理函数,这样可以确定中断处理函数是哪一个(特别注意注册中断处理函数时参数是指针传递,而不是值传递,值传递的话只是局部)。