ARM的串行口实验
一、 实验目的
1.掌握ARM 的串行口工作原理。
2.学习编程实现ARM 的UART 通讯。
3.掌握CPU 利用串口通讯的方法。
二、 实验内容
学习串行通讯原理,了解串行通讯控制器,阅读ARM 芯片文档,掌握ARM 的UART
相关寄存器的功能,熟悉ARM 系统硬件的UART 相关接口。编程实现ARM 和计算机之间
串行通讯:
ARM 监视串行口,将接收到的字符在液晶屏上显示出来。(计算机向串口发送数据是通
过键盘来实现的)
三、 预备知识
1、用ARM SDT 2.5 集成开发环境,编写和调试程序的基本过程。
2、ARM 应用程序的框架结构。
3、会使用Source Insight 3 编辑C 语言源程序。
4、了解串行总线
四、 实验设备及工具
硬件:ARM 嵌入式开发板、用于ARM7TDMI 的JTAG 仿真器、PC 机Pentumn100 以
上、串口线
软件:PC 机操作系统win98、ARM SDT 2.51 集成开发环境、仿真器驱动程序、Source
Insight 3.0
五、 实验原理及说明
1.异步串行I/O
异步串行方式是将传输数据的每个字符一位接一位(例如先低位、后高位)地传送。数据
的各不同位可以分时使用同一传输通道,因此串行I/O 可以减少信号连线,最少用一对线
即可进行。接收方对于同一根线上一连串的数字信号,首先要分割成位,再按位组成字符。
为了恢复发送的信息,双方必须协调工作。在微型计算机中大量使用异步串行I/O 方式,
双方使用各自的时钟信号,而且允许时钟频率有一定误差,因此实现较容易。但是由于每个
字符都要独立确定起始和结束(即每个字符都要重新同步),字符和字符间还可能有长度不定
的空闲时间,因此效率较低。
图3-1 串行通信字符格式
图3-1l 给出异步串行通信中一个字符的传送格式。开始前,线路处于空闲状态,送出连
续“1”。传送开始时首先发一个“0”作为起始位,然后出现在通信线上的是字符的二进制
编码数据。每个字符的数据位长可以约定为5 位、6 位、7 位或8 位,一般采用ASCII 编码。
后面是奇偶校验位,根据约定,用奇偶校验位将所传字符中为“1”的位数凑成奇数个或偶数个。也可以约定不要奇偶校验,这样就取消奇偶校验位。最后是表示停止位的“1”信号,
这个停止位可以约定持续1 位、1.5 位或2 位的时间宽度。至此一个字符传送完毕,线路又
进入空闲,持续为“1”。经过一段随机的时间后,下一个字符开始传送才又发出起始位。
每一个数据位的宽度等于传送波特率的倒数。微机异步串行通信中,常用的波特率为
50,95,110,150,300,600,1200,2400,4800,9600 等。
接收方按约定的格式接收数据,并进行检查,可以查出以下三种错误:
1.奇偶错:在约定奇偶检查的情况下,接收到的字符奇偶状态和约定不符。
2.帧格式错:一个字符从起始位到停止位的总位数不对。
3.溢出错:若先接收的字符尚未被微机读取,后面的字符又传送过来,则产生溢出错。
每一种错误都会给出相应的出错信息,提示用户处理。
2.串行接口的物理层标准
通用的串行I/O 接口有许多种,现仅就最常见的两种标准作简单介绍。
(—)EIA RS—232C
这是美国电子工业协会推荐的一种标准(Electronic industries Association Recoil-mended
Standard)。它在一种25 针接插件(DB—25)上定义了串行通信的有关信号。这个标准后来被
世界各国所接受并使用到计算机的I/O 接口中。
⑴ 信号连线
在实际异步串行通信中,并不要求用全部的RS—232C 信号,许多PC/XT 兼容机仅用
15 针接插件(DB—15)来引出其异步串行I/O 信号,而PC 中更是大量采用9 针接插件(DB
—9)来担当此任,因此这里也不打算就RS—232C 的全部信号作详细解释。图5.6.2 给出两
台微机利用RS—232C 接口通信的连线(无MODEM),我们按DB—25 的引脚号标注各个信
号。
下面对图5.6.2 中几个主要信号作简要说明。
保护地 通信线两端所接设备的金属外壳通过此线相联。当通信电缆使用屏蔽线时,常
利用其外皮金属屏蔽网来实现。由于各设备往往已通过电源线接通保护地,因此,通信线中
不必重复接此地线(图中用虚线表示)。例如使用9 针插头(DB—9)的异步串行I/O 接口就没
有引出保护地信号。
TXD/RXD 是一对数据线,TXD 称发送数据输出,RXD 称接收数据输入。当两台微
机以全双工方式直接通信(无MODEM 方式)时,双方的这两根线应交叉联接(扭接)。
信号地 所有的信号都要通过信号地线构成耦合回路。通信线有以上三条(TXD、RXD
和信号地)就能工作了。其余信号主要用于双方设备通信过程中的联络(握手信号),而且有些
信号仅用于和MODEM 的联络。若采取微型机对微型机直接通信,且双方可直接对异步串
行通信电路芯片编程,若设置成不要任何联络信号,则其它线都可不接。有时在通信线的同
一端将相关信号短接以“自握手”方式满足联络要求。这就是如图3-2(a)所示的情况。
RTS/CTS 请求发送信号RTS 是发送器输出的准备好信号。接收方准备好后送回清除
发送信号CTS 后,发送数据开始进行,在同一端将这两个信号短接就意味着只要发送器准
备好即可发送。
DCD 载波检测(又称接收线路信号检测)。本意是MODEM 检测到线路中的载波信号
后,通知终端准备接收数据的信号,在没有接MODEM 的情况下,也可以和RTS、CTS 短
接。
相对于MODEM 而言,微型机和终端机一样被称为数据终端DTE(Data Terminal
Equipment)而MODEM 被称为数据通信装置DCE(Data Communications Equipment),DTE 和
DCE 之间的连接不能像图3-2 中有“扭接”现象,而应该是按接插件芯号,同名端对应相
接。此处介绍的RS—232C 的信号名称及信号流向都是对DTE 而言的。
DTR/DSR 数据终端准备好时发DTR 信号,在收到数据通信装置装备好DSR 信号后,
方可通信。图3-2(a)中将这一对信号以“自握手”方式短接。
RI 原意是在MODEM 接收到电话交换机有效的拨号时,使RI 有效,通知数据终端准
备传送。在无MODEM 时也可和DTR 相接。
图3-2(b)给出了无MODEM 情况下,DTE 对DTE 异步串行通信线路的完整连接,它不
仅适用于微型机和微型机之间的通信,还适用于微型机和异步串行外部设备(如终端机、绘
图仪、数字化仪等)的连接。
⑵ 信号电平规定
RS—232C 规定了双极性的信号逻辑电平:
-3V 到-25V 之间的电平表示逻辑“1”。
+3V 到+25V 之间的电平表示逻辑“0”。
因此这是一套负逻辑定义。
以上标准称为EIA 电平。PC/XT 系列使用的信号电平是-12V 和+12V,符合EIA 标准,
但在计算机内部流动的信号都是TTL 电平,因此这中间需要用电平转换电路。常用芯片
MCl488 或SN75150 将TTL 电平转换为EIA 电平,MCl489 或SN75154 将EIA 电平转换为
TTL 电平。PC/XT 系列以这种方式进行串行通信时,在波特率不高于9600 的情况下,理
论上通信线的长度限制为15 米。
(二)20mA 电流环
20mA 电流环并没有形成一套完整的标准,主要是将数字信号的表示方法不使用电子的
高低,而改用20mA 电流的有无:“1”信号在环路中产生20mA 电流;“0”信号无电流产生。
当然也需要有电路来实现TTL 电平和20mA 电流之间的转换。图3-3 是PC/XT 微机中使用的一种20mA 电流环接口。当发送方SOUT=1 时,便有20mA 电流灌入接收方的光耦合器,
于是光耦合器导通,使SIN=1。反之当发送方SOUT=0 时环路电流为零,接收方光耦合器截
止,SIN=0。显然,当要求双工方式通信时,双方都应各有收发电路,通信联线至少要4 根。
由于通信双方利用光耦合器实现电气上隔离,而且信号又是双端回路方式,故有很强的抗干
扰性,可以传送远至1 千米的距离。
“0”、“1”信号的表示方法不同外,其他方面(如字符的传输格式)常借用RS—232C 标
准。因此PC/XT 微机中的异步串行道信接口往往将这两种标准做在一起,实际通过跨接线
从二者中择一使用。
3.ARM 自带的串行口寄存器
ARM 自带两个串行口,各带有16 字节的FIFO(先入先出寄存器),最大波特率115.2K。
每个UART 有7 种状态:溢出错误,校验错误,帧错误,暂停态,接收缓冲区准备好,发
送缓冲区空,发送移位缓冲器空,这些状态可以由相应的UTRSTATn/UERSTATn 表示,并
且与发送接收缓冲区相对应的有错误缓冲区。波特率的大小可以通过控制波特率寄存器
(UBRDIVn)控制,计算公式如下:
由上表可以看出,该寄存器的第6 位决定是否使用红外模式,位5~3 决定校验方式,
位2 决定停止位长度,位1 和0决定每帧的数据位数。
⑵ UART 控制寄存器UCONn,该寄存器决定UART 的各种模式。UART FIFO 控制寄
存器UFCONn,UART MODEM 控制寄存器,分别决定UART FIFO 和MODEM 的模式。其
中UFCONn 的第0 位决定是否启用FIFO,UMCONn 的第0 位是请求发送位,对我们来说
是比较重要的。另外读写状态寄存器UTRSTAT 以及错误状态寄存UERSTAT,可以反映芯
片目前的读写状态以及错误类型。FIFO 状态寄存器UFSTAT 和MODEM 状态寄存器
UMSTAT,通过前者可以读出目前FIFO 是否满以及其中的字节数;通过后者可以读出目前
MODEM 的CTS 状态。
⑶ 发送寄存器UTXH 和接收寄存器URXH,这两个寄存器存放着发送和接收的数据,
当然只有一个字节8位数据。需要注意的是在发生溢出错误的时候,接收的数据必须要被读
出来,否则会引发下次溢出错误。
⑷ 最后是波特率引子寄存器UBRDIV。该寄存器为十六位,算法参见上页的部分。
注意:由于ARM 工作时存在小端和大端两种工作模式,所以同样一个寄存器在不同模
式时地址也不一样,需要加以区别。
六、 实验步骤
1.不带操作系统的ARM 实现串行功能
⑴ 学习上述串行通讯原理,了解ARM 上相应寄存器的功能和各位的意义。
⑵ 打开一个新工程,定义与UART 有关的各个寄存器地址和一些特殊的位命令。主
要有以下各寄存器:
/* UART 的全部功能寄存器 */
#define rULCON0 (*(volatile unsigned *)0x1d00000)
#define rULCON1 (*(volatile unsigned *)0x1d04000)
#define rUCON0 (*(volatile unsigned *)0x1d00004)
#define rUCON1 (*(volatile unsigned *)0x1d04004)
#define rUFCON0 (*(volatile unsigned *)0x1d00008)
#define rUFCON1 (*(volatile unsigned *)0x1d04008)
#define rUMCON0 (*(volatile unsigned *)0x1d0000c)
#define rUMCON1 (*(volatile unsigned *)0x1d0400c)
#define rUTRSTAT0 (*(volatile unsigned *)0x1d00010)
#define rUTRSTAT1 (*(volatile unsigned *)0x1d04010)
#define rUERSTAT0 (*(volatile unsigned *)0x1d00014)
#define rUERSTAT1 (*(volatile unsigned *)0x1d04014)
#define rUFSTAT0 (*(volatile unsigned *)0x1d00018)
#define rUFSTAT1 (*(volatile unsigned *)0x1d04018)
#define rUMSTAT0 (*(volatile unsigned *)0x1d0001c)
#define rUMSTAT1 (*(volatile unsigned *)0x1d0401c)
#define rUBRDIV0 (*(volatile unsigned *)0x1d00028)
#define rUBRDIV1 (*(volatile unsigned *)0x1d04028)
#ifdef __BIG_ENDIAN //大端模式
#define rUTXH0 (*(volatile unsigned char *)0x1d00023)
#define rUTXH1 (*(volatile unsigned char *)0x1d04023)
#define rURXH0 (*(volatile unsigned char *)0x1d00027)
#define rURXH1 (*(volatile unsigned char *)0x1d04027)
#define WrUTXH0(ch) (*(volatile unsigned char *)(0x1d00023))=(unsigned char)(ch)
#define WrUTXH1(ch) (*(volatile unsigned char *)(0x1d04023))=(unsigned char)(ch)
#define RdURXH0() (*(volatile unsigned char *)(0x1d00027))
#define RdURXH1() (*(volatile unsigned char *)(0x1d04027))
#define UTXH0 (0x1d00020+3) //byte_access address by BDMA
#define UTXH1 (0x1d04020+3)
#define URXH0 (0x1d00024+3)
#define URXH1 (0x1d04024+3)
#else //小端模式
#define rUTXH0 (*(volatile unsigned char *)0x1d00020)
#define rUTXH1 (*(volatile unsigned char *)0x1d04020)
#define rURXH0 (*(volatile unsigned char *)0x1d00024)
#define rURXH1 (*(volatile unsigned char *)0x1d04024)
#define WrUTXH0(ch) (*(volatile unsigned char *)0x1d00020)=(unsigned char)(ch)
#define WrUTXH1(ch) (*(volatile unsigned char *)0x1d04020)=(unsigned char)(ch)
#define RdURXH0() (*(volatile unsigned char *)0x1d00024)
#define RdURXH1() (*(volatile unsigned char *)0x1d04024)
#define UTXH0 (0x1d00020) //byte_access address by BDMA
#define UTXH1 (0x1d04020)
#define URXH0 (0x1d00024)
#define URXH1 (0x1d04024)
#endif
将上述定义为一个AD 功能的头文件,并且加入到工程中(44b.h)。
⑶ 在上面的文件中添加下面的执行函数,并且建立一个库文件(*.C)包含各函
数。主要为如下几个:
void Uart_Init(int Uartnum, int mclk,int baud)//初始化函数,参数为端口号,时钟,波特率
{
int i;
if(mclk==0)
mclk=MCLK;
if(Uartnum==0){ //UART0
rUFCON0=0x0; //FIFO disable
rUMCON0=0x0; //UART0
rULCON0=0x3; //Normal,No parity,1 stop,8 bit
rUCON0=0x245; //rx=edge,tx=level,disable timeout int.,enable rx
//error int.,normal,interrupt or polling
rUBRDIV0=( (int)(mclk/16./baud + 0.5) -1 );
}
else{
rUFCON1=0x0;
rUMCON1=0x0; //UART1
rULCON1=0x3;
rUCON1=0x245;
rUBRDIV1=( (int)(mclk/16./baud + 0.5) -1 );
}
for(i=0;i<100;i++);
}
void Uart_SendString(int Uartnum, char *pt)//发送字符串函数,参数为端口号,发送数组
{
while(*pt){
if(*pt=='\n'){
Uart_SendByte(Uartnum, '\r');
Uart_SendByte(Uartnum, *pt++);
}
else
Uart_SendByte(Uartnum, *pt++);
}
}
void Uart_Printf(char *fmt,...) //串行口发送字符串函数,仅向口0发送。
{
va_list ap;
char string[256];
va_start(ap,fmt);
vsprintf(string,fmt,ap);
Uart_SendString(0, string);
va_end(ap);
}
void Uart_TxEmpty(int Uartnum) //检查发送缓冲区是否满函数,参数为端口号
{
if(Uartnum==0)
while(!(rUTRSTAT0 & 0x4)); //wait until tx shifter is empty.
else
while(!(rUTRSTAT1 & 0x4)); //wait until tx shifter is empty.
}
char Uart_Getch(char* Revdata, int Uartnum, int timeout) //串行口检测函数,将串口接收
//到的数据放入变量,参数为变量,端口号,超时时间
{
int i=0;
if(Uartnum==0){
while(!(rUTRSTAT0 & 0x1)){ //Receive data read
OSTimeDly(1);
if(timeout==0)
continue;
if(++i>=timeout)
return FALSE;
}
*Revdata=RdURXH0();
return TRUE;
}
else{
while(!(rUTRSTAT1 & 0x1)){ //Receive data read
OSTimeDly(1);
if(timeout==0)
continue;
if(++i>=timeout)
return FALSE;
}
*Revdata=RdURXH1();
return TRUE;
}
}
上面的是与串行口操作有关的各种函数(44blib.c)。
⑷ 在主函数中首先初始化串行口0,然后通过串行口0 向外发送字符"U"。
/*************************串行口发送主程序**************************/
int Main(int argc, char **argv)
{
char c1;
Uart_Init(0,115200);
while(1)
{
Uart_SendByte(0xa);
Uart_SendByte(0xd);
c1=Uart_Getch();
Uart_SendByte(c1);
}
return 0;
}
2.带操作系统的ARM 实现串行口功能
当操作系统启动时,将自动初始化各串行口,所以应用程序调用串行口资源将变得非常
容易。值的注意的是,应用程序往往是多任务系统,为了实时监测串行口信息,在本操作环
境中必须单开一个串行口扫描任务,保证信息不丢失。
⑴ 打开一个已有的工程文件,在其中的主函数MAIN 中添加串行口的寄存器初始化
代码,并添加串行口和键盘扫描任务,串行口扫描任务的代码如下:
void Uart_Scan_Task1(void *Id)
{
char c1;
POSMSG pmsg1;
for (;;){
if(Uart_Getch(&c1,0,1))
{
pmsg1=OSCreateMessage(NULL,OSM_SERIAL,0,c1);
if(pmsg1)
SendMessage(pmsg1);
}
}
}//Uart_Scan_Task
当系统收到串行口信息时,将会自动向主任务发送一个串行口消息。主任务接收到该消
息,将会调用响应函数,响应该消息。
⑵ 添加消息响应函数的代码如下:
void onSerial(int portn, char c)
{
LCD_ChangeMode(DspTxtMode);
LCD_printf("%c\n",c);
Uart_SendByte(0,c);
}
⑶ 添加主任务
void Main_Task(void *Id) //Main_Test_Task
{
POSMSG pMsg=0;
ClearScreen();
//消息循环
for(;;){
pMsg=WaitMessage(0); //等待消息switch(pMsg->Message)
{
case OSM_SERIAL:
onSerial(pMsg->WParam,pMsg->LParam);
break;
}
DeleteMessage(pMsg);//删除消息,释放资源
}
}
通过上面两小段代码,系统就可以调用串行口资源发送信息和接收信息,相对于不
带操作系统串行口任务调用,上面的程序要简单的多。从中大家也可以体会到在操作系
统的基础上开发应用程序的好处。
七、 思考题
1.232 串行通讯的数据格式是什么?
2.串行通讯最少需要几根线,分别如何连接?
3.ARM 的串行口有几个,相应的寄存器是什么?
4.带操作系统和不带操作系统控制ARM 的串行口有什么区别?
5.为什么需要建立串行口扫描任务?
评论
查看更多