嵌入式硬件及接口开发实践

时间:2021-04-30 19:21:41

了解嵌入式系统电路设计

时钟模块

1、如下是时钟模块结构图
嵌入式硬件及接口开发实践
在图中我们看到XTIPLL是外部晶振,EXTCLK是外部时钟,他们为时钟源,2个PLL,他们可以产生需要的高频时钟
2、时钟源的选择,软件没有对MPLLCON寄存器设置,使用外部晶振或外部时钟为系统时钟
3、锁相环PLL模块结构图
嵌入式硬件及接口开发实践
从结构图中我们获得输出时钟频率的表示为:
嵌入式硬件及接口开发实践
其中,m=M(分配器M的值)+8,p=P(分配器P的值)+2
4、时钟控制逻辑决定哪个时钟源被使用,下图是电源在上电重启时的时钟行为
嵌入式硬件及接口开发实践
从这个时序图,我们可以知道在没有配置PLLCON寄存器时,直接使用FIN为MPLL的FCLK,下图是正常模式下改变PLL设置时的时序
嵌入式硬件及接口开发实践
从这个图可以知道,我们可以通过写P M L三个分配器的值(在PLL锁存时间会被自动的插入)改变FCLK,下图是PLL参数推荐值
嵌入式硬件及接口开发实践

5、使用分频系数控制寄存器设置FCLK(用于CPU核),HCLK(用于AHB设备,如存储器控制,中断控制,DMA,LCD控制,USB主模块,),PCLK(用于APB,如看门狗,UART,RTC,GPIO等)三者的比例。
6、系统时钟设置的步骤
6.1、确定外部输入晶振频率,比如,Fin = 12MHz
6.2、确定系统输出时钟频率,比如,FCLk=400MHZ
6.3、对照PLL参数推荐值表,找到合适的MDIV,PDIV.SDIV,设置MPLLCON。
6.4、设置UPLLCON
6.5、确定FCLK,HCLK,PCLK比例系数,设置时钟分频系数寄存器CLKDIVN,从而确定当前系统下的FCLK,HCLK,PCLK的具体频率值

GPIO(通用的输入输出端口)

1、2440包含GPA,GPB,GPC,GPD,GPE,GPF,GPG,GPH,GPJ 9组端口
2、GPxCON:选择引脚工作模式,每两位控制一个引脚,00(输入),01(输出),10(特殊用途),11(保留)
3、GPxDAT:读写引脚数据,每一位控制一个引脚,0表示引脚为低电平,1表示引脚为高电平
4、GPxUP:确定是否使用内部上拉电阻,每一位控制一个引脚,0表示使用内部上拉电阻,1表示无内部上拉电阻

UART(通用异步收发器)

1、下面是模块图:
嵌入式硬件及接口开发实践
UART属于APH设备,包含控制单元,波特率发生器,发送器和接收器。
2、发送或接收的数据构成为1个开始位,5-8个数据位,1个可选的奇偶校验位和1-2个停止位,由线性控制寄存器ULCONn来设置,如下图
嵌入式硬件及接口开发实践
3、波特率描述串行通信数据传输速率,每秒传输的二进制位数,单位是bps,从TxD管脚发送,从RxD管脚接收。
4、TTL/CMOS逻辑电平转换为RS-232逻辑电平
5、异步数据传输方式,依靠起始位来实现发送和接收发的时钟自同步
6、发送数据流程,SOC将数据写入发送FIFO->发送移位器->TxD数据线
7、接收数据流程,RxD数据线->接收移位器->SOC从接收FIFO读取数据
8、串口硬件流控制
9、UART初始化
9.1、波特率除数因子寄存器UBRDIVn,设置波特率,计算方法如下图:
嵌入式硬件及接口开发实践
9.2、行寄存器ULCONn,设置传输格式
9.3、模式控制寄存器UCONn,选择UART时钟源,设置UART中断方式等
9.4、FIFO控制寄存器UFCONn,决定是否使用FIFO
9.5、状态寄存器UTRSTATn,表明数据是否已经被发送完毕、是否已经接收到数据
9.6、SOC将数据写入这个寄存器(UTXHn),SOC读取这个寄存器(URXHn)

中断控制

1、CPU和外设之间的数据传输控制方式通常包含三种,查询方式,中断方式,DMA(Direct Memory access),中断就是CPU在运行程序时,出现了紧急事件,必须转去处理它(执行中断服务程序),并在处理完后再返回的过程
2、中断过程:请求->仲裁->响应->处理->返回,下图是arm9中断处理流程
嵌入式硬件及接口开发实践
3、中断控制器支持60个中断源
4、如下是s3c2440A中断过程
嵌入式硬件及接口开发实践
4.1、中断源未决寄存器SRCPND,每一个位与一个中断源有关,自动置位,指示那个中断源正在等待服务
4.2、中断模式寄存器INTMOD,每一个位与一个中断源有关,为1时,相应的中断将在FIQ模式下处理
4.3、中断屏蔽寄存器INTMSK,每一个位与一个中断源有关,为1时,CPU不会服务相应中断源的中断请求
4.4、优先级寄存器PRIORITY
4.5、SUBSRCPND寄存器,S3C2440有15个子中断,当这些子中断发生,且未被屏蔽,他们对应的父中断将会在SRCPND中被置位
4.6、中断偏移寄存器

C和汇编语言的混合编程

1、内嵌的汇编指令用法
1.1、操作数可以是表达式,表达式表示的信息会被作为无符号数进行操作,表达式不要太复杂。
1.2、内嵌的汇编指令如果包含常量操作数,该指令肯能会被汇编器展开成几条指令,并且该常量前的符号#可以省略。
1.3、只有指令B能使用C程序中的标号
2、在C程序中使用内嵌的汇编指令的语法格式和注意事项
2.1、语法格式
嵌入式硬件及接口开发实践
2.2、注意事项
2.2.1、汇编指令段中可以使用C语言的注释
2.2.2、在汇编指令中“,”用作为分隔符
2.2.3、最好是不要在内嵌的汇编指令中使用物理寄存器,可以使用变量来实现
2.2.4、不要用物理寄存器去引用一个C变量
2.2.5、对于内嵌汇编指令可能会用到的寄存器,没有必要保护和恢复他们

实验一

学习使用ARM汇编指令,实现蜂鸣器BEEP鸣叫,四个LED点亮、熄灭实验。
电路控制,如图:
嵌入式硬件及接口开发实践
代码(arm汇编版本):

    AREA    beep, CODE, READONLY
ENTRY
CODE32
pGPBCON EQU 0x56000010
pGPBDAT EQU 0x56000014
pGPBUP EQU 0x56000018

START ;配置GPBCON[1:0]=01使引脚GPB0 为输出工作模式
LDR R0, =pGPBCON
LDR R1, [R0]
LDR R2, =0x15401
ORR R1, R1, R2
STR R1, [R0]
;配置GPBUP[0]=1使引脚GPB0无内部上拉电阻
LDR R0, =pGPBUP
LDR R1, [R0]
LDR R2, =0x07ff
ORR R1, R1, R2
STR R1, [R0]
;light on
beep_on
LDR R0, =pGPBDAT
MOV R2, #0x01
STR R2, [R0]

MOV R2, #0x10000
BL delay

; b .

;light off
beep_off
LDR R0, =pGPBDAT
MOV R2, #0x1e0
STR R2, [R0]

MOV R2, #0x100000
BL delay

b beep_on

delay
SUB R2, R2, #0x1
CMP R2, #0x0
BNE delay
MOV PC, LR

NOP
END

实验二

;C - > 汇编程序
通过C语言程序调用汇编子程序字符串拷贝函数
代码:
心得:
1、设置系统时钟,

实验三

汇编程序->C
汇编程序调用C程序g()计算5个整数i, 2*i, 3*i, 4*i, 5*i的和
本程序使用5个参数,分别使用寄存器R0存储第一个参数,R1、R2、R3分别存储第二、三、四个参数,第五个参数利用堆栈传送
函数返回值保持在R0中
代码(C语言程序部分):

//C程序g()返回5个整数的和
int g(int a, int b, int c, int d ,int e)
{
return (a + b + c + d + e);
}

代码(汇编语言部分):

Stack_Size      EQU         0x00000400      ;指定栈的大小
AREA STACK, DATA, NOINIT, READWRITE, ALIGN=3 ;初始化一个数据段,初始化为0
Stack_Mem SPACE Stack_Size ;分配一片内存单元用作栈空间
PRESERVE8 ;声明8字节对齐
AREA asmCT1, CODE, READONLY ;申明一个代码段
ENTRY ;定义程序入口
CODE32 ;声明32位arm指令
START
;初始化栈
LDR R0, =Stack_Mem
; set its Stack Pointer
MOV SP, R0
; MUL SL, SP, #Stack_Size

BL call_g
B stop

call_g
IMPORT g
STR LR, [sp, #-4]!;保存返回地址
;set parameter passed to function
MOV R0, #01 ;if i = 1
ADD R1, R0, R0 ;R1 = 2i
ADD R2, R0, R1 ;R2 = 3i
ADD R3, R1, R2 ;R3 = 5i
;先移动栈指针,然后将R3压入堆栈
STR R3, [SP, #-4]!
ADD R3, R1, R1 ;R3 = 4i
BL g ;调用C程序
NOP
ADD sp, sp, #4;调整数据栈指针,准备返回
LDR PC, [SP], #4;返回

stop
b stop
END

心得:
1、伪操作SPACE的用法,它的功能是分配一块内存单元,并用0初始化
2、伪操作EQU的用法,语法格式为name EQU expr type,它的功能是为数字常量(32位地址或32位常数),基于寄存器的地址值,程序中的标号(基于PC的值)定义一个字符名称(命名规范?)。其中type指示expr的数据类型时CODE16,CODE32,DATA
3、伪操作AREA的用法,语法格式为 AREA sectionname {,attr,attr}…。它的功能是定义个代码段或数据段,轻重attr是段的属性,各个属性用,隔开,比如属性CODE(代码段),DATA(表示数据段),COMMON(一个通用的段,不包含代码和数据),COMDEF(一个通用的段,可以包含代码和数据),READONLY,READWRITE,ALIGN=expression,NOINIT,ASSOC等。
4、CODE32,CODE12告诉汇编编译器后面指令的类型,ENTRY指定程序的入口点,END告诉编译器已经到了源程序结尾,
5、伪操作PRESERVE8指示当前代码数据栈是8字节对齐
6、伪操作IMPORT的用法,它的语法格式是IMPORT symbol[WEAK],它的功能是告诉编译器符号symbol不是在本源文件定义的,而是在其他源文件中定义的。
7,子程序参数传递和返回值的规则:参数数量不超过4个时,使用R0-R3传递,否则,可以使用栈(sp)传递。
8、栈的生长方向是向低地址增长,栈内存放的值一般是指针类型,这样一来就通过栈间接操作内存单元。比如
如果栈指针拿到的地址是0x30800000,sp-4拿到的地址就是0x307ffffc,这时如果内存单元0x307ffffc存储的数据为,0x3000000C,那么执行指令STR LR, [sp, #-4]!后,链接寄存器LR=,0x3000000C,并且更新了sp,此时sp=0x307ffffc

实验四

在终端运行uart_test程序,PC端通过超级终端向串口发送一行字符(直到敲入回车键结束),通过串口0发送到开发板,终端接收串口数据后,保存在数组中,再传回到PC端,通过超级终端回显。
代码:

//串口发送一个字符=======
void Uart_SendByte(int data)
{
if(whichUart==0)
{
if(data=='\n')
{
while(!(rUTRSTAT0 & 0x2));
Delay(10); //because the slow response of hyper_terminal
WrUTXH0('\r');
}
while(!(rUTRSTAT0 & 0x2)); //Wait until THR is empty.
Delay(10);
WrUTXH0(data);//把字符送到发送缓冲寄存器
}
else if(whichUart==1)
{
if(data=='\n')
{
while(!(rUTRSTAT1 & 0x2));
Delay(10); //because the slow response of hyper_terminal
rUTXH1 = '\r';
}
while(!(rUTRSTAT1 & 0x2)); //Wait until THR is empty.
Delay(10);
rUTXH1 = data;
}
else if(whichUart==2)
{
if(data=='\n')
{
while(!(rUTRSTAT2 & 0x2));
Delay(10); //because the slow response of hyper_terminal
rUTXH2 = '\r';
}
while(!(rUTRSTAT2 & 0x2)); //Wait until THR is empty.
Delay(10);
rUTXH2 = data;
}
}

//串口接收一个字符=====================================================
char Uart_Getch(void)
{
if(whichUart==0)
{
while(!(rUTRSTAT0 & 0x1)); //Receive data ready
return RdURXH0();
}
else if(whichUart==1)
{
while(!(rUTRSTAT1 & 0x1)); //Receive data ready
return RdURXH1();
}
else if(whichUart==2)
{
while(!(rUTRSTAT2 & 0x1)); //Receive data ready
return RdURXH2();
}
return 0;
}
/*********************************************************************************************
* name: uart0_test
* func: uart test function
* para: none
* ret: none
* modify:
* comment:
*********************************************************************************************/

void uart0_test()
{
char cInput[256];
UINT8T ucInNo=0;
char c;
//
Uart_Init( 0,115200 );
Uart_Select( 0 ); // 使用串口0

Uart_Printf("\n UART0 Communication Test Example\n");
Uart_Printf(" Please input words, then press Enter:\n");

while(1)
{
c=Uart_Getch();
Uart_Printf("%c",c);
if(c!='\r') //enter key
cInput[ucInNo++]=c;
else
{
cInput[ucInNo]='\0';
break;
}
}
Delay(1000);

Uart_Printf("\n The words that you input are: \n %s\n",cInput);
Uart_Printf(" end.\n");
}

心得:
1、串口0一次接收一个字符,直到接收到回车键‘\r‘结束。串口0一次发送一个字符,直到遇到回车键’\r’结束。
2、串口初始化实质是依次配置行控制寄存器,控制寄存器,波特率分频系数寄存器,FIFO控制寄存器,MODEL控制寄存器。
3、发送缓冲寄存器为空时自动设置TX/RX状态寄存器的相应位为1,所以检测TX/RX状态寄存器的相应位,如果为1,则SOC能把数据位写到发送缓冲寄存器。接收缓冲寄存器包含有效数据时自动设置TX/RX状态寄存器的相应位为1,所以检测TX/RX状态寄存器的相应位,如果为1,则SOC能从接收缓冲器读到数据

实验五

(1)实现单按键中断处理程序。
(2)实现六按键中断处理程序。
(3)实现按键控制LED点亮/熄灭实验。
eg:
K1 - 点亮LED1,其他3个LED熄灭
K2 - 点亮LED2,其他3个LED熄灭
K3 - 点亮LED3,其他3个LED熄灭
K4 - 点亮LED4,其他3个LED熄灭
K5 - 点亮4个LED
K6 - 全熄灭4个LED
代码:


//扫描6按键所接GPIO口,观察GPIOx电平值,键按下:低电平; 键抬起:高电平
//返回当前按下键所对应的键值
U8 Key_Scan( void )
{
Delay( 80 ) ;

if( (rGPGDAT&(1<< 0)) == 0 ) // K1按下
return 1 ;
else if( (rGPGDAT&(1<< 3)) == 0 ) // K2按下
return 2;
else if( (rGPGDAT&(1<< 5)) == 0 ) // K3按下
return 3 ;
else if( (rGPGDAT&(1<< 6)) == 0 ) // K4按下
return 4 ;
else if( (rGPGDAT&(1<< 7)) == 0 ) // K5按下
return 5 ;
else if( (rGPGDAT&(1<< 11)) == 0 ) // K6按下
return 6 ;
else
return 0xff;
}

// 按键中断处理程序
void __irq Key_ISR(void)
{
U8 key;
U32 r;
//Uart_Printf("\nKey_ISR+!\n");
EnterCritical(&r); // 进入临界区
if(rINTPND==BIT_EINT8_23) { //判断INTPEND寄存器中是否为EINT8_23触发中断,如果EINT8_23触发中断,则INTPEND寄存器中对应bit位被置一
ClearPending(BIT_EINT8_23); //清空BIT_EINT8_23位
// 继续比较EINTPEND寄存器,确定外面中断源
if(rEINTPEND&(1<<8)) { //EINT8触发中断
Uart_Printf("eint8\n");
rEINTPEND |= 1<< 8; // 清空EINTPEND寄存器中EINT8对应的位
}
if(rEINTPEND&(1<<11)) { //EINT11触发中断
Uart_Printf("eint11\n");
rEINTPEND |= 1<< 11;
}
if(rEINTPEND&(1<<13)) { //EINT13触发中断
Uart_Printf("eint13\n");
rEINTPEND |= 1<< 13;
}
if(rEINTPEND&(1<<14)) { //EINT14触发中断
Uart_Printf("eint14\n");
rEINTPEND |= 1<< 14;
}
if(rEINTPEND&(1<<15)) { //EINT15触发中断
Uart_Printf("eint15\n");
rEINTPEND |= 1<< 15;
}
if(rEINTPEND&(1<<19)) { //EINT19触发中断
Uart_Printf("eint19\n");
rEINTPEND |= 1<< 19;
}

}
key=Key_Scan(); //扫描GPGx端口,返回按键键值
if( key != 0xff )
Uart_Printf( "Interrupt occur... K%d is pressed!\n", key) ;

ExitCritical(&r); // 出临界区
//Uart_Printf("\nKey_ISR-!\n");
}

void KeyScan_Test(void)
{
Uart_Printf("\nKey Scan Test, press ESC key to exit !\n");

// 配置GPGCON,设置6按键对应的GPGx管脚功能为外部中断引脚EINT
rGPGCON = rGPGCON & (~((3<<0)|(3<<6))) | ((2<<0)|(2<<6)) ; //GPG0,11 set EINT
rGPGCON = rGPGCON & (~((3<<10)|(3<<12))) | ((2<<10)|(2<<12)) ; //GPG5,6 set EINT
rGPGCON = rGPGCON & (~((3<<14)|(3<<22))) | ((2<<14)|(2<<22)) ; //GPG7,11 set EINT

// 设置中断触发方式
rEXTINT1 &= ~(7<<0);
rEXTINT1 |= (2<<0); //set eint8 falling edge int

rEXTINT1 &= ~(7<<12);
rEXTINT1 |= (2<<12); //set eint11 falling edge int

rEXTINT1 &= ~(7<<20);
rEXTINT1 |= (2<<20); //set eint13 falling edge int

rEXTINT1 &= ~(7<<24);
rEXTINT1 |= (2<<24); //set eint14 falling edge int

rEXTINT1 &= ~(7<<28);
rEXTINT1 |= (2<<28); //set eint15 falling edge int

rEXTINT2 &= ~(7<<12);
rEXTINT2 |= (2<<12); //set eint19 falling edge int

// 将按键中断处理程序注册,入口地址对应EINT8_23中断IRQ
pISR_EINT8_23 = (U32)Key_ISR;

rEINTPEND = 0xFFFFFF; //清空 EINTPEND中断请求
rSRCPND = BIT_EINT8_23; //to clear the previous pending states in SRCPND
rINTPND = BIT_EINT8_23; // to clear the previous pending states in INTPND

rEINTMASK=~( (1<<8)|(1<<11)|(1<<13)|(1<<14)|(1<<15)|(1<<19) ); //清空六个外部中断对应的中断屏蔽位
rINTMSK=~(BIT_EINT8_23); // 清空BIT_EINT8_23对应的中断屏蔽位


Uart_Printf("\nPlease press the Key to test !\n");
Uart_Printf("\nrINTMSK=0x%x\n",rINTMSK);
while( Uart_GetKey() != ESC_KEY ) ; // 无限循环,直到用户键入ESC键,退出。但此时可被中断打断
Uart_Printf("\nExit Int test !\n");
rEINTMASK=0xFFFFFF; // 重新设置EINTMASK屏蔽位
rINTMSK=BIT_ALLMSK; // 重新设置INTMSK屏蔽位
}

心得:
1、设置6按键对应的GPGx管脚功能为外部中断引脚EINT
2、设置中断触发方式,
3、将按键中断处理程序注册,入口地址对应EINT8_23中断IRQ
4、#define rSRCPND ((volatile unsigned )0x4a000000) //Interrupt request status
4.1、上述表达式拆开来分析,首先(volatile unsigned *) 0x4a000000的意思是把0x4a000000强制转换成volatile unsigned 类型的指针,暂记为p,那么就是#define A *p,即A为P指针指向位置的内容了。这里就是通过内存寻址访问到寄存器A,可以读/写操作。
4.2、unsigned 类型指针,意思是说读写这个地址时,要写进unsigned 的数据,读出也是unsigned。
4.3、volatile变量可变 允许除了程序之外的比如硬件来修改他的内容
4.4、访问该数据任何时候都会直接访问该地址处内容,即通过cache提高访问速度的优化被取消
4.5、简而言之,我们把rSRCPND 看成是一个寄存器变量