开发环境
开发板:内核是Cortex-A9的ARM处理器
交叉开发环境搭建:先在ubuntu下安装交叉编译工具链(主要进行代码的编写),然后在windows系统安装SecureCRT终端软件(主要用于链接开发板,并且写入程序进行测试)
硬件控制原理:CPU本身是不能直接控制硬件的,硬件一般是由其对应的控制器来控制, SOC中将各个硬件控制器的寄存器映射到了CPU地址空间中的一段范围(地址映射表),这样CPU就可以通过读写寄存器来间接控制硬件
注:这里的寄存器在SOC内但在CPU之外,有地址,访问方式与内存一样,常用于控制硬件
地址映射表:在一个处理器中,一般会将Flash、RAM、寄存器等存储设备分别映射到寻址空间中的不同地址段,我们将这个映射关系成为这个处理器的地址映射表
实践问题
1.简述交叉编译器和普通编译器的本质区别是什么
交叉编译器和普通编译器的本质区别在于它们针对的目标平台不同。
普通编译器是针对当前运行编译器的平台进行编译,生成针对同一平台的可执行文件。
而交叉编译器则是针对不同的目标平台进行编译,生成针对目标平台的可执行文件。
2.简述ARM处理器中的寄存器(如PC、LR、SP)与硬件控制器中的寄存器的本质区别是什么
ARM处理器中的寄存器是用于存储和处理数据的组件,它们位于处理器内部。这些寄存器用于存储临时数据、地址、指令等,用于执行程序的运算和控制流程。
而硬件控制器中的寄存器是用于配置和控制硬件设备。它们位于硬件设备内部。这些寄存器用于设置设备的工作模式、传输数据、接收命令等,用于控制硬件设备的行为。
GPIO实验
GPIO介绍:GPIO(General-purpose input/output)即通用型输入输出,GPIO可以控制连接在其之上的引脚实现信号的输入和输出,芯片的引脚与外部设备相连,从而实现与外部硬件设备的通讯、控制及信号采集等功能
LED灯点亮实验:
1.通过电路原理图分析LED的控制逻辑:分析得知输入高电平则是点亮信号反之则是关闭
2.通过电路原理图查找LED与Exynos4412(主板型号)的连接关系:
如上图所示,可以发现LED2与CHG_COK信号相连,那么查找该信号连接的CPU引脚如下图
可以看见,CPU控制LED2的引脚为GPX2_7,找到了对应的引脚那么就需要查看该引脚的控制方式
3.通过数据手册分析GPIO中哪些寄存器可以控制LED :查看手册中的GPIO章节,并找到相应的寄存器控制介绍,
如上图可以看见需要将GPX2寄存器的CON的28到31位控制的是GPX2CON[7],也就是GPX2_7,并且需要将其设置成0x1,表示输出信号模式,设置完成输出信号模式后,就需要向此寄存器发送高电平信号点亮LED2
如上图,展示了GPX2寄存器的DAT的基地址以及如何赋值完成对应的控制,而GPX2_7对应的是GPX2CON7和GPX2DAT7,那么也就是要将GPX2DAT的第7位至1才能由GPX2_7引脚向开发板发送高电平信号,分析完成,开始代码实现(使用汇编语言)
MAIN:
BL LED_CONFIG
LOOP:
BL LED_ON
BL DELAY
BL LED_OFF
BL DELAY
B LOOP
LED_CONFIG: @LED2的配置
LDR R2, =0x11000c40 @将GPX2-7引脚的地址存入R2寄存器
LDR R1, =0x10000000 @将0x10000000这个数存入R1寄存器
STR R1, [R2]@将R1寄存器内的数值写入R2寄存器内的地址,也就是将GPX2-7引脚设置为输出信号模式
MOV PC, LR
LED_ON: @在配置完成LED2的基本配置后,进行输入高低电平控制,也就是向GPX2DAT寄存器存值
LDR R2, =0x11000c44 @找到GPX2DAT的地址
LDR R1, =0x00000080 @将GPX2DAT的第7位至1,也就是高电平信号,灯亮
STR R1, [R2]
MOV PC, LR
LED_OFF:
LDR R2, =0x11000c44
LDR R1, =0x00000000 @将GPX2DAT的第7位至0,也就是低电平信号,灯灭
STR R1, [R2]
MOV PC, LR
DELAY:
LDR R1, =100000000 @设置延迟时间,汇编语言大约是1秒11行代码的执行速度
L:
SUB R1, R1, #1 @由100000000开始减,每次减1,直到为0跳出循环
CMP R1, #0
BNE L
MOV PC, LR
STOP:
B STOP
.end
C工程分析和代码实现
在上面,使用了汇编语言对LED2灯亮进行了实验,但是对于我们开发过程中,还是会以C语言为主,因此,需要编写一个C的工程项目,来满足使用C语言开发ARM项目的需求。最主要的就是为了能够让C语言能够在开发板是运行,那么就需要编写一个启动项。
启动项的作用:启动代码主要功能用于开发板通电之后运行C语言代码之前需要做的一些准备工作,首先,对异常向量表的位置进行配置,并使用于异常处理的协处理器能够准确跳转至配置好的异常向量表。然后,对ARM处理器的工作模式进行修改,暂时关闭FIQ和IRQ的异常接收。最后,对处理器内的不同工作模式的栈空间进行初始化,为了符合ARM处理器的满减栈的特征,将不同工作模式的栈指针初始化至栈空间的末尾,然后打开FIQ和IRQ的异常接收,跳转至C语言main主函数,开始执行
寄存器代码封装:此外,通过GPIO的实验我们可以发现,一个寄存器,好比GPX2-7,这个寄存器内,还细分了很多其他的寄存器,比如GPX2CON,GPX2DAT等等,分别控制了GPX2-7这个引脚的不同功能,我们是否可以将其想象成一个结构体,名称为GPX2-7,而这个结构体内包含了GPX2CON、GPX2DAT等数据项,因为我们也可以发现,其内部寄存器的基地址是连续的,因此我们可以对其进行封装,简化后续的开发下面对GPX2-7引脚进行封装代码示例:
/* GPX2 */
typedef struct {
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (* (volatile gpx2 *)0x11000C40 )
分析上面代码:将GPX2-7的基地址强转成一个gpx2结构体的指针,表示指向该地址的gpx2结构体空间,然后将其定义为GPX2,也就是说以上代码定义了一个名为gpx2
的结构体,并使用typedef
关键字将gpx2
重命名为GPX2
。该结构体包含了4个无符号整型成员变量:CON
、DAT
、PUD
、DRV
。 接下来,使用#define
预处理指令将地址为0x11000C40
的内存映射到名为GPX2
的结构体变量上,从而可以通过GPX2
来访问该地址对应的寄存器。因为使用了volatile
关键字,表示该结构体变量是易变的,编译器不会对其进行优化,保证每次访问都是实时的。 通过使用GPX2.CON
、GPX2.DAT
、GPX2.PUD
、GPX2.DRV
可以分别访问对应寄存器的值。
C语言实现LED2灯的亮灭
首先,代码不进行任何的封装,这个时候可以发现代码写的非常混乱
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
/*通过设置GPX2CON寄存器来将GPX2_7引脚设置成输出功能*/
*(unsigned int *)0x11000c40 = 0x10000000;
while(1)
{
/*点亮LED2*/
*(unsigned int *)0x11000c44 = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
*(unsigned int *)0x11000c44 = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
其次,对代码进行封装后的处理
#if 0
#define GPX2CON (*(unsigned int *)0x11000c40)
#define GPX2DAT (*(unsigned int *)0x11000c44)
int main()
{
GPX2CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
#if 0
typedef struct
{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(gpx2 *)0x11000c40)
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
#if 0
#include "exynos_4412.h"
int main()
{
GPX2.CON = 0x10000000;
while(1)
{
/*点亮LED2*/
GPX2.DAT = 0x00000080;
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = 0x00000000;
/*延时*/
Delay(1000000);
}
return 0;
}
#endif
但是赋值的时候还是十分的混乱,对赋值的规则进行深度研究
#include "exynos_4412.h"
int main()
{
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
while(1)
{
/*点亮LED2*/
GPX2.DAT = GPX2.DAT | (1 << 7);
/*延时*/
Delay(1000000);
/*熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
/*延时*/
Delay(1000000);
}
return 0;
}
/*
* 1.unsigned int a; 将a的第3位置1,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****1***
* 00000000 00000000 00000000 00001000
*
* a = a | (1 << 3);
*
* 2.unsigned int a; 将a的第3位置0,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** ****0***
* 11111111 11111111 11111111 11110111
*
* a = a & (~(1 << 3));
*
* 3.unsigned int a; 将a的第[7:4]位置为0101,其他位保持不变
* ******** ******** ******** ********
* ******** ******** ******** 0101****
*
* 1).先清零
* 11111111 11111111 11111111 00001111
* 00000000 00000000 00000000 11110000
* 00000000 00000000 00000000 00001111
*
* a = a & (~(0xF << 4));
*
* 2).再置位
* 00000000 00000000 00000000 01010000
* 00000000 00000000 00000000 00000101
*
* a = a | (0x5 << 4);
*
* => a = a & (~(0xF << 4)) | (0x5 << 4);
*/
实践问题
1.使用C语言实现流水灯(LED2、LED3、LED4、LED5依次闪烁)
#include "exynos_4412.h"
void Delay(unsigned int Time){
while(Time--);
}
int main()
{
GPX1.CON = (GPX1.CON | 1);
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (1 << 28);
//GPF3.CON = GPF3.CON | (1 << 16) | (1 << 20);
GPF3.CON = GPF3.CON | (0x11 << 16);
while(1){
Delay(1000000);
GPX2.DAT = GPX2.DAT & (~(1 << 7));
Delay(1000000);
GPX1.DAT = GPX1.DAT | 1;
Delay(1000000);
GPX1.DAT = GPX1.DAT & (~1);
Delay(1000000);
GPF3.DAT = GPF3.DAT | (1 << 4);
Delay(1000000);
GPF3.DAT = GPF3.DAT & (~(1 << 4));
Delay(1000000);
GPF3.DAT = GPF3.DAT | (1 << 5);
Delay(1000000);
GPF3.DAT = GPF3.DAT & (~(1 << 5));
Delay(1000000);
GPX2.DAT = GPX2.DAT | (1 << 7);
}
return 0;
}
UART实验
UART介绍:Universal Asynchronous Receiver Transmitter 即通用异步收发器,是一种通用的串行、异步通信总线该总线有两条数据线,可以实现全双工的发送和接收在嵌入式系统中常用于主机与辅助设备之间的通信
波特率介绍:波特率用于描述UART通信时的通信速度,其单位为bps(bit per second)即每秒钟传送的bit的数量
UART串口发送数据分析:1BYTE(字节) = 8BIT(位)
虽然说并行通信是速度较快的通信方式,但是相对于串行通信来说,并行的总线与总线之间可能会有干扰,因此嵌入式方面的开发大多采用的还是串行总线,串口通信不允许连续发送,其目的在于防止累计误差的产生
单工通信:数据传输方向是单向的
双工通信:数据传输方向是双向的,半双工通信不能同时进行发送和接收
波特率:每秒钟传送的2进制位的数量
校验位:发送的数据内,1的个数为偶数时,校验位为1,反之为0
UART内部硬件连接示例图:
UART串口发送数据实验
和之前点亮LED2的实验一样,需要分析UART串口连接的引脚以及控制该引脚的寄存器,
观察上图可以发现,UART2的输入和输出引脚分别由GPA1-0和GPA1-1进行控制
分析可以得出,要想对UART2串口进行数据的传输,那么就需要对GAP1CON寄存器的0到3位和4到7位都设置为0x2才可以
除此之外,因为UART和GPIO的差异,UART的引脚的寄存器需要设置的步骤还需要以下几步,才能进行数据传输
/*1.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
/*2.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
/*3.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2*/
实现代码如下:
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将要发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat = 0;
/*判断接收寄存器是否接收到了数据 UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1)
{
/*从接收寄存器中读取接收到的数据 URXH2*/
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
void UART_Send_Str(char * pstr)
{
while(*pstr != '\0')
UART_Send_Byte(*pstr++);
}
int main()
{
char RecDat = 0;
UART_Init();
while(1)
{
RecDat = UART_Rec_Byte();
if(RecDat == 0)
{
}
else
{
RecDat = RecDat + 1;
UART_Send_Byte(RecDat);
}
/*
UART_Send_Str("Hello World\n");
*/
//printf("Hello World\n");
}
return 0;
}
实践问题
1.编程实现电脑远程控制LED状态
注:在终端上输入‘2’,LED2点亮,再次输入‘2’,LED2熄灭...
#include "exynos_4412.h"
void UART_Init(void)
{
/*1.将GPA1_0和GPA1_1设置成UART2的接收和发送引脚 GPA1CON[7:0]*/
GPA1.CON = GPA1.CON & (~(0xFF << 0)) | (0x22 << 0);
/*2.设置UART2的帧格式 8位数据位 1位停止位 无校验 正常模式 ULCON2[6:0]*/
UART2.ULCON2 = UART2.ULCON2 & (~(0x7F << 0)) | (0x3 << 0);
/*3.设置UART2的接收和发送模式为轮询模式 UCON2[3:0]*/
UART2.UCON2 = UART2.UCON2 & (~(0xF << 0)) | (0x5 << 0);
/*4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2*/
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4;
/*5.将GPX2_7设置成LED的接收引脚*/
GPX2.CON = GPX2.CON & (~(0xF << 28)) | (0x1 << 28);
/*6.熄灭LED2*/
GPX2.DAT = GPX2.DAT & (~(1 << 7));
}
void UART_Send_Byte(char Dat)
{
/*等待发送寄存器为空,即上一个数据已经发送完成 UTRSTAT2[1]*/
while(!(UART2.UTRSTAT2 & (1 << 1)));
/*将要发送的数据写入发送寄存器 UTXH2*/
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void)
{
char Dat = 0;
/*判断接收寄存器是否接收到了数据 UTRSTAT2[0]*/
if(UART2.UTRSTAT2 & 1)
{
/*从接收寄存器中读取接收到的数据 URXH2*/
Dat = UART2.URXH2;
return Dat;
}
else
{
return 0;
}
}
int main()
{
char RecDat = 0;
int count = 0;
UART_Init();
while(1)
{
RecDat = UART_Rec_Byte();
if(RecDat == 0)
{
}
else if(RecDat == '2')
{
count++;
if(count % 2){
GPX2.DAT = GPX2.DAT | (1 << 7);
UART_Send_Byte(RecDat);
printf(" input>>2,LED2 ON\n");
}
else{
GPX2.DAT = GPX2.DAT & (~(1 << 7));
UART_Send_Byte(RecDat);
printf(" input>>2 again,LED2 OFF\n");
}
}
}
return 0;
}