您的位置:首页 > 其它

裸机 程序 编写诀窍

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中断里通过传递中断号,确定应该执行哪一个中断处理函数。(之前初始化时已经注册过中断处理函数了)。

④在初始化中注册中断处理函数,这样可以确定中断处理函数是哪一个(特别注意注册中断处理函数时参数是指针传递,而不是值传递,值传递的话只是局部)。

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐