目录
目录
原理
交叉开发环境搭建
- 在ubuntu下安装交叉编译工具链
- 在windows下安装SecureCRT
- 在windows下安装USB转串口驱动
- 下载测试程序
地址映射
在一个处理器中,一般会将Flash、RAM、寄存器等存储设备分别映射到寻址空间中的不同地址段,我们将这个映射关系成为这个处理器的地址映射表
硬件控制原理
CPU本身是不能直接控制硬件的,硬件一般是由其对应的控制器来控制, SOC中将各个硬件控制器的寄存器映射到了CPU地址空间中的一段范围,这样CPU就可以通过读写寄存器来间接控制硬件
注:这里的寄存器在SOC中但在CPU之外,有地址,访问方式与内存一样,常用于控制硬件
GPIO
GPIO(General-purpose input/output)即通用型输入输出,GPIO可以控制连接在其之上的引脚实现信号的输入和输出。
芯片的引脚与外部设备相连,从而实现与外部硬件设备的通讯、控制及信号采集等功能。
GPIO的功能包括
- 控制172外部中断
- 控制32外部唤醒中断
- 252多功能输入/输出端口
- 在睡眠模式下控制除GPX0、GPX1、GPX2和GPX3以外的引脚状态(GPX*引脚为活动焊盘)
LED实验
实验步骤:
- 通过电路原理图分析LED的控制逻辑- 高电平点亮、低电平熄灭
- 通过电路原理图查找LED与Exynos4412的连接关系 - GPX2_7
- 通过数据手册分析GPIO中哪些寄存器可以控制LED - GPX2CON、GPX2DAT
- 通过程序去操控对应的寄存器完成对LED的控制
Register
|
Offset
|
Description
| Reset Value |
GPX2CON
| 0x0C40 | Port group GPX2 configuration register | 0x0000_0000 |
GPX2DAT | 0x0C44 | Port group GPX2 data register | 0x00 |
GPX2PUD
|
0x0C48
|
Port group GPX2 pull-up/ pull-down register
|
0x5555
|
GPX2DRV
|
0x0C4C
|
Port group GPX2 drive strength control register
|
0x00_0000
|
描述:当您将端口配置为输入端口时,相应的位是引脚状态。当配置为输出端口,然后引脚状态应与相应的位相同。当端口配置为功能引脚时,将读取未定义的值。
解释:GPX2DAT是32位 但是高24位没用 只用了低8位 如果在0位写0 那么GPX2_0就是0 即低电平 若写1 则GPX2_0是 1 是高电平。
故要实现LED亮灭 则将GPX2_7改为输出模式(0x1) GPX2DAT 的低第七位改为1/高电平/亮,或0/低电平/灭。
代码(熄灭LED2)
.text
_start:
LED_CONFIG:
LDR R2,=0x11000C40 @寄存器GPX2CON的地址
LDR R1,=0x10000000 @寄存器GPX2CON的[31:28]写为0x1 = Output
STR R1,[R2] @把R1的数放进R2的地址 把GPX2_7(LED)设为输出功能
LED_OFF:
LDR R2,=0x11000C44 @寄存器GPX2DAT的地址
LDR R1,=0x00000000 @寄存器GPX2DAT的第7位写为0
STR R1,[R2] @综合上面意为输出低电平(灯灭)
STOP:
B STOP
.end
Makefile
TARGET = led
CROSS_COMPILE = arm-none-linux-gnueabi-
CC = $(CROSS_COMPILE)gcc
LD = $(CROSS_COMPILE)ld
OBJCOPY = $(CROSS_COMPILE)objcopy
all:
$(CC) -c $(TARGET).s -o $(TARGET).o
$(LD) $(TARGET).o -Ttext 0x40008000 -o $(TARGET).elf
$(OBJCOPY) -O binary -S $(TARGET).elf $(TARGET).bin
clean:
rm $(TARGET).o $(TARGET).elf $(TARGET).bin
~
LED2闪烁代码
.text
_start:
MAIN:
BL LED_CONFIG
LOOP:
BL LED_ON
BL DELAY
BL LED_OFF
BL DELAY
B LOOP
LED_CONFIG:
LDR R2,=0x11000C40 @寄存器GPX2CON的地址
LDR R1,=0x10000000 @寄存器GPX2CON的[31:28]写为0x1 = Output
STR R1,[R2] @把R1的数放进R2的地址 把GPX2_7(LED)设为输出功能
MOV PC,LR
LED_ON:
LDR R2,=0x11000C44 @寄存器GPX2DAT的地址
LDR R1,=0x00000080 @寄存器GPX2DAT的第7位写为0
STR R1,[R2] @综合上面意为输出高电平(灯亮)
MOV PC,LR
LED_OFF:
LDR R2,=0x11000C44 @寄存器GPX2DAT的地址
LDR R1,=0x00000000 @寄存器GPX2DAT的第7位写为0
STR R1,[R2] @综合上面意为输出低电平(灯灭)
MOV PC,LR
DELAY:
LDR R1,=50000000
L:
SUB R1,R1,#1
CMP R1,#0
BNE L
MOV PC,LR
STOP:
B STOP
.end
C工程与寄存器封装
C语言工程模板启动代码:
这段 C 语言工程启动代码模板主要完成了几个重要的初始化任务,包括向量表的设置、CPU 模式的配置、缓存和 MMU 的管理、栈的初始化等,确保系统在启动后处于正确的状态。下面是代码的逐步解析:
-
向量表 (Vector Table):
.text .global _start _start: b reset b . b . b . b . b . b . b .
向量表定义了不同异常类型的入口地址。在这个模板中,所有的异常向量都跳转到
reset
标签。 -
复位处理 (Reset Handler):
reset: ldr r0, =_start mcr p15, 0, r0, c12, c0, 0 @ Set VBAR
设置 CP15 VBAR 寄存器为向量表的起始地址。
-
设置 CPU 为 SVC32 模式并禁用 FIQ/IRQ:
mrs r0, cpsr bic r0, r0, #0x1f orr r0, r0, #0xd3 msr cpsr, r0
修改 CPSR 寄存器,使 CPU 进入 SVC32 模式并禁用 FIQ 和 IRQ 中断。
-
定义协处理器的访问权限:
mov r0, #0xfffffff mcr p15, 0, r0, c1, c0, 2
设置协处理器的访问权限。
-
无效化一级缓存和 TLB:
mov r0, #0 mcr p15, 0, r0, c8, c7, 0 @ Invalidate TLBs mcr p15, 0, r0, c7, c5, 0 @ Invalidate icache
无效化指令缓存和 TLB(页表)。
-
启用浮点单元 (FPU):
mov r3, #0x40000000 fmxr FPEXC, r3
设置 FPEXC 寄存器以启用 FPU。
-
禁用 MMU 和缓存:
mrc p15, 0, r0, c1, c0, 0 bic r0, r0, #0x00002000 @ Clear bits 13 (--V-) bic r0, r0, #0x00000007 @ Clear bits 2:0 (-CAM) orr r0, r0, #0x00001000 @ Set bit 12 (---I) Icache orr r0, r0, #0x00000002 @ Set bit 1 (--A-) Align orr r0, r0, #0x00000800 @ Set bit 11 (Z---) BTB mcr p15, 0, r0, c1, c0, 0
设置控制寄存器,禁用 MMU(物理地址<--转换-->虚拟地址) 和部分缓存。
-
初始化各个模式的栈:
init_stack: msr cpsr, #0xd3 ldr sp, _stack_svc_end msr cpsr, #0xdb ldr sp, _stack_und_end msr cpsr, #0xd7 ldr sp, _stack_abt_end msr cpsr, #0xd2 ldr sp, _stack_irq_end msr cpsr, #0xd1 ldr sp, _stack_fiq_end msr cpsr, #0x10 ldr sp, _stack_usr_end
为每种 CPU 模式(SVC、未定义、异常、IRQ、FIQ、用户模式)设置栈指针。
-
跳转到主程序 (main):
b main
-
定义栈的结束地址和栈空间:
_stack_svc_end: .word stack_svc + 512 _stack_und_end: .word stack_und + 512 _stack_abt_end: .word stack_abt + 512 _stack_irq_end: .word stack_irq + 512 _stack_fiq_end: .word stack_fiq + 512 _stack_usr_end: .word stack_usr + 512 .data stack_svc: .space 512 stack_und: .space 512 stack_abt: .space 512 stack_irq: .space 512 stack_fiq: .space 512 stack_usr: .space 512
为每个模式分配 512 字节的栈空间。
总结来说,这段启动代码完成了系统复位后的基本设置,包括向量表的初始化、CPU 模式的配置、缓存和 TLB 的管理、FPU 的启用、各模式栈的初始化,最后跳转到主程序 main
。
C语言实现LED闪烁:
//C语言实现LED闪烁
/*
一.汇编语言访问存储器
1.读存储器
LDR R1,[R2]
2.写存储器
STR R1,[R2]
二.C语言访问存储器
1.读存储器
data = *ADDR
2.写存储器
*ADDR = data
*/
void Delay(unsigned int Time){
while(Time--);
}
#if 0
int main(){
//通过设置GPX2CON寄存器来讲GPX2_7引脚设置为输出功能
*(unsigned int *)0x11000c40 = 0x10000000;
while(1){
//点亮LED2
*(unsigned int *)0x11000c44 = 0x00000080;
//熄灭LED2
*(unsigned int *)0x11000c44 = 0x00000000;
}
Delay(1000000);
return 0;
}
#endif
//优化:寄存器封装
//方式 1 (宏定义)
#if 0
#define GPX2CON (*(unsigned int *)0x11000c40)
#define GPX2DAT (*(unsigned int *)0x11000c44)
int main(){
GPX2CON = 0x10000000;
while(1){
//点亮LED2
GPX2DAT = 0x00000080;
//熄灭LED2
GPX2DAT = 0x00000000;
}
Delay(1000000);
return 0;
}
#endif
//方式 2 (结构体)
//方式 3 整个存储地址都在头文件 " exynos_4412.h"中 用时候直接include
#if 0
typedef struct{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(volatile gpx2 *)0X11000C40)
int main(){
GPX2.CON = 0x10000000;
while(1){
//点亮LED2
GPX2.DAT = 0x00000080;
//熄灭LED2
GPX2.DAT = 0x00000000;
}
Delay(1000000);
return 0;
}
#endif
逻辑运算
要通过与、或、非运算将一个任意四位二进制数变成全0或者全1,我们可以使用逻辑运算来实现。
- 任何数与 0 进行 与 运算结果都是 0
- 任何数与 1 进行 或 运算结果都是 1
def to_all_zeros(binary_number):
return binary_number & 0
def to_all_ones(binary_number):
return binary_number | 0b1111
//输入任意四位二进制数
input_number = 0b1010
//转换为全0
all_zeros = to_all_zeros(input_number)
print(f'全0结果: {bin(all_zeros)}')
//转换为全1
all_ones = to_all_ones(input_number)
print(f'全1结果: {bin(all_ones)}')
解决问题
当设置一个引脚功能时 可能会影响其他引脚 造成其他引脚误操作(改变其他引脚的功能)
1.unsigned int a; 将a的第三位置为1 其他位置保持不变
******** -> ****1***
让 a 去 与 00001000(08) 做或运算 即
a = a | 0x00000008;
或者
a = a | ( 1 << 3 ); 1左移三位 做或运算
2. unsigned int a; 将a的第三位置为0 其他位置保持不变
******** -> ****0***
让 a 去 与 11110111(F7) 做与运算 即
a = a & 0xFFFFFFF7;
或者
a = a & (~( 1 << 3 )); 1左移三位 再按位取反 做与运算
3.unsinged int a; 将a的第[7:4]位置为0101 其他位置保持不变
********* -> 0101****
1).先清零
11111111 11111111 11111111 00001111
00000000 00000000 00000000 11110000
00000000 00000000 00000000 00001111 ( 0xF )
a = a & (~( 0xF << 4 ));
2).再置位
00000000 00000000 00000000 01010000
00000000 00000000 00000000 00000101 ( 0x5 )
a = a | ( 0x5 <<4 );
合并: a = a & (~( 0xF << 4 )) | ( 0x5 <<4 );
/*
1.unsigned int a; 将a的第三位置为1 其他位置保持不变
******** -> ****1***
让 a 去 与 00001000(08) 做或运算 即
a = a | 0x00000008;
或者
a = a | ( 1 << 3 ); 1左移三位 做或运算
2. unsigned int a; 将a的第三位置为0 其他位置保持不变
******** -> ****0***
让 a 去 与 11110111(F7) 做与运算 即
a = a & 0xFFFFFFF7;
或者
a = a & (~( 1 << 3 )); 1左移三位 再按位取反 做与运算
3.unsinged int a; 将a的第[7:4]位置为0101 其他位置保持不变
********* -> 0101****
1).先清零
11111111 11111111 11111111 00001111
00000000 00000000 00000000 11110000
00000000 00000000 00000000 00001111 ( 0xF )
a = a & (~( 0xF << 4 ));
2).再置位
00000000 00000000 00000000 01010000
00000000 00000000 00000000 00000101 ( 0x5 )
a = a | ( 0x5 <<4 );
合并: a = a & (~( 0xF << 4 )) | ( 0x5 <<4 );
*/
typedef struct{
unsigned int CON;
unsigned int DAT;
unsigned int PUD;
unsigned int DRV;
}gpx2;
#define GPX2 (*(volatile gpx2 *)0X11000C40)
int main(){
GPX2.CON = GPX2.CON & (~( 0xF << 28 )) | ( 0x1 << 28 );
while(1){
//点亮LED2
GPX2.DAT =GPX2.DAT | (1<<7);
//熄灭LED2
GPX2.DAT =GPX2.DAT | (~(1<<7));
}
Delay(1000000);
return 0;
}
实践:流水灯
控制LED2 LED3 LED4 LED5的引脚分别为:GPX2_7 GPX1_0 GPF3_4 GPF3_5
UART
UART(Universal Asynchronous Receiver Transmitter )即通用异步收发器,是一种通用的串行、异步通信总线。该总线有两条数据线,可以实现全双工的发送和接收,在嵌入式系统中常用于主机与辅助设备之间的通信
通信基础
并行通信
串行通信
单工通信
双工通信
波特率
波特率(比特率)用于描述UART通信时的通信速度,其单位为bps(bit per second)即每秒钟传送的bit的数量
UART帧格式
UART硬件连接
UART控制器
一般情况下处理器中都会集成UART控制器。我们使用UART进行通信时候只需对其内部的相关寄存器进行设置即可。
引脚功能设置
RXD2 和TXD2分别连接的GPA1_0 与 GPA1_1
设置引脚功能的实质是让引脚在芯片内部连接到某一个对应的控制器上
Exynos4412下的UART控制器
Exynos 4412 SCP中的通用异步接收器和发射器(UART)提供四个独立的通道,它们具有通用的异步和串行输入/输出(I/O)端口(Ch0至3)。它还提供了一个与全球定位系统(GPS)(Ch4)通信的专用通道。所有端口都以基于中断或基于dma的模式运行。UART生成一个中断或一个DMA请求,以向CPU和UART传输数据。UART支持高达4 Mbps的比特率。每个UART信道包含两个先出(FIFO),用于接收和传输数据,如下所示:
- 256 bytes in Ch0
- 64 bytes in Ch1 and Ch4
- 16 bytes in Ch2 and Ch3
UART 包含:
- 可编程波特率
- 红外线(IR)发射机/接收机
- 一个或两个停止位插入
- 5位、6位、7位或8位数据宽度和奇偶校验
如图所示,每个UART都包含:波特率发生器、发送器、接收器、控制单元
控制逻辑:波特率发生器使用SCLK_UART(时钟,频率 100M)。发射机和接收机包含fifo和数据转换器。要传输的数据被写入Tx FIFO(发送队列),并复制到发送移位器。然后通过发送数据引脚(TxDn)移动数据。接收到的数据从接收数据引脚(RxDn)移位,并从移位器复制到Rx FIFO。
UART 寄存器详解
RXD2 和TXD2分别连接的GPA1_0 与 GPA1_1
Loop-Back Mode (回环模式) 自己发自己收
CPU和外围硬件包括串口进行数据交互有三种工作模式:
1.轮寻(polling)模式:
接收器接收数据。CPU工作,工作一段时间后查看接收器中是否有数据,若没有数据,就回来继续工作,直到接收器中有数据,把数据读走。
形象理解可以认为点外卖,点完外卖就工作,工作一段时间后,就去拿起手机看看外卖到了没,如果没有到就继续工作,如果到了就取外卖。
2.中断(interrupt)模式:
CPU和串口控制器约定,如果串口接收器接收到了信号就给CPU发送一个中断信号。
形象理解可以认为点外卖,点完外卖给外卖小哥说,等到了打电话说一声,去取外卖。
3.DMA模式:
DMA(Direct Memory Access)直接存储器访问。将串口接收器中接收到的数据直接放到内存中。
形象理解可以认为去饭店点餐,点完餐以后什么都不用管,自然有人给你端到桌子上,等着吃就行了。
UART编程
#include "exynos_4412.h"
void UART_Iint(void){
//1.GPA1_0 和GPA1_1 分别设置为UART2的接收引脚和发送引脚 GPA1CON[7:0]
GPA1.CON = GPA1.CON & ( ~(0xFF<<0)) | (0x22 << 0 );
//2.设置UART2的帧格式 ULCON2 8位数据位 1位停止位 无校验 正常模式
UART2.ULCON2 = UART2.ULCON2 &( ~(0x7F<<0)) | (0x3 << 0);
//3.设置UART2的接收和发送模式为轮询模式(polling) UCON2[3:0] (0101)
UART2.UCON2 = UART2.UCON2 &( ~(0xF<<0)) | (0x5 << 0);
//4.设置UART2的波特率为115200 UBRDIV2/UFRACVAL2 (53.25)
UART2.UBRDIV2 = 53;
UART2.UFRACVAL2 = 4; //0.25*16 = 4
}
void UART_Send_Byte(char Dat){
//等待发送缓冲区空闲
while(!(UART2.UTRSTAT2 & (1<<1)));
//将要发送的数据写入发送寄存器 UTXH2
UART2.UTXH2 = Dat;
}
char UART_Rec_Byte(void){
char Dat = 0;
//判断接收寄存器是否已经接收到数据
if(UART2.UTRSTAT2 & 1){
Dat = UART2.URXH2;
return Dat;
} else {
return 0;
}
}
int main(){
char RecDat = 0;
UART_Iint();
while(1){
RecDat = UART_Rec_Byte();
if(RecDat = 0) {
}
else{
RecDat = RecDat + 1;
UART_Send_Byte(RecDat);
}
}
}
WDT
WDT(Watch Dog Timer)即看门狗定时器,其主要作用是当发生软件故障时可产生复位信号使SOC复位,其本质是一个计数器。
工作原理
看门狗定时器一直递减 cpu在一直喂 但当CPU出现异常时不会再喂狗了 看门狗就会减到0就会给CPU发送复位信号。
Exynos4412下的WDT控制器
概述:
Exynos 4412 SCP中的监视器计时器(WDT)是一种计时设备。由于噪声和系统错误导致故障后,可以使用此设备恢复控制器的操作。您可以使用WDT作为一个普通的16位间隔计时器来请求中断服务。WDT产生复位信号。
WDT的特点:
- 支持具有中断请求的正常时间间隔计时器模式。
- 如果计时器计数值达到0(超时),激活内部重位信号。
- 支持电平触发的中断机制。
工作原理:
WDT使用PCLK作为其源时钟(100M)。8位(1-256倍)预计算器按PCLK频率生成相应的WDT,并再次划分得到的频率(分频、降频)。
图26-1显示了WDT的功能方框图。
监视机构计时器控制(WTCON)指定预调节器值和频率划分因子。有效的预计算值范围从0到(2 8 -1)。可以选择频率划分因子为: 16、32、64或128。
使用此公式来计算WDT时钟频率和每个计时器时钟周期的持续时间:
WDT寄存器详解
WTCON :看门狗控制寄存器。如一级分频[15:8] 二级分频[4:3] 、产生中断信号是WTCON[2] 、复位信号是WTCON[0]
WTDAT:中断相关数据寄存器。
WTCNT:存储递减的数据(不要超过65535)。
WTCLRINT :中断相关。
WDT编程
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
int main()
{
/*设置一级分频 [15:8]*/
WDT.WTCON = WDT.WTCON | (0xFF << 8);
/*设置二级分频*/
/*WTCNT递减频率 = PLCK(100000000)/(0xFF + 1)/128 = 3052*/
WDT.WTCON = WDT.WTCON | (0x3 << 3);
/*禁止WDT产生中断信号*/
WDT.WTCON = WDT.WTCON & (~(1 << 2));
/*使能WDT产生复位信号*/
WDT.WTCON = WDT.WTCON | 1;
/*设置计数器的初始值*/
WDT.WTCNT = (3052 * 5);
/*使能WDT,计数器开始递减*/
WDT.WTCON = WDT.WTCON | (1 << 5);
while(1)
{
printf("WDT.WTCNT = %d\n",WDT.WTCNT);
/*喂狗*/
WDT.WTCNT = 3052;
Delay(100000);
}
return 0;
}
中断
CPU与硬件的交互方式
1.轮询(polling):CPU执行程序时不断地询问硬件是否需要其服务,若需要则给予其服务,若不需要一段时间后再次询问,周而复始
2.中断(interrupt):CPU执行程序时若硬件需要其服务,对应的硬件给CPU发送中断信号,CPU接收到中断信号后将当前的程序暂停下来,转而去执行中断服务程序,执行完成后再返回到被打断的点继续执行
3.DMA(Direct Memory Access)直接存储器访问:硬件产生数据后,硬件控制器可将产生的数据直接写入到存储器中,整个过程无需CPU的参与。
轮训方式实现按键实验
key电路图
注意:这个XEINT9 找到对应的中断id表终端EINT[9]的id编号:57
GPX1CON[0]是设置GPX1_0 。 故我们要设置 GPX1CON[1] 为0xF以设置GPX1_1
设置为全1时 就是触发中断的功能(INT41系列寄存器用来设置GPX1中断相关功能的)
GPX1.CON = GPX1.CON | (0xF << 4);
代码:
#include "exynos_4412.h"
int main()
{
//将GPX1_1设置成输入功能
GPX1.CON = GPX1.CON & (~(0xF << 4));
while(1)
{
//判断GPX1_1引脚的状态,即判断按键是否按下
if(!(GPX1.DAT & (1 << 1)))
{
printf("Key2 Pressed\n");
//等待松手
while(!(GPX1.DAT & (1 << 1)));
}
else
{
}
}
return 0;
}
GPIO中断
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
MASK设置中断开关(0开/1关)
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
PEND负责挂起(当CPU无法立即响应中断时 会将现在的异常源挂起 待能够处理的时候执行)
1:产生中断时自动变为1 / 0:表示当时没有中断
处理结束之后 需要人为将寄存器清零*(写1清零)
//清除GPIO控制器的中断挂起位
EXT_INT41_PEND = (1<<1);
中断控制器
4412中有160个外部设备控制器可以为CPU发送中断信号
中断控制器作用
- 多个中断同时产生时可对这些中断挂起排队,然后按照优先级依次发送给CPU处理
- 可以为每一个中断分配一个优先级
- 一个中断正在处理时若又产生其它中断,可将新的中断挂起,待CPU空闲时再发送
- 可以为每一个中断选择一个CPU处理
- 可以为每一个中断选择一个中断类型(FIQ或IRQ)
- CPU接收到中断信号后并不能区分是哪个外设产生的,此时CPU可查询中断控制器来获取当前的中断信号是由哪个硬件产生的,然后再进行对应的处理
- 可以打开或禁止每一个中断
- ... ...
Exynos4412的中断控制器
概述:通用中断控制器(GIC)是一种集中式资源,它支持和管理系统中的中断。
GIC提供:寄存器管理中断源、中断行为和中断路由到一个或多个处理器
GIC支持:
- ARM架构安全扩展
- 启用、禁用和从硬件(外围)中断源生成处理器中断
- 生成软件中断
- 中断屏蔽和优先级
GIC接收在系统级别上断言的中断,并向每个连接的处理器发送适当的信号。当GIC实现安全扩展时,它可以向一个已连接的处理器实现两个中断请求。该体系结构将这些请求标识为IRQ和FIQ。
GIC的特点:
- 支持三种中断类型:
- 软件生成的中断(SGI)
- 私有外设中断(PPI)
- 共享外设中断(SPI)
- 可编程中断,使您能够设置:
- 安全状态的中断。
- 中断的优先级级别。
- 启用或禁用一个中断。
- 接收中断的处理器
中断控制器寄存器详解
ICDISER
ICDDCR用于设置 接收(1)/忽略(0)外部的中断信号。 可以理解为中断控制器GIC的总开关。
ICDISER_CPU用于设置这160个中断信号的打开与关闭的。一共有32位 则需要五个寄存器来控制160个不同中断的开关。
图9-7显示了ICDISERn地址映射。
如n=0时,第0个寄存器管理前31个中断的开关;
n=1时(即 ICDISER1_CPU)管理第32个到第63个之间的中断...
故 57 号中断 是由 ICDISER1_CPU 的 第25位 管理的。
ICDIPTR
ICDIPTR用于设置160个信号发送给哪一个CPU。
描述:系统编号中的处理器,CPU目标字段中的每个位表示对应的处理器。有关详细信息,请参阅表9-10。例如,值0x3意味着待定中断被发送到处理器0和1。对于ICDIPTR0到ICDIPTR7,任何CPU目标的读取字段都会返回执行该读取的处理器的数量。
一个8位的对应一个中断信号,共有160个中断信号,则需要160 x 8bit = 1280bit
而 一个寄存器有32位用于负责这项功能 。1280bit / 32bit = 40个寄存器 故需要40个ICDIPTR寄存器负责处理中断的CPU选择。
图9-14显示了ICDIPTRn地址映射。
如想要将ID为6的中断交给CPU2进行处理,则需将0x804的第[16:23]为设置为 0000 0100(第二位为1)。 则0x838地址负责第56-59的四个中断。
故 57 号中断 是由 偏移量为0x838 (ICDIPTR14) 的 第[8:15]位 管理的。
ICCICR
作用:用于中断信号能通过中断控制器的中断接口(设置闭合)到达处理器进行处理。(1能/0不能)
中断处理
异常
处理器在正常执行程序的过程中可能会遇到一些不正常的事件发生,这时处理器就要将当前的程序暂停下来转而去处理这个异常的事件,异常事件处理完成之后再返回到被异常打断的点继续执行程序。
异常处理机制
不同的处理器对异常的处理的流程大体相似,但是不同的处理器在具体实现的机制上有所不同;比如处理器遇到哪些事件认为是异常事件遇到异常事件之后处理器有哪些动作、处理器如何跳转到异常处理程序如何处理异常、处理完异常之后又如何返回到被打断的程序继续执行等我们将这些细节的实现称为处理器的异常处理机制。
ARM异常源
导致异常产生的事件称为异常源。包含:
- FIQ 快速中断请求引脚有效
- IRQ 外部中断请求引脚有效
- Reset 复位电平有效
- Software Interrupt 执行swi指令
- Data Abort 数据终止
- Prefetch Abort 指令预取终止
- Undefined Instruction 遇到不能处理的指令
ARM异常模式
在ARM的基本工作模式中有5个属于异常模式,即ARM遇到异常后会切换成对应的异常模式。
ARM异常响应
ARM产生异常后的动作(自动完成)
1.拷贝CPSR中的内容到对应异常模式下的SPSR_<mode>
2.修改CPSR的值
2.1.修改中断禁止位禁止相应的中断
2.2.修改模式位进入相应的异常模式
2.3.修改状态位进入ARM状态
3.保存返回地址到对应异常模式下的LR_<mode>
4.设置PC为相应的异常向量(异常向量表对应的地址)
异常向量表
> 异常向量表的本质是内存中的一段代码
> 表中为每个异常源分配了四个字节的存储空间
> 遇到异常后处理器自动将PC修改为对应的地址
> 因为异常向量表空间有限一般我们不会再这里写异常处理程序,而是在对应的位置写一条跳转指令使其跳转到指定的异常处理程序的入口
注:ARM的异常向量表的基地址默认在0x00地址 , 但可以通过配置协处理器来修改其地址
异常返回
ARM异常返回的动作(自己编写)
1.将 SPSR_<mode> 的值复制给CPSR,使处理器恢复之前的状态
2.将 LR_<mode> 的值复制给PC,使程序跳转回被打断的地址继续执行
异常优先级
多个异常同时产生时的服务顺序:(高优先级可以打断低优先级)
高:Reset > Data Abort > FIQ > IRQ > Prefetch Abort >Software Interrupt >Undefined instruction 低
LR寄存器
R14(LR,Link Register) 链接寄存器,一般有以下两种用途:
> 执行跳转指令(BL/BLX)时,LR会自动保存跳转指令下一条指令的地址 程序需要返回时将LR的值复制到PC即可实现
> 产生异常时,对应异常模式下的LR会自动保存被异常打断的指令的下 一条指令的地址,异常处理结束后将LR的值复制到PC可实现程序返回
原理
当执行跳转指令或产生异常时,LR寄存器中不会凭空产生一个返回地址 其原理是当执行跳转指令或产生异常时,处理器内部会将PC寄存器中的 值拷贝到LR寄存器中,然后再将LR寄存器中的值自减4,以获取返回地址
BL(执行跳转指令)
当执行BL指令时,指令执行过程中处理器内部就会将PC寄存器的值拷贝到LR寄存器,然后再将LR寄存器中的值自减4, 所以LR寄存器中保存的就是BL指令下一条指令的地址
该时刻PC=N+8 LR=N+4
IRQ中断
当执行一条指令时产生了一个IRQ中断,执行这条指令过程中处理器不会保存返回地址,而是执行完成后才会保存,但执行完成后PC的值又会自动增4,所以对于IRQ来说LR中保存的是被中断打断的指令的下下条指令的地址 该时刻PC=N+12 LR=N+8
故在处理IRQ异常之后要人为修正(自减4)
ICCIAR获取中断号
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
ICCEOIR
作用: 将某中断号写回中断控制器,以告知当前中断已经处理完成可以发送其他中断。
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time--);
}
//IRQ异常处理
void do_irq(void)
{
unsigned int IrqNum = 0;
/*从中断控制器中获取当前中断的中断号*/
IrqNum = CPU0.ICCIAR & 0x3FF;
/*根据中断号处理不同的中断*/
switch(IrqNum)
{
case 0:
//0号中断的处理程序
break;
case 1:
//1号中断的处理程序
break;
/*
* ... ...
*/
case 57:
printf("Key2 Pressed\n");
/*清除GPIO控制器中GPX1_1的中断挂起标志位*/
EXT_INT41_PEND = (1 << 1);
/*将当前中断的中断号写回到中断控制器中,以这种方式来告知中断控制器当前的中断已经处理完成,可以发送其它中断*/
CPU0.ICCEOIR = CPU0.ICCEOIR & (~(0x3FF)) | (57);
break;
/*
* ... ...
*/
case 159:
//159号中断的处理程序
break;
default:
break;
}
}
int main()
{
/*外设层次 - 让外部的硬件控制器产生一个中断信号发送给中断控制器*/
/*将GPX1_1设置成中断功能*/
GPX1.CON = GPX1.CON | (0xF << 4);
/*设置GPX1_1的中断触发方式为下降沿触发*/
EXT_INT41_CON = EXT_INT41_CON & (~(0x7 << 4)) | (0x2 << 4);
/*使能GPX1_1的中断功能*/
EXT_INT41_MASK = EXT_INT41_MASK & (~(1 << 1));
/*中断控制器层次 - 让中断控制器接收外设产生的中断信号并对其进行管理然后再转发给CPU处理*/
/*全局使能中断控制器使其能接收外设产生的中断信号并转发到CPU接口*/
ICDDCR = ICDDCR | 1;
/*在中断控制器中使能57号中断,使中断控制器接收到57号中断后能将其转发到CPU接口*/
ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 25);
/*选择由CPU0来处理57号中断*/
ICDIPTR.ICDIPTR14 = ICDIPTR.ICDIPTR14 & (~(0xFF << 8)) | (0x01 << 8);
/*使能中断控制器和CPU0之间的接口,使中断控制器转发的中断信号能够到达CPU0*/
CPU0.ICCICR = CPU0.ICCICR | 1;
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;
}
start.s
.text
.global _start
_start:
/*
* Vector table
*/
b reset
b .
b .
b .
b .
b .
/*
* 从异常向量表再跳转到IRQ的异常处理程序
*/
b irq_handler
b .
reset:
/*
* Set vector address in CP15 VBAR register
*/
ldr r0, =_start
mcr p15, 0, r0, c12, c0, 0 @Set VBAR
/*
* Set the cpu to SVC32 mode, Disable FIQ/IRQ
*/
mrs r0, cpsr
bic r0, r0, #0x1f
orr r0, r0, #0xd3
msr cpsr ,r0
/*
* Defines access permissions for each coprocessor
*/
mov r0, #0xfffffff
mcr p15, 0, r0, c1, c0, 2
/*
* Invalidate L1 I/D
*/
mov r0, #0 @Set up for MCR
mcr p15, 0, r0, c8, c7, 0 @Invalidate TLBs
mcr p15, 0, r0, c7, c5, 0 @Invalidate icache
/*
* Set the FPEXC EN bit to enable the FPU
*/
mov r3, #0x40000000
fmxr FPEXC, r3
/*
* Disable MMU stuff and caches
*/
mrc p15, 0, r0, c1, c0, 0
bic r0, r0, #0x00002000 @Clear bits 13 (--V-)
bic r0, r0, #0x00000007 @Clear bits 2:0 (-CAM)
orr r0, r0, #0x00001000 @Set bit 12 (---I) Icache
orr r0, r0, #0x00000002 @Set bit 1 (--A-) Align
orr r0, r0, #0x00000800 @Set bit 11 (Z---) BTB
mcr p15, 0, r0, c1, c0, 0
/*
* Initialize stacks
*/
init_stack:
/*svc mode stack*/
msr cpsr, #0xd3
ldr sp, _stack_svc_end
/*undef mode stack*/
msr cpsr, #0xdb
ldr sp, _stack_und_end
/*abort mode stack*/
msr cpsr,#0xd7
ldr sp,_stack_abt_end
/*irq mode stack*/
msr cpsr,#0xd2
ldr sp, _stack_irq_end
/*fiq mode stack*/
msr cpsr,#0xd1
ldr sp, _stack_fiq_end
/*user mode stack, enable FIQ/IRQ*/
msr cpsr,#0x10
ldr sp, _stack_usr_end
/*Call main*/
b main
/*
* IRQ的异常处理程序
*/
irq_handler:
/*
* 因为产生IRQ异常后ARM自动保存到LR中的返回地址是被IRQ打断的指令
* 的下一条再下一条指令的地址,所以我们需要人为的去修正一下
*/
sub lr, lr, #4
/*
* 因为IRQ模式下使用的R0-R12寄存器和USER模式下使用的是同一组
* 所以在处理异常之前需要先将之前寄存器中的值压栈保护
*/
stmfd sp!, {r0-r12,lr}
/*
* 跳转到do_irq处理异常
*/
bl do_irq
/*
* 异常返回
* 1.将R0-R12寄存器中的值出栈,使其恢复到被异常打断之前的值
* 2.将SPSR寄存器中的值恢复到CPSR,使CPU的状态恢复到被异常打断之前
* 3.将栈中保存的LR寄存器的值出栈给PC,使程序跳转回被异常打断的点继续执行
*/
ldmfd sp!,{r0-r12,pc}^
_stack_svc_end:
.word stack_svc + 512
_stack_und_end:
.word stack_und + 512
_stack_abt_end:
.word stack_abt + 512
_stack_irq_end:
.word stack_irq + 512
_stack_fiq_end:
.word stack_fiq + 512
_stack_usr_end:
.word stack_usr + 512
.data
stack_svc:
.space 512
stack_und:
.space 512
stack_abt:
.space 512
stack_irq:
.space 512
stack_fiq:
.space 512
stack_usr:
.space 512
中断编程补充
ADC
ADC(Analog to Digital Converter)模数转换器,指能将模拟信号转化为数字信号的电子元件。
分辨率(精度):ADC的分辨率一般以输出二进制数的位数来表示,当最大输入电压一定时,位数越高,分辨率越高; n位的ADC能区分输入电压的最小值为满量程输入的1/2^n;
比如一个12位的ADC,最大输入电压为1.8v,那么该ADC能区分的最小电压为1.8v/2^12≈0.00044v,当转换的结果为m时,则实际的电压值为m*(1.8v/2^12)。
Exynos4412下的ADC控制器
ADC概述:10位或12位CMOS模拟到数字转换器(ADC)包括4通道模拟输入。它通过5MHz A/D转换器时钟,以最大转换率为1MSPS,将模拟输入信号转换为10位或12位二进制数字码。A/D转换器具有片上采样和保持功能。ADC支持低功耗模式。
用ADC控制电机的主要特点(电气特性):
- 分辨率:10位/12位(可选)
- 微分非线性误差:±2.0 LSB(最大值)
- 积分非线性误差:±4.0 LSB(最大值)
- 顶部偏移错误: 0 ~ + 55 LSB
- 底部偏移错误: 0 ~ - 55 LSB
- 最大转换率:1 MSPS
- 低功耗
- 电源电压:1.8V(类型),1.0V(类型,数字输入/输出接口)
- 模拟输入范围:0 ~ 1.8V
A/D转换时间
当APB总线时钟(PCLK)频率为66MHz且预刻度器值为65时,总12位转换时间如下。
- 模数转换器频率=66MHz/(65+1)=1MHz
- 转换时间=1/(1MHZ/5个周期)=1/200kHz=5us
注意:这个A/D转换器被设计为在最大5MHz时钟下工作,所以转换率可以上升到1MSPS。
ADC寄存器详解
- ADCCON:ADC控制寄存器
- ADCDLY:ADC延迟寄存器
- ADCDAT: 存放ADC转换结果
- CLRINTADC :ADC中断功能
- ADCMUX:ADC通道选择
ADCCON
/*设置ADC精度为12bit*/
ADCCON = ADCCON | (1 << 16);
/*使能ADC分频器*/
ADCCON = ADCCON | (1 << 14);
/*设置ADC分频值 ADC时钟频率=PLCK/(19+1)=5MHZ ADC转换频率=5MHZ/5=1MHZ*/
ADCCON = ADCCON & (~(0xFF << 6)) | (19 << 6);
第2位:设置 待机模式(1) / 正常模式(0)
ADCCON = ADCCON & (~(1 << 2));
/*关闭通过读使能AD转换*/
第0/1位:选择触发模式。
/*关闭通过读使能AD转换*/
ADCCON = ADCCON & (~(1 << 1));
ADCDAT
CLRINTADC与ADCMUX
/*选择转换通道,3通道*/
ADCMUX = 3;
#include "exynos_4412.h"
int main()
{
unsigned int AdcValue;
/*设置ADC精度为12bit*/
ADCCON = ADCCON | (1 << 16);
/*使能ADC分频器*/
ADCCON = ADCCON | (1 << 14);
/*设置ADC分频值 ADC时钟频率=PLCK/(19+1)=5MHZ ADC转换频率=5MHZ/5=1MHZ*/
ADCCON = ADCCON & (~(0xFF << 6)) | (19 << 6);
/*关闭待机模式,使能正常模式*/
ADCCON = ADCCON & (~(1 << 2));
/*关闭通过读使能AD转换*/
ADCCON = ADCCON & (~(1 << 1));
/*选择转换通道,3通道*/
ADCMUX = 3;
while(1)
{
/*开始转换*/
ADCCON = ADCCON | 1;
/*等待转换完成*/
while(!(ADCCON & (1 << 15)));
/*读取转换结果*/
AdcValue = ADCDAT & 0xFFF;
/*将结果转换成实际的电压值mv*/
AdcValue = AdcValue * 0.44;
/*打印转换结果*/
printf("AdcValue = %dmv\n",AdcValue);
}
return 0;
}
RCT
Exynos4412下的RTC控制器
- 支持BCD码,即年\月\周几\日\时\分\秒。
- 支持闰年发生器
- 支持报警功能,即,报警中断或唤醒从断电模式。断电模式有:怠速、深怠速、停止、深停止和休眠。
- 支持滴答计数器功能,即滴答中断或唤醒从关机模式(空闲,深空闲,停止,深停止和休眠)
- 支持独立电源引脚(RTCVDD)
- 支持RTOS内核滴答时钟的毫秒滴答时间中断。
RTC寄存器详解
Register Map Summary
上述七个寄存器用于存储年\月\周几\日\时\分\秒的数据(以BCD码的格式存储)。
上述七个寄存器用于设置闹铃功能,到达预期时间的时候会产生中断信号。
用每个四位的区域标识一个十进制数。
如2023年:(千位默认为2)0000 0010 0011【低12位(023)】
月:十位[0/1] 个位[3:0]位
日:十位[5:4] (00~11) 个位[3:0]位
星期:[2:0]表示1~7
注意注意注意:手册上面的 周(+0x007c)和 日(+0x0080) 写反了,使用时候对调一下。
下列同理
要想修改时间的话需要将CTLEN这一位置1。
RCT编程
#include "exynos_4412.h"
int main()
{
unsigned int OldSec = 0, NewSec = 0;
/*使能RTC控制*/
RTCCON = RTCCON | 1;
/*校准时间信息2024-07-01 星期1 10:59:59*/
RTC.BCDYEAR = 0x024;
RTC.BCDMON = 0x07;
RTC.BCDDAY = 0x1;
RTC.BCDWEEK = 0x01;
RTC.BCDHOUR = 0x10;
RTC.BCDMIN = 0x59;
RTC.BCDSEC = 0x50;
/*禁止RTC控制,锁住以防误操作*/
RTCCON = RTCCON & (~(1));
while(1)
{
NewSec = RTC.BCDSEC;
if(OldSec != NewSec)
{
printf("20%x-%x-%x 星期%x %x:%x:%x\n",RTC.BCDYEAR, RTC.BCDMON, RTC.BCDWEEK, RTC.BCDDAY, RTC.BCDHOUR, RTC.BCDMIN, RTC.BCDSEC);
OldSec = NewSec;
}
}
return 0;
}
蜂鸣器工作原理
- 有源蜂鸣器:有源蜂鸣器只要接上额定电源就可以发出声音
- 无源蜂鸣器:无源蜂鸣器利用电磁感应原理,为音圈接入交变电流后形成的电磁铁与永磁铁相吸或相斥而推动振膜发声
GPIO控制:
while(1)
{
GPX2.DAT=GPX2.DAT | (1 << 7);
Delay(1000000);
GPX2.DAT=GPX2.DAT & (~(1 << 7));
Delay(1000000);
}
PWM
PWM(Pulse Width Modulation)即脉冲宽度调制,通过对脉冲的宽度进行调制来获得所需要的波形。
PWM参数
周期
占空比:一个周期中高电平时间与整个周期的比例称为占空比
Exynos4412下的PWM控制器
概述:Exynos 4412 SCP有5个32位脉宽调制(PWM)定时器。这些计时器会为ARM子系统生成内部中断。此外,计时器0、1、2和3还包括一个驱动外部I/O信号的PWM函数。定时器0中的PWM具有可选的死区发生器能力,以支持大电流设备。计时器4是一个没有输出引脚的内部计时器。
计时器使用APB-PCLK(100M)作为源时钟。计时器0和1共享一个可编程的8位预分频器,为PCLK提供第一级分频。计时器2、3和4共享一个不同的8位预计算器。每个计时器都有自己的私有时钟分频器,它提供了一个第二级的时钟分频器(预调节器除以2、4、8或16)。
注:故真正的PWM频率是PLCK(100M) -> 一级分频 -> 二级分频 -> PWM,也就是递减计数器递减的频率
工作原理:每个计时器都有它的32位下行计数器;计时器时钟驱动这个计数器。计时器计数缓冲区寄存器(TCNTBn)加载下行计数器的初始值。如果下计数器达到零,它将生成计时器中断请求,以通知CPU计时器操作已完成。如果定时器下计数器达到零,则相应的TCNTBn的值将自动重新加载到下计数器中,以开始下一个循环。但是,如果计时器停止,例如,在计时器运行模式期间通过清除计时器TCONn的启用位,TCNTBn的值不会重新加载到计数器中。
PWM函数使用TCMPBn寄存器的值。如果下计数器的值与计时器控制逻辑中的比较寄存器的值匹配,则计时器控制逻辑将更改输出级别。因此,比较寄存器决定了PWM输出的开启时间或关闭时间。
每个计时器都是双缓冲区结构,具有TCNTBn和TCMPBn寄存器,允许计时器参数在周期中间更新。新值只有在当前计时器周期完成后才会生效。
下图说明了一个PWM循环的简单示例。
上图表示用PWM实现一个低电平时间50 高电平时间为109的脉冲信号。
周期:50+109 = 159 占空比:109/159
使用PWM作为脉冲发生器的步骤是:
- 用159(50+109)初始化TCNTBn寄存器(周期),用109初始化TCMPBn寄存器(高电时间)。
- 开始计时器:设置开始位,并手动更新此位为关闭。
- 将TCNTBn值159加载到递减计数器中,然后将输出的TOUTn设置为较低。
- 如果递减计数器将TCNTBn倒数到TCMPBn寄存器109中的值,输出从低变为高。
- 如果递减行计数器达到0,则它将生成一个中断请求。
- 递减计数器会自动重新加载TCNTBn。然后重新启动该循环
反向输出功能
PWM寄存器详解
TCNTBn:周期
TCMPBn:高电平时间
TCNTOn:存储递减计数器的数据
TCFG0: 控制一级分频
[23:16]:死区宽度
[15:8]:预分频器1 : PWM 2/3/4
[7:0]:预分频器0 :PWM 0/1
TCFG1: 控制二级分频
- [19:16] 用于设置PWM4的二级分频
- [15:12] 用于设置PWM3的二级分频
- [11: 8] 用于设置PWM2的二级分频
- [ 7: 4] 用于设置PWM1的二级分频
- [ 3: 0] 用于设置PWM0的二级分频
TCON
第3位:设置timer0 的自动重载开关。(产生连续周期脉冲信号)。
第2位:设置timer0 的反向功能开关。(高低电平翻转)
第1位:设置timer0 的手动更新,置为1后 后续会自动更新TCNTB0和TCMPB0。
第0位:设置timer0 的开关。
PWM编程
#include "exynos_4412.h"
void Delay(unsigned int Time)
{
while(Time --);
}
int main()
{
/*1.将GPD0_0引脚设置成PWM0的输出引脚*/
GPD0.CON = GPD0.CON & (~(0xF)) | (0x2);
/*2.设置PWM0的一级分频 一级分频倍数设置为100倍*/
PWM.TCFG0 = PWM.TCFG0 & (~(0xFF)) | 99;
/*2.设置PWM0的二级分频 二级分频倍数设置为1倍 递减计数器递减频率 = PLCK / (99 + 1) / 1 = 1M*/
PWM.TCFG1 = PWM.TCFG1 & (~(0xF));
/*4.设置PWM0为自动重装载,使其能够产生连续的脉冲信号*/
PWM.TCON = PWM.TCON | (1 << 3);
/*5.设置PWM0的频率为500HZ*/
PWM.TCNTB0 = 2000;
/*6.设置PWM0的占空比为50%*/
PWM.TCMPB0 = 1000;
/*7.将TCNTB0中的值手动装载到递减计数器*/
PWM.TCON = PWM.TCON | (1 << 1);
/*8.关闭手动更新*/
PWM.TCON = PWM.TCON & (~(1 << 1));
/*9.使能PWM0,递减计数器开始递减*/
PWM.TCON = PWM.TCON | 1;
while(1)
{
PWM.TCON = PWM.TCON | 1;
Delay(1000000);
PWM.TCON = PWM.TCON & (~(1));
Delay(1000000);
}
return 0;
}