裸机 程序 编写诀窍

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

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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值