写在前面
本报告因为期末将至,后续还需要完成一次课程设计,故本次实验较为简单,完成的时间也非常匆忙,故文章内容较为单薄,也可能有着更多疏忽之处,还望大家海涵。
1. 项目任务
1) 利用S3C2440手册,掌握嵌入式系统三个时钟关系(FCLK、HCLK、PCLK)和异步通信协议,分析解读异步通信寄存器的初始化;
2) 利用ARM机自带键盘,用串行方式实现ARM机的数据传送程序;
3) 实现两台ARM机之间异步通信。具体内容:实现带启动的三键控制控制对方三灯程序。即启动键有效时,一个键点亮另一台ARM机的三个灯,一个键关闭另一台ARM机的三个灯,一个键控制另一台ARM机的三灯跑马。启动键无效时,三键不起控制作用(单键用查询);
4) 编写交叉编译文件Makefile;
2. 项目方案设计
本实验为ARM裸机开发——双机异步串行通信,主要目的是通过ARM裸机开发实现双机异步串行通信。本项目的实现框图如图2.1所示。
3. 串行通信原理
本实验将以经典的UART通信,即全双工异步串行通信协议,进行通信协议的学习与运用,并且通过UART通信进一步理解嵌入式系统裸机开发原理。
3.1. 异步串行通信原理(UART)
以最为经典的AT89C52单片机的异步串行通信为例,UART通信为一种全双工、异步、串行通信协议。全双工,意味着双方可以同时进行收发,即“我”可以接收到“你”的数据,同样可以把数据发送给“你”,也即一对一,无法多方通信;异步,实际是数据收发与时钟不同步的概念,即接收方不确定数据在何时到来;串行,数据需要依次通过数据线进行发送,如同绳串一样,发送完当前数据后,才可以发送下一个数据。
在清晰UART通信协议含义后,如何具体实现UART通信,就需要清晰应用概念。如图3.1所示,介绍部分UART通信应用概念。
1) 空闲位。当UART双方无数据收发时,双方引脚均保持高电平,即数据为‘1‘,处于空闲状态。
2) 起始位。当UART开始收发数据时,发送方发送低电平,即逻辑‘0’,表示数据通信开始。因为空闲位为高电平,所以起始位需要区别于空闲位。
3) 数据位。数据位可以设置为5至8位,对于有线通信而言,通常采用8位数据位一个字节方便读取存储;对于无线红外通信模式,使用较少的数据位可以减少丢包率。
4) 奇偶校验位。奇偶校验是简单实用的数据传输校验方式,通过计数数据传输中逻辑‘1’的数量为奇数还是偶数,进行简单的数据校验。由于UART通信通常适用于传输速度低,数据量较少的情况下,利用简单的奇偶校验即可一定程度上保证数据准确。
5) 停止位。当数据发送完成时,发送方发送高电平为停止位,以表示数据收发结束,可以设置1至2位停止位。
由于UART通信无时钟线,那如何保证数据通信的准确性,换句话说,当第一位数据结束时,何时为下一位数据位,双方如何保证当前数据位为第几位数据位。打个比方,两人通话时,如果一方的语速超过另一方大脑接收语言的处理速度,那么就无法正常通话,在这里的语速,就是数据收发的速率,即波特率。
实际上,波特率,即为每位数据位的传输时间的倒数,换句话说波特率的倒数即为每隔多少时间需要数据采样一次,如此才能保证在无时钟线的情况下,保证数据双方进行数据通信的准确性。由于串口通信协议的芯片电气传输特性限制的最大传输速率为115200bps,所以通常采用的波特率为115200的分频数,如9600、19200bps。例如本次实验波特率采用了19200bps,利用示波器测量串口通信数据,如图3.2所示。
b a u d r a t e = 1 b i t p e r i o d = 1 52.10 u s = 19194 b p s ≈ 19200 b p s baud rate= \frac{1}{bit period} = \frac{1}{52.10us} = 19194bps ≈ 19200bps baudrate=bitperiod1=52.10us1=19194bps≈19200bps
根据上述的实验,可见实际波特率的测量与理论波特率误差极小,换句话说在数据传输较慢时,UART通信的丢包率较小。
AT89C52单片机中的UART通信为最为经典的串口通信协议,嵌入式系统在除上述基本概念以外,引入了FIFO存储器、流控控制、状态检测等控制方式。
FIFO存储器,即先进先出、后进后出存储器,为队列存储器,当UART收到数据后,随即将数据压入队列存储器中,直到队列存储器满。FIFO存储器的使用框图如图3.3所示。
3.2. 寄存器配置方式
与实验二中存储器外部设备寄存器初始化相类似,UART配置流程如图3.4所示。
根据图3.4 UART配置流程。
1) ULCON:UART线路控制寄存器,主要控制UART数据发送方式,即数据位长度与停止位个数,以及奇偶校验位选择。如ULCON=0x03;即8数据位,1起始位,1停止位,无奇偶校验位模式。
2) UCON:UART控制寄存器,控制UART时钟源设置,以及时钟源分频设置,以及UART收发采用方式设置,由于中文数据手册对于该寄存器说明有所缺失,在此补充寄存器[3:0]位说明,如图3.6。如UCON=0x05;即均采用中断或者查询模式,时钟源为PCLK。
3) UFCON:UART FIFO控制寄存器,控制FIFO存储器使用的相关配置,本实验未使用FIFO,为此UFCON=0x00。
4) UMCON:UART Modem寄存器,即流控控制寄存器,本实验不使用流控方式,为此UMCON=0x00。
5) UBRDIV:波特率寄存器,通过该寄存器具体设置波特率分频值,本实验中设置波特率为19200bps,根据公式UBRDIV=(int)(UART 时钟/(baud rate×16))-1
综上,五个UART寄存器即可实现UART通信基本配置,但是由于在实际运行中,无法得知数据会在何时到来,所以通常对于接收数据采用接收中断的方式,而发送数据采用轮询的方式,为此需要配置接收中断的寄存器,以实验二中断配置为例,本实验不赘述。
同时,UART通信空闲位为1,所以需要配置UART管脚上拉,同时配置管脚准双向输入输出模式。
4. 嵌入式时钟原理
UART通信中引入了时钟的设置,借以此引入嵌入式时钟树,并尝试与单片机时钟比较,探讨嵌入式时钟树的作用与意义,将以嵌入式时钟树原理、时钟树配置方式两方面阐述。
4.1. 嵌入式时钟树原理
与单片机有所不同,嵌入式系统的高性能往往带来了外部设备众多,系统更为复杂,采用单一的时钟总线的方法,无法满足所有外部设备的时钟要求。对于51单片机而言,其运用场合往往是低速、简单控制、设备稳定的场合,采用低速时钟线,可以降低功耗。
为满足低速信号、高速信号、CPU等设备的时钟需求,S3C2440将主要的时钟线为三种,即提供给CPU的FCLK,提供AHB总线外设的HCLK以及提供给APB总线外设的PCLK,其中AHB总线为高级高性能外设总线,APB为精简低功耗外设总线,以此来区分三种时钟线。打个比方,CPU为大脑,需要提供最为快速的信号处理与反应,所以需要提供最为快速的FCLK时钟线;AHB总线外设为神经系统,需要尽可能快地传递感受信息,需要高速HCLK;APB总线为执行器,无法做到与神经反应相同的反应速度,所以利用慢速PCLK时钟线即可。嵌入式整体的时钟处理框图如图4.1所示。
1) 时钟源产生时钟信号作为
F
i
n
F_{in}
Fin,提供给PLL锁相环。
2) PLL锁相环,得到
F
i
n
F_{in}
Fin后,将
F
i
n
F_{in}
Fin信号通过分频或倍频的方式,同时将信号相位同步后,得到时钟参考信号MPLL,具体MPLL频率计算公式为
M
P
L
L
=
2
×
m
×
F
i
n
p
×
2
S
MPLL=\frac{2×m×F_{in}}{p×2^S}
MPLL=p×2S2×m×Fin
3) Power Management,得到MPLL参考时钟信号后,通过设置分频器和电源管理模块使能,得到FCLK、HCLK、PCLK三时钟信号。
以上便是时钟信号的具体产生过程,在下一部分具体分析时钟信号配置方法。
4.2. 时钟信号配置方法
时钟线具体配置,实际即确定三时钟线的时钟频率,以本实验的时钟设定为具体演示。令FCLK=200MHz, HCLK=100MHz, PCLK=50MHz根据图4.2时钟产生方法,编写了clock_init函数,如图4.3所示
5. 嵌入式系统程序设计
本部分主要演示代码框架和具体代码内容,程序设计将分为代码实现、代码调试问题两部分阐述。
5.1. 代码实现
本实验整体框架与实验二类似,均采用了中断得到系统状态标志位,主循环根据状态标志位进行不同模式的程序执行,启动引导代码与实验二一致,故本程序设计不涉及启动引导代码,流程如图5.1所示
本次实验与实验二,整体思路与框架一致,实验较为简单,暂无太多代码改变之处,主要放出与UART相关代码,如下
/***********************************************
* void UART_Init(void)
* 函数功能:UART初始化函数,使用UART1,TXD-GPH4, RXD-GPH5
* 协议:数据位8位,1位起始位,1位停止位,无奇偶校验位
* 波特率19200
* 无流控
* 暂时不带FIFO
* 无错误检查
* 暂时固定式初始化,后续借鉴HAL库中的UART初始化方式
* ***********************************************/
void UART_Init(void)
{
// UART Line Control 线路控制寄存器,由于控制通信数据协议
ULCON1 = 0X03; // 8数据位,1起始位,1停止位,无奇偶校验位
// UART Control 控制寄存器,控制时钟,中断等模式
UCON1 = 0x05; // 发送与接收均采用中断或者查询模式,时钟为PCLK
// 中文数据手册中缺少了部分说明 其中UCON[3:0]控制UART的传输模式,如屏蔽,中断查询模式
// UART FIFO控制寄存器,FIFO为队列存储器,先进先出,后进后出
UFCON1 = 0x00; // 不使用FIFO
// Modem 控制器,控制流控
UMCON1 = 0x00; // 不使用流控
// 波特率分频寄存器
UBRDIV1 = UART_BRD; // 波特率为19200
GPHCON |= 0x000a00; // GPH4,GPH5用作TXD1,RXD1
GPHUP &= ~(0x030); // GPH4,GPH5内部上拉
// 设置接收中断
// 配置中断模式,均为普通中断IRQ
INTMOD &= ~(BIT_UART1); // BIT_xx 对应中断源位
// 优先级采用复位值
INTSUBMSK &= ~(BIT_SUB_RXD1); // 接收中断使能
INTMSK &= ~(BIT_UART1);
}
/****************
* int fputc(int c, FILE *f)
* 重定向printf函数,但是对于ARM裸机开发,调用标准库的过程暂未解决
* **************/
// int fputc(int c, FILE *f)
// {
// while(!(UTRSTAT1 & TXDREADY));
// UTXH1 = c;
// return c;
// }
/*****************************************
* void void UART_SendData(unsigned char c)
* UART单字节发送函数
* *************************************/
void UART_SendData(unsigned char c)
{
/* 等待,直到发送缓冲区中的数据已经全部发送出去 */
while (!(UTRSTAT1 & TXDREADY));
/* 向UTXH1寄存器中写入数据,UART即自动将它发送出去 */
UTXH1 = c;
}
/*
* 接收字符
*/
unsigned char UART_GetData(void)
{
/* 等待,直到接收缓冲区中的有数据 */
while (!(UTRSTAT1 & RXDREADY));
/* 直接读取URXH1寄存器,即可获得接收到的数据 */
return URXH1;
}
/*****************************************
* void UART1_IRQHandle(void)
* 暂时仅为接收中断函数,对于发送通常采用轮询模式
* *************************************/
void UART1_IRQHandle(void)
{
SUBSRCPND = (1<<3 | 1<<4);
// while(!(UTRSTAT1 & RXDREADY));
flag = URXH1;
}
具体的代码实现均放在附件,本次实验效果如图5.2所示。
5.2. 代码调试问题
本次实验整体问题不大,但是在调试过程中,利用示波器和电脑串口测试通信过程中,发现当按键按下过程中,会出现按一次键,发送多次数据的情况,也即按键进入多次中断。针对该问题,尝试在外部中断内部添加延时函数,会发现数据明显减少,证明问题产生符合按键抖动的现象。但是,硬件中断函数中添加延时函数是不符合嵌入式开发思想的,中断程序为处理异常的程序,不应该作为主进程使用,对于查询方式的按键,消抖是较为简单的,但是对于外部中断函数而言,可以采用软件中断的方式进行按键消抖。但是这样较为复杂。
S3C2440提供更为简便的按键中断消抖的硬件方式,即采用双边沿触发中断的方式,如图5.3所示设置对应外部中断的触发方式为双边沿触发。即可得到较为稳定的机械消抖方式。
objs := startup.o gpio.o init.o interrupt.o main.o nand.o uart.o
exit.bin: $(objs)
# 连接指令,将exit.lds链接文件,编译为exit_elf, 即各文件接口
arm-linux-ld -Texit.lds -o exit_elf $^
# 将各个可执行文件根据exit_elf文件转译为二进制文件
arm-linux-objcopy -O binary -S exit_elf $@
# 将各个可执行文件根据exit_elf文件转译为反汇编文件,即转译为汇编语言,根据ARM指令
arm-linux-objdump -D -m arm exit_elf > exit.dis
# 将.c文件编译为可执行文件
%.o:%.c
arm-linux-gcc -Wall -O2 -c -o $@ $<
# 将.S文件编译为可执行文件
%.o:%.S
arm-linux-gcc -Wall -O2 -c -o $@ $<
# 删除当前目录下的编译文件
clean:
rm -f exit.bin exit_elf exit.dis *.o
1) 编译器,即arm-linux-gcc,该编译器即为满足c99标准的arm-linux交叉C语言编译器,gcc中内置连接器,但是较为复杂。
2) 链接器,即arm-linux-ld,将编译器得到的各可执行文件,连接为一个文件并生成二进制文件。
3) 反汇编器,即arm-linux-objdump,将链接器生成二进制文件反汇编为汇编语言,以提供给程序分析。
根据上述对于交叉工具链的分析,如果需要使用C语言标准库,则必须要将静态链接库与实验代码相链接,由于stdio.h为C语言标准库,编译器内置文件中无对应源码,仅包含二进制文件,采用ld链接器的方式,出现了gcc与链接器版本不兼容的情况。因此,希望采用gcc内置链接器进行代码链接,以得到标准静态链接库;只是发现ubuntu9版本较旧,其配置gcc编译器版本也与市面主流版本不同,暂时没有解决。
6. 实验总结
6.1. 实验结果
本次实验内容较少,实验任务全部完成,深入了解了UART通信原理以及嵌入式系统时钟分配方法。
6.2. 心得体会
本次实验内容较少,在实验本身实现过程中,没有太多的拓展空间,几乎与实验二内容相同,对于外部设备的使用而言,几乎所有的外部设备使用都有相似的工作流程,即设置设备工作方式(工作模式)——设置设备与CPU通信协议(数据传输方式)——设置CPU响应设备方式(中断或轮询等)——设备使能与管脚分配方式,所以在实验二中了解完外部设备的控制方式后,对于本次实验几乎没有区别。
但是,在本次实验探究C语言标准库的使用过程上,深入理解了ARM裸机开发的本质以及交叉编译的实质,但是受限于本次实验时间较短,无法完全实现printf重定向,略显遗憾,在日后的ARM裸机开发中,尝试解决该问题。
最后
源代码将考虑以git的形式开源上传,在完成大作业后统一开源分享。