arm裸机点亮led volatile的理解
一、初始化流程
初始化时钟
设置GPIO的复用IO口
设置GPIO的电气属性
初始化GPIO
二、c语言初始化寄存器volatile理解
#define CCM_CCGR0 *((volatile unsigned int *)0x020C4068)
对 volatile的理解
其中volatile关键字有以下用途:
(1)用来同步,因为同一个东西可能在不同的存储介质中有多个副本,有些情
况下会使得这些副本中的值不同,这是不允许的,所以干脆用volatile,让它只
有一个,没有其他的副本,这样就不会发生不同步的问题。
(2)防止编译器优化去掉某些语句,像我在arm中见到个寄存器非常奇怪,
当中断来的时候,相对应的位置1,而清0又不能向这位写0,向这位写1才是1才
是清中断(清0)
// 假设0x560012300 为寄存器地址
#define INTPAND *(volatile unsigned int *)0x560012300
INTPAND = INTPAND; // 清中断
像编译器如果看到有INTPAND = INTPAND;这种看似无用的操作,如果没有
volatile说明,编译器就很有可能会去掉INTPAND = INTPAND;实际上有用的东
西,却被编译器当没用的东西优化掉了。
(3)当地址是io端口的时候,读写这个地址是不能对它进行缓存的,这是相对
于某些嵌入式中有cache(缓存)才有这个。比如写这个io端口的时候,如果没有这个
volatile,很可能由于编译器的优化,会先把值先写到一个缓冲区,到一定时候
再写到io端口,这样就不能使数据及时的写到io端口,有了volatile说明以后,
就不会再经过cache,write buffer这种,而是直接写到io端口,从而避免了读写
io端口的延时。Cache 是什么? - 知乎 (zhihu.com)
三、c语言版led流程
1.编写启动文件start.s
.global _start
_start:
@ 设置处理器进入SVC模式
mrs r0, cpsr //读取cpsr到r0
bic r0, r0, #0x1f //BIC Rd, Rn, #immed Rd = Rn & (~#immed) 0x1f 1 1111
orr r0, r0, #0x13 //使用SVC模式 ORR Rd, Rn, #immed Rd = Rn | #immed 0x13 1 0011
msr cpsr, r0 //将r0写入到cpsr
@ 设置sp指针
ldr sp, = 0x80200000
b main //跳转到c语言到main函数
2.编写main.h文件
#ifndef _MAIN_H
#define _MAIN_H
// 所有时钟寄存器地址
#define CCM_CCGR0 *((volatile unsigned int *)0x020C4068)
#define CCM_CCGR1 *((volatile unsigned int *)0x020C406C)
#define CCM_CCGR2 *((volatile unsigned int *)0x020C4070)
#define CCM_CCGR3 *((volatile unsigned int *)0x020C4074)
#define CCM_CCGR4 *((volatile unsigned int *)0x020C4078)
#define CCM_CCGR5 *((volatile unsigned int *)0x020C407C)
#define CCM_CCGR6 *((volatile unsigned int *)0x020C4080)
// 设置复用IO口地址
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0x020E0068)
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0x020E02F4)
// GPIO相关寄存器地址
#define GPIO1_DR *((volatile unsigned int *)0x0209C000)
#define GPIO1_GDIR *((volatile unsigned int *)0x0209C004)
#define GPIO1_PSR *((volatile unsigned int *)0x0209C008)
#define GPIO1_ICR1 *((volatile unsigned int *)0x0209C00C)
#define GPIO1_ICR2 *((volatile unsigned int *)0x0209C00C)
#define GPIO1_IMR *((volatile unsigned int *)0x0209C00C)
#define GPIO1_ISR *((volatile unsigned int *)0x0209C00C)
#define GPIO1_EDGE_SEL *((volatile unsigned int *)0x0209C00C)
#endif // !_MAIN_H
3.编写main.c文件
#include "main.h"
// 使能外设时钟
void clk_enable(void)
{
CCM_CCGR0 = 0xFFFFFFFF;
CCM_CCGR1 = 0xFFFFFFFF;
CCM_CCGR2 = 0xFFFFFFFF;
CCM_CCGR3 = 0xFFFFFFFF;
CCM_CCGR4 = 0xFFFFFFFF;
CCM_CCGR5 = 0xFFFFFFFF;
CCM_CCGR6 = 0xFFFFFFFF;
}
// 初始化LED
void led_init(void)
{
// 复用IO口
SW_MUX_GPIO1_IO03 = 0x5;
// 设置IO口的电气属性
SW_PAD_GPIO1_IO03 = 0x10B0;
// GPIO初始化
GPIO1_GDIR = 0x08; //设置为输出
GPIO1_DR = 0x0; //设置为打开灯
}
// 短延时
void delay_short(volatile unsigned int n)
{
while(n--){}
}
// 一次大概是1ms 主频396MHZ
// n代表延时的时间
void delay(volatile unsigned int n)
{
while(n--)
{
delay_short(0x7FF);
}
}
// 打开led
void led_on(void)
{
GPIO1_DR &= ~(1<<3); // bit3清0 1000 反 0111
}
// 关闭led
void led_off(void)
{
GPIO1_DR |= (1<<3); // bit3清0 1000 反 0111
}
int main(void)
{
// 使能时钟
clk_enable();
// 初始化led
led_init();
while (1)
{
led_on();
delay(500);
led_off();
delay(500);
}
return 0;
// 初始化led
// 闪烁led灯
}
4.编写makefile文件
makefile 文件

# 依赖文件放在前面,链接编译后在前,start.o放在main.o 前
# 可以在反汇编.s文件中查看编译后的执行顺序
objs = start.o main.o
led.bin : $(objs)
arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S led.elf $@
arm-linux-gnueabihf-objdump -D -m arm led.elf > led.dis
# -Wall表示显示编译时的所有警告
# nostdlib表示不链接系统标准启动文件和库文件
# -O表示优化等级
%.o : %.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o : %.S
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
clean:
rm -rf *.o led.bin led.elf led.dis
以上是没有用到链接文件的
arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o led.elf
-Ttext 0x87800000 指定链接首地址
使用链接文件如下
SECTIONS{
. = 0X87800000;
.text :
{
start.o
main.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
__bss_start = .;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = .;
}
编译部分改为用链接文件
arm-linux-gnueabihf-ld -Timx6u.lds $^ -o led.elf
var code = "c01da795-9bfb-4168-89ef-6424a65cb617"

浙公网安备 33010602011771号