ARM是什么:
ARM是Advanced RISC Machines的简写
RISC:精简指令集计算机(Reduced Instruction Set Computer)
嵌入式硬件可以分为三部分:
1、微处理器
2、外围电路
3、外部设备
嵌入式的软件系统可分为四个层次:
1、板级支持包(BSP, Board Support Packet)
2、实时操作系统(RTOS, Real Time Operstion System)
3、应用编程接口(API, Application Programmable Interface)
4、应用程序
根据结构和功能特点不同,嵌入式处理器可分为:
1、嵌入式微处理器(MPU, MicroProcessor Unit)
2、嵌入式微控制器(MCU, MicroController Unit)
3、嵌入式数字信号处理器(DSP, Digital Single Processor)
//4、片上系统SOC(System on Chip)
简述MCU和DSP的区别:
MCU是微控制器,DSP是数字信号处理器
MCU相当于小型电脑,集成度高,偏重于控制
DSP是专用的信息处理器,处理速度快,偏重于信号处理
ARM体系结构有哪几种工作状态?又有哪几种运行模式?其中哪些为特权模式?哪些为异常模式?并指出处理器在什么情况下进入相应模式?
工作状态:
第一种:ARM状态。处理器执行32位的字对齐的ARM指令
第二种:Thumb状态。处理器执行16位的半字对齐的Thumb指令
运行模式:
用户模式、管理模式、系统模式、快速中断模式、外部中断模式、数据访问终止模式、未定义指令终止模式。
在这7种运行模式中,除了用户模式外,其余6种为特权模式。
在这6种特权模式中,除了系统模式外,其它5种特权模式为异常模式。
程序正常执行的时候是在用户模式下,如果有异常发生,处理器会自动切换工作模式。
ARM体系可以用两种方法存储字数据:
大端格式(Big Endian):
数据的低字节存储在高地址中。
小端格式(Little Endian):
数据的低字节存储在低地址中。
ARM处理器寄存器组织:
共有37个寄存器,其中31个通用寄存器,6个状态寄存器
嵌入式Linux系统移植:
嵌入式Linux系统移植主要由四大部分组成:
一、搭建交叉开发环境
二、BootLoader的选择和移植
三、kernel的配置、编译、和移植
四、根文件系统的制作
Bootloader启动步骤:
stage1(汇编语言实现)
1、硬件设备初始化
2、为加载Bootloader的stage2准备RAM空间
3、复制Bootloader的stage2到RAM空间中
4、设置好堆栈,堆栈指针的设置是为执行C语言代码做好准备
5、跳转到stage2的C语言入口点
stage2(C语言实现)
1、初始化本阶段要使用到的硬件设备
2、检测系统内存映射
3、kernel映像和根文件映像从Flash上读到RAM空间中
4、为内核设置启动参数
5、调用内核
ARM常用的Bootloader程序有哪些:
U-boot、Blob、ARMBoot、RedBoot、vivi。
大多数Bootloader程序都包括两种不同的操作模式:
1、启动加载模式(Bootloading)
2、下载模式(Downloading)
MakeFile文件的编写(使用了预定义变量):
书本上:
CC=gcc
TARGET=All
OBJIECTS=m.o visit.o listen.o watch.o study.o play.o
$(TARGET):$(OBJIECTS)
$(CC) $^ -o m ($^规则中所以依赖的列表,以空格为分隔符)
*.o:*.c
$(CC) -c $< -o $@ ($<规则中的第一个依赖文件名 $@规则中的目标所对应的文件名)
clean:
rm *.o
简单的hello.c的Makefile文件:
all:hello.o
gcc helllo.o -o hello
hello.o:hello.c
gcc -c hello.c -o hello.o
clean:
rm *.o
make和Makefile之间的关系:
make是一种命令,是根据Makefile文件的规则决定如何编译和连接程序或其他的动作。
makefile文件需要按照某种语法进行编写,文件中需要说明如何编译各个源文件并连接生成可执行文件,并要求定义源文件之间的依赖关系。
简单的说 makefile 就像批处理的脚本文件,里边写好了一些命令的集合,当运行 make 命令时 ,便会按着 makefile 提供的命令及顺序来完成编译。
#make
#make -f makefile文件名
-f 选项就是指定将哪个文件作为makefile文件。如果没有使用-f选项,标准版本make命令将首先在当前目录下找名字为makefile的文件,找不到会继续查找Makefile的文件。
BusyBox工具的功能:
BusyBox工具用来精简基本用户命令和程序,它将数以百计的常用Unix/Linux命令集成到一个可执行文件中。
Sqlite数据库运行环境:
1、库文件安装的目录:/usr/local/lib
2、可执行文件安装的目录:/usr/local/bin
3、头文件安装的目录:/usr/local/include
说明:
系统自动在哪个目录搜索库是由/etc/ld.so.conf文件决定的。
因此需修改/etc/ld.so.conf文件,在文件中添加上一行/usr/local/lib。
然后让文件内容生效,则要运行命令#/sbin/ldconfig。
Sqlite数据库常用操作:
//创建数据库文件stu.db
#sqlite3 stu.db
//创建数据表student
sqlite>create table student(ID integer primary key, Name varchar(20),Age integer,Sex varchar(10))
//添加记录
sqlite>insert into student values(1001,'zhangsan',20,'female')
//查询操作
sqlite>select * from student
//查看数据库stu.db已经拥有的数据表
sqlite>.tables
//退出
sqlite>.quit
Sqlite数据库编程:
#include<stdio.h>
#include<sqlite3.h>
int main()
{
sqlite3 *db=NULL;
int rc;
char *Errormsg,*sql;
int nrow;
int ncol;
char **Result;
int i=0;
rc=sqlite3_open("stu.db",&db);
if(rc){
fprintf(stderr,"can't open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return 1;
}else
printf("open database successly!!!\n");
//创建学生student表
sql="create table student(ID integer primary key,Name varchar(20),Age integer,Sex varchar(10))";
sqlite3_exec(db,sql,0,0,&Errormsg);
//插入两条记录
sql="insert into student values(10001,'zhangsan',20,'female')";
sqlite3_exec(db,sql,0,0,&Errormsg);
sql="insert into student values(10002,'lisi',19,'male')";
sqlite3_exec(db,sql,0,0,&Errormsg);
//获得数据表
sql="select * from student";
sqlite3_get_table(db,sql,&Result,&nrow,&ncol,&Errormsg);
//输出行数与列数
printf("row=%d column=%d\n",nrow,ncol);
//输出结果,打印显示
printf("the result is:\n");
printf("-----------------------------------------\n");
for(i=0;i<(nrow+1)*ncol;i++){
printf("%20s",Result[i]);
if((i+1)%4 == 0) printf("\n");
}
sqlite3_free(Errormsg);
sqlite3_free_table(Result);
sqlite3_close(db);
return 0;
}
驱动程序连接到内核的两种方法:
静态连接:将驱动程序源码保存在内核源码指定的位置,作为内核源码的一部分,然后重新编译内核,这时驱动程序被编译到内核映像文件之中。
动态连接:将驱动程序作为一个模块(module)单独编译,在需要它的时候再动态加载到内核,如果不需要它,又可以将它从内核中删除。
设备驱动程序的功能:
1、对设备初始化和释放
2、数据传送
3、检测和处理设备出现的错误
设备驱动程序的组成:
1、自动配置和初始化子程序
2、服务于I/O请求的子程序,又称为驱动程序的上半部分
3、中断服务子程序,又称为驱动程序的下半部分
设备文件:
设备文件有时也称为设备节点,一般存在/dev目录下。正常情况下,/dev目录下的每一个设备文件对应一个设备(包括虚拟设备),设备文件的命名一般为“设备名+数字或字母”。
设备文件创建方式:
自动创建:指驱动程序加载时,驱动程序内部调用了自动创建设备文件的函数,从而由内核完成设备文件的创建工作。
手动创建:指用户通过mknod命令来创建设备文件。
简述设备文件、驱动程序、主设备号和次设备号之间的关系:
驱动程序加载到内核后会有一个主设备号。在linux内核中,主设备号标识设备对应的驱动程序,告诉linux内核使用哪一个驱动程序为该设备(也就是/dev下的设备文件)服务,而次设备号则用来标识具体且唯一的某个设备。
简述字符设备驱动程序提供的常用入口点及各自的功能:
open入口点:对将要进行的I/O操作做好必要的准备工作,如清除缓冲区。
read入口点:当从设备上读取数据时,需要调用read子程序。
write入口点:当要向设备上读数据时,需要调用write子程序。
close入口点:当设备操作结束时,需要调用close子程序关闭设备。
ioctl入口点:主要用于对设备进行读之外的其它操作,比如配置设备,进入或退出某种操作模式。
设备驱动模块化编程三个过程:
加载过程:当执行insmod命令加载驱动程序时,首先调用驱动程序中的入口函数module_init,该函数完成设备驱动的初始化工作。
向内核注册该设备,字符设备调用register_chrdev函数完成注册,块设备调用register_blkdev函数完成注册。
注册成功后,该设备将获得系统分配的主设备号、自定义的次设备号,并建立起与文件系统的关联。
系统调用过程:当驱动程序加载后,就一直等待应用进程来调用。应用程序可以利用设备文件对其进行操作,如调用open、read、write、ioctl和close等函数。
卸载过程:当执行rmmod命令卸载驱动程序时,则会调用驱动程序中的module_exit函数,该函数完成后回收相应的资源。
字符设备调用unregister_chrdev函数完成注销,块设备调用unregister_blkdev函数完成注销。
设备驱动程序操作命令的使用方法:
1、lsmod命令 用于显示当前已加载的模块
#lnsmod
2、insmod命令 用于将驱动模块加载到操作系统内核
#insmod file_name
//如将数码管驱动程序(tube.o)加载到内核
#insmod tube.o
3、驱动程序加载时,如果系统支持设备文件系统,则系统会自动创建设备文件,否则需要手动创建。
#mknod dev/设备文件名 flag 主设备号 次设备号
//如手动创建设备文件
#mknod dev/tube c 240 0
说明:flag标志。b 标志表示这个特殊文件是面向块的设备(磁盘、软盘或磁带),c 标志表示这个特殊文件是面向字符的设备(其他设备)。
4、rmmod命令 用于将驱动模块从内核中删除
#rmmod module_name
//如将rube模块删除
#rmmod tube
常用Linux命令的使用:
在实验箱上挂载SD卡:
#mkdir sdcard
#mount /dev/mmcblkp1 sdcard
将U盘挂接到Linux系统/mnt/usb目录上,可使用如下命令
#mount /dev/sda1 /mnt/usb
将当前目录下的ab.png更名为xyz.png
#mv ab.png xyz.png
将文本文件33.txt和44.txt两文件的内容合并到aa.txt文件中
#cat 33.txt 44.txt > aa.txt
在/root目录,查找第一个字符为h的文件。
#find /root -name h*.*
从普通用户切换到root用户,及从root用户切换回普通用户
$su root
#exit
常用汇编指令:
ARM指令代码格式:
<opcode>{<cond>}{S}<Rd>,<Rn>{<OP2>}
格式中<>的内容必不可少,{}中的内容可省略
<opcode>是操作码,如ADD表示算数加法
{<cond>}表示指令执行的条件域,如EQ、NE等。
{S}决定指令的执行结果是否影响CPSR的值,使用该后缀则指令执行的结果影响CPSR的值,否则不影响。
<Rd>表示目的寄存器
<Rn>表示第一个操作数,为寄存器
<op2>表示第二个操作数,可以是立即数、寄存器或寄存器移位操作数。
数据处理指令:
1、ADD 加法
指令格式: ADD{cond}{S} Rd,Rn,op2 (op2可以是寄存器、立即数或被移位的寄存器)
功能: Rd=Rn +op2 将Rn的值与操作数op2相加,结果存放到目的寄存器Rd中。
示例: ADD R0,R1,R2 R0=R1+R2
ADD R0,R1,#256 R0=R1+256
ADD R0,R2,R3,LSL#1 R0=R2+(R3<<1)
2、SUB 减法
指令格式:SUB{cond}{S} Rd,Rn,op2 (op2同上)
功能: Rd=Rn -op2 将Rn的值与操作数op2相减,结果存放到目的寄存器Rd中。
示例: SUB R0,R1,R2 R0=R1-R2
SUB R0,R1,#256 R0=R1-256
SUB R0,R2,R3,LSL#1 R0=R2-(R3<<1)
3、BIC 位清除
指令格式: BIC{cond}{S} Rd,Rn,op2 (op2同上)
功能: Rd=Rn AND (!op2) 将Rn 的值与操作数op2 的反码按位逻辑”与”,结果存放到目的寄存器Rd 中。
示例: BIC R0,R0,#0x0F 将R0最低4位清零,其余位不变。
BIC R9,R8,#0xFF00 将R8中8~15位清零, 其余位不变。
4、EOR 逻辑异或
指令格式:EOR{cond}{S} Rd,Rn,op2 (op2同上)
功能: Rd=Rn EOR op2 将Rn的值与操作数op2进行异或,结果存放到目的寄存器Rd 中。
5、MOV 移动
指令格式:MOV{cond}{S} Rn,op1 (op1可以是寄存器、立即数或被移位的寄存器)
功能: Rd=op1 数据传送
示例: MOV R2,R0 R0的值送给R2
MOV R0, R0, LSL#3 R0 = (R0<<3)
6、CMP 比较
指令格式: CMP{cond} Rn,op1 (op1为寄存器或立即数)
功能: Rn-op1 将Rn的值与op1比较
示例: CMP R1, R2 比较R1和R2的值
CMP R1,#1000 R1的值和1000比较
加载/储存指令:
1、LDR 加载字数据指令
指令格式:LDR{cond}Rd,addr
功能: Rd<--[addr]
示例: LDR R0,[R1] 将存储器地址为R1的字节数据读入寄存器R0中
LDR R1,[R0,R2] 将R0+R2地址的数据读出,保存到R1中(R0的值不变)
LDR R1,[R0,R2,LSL #2] 将R0+R2×4地址处的数据读出,保存到R1中(R0、R2的值不变)
2、STR 存储字数据指令
指令格式: STR{cond}Rd,addr
功能: [addr]<--Rd
示例: STR R0,[R1] 将寄存器R0中的字节写入地址为R1的存储器中
3、LDM 多寄存器加载指令
指令格式: LDM{cond}{mode}Rn(!),reglist
功能: reglist<--[Rn……]
4、STM 多寄存器存储指令
指令格式: STMM{cond}{mode}Rn(!),reglist
功能: [Rn……]<--reglist
说明:
LDM/STM 的主要用途有现场保护、数据复制和参数传递等。
其模式有 8 种,其中前面 4 种用于数据块的传输,后面4种是堆栈操作,如下所示。
(1)IA:每次传送后地址加 4。 (2)IB:每次传送前地址加 4。 (3)DA:每次传送后地址减 4。 (4)DB:每次传送前地址减 4。
(5)FD:满递减堆栈。 (6)ED:空递增堆栈。 (7)FA:满递增堆栈。 (8)EA:空递增堆栈
协处理器指令:
coproc:协处理器名,标准名为Pn,n为1~15
opcode1:协处理器的特定操作码
CRd:作为目标寄存的协处理器寄存器
CRn: 存放第1个操作数的协处理器寄存器
CRm: 存放第2个操作数的协处理器寄存器
opcode2:可选的协处理器的特定操作码
1、ARM寄存器到协处理器寄存器的数据传送(MCR)
格式:MCR{cond} coproc,opcode1,CRd,CRn,CRm(,opcode2)
举例:MCR P5,2,R7,C1,C2
2、协处理器寄存器到ARM寄存器的数据传送(MRC)
格式:MRC{cond} coproc,opcode1,CRd,CRn,CRm(,opcode2)
举例:MRC P5,2,R7,C1,C2
课本上ADC驱动程序的框架:
S3c2410_adc_open() {…..}
S3c2410_adc_read() {…..}
S3c2410_adc_write() {…..}
S3c2410_adc_release() {…..}
static struct file_operations s3c2410_fops = {
open: s3c2410_adc_open,
read: s3c2410_adc_read,
write: s3c2410_adc_write,
release: s3c2410_adc_release,
};
S3c2410_adc_init() {…..}
module_init(S3c2410_adc_init);
S3c2410_adc_exit() {…..}
module_exit(S3c2410_adc_exit);
解读:
安装驱动程序时,即执行insmod adc.o时,会运行该函数S3c2410_adc_init() {…..}
可以用lsmod来查看执行insmod adc.o后,生成一个名为adc的模块驱动程序
卸载驱动程序时,即执行rmmod adc时,会运行该函数S3c2410_adc_exit() {…..}
LINUX设备驱动模型之基于设备数的platform(平台)总线:
Linux设备驱动模型为了保持设备驱动的统一性而虚拟出来的总线
platform总线管理:
两个结构体platform_device和platform_driver
1、把硬件设备添加到设备数
xxx_platfrom@xxxxx{
compatible = "company_name, device_name";
reg = <0x11000c40 1>,<0x11000c20 1>;
};
2、更改驱动程序中的加载入口
不需要再使用字符设备的3大模块, 改为:
module_platform_driver(xxx_platform_driver); //入口声明
MODULE_LICENSE("Dual BSD/GPL"); //模块许可声明
从入口声明的xxx_platform_driver结构体找到驱动的加载/卸载
struct platform_driver xxx_platform_driver{
.driver = {
.name = "driver_name",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(xxx_dt_of_matches),
},
.probe = xxx_init,
.remove = xxx_exit,
};
说明:
(1)结构体提供的.probe, .remove对应的函数,就是模块加载/卸载的入口;
(2).of_match_table就是内核在设备驱动加载时查找的设备驱动表(这里表名字xxx_dt_of_matches)
static const struct of_device_id xxx_dt_of_matches[] = {
{ .compatible = "company_name,device_name"},
};
3、驱动程序的修改