ARM嵌入式裸机简单了解
基于正点原子 ALPHA开发板
开发环境的搭建
文件互传
FTP
打开Ubuntu的FTP服务
sudo apt-get install vsftpd #失败重启重新执行即可
打开配置文件
sudo vi /etc/vsftpd.conf
#确保
#local_enable=YES
#write_enable=YES
重启ftp
sudo /etc/init.d/vsftpd restart
window安装FileZilla客户端软件。
通过FTP协议互传文档
samba
NFS和SSH服务
NFS就是Network File System的缩写,它最大的功能就是可以通过网络,让不同的机器、不同的操作系统可以共享彼此的文件。NFS服务器可以让PC将网络中的NFS服务器共享的目录挂载到本地端的文件系统中,而在本地端的系统中来看,那个远程主机的目录就好像是自己的一个磁盘分区一样,在使用上相当便利
安装nfs
sudo apt-get install nfs-kernel-server rpcbind
创建nfs文件夹
创建的文件夹供 nfs 服务器使用,以后我们可以在开发板上通过网络文件系统来访问 nfs 文件夹,要先配置 nfs
sudo vi /etc/exports
添加下面的内容
/home/zuozhongkai/linux/nfs *(rw,sync,no_root_squash)
重启NFS
sudo /etc/init.d/nfs-kernel-server restart
开启 Ubuntu 的 SSH 服务以后我们就可以在 Windwos 下使用终端软件登陆到 Ubuntu
安装命令
sudo apt-get install openssh-server
交叉编译器的安装
官网下载64位(gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz)交叉编译工具
开始操作:
创建目录
sudo mkdir /usr/local/arm
将文件拷贝到该目录
sudo cp gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz /usr/local/arm/ -f
解压
sudo tar -vxf gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf.tar.xz
添加环境变量
配置文件/etc/profile
最后一行添加
export PATH=$PATH:/usr/local/arm/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin
安装相关库
sudo apt-get install lsb-core lib32stdc++6
验证是否安装成功
arm-linux-gnueabihf-gcc -v
重启Ubuntu
使用汇编简单点亮LED灯
本程序基于正点原子Linux开发板。灯对应引脚为GPIO1_IO3
为什么要学习Cortex-A汇编
①、需要用汇编初始化一些SOC外设。
②、使用汇编初始化DDR,I.MX6U不需要。
③、设置sp指针,一般指向DDR,设置好C语言运行环境。
ALPHA开发板LED灯硬件驱动流程
STM32 IO初始化流程:
①、使能GPIO时钟。
②、设置IO复用,将其复用为GPIO
③、配置GPIO的电气属性。
④、使用GPIO,输出高/低电平。
I.MX6ULL IO初始化:
①、使能时钟,CCGR0CCGR6这7个寄存器控制着6ULL所有外设时钟的使能。为了简单,设置CCGR0CCGR6这7个寄存器全部为0XFFFFFFFF,相当于使能所有外设时钟。
②、IO复用,将寄存器IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03的bit3~0设置为0101=5,这样GPIO1_IO03就复用为GPIO。
③、寄存器IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03是设置GPIO1_IO03的电气属性。包括压摆率、速度、驱动能力、开漏、上下拉等。
④、配置GPIO功能,设置输入输出。设置GPIO1_DR寄存器bit3为1,也就是设置为输出模式。设置GPIO1_DR寄存器的bit3,为1表示输出高电平,为0表示输出低电平。
汇编简介
汇编由一条一条指令构成,指令就涉及到汇编指令。
假设a地址为0X20,b地址为0x30
C赋值:
int a,b;
a=b;
Contex-A汇编赋值
LDR R0, =0X30
LDR R1, [R0]
LDR R0, =0X20
STR R1, [R0]
在使用汇编编写驱动的时候最常用的就是LDR和STR这两个指令。
编写驱动
.global _start
_start:
/*使能外设时钟*/
ldr r0, =0x020C4068 @CCGR0
ldr r1, =0xffffffff @CCGR0要写入的数据
str r1, [r0] @将数据写入寄存器
ldr r0, = 0x020C406C @CCGR1
str r1, [r0] @将数据写入寄存器
ldr r0, = 0x020C4070 @CCGR2
str r1, [r0] @将数据写入寄存器
ldr r0, = 0x020C4074 @CCGR3
str r1, [r0] @将数据写入寄存器
ldr r0, = 0x020C4078 @CCGR4
str r1, [r0] @将数据写入寄存器
ldr r0, = 0x020C407C @CCGR5
str r1, [r0] @将数据写入寄存器
ldr r0, = 0x020C4080 @CCGR6
str r1, [r0] @将数据写入寄存器
/*配置 GPIO1_IO3 */
/**pin的复用为GPIO
* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03=5
*/
ldr r0, =0x020E0068 @IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
ldr r1, =5 @CCGR0要写入的数据
str r1, [r0] @将数据写入寄存器
/**电气属性
* IOMUXC_SW_PAD_CTL_PAD_GPIO1_IO03
[0] : 0 低速率
[3:5] : 110 驱动能力R0/6-
[6:7] : 10 100MHZ
[11] : 0 关闭开漏输出
[12] : 1 使能pull/keeper
[13] : 0 keeper
[14:15]: 00 100k电阻
[16] : 0 关闭hys
*/
ldr r0, =0x020E02F4 @IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
ldr r1, =0x000010B0 @写入的数据
str r1, [r0] @将数据写入寄存器
/**设置GPIO 方向输出*/
ldr r0, =0x0209C004 @GPIO1_GDIR
ldr r1, =0x08 @写入的数据
str r1, [r0] @将数据写入寄存器
/*打开LED 低电平 */
ldr r0, =0x0209C000 @GPIO1_DR
ldr r1, =0 @写入的数据
str r1, [r0] @将数据写入寄存器
/*死循环 */
loop:
b loop
/*现象:当运行时开发板上的LED灯会亮*/
编译程序
编译程序
①、使用arm-linux-gnueabihf-gcc,将.c .s文件变为.o
②、将所有的.o文件连接为elf格式的可执行文件。
③、将elf文件转为bin文件。
④、将elf文件转为汇编,反汇编。
链接:
链接就是将所有.o文件链接在一起,并且链接到指定的地方。本实验链接的时候要指定链接起始地址。链接起始地址就是代码运行的起始地址。
对于6ULL来说,链接起始地址应该指向RAM地址。RAM分为内部RAM和外部RAM,也就是 DDR。6ULL内部RAM地址范围0X9000000X91FFFF。也可以放到外部DDR中,对于I.MX6U-ALPHA开发板,512MB字节DDR版本的核心板,DDR范围就是0X800000000X9FFFFFFF。对于256MB的DDR来说,那就是0X80000000~0X8FFFFFFF。
本系列视频,裸机代码的链接起始地址为0X87800000。要使用DDR,那么必须要初始化DDR,对于I.MX来说bin文件不能直接运行,需要添加一个头部,这个头部信息包含了DDR的初始化参数,I.MX系列SOC内部boot rom会从SD卡,EMMC等外置存储中读取头部信息,然后初始化DDR,并且将bin文件拷贝到指定的地方。
bin的运行地址一定要和链接起始地址一致。位置无关代码除外。
Makefile代码
#leds.s为需要编译的汇编程序
led.bin : leds.s
arm-linux-gnueabihf-gcc -g -c leds.s -o leds.o
arm-linux-gnueabihf-ld -Ttext 0x87800000 leds.o -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf led.bin
arm-linux-gnueabihf-objdump -D led.elf > led.dis
clean:
rm -rf *.o *.bin *.elf *.dis
烧写bin文件
STM32烧写到内部FLASH。
6ULL支持SD卡、EMMC、NAND、nor、SPI flash等等启动。裸机例程选择烧写到SD卡里面。
在ubuntu下向SD卡烧写裸机bin文件。烧写不是将bin文件拷贝到SD卡中,而是将bin文件烧写到SD卡绝对地址上。而且对于I.MX而言,不能直接烧写bin文件,比如先在bin文件前面添加头部。完成这个工作,需要使用正点原子提供的imxdownload软件。
imxdownload使用方法,确定要烧写的SD卡文件,我的是/dev/sdf。
给予imxdownload可执行权限:
chmod 775 imxdownload
烧写:
./imxdownload led.bin /dev/sdb #不一定是sdb,如果usb3.0接在了sde、sdf等,就需要改成对应的设备
imxdownlaod会向led.bin添加一个头部,生成新的load.imx文件,这个load.imx文件就是最终烧写到SD卡里面去的。
IMX启动方式
正点原子通过拨码开关1~8来进行模式的选择。
硬件启动
- 6ULL支持从多种外置flash启动程序
- 启动方式选择
- 选择启动设备
启动方式的选择
设置MODE1、MODE0两个引脚。正点原子开发板可通过拨码开关的1、2进行配置。默认是从内部BOOT启动的,也就是MODE1=1,MODE0=0。
启动设备的选择
支持设备:
NOR flash,oneNAND、NAND Flash、QSPI flash、SD/EMMC、EEPROM。最常用的就是NAND、SD、EMMC甚至QSPI Flash
正点原子开发板只对其中的NAND、SD、EMMC启动做了支持。通过开发板上BOOT_CFG的拨码开关3~6进行选择。
具体修改的原理为:BOOT_CFG1、BOOT_CFG2、BOOT_CFG4三个寄存器,每个寄存器八位,正点原子开发板对其中大多数都已经固定好了电平大小,只留下其中六位可通过拨码开关进行修改电平,也就是拨码开关的3,4,5,6,7,8六位。
启动头信息
当设置为内部BOOT启动时。内部的BOOTROM就会运行,根据板子上的启动设备读取加了头的bin文件
BOOTROM作用
- 设置内核时钟为396MHz
- 使能MMU和Cache, 使能L1cache L2 cache MMU,提高boot速度
- 从BOOT_CFG设置的外置存储中,读取镜像,然后做相应的处理
IVT(Image Vector Table)和BootData数据
Boot Device Type | Image Vector Table Offset | Initial Load Region Size |
---|---|---|
NOR | 4 Kbyte = 0x1000 bytes | Entire Image Size |
OneNAND | 256 bytes = 0x100 bytes | 1 Kbyte |
SD/MMC/eSD/eMMC/SDXC | 1 Kbyte = 0x400 bytes | 4 Kbyte |
SPI EEPROM | 1 Kbyte = 0x400 bytes | 4 Kbyte |
有点很操蛋的是,BIN文件的头部资料比较少,下面是正点原子大佬查阅多方资料加上了自己的理解,所以不一定是正确的
Bin文件前面要添加头部。烧写到SD卡中的load.imx文件在SD卡中的起始地址是0x400,也就是1024。
头部大小为3KB,加上偏移的1KB,一共是4KB,因此在SD卡中bin文件起始地址为4096。
IVT大小为32B/4=8条。
IVT+Boot Data的数据,很多是正点原子从NXP官方u-boot.imx文件里面提取出来的。
DCD数据
Device Configuration Data,DCD数据配置6ULL内部寄存器。
首先,将CCRG0~CCGR6全部写为0XFFFFFFFF,表示打开所有外设时钟。然后就是DDR初始化参数。设置DDR控制器,也就是初始化DDR。
其他的数据
检查数据命令、NOP命令、解锁命令。这些其实也都属于DCD
C语言点亮LED灯
C语言运行环境构建
设置处理器模式
设置6ULL处于SVC模式 下。设置CPSR寄存器的bit4-0,也就是M[4:0] : 10011=0X13。读写状态寄存器需要用到MRS和MSR两条汇编指令。
这两条指令用于特殊寄存器的读写。LDR和STR为通用寄存器的读写。
MRS : CPSR寄存器数据读出到通用寄存器里面。
MSR : 通用寄存器的值写入到CPSR寄存器里面。
设置sp指针(栈指针)
sp可以指向内部RAM,也可以指向DDR,我们将其指向DDR。
sp设置到哪里?512MB的范围0x80000000~0x9FFFFFFF。栈大小,0x200000=2MB。处理器栈增长方式,对于A7而言是向下增长的。设置sp指向0x80200000。
跳转到C语言
使用b指令,跳转到C语言函数,比如main函数。
.global _start
_start:
/*设置 处理器进入SVC模式*/
mrs r0, cpsr @读取cpsr到r0
bic r0, r0, #0x1f @清除cpsr的[4:0]位
orr r0, r0, #0x13 @[4:0] 进入svc模式数据
msr cpsr, r0 @进入SVC模式
/** 设置SP指针*/
ldr sp, = 0x80200000
b main /*跳转到C语言的main函数 */
C软件编写
h文件
#ifndef _LEDC_H_
#define _LEDC_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)
/*
* IOMUX相关寄存器地址
*/
#define SW_MUX_GPIO1_IO03 *((volatile unsigned int *)0X020E0068)
#define SW_PAD_GPIO1_IO03 *((volatile unsigned int *)0X020E02F4)
/*
* GPIO1相关寄存器地址
*/
#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 *)0X0209C010)
#define GPIO1_IMR *((volatile unsigned int *)0X0209C014)
#define GPIO1_ISR *((volatile unsigned int *)0X0209C018)
#define GPIO1_EDGE_SEL *((volatile unsigned int *)0X0209C01C)
#endif
C文件
#include "ledc.h"
static 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;
}
static void led_init(void)
{
SW_MUX_GPIO1_IO03 = 0x05;
SW_PAD_GPIO1_IO03 = 0x10b0;
GPIO1_GDIR = 0x08;
GPIO1_DR = 0x0;
}
/**
* @brief 延时
*/
static void delay_nop(volatile int n)
{
while(n--){}
}
/**
* @brief 延时nms
*/
static void delay_ms(volatile int n)
{
while(n--){
delay_nop(0x7FF);
}
}
/**
* @brief 打开灯
*/
static void led_on(void){
GPIO1_DR &= ~(0x01 << 3);
}
/**
* @brief 关闭灯
*/
static void led_off(void){
GPIO1_DR |= (0x01 << 3);
}
int main()
{
//初始化时钟和led,参考汇编注释
clk_enable();
led_init();
while(1){
led_on();
delay_ms(1000);//信你个鬼的1s,大概2~3s
led_off();
delay_ms(1000);
}
}
该实验现象为led灯循环亮灭
makefile
objs = start.o ledc.o
ledc.bin : $(objs)
arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o led.elf
arm-linux-gnueabihf-objcopy -O binary -S -g led.elf $@
arm-linux-gnueabihf-objdump -D led.elf > led.dis
%.o : %.c
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
%.o : %.s
arm-linux-gnueabihf-gcc -Wall -nostdlib -c -o $@ $<
clean:
rm -rf *.o *.bin *.elf *.dis
第一行start.o需放在前面,先进行链接,以便构建C语言运行环境。否则程序无法运行
链接脚本
指定需要编译之后的链接文件、数据和首地址。
代码如下
SECTIONS{
. = 0X87800000;
.text :
{
start.o
main.o
*(.text)
}
.rodata ALIGN(4) : {*(.rodata*)}
.data ALIGN(4) : { *(.data) }
. = ALIGN(4);
__bss_start = . ;
.bss ALIGN(4) : { *(.bss) *(COMMON) }
__bss_end = . ;
}
当有链接脚本时,将makefile中
arm-linux-gnueabihf-ld -Ttext 0x87800000 $^ -o led.elf
改为
arm-linux-gnueabihf-ld -Txxx $^ -o led.elf #xxx为链接脚本文件名字
此时makefile中start.o不需要必须放在最前面
BSS
BSS段 (bss segment)通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
该段用于存储未初始化的全局变量或者是默认初始化为0的全局变量,它不占用程序文件的大小,但是占用程序运行时的内存空间。所以需要清0
BSS清0汇编代码
.global _start
.global _bss_start
_bss_start:
.word __bss_start
.global _bss_end
_bss_end:
.word __bss_end
_start:
/*设置 处理器进入SVC模式*/
mrs r0, cpsr @读取cpsr到r0
bic r0, r0, #0x1f @清除cpsr的[4:0]位
orr r0, r0, #0x13 @[4:0] 进入svc模式数据
msr cpsr, r0 @进入SVC模式
/**BSS段 清0*/
ldr r0, _bss_start
ldr r1, _bss_end
mov r2, #0
/*从起始地址到结束地址*/
bssclear_loop:
stmia r0!, {r2} @将r2的值写入到r0指向的地址
cmp r0,r1 @比较r0和r1的值
ble bssclear_loop @b跳转 le为r0小于r1 ble当上一句为第一个元素小于第二个元素时 ,进行跳转
/** 设置SP指针*/
ldr sp, = 0x80200000
b main /*跳转到C语言的main函数 */
系列视频中的蜂鸣器驱动和按键输入实验等只是引脚不同以及引脚配置少许改变,不再做笔记
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!