第4章HELLO WORLD——重现经典
“HELLO WORLD”是一个经典程序,在一些编程书籍或教程中几乎都有出现,并作为入门级的简单练习提供给学习者。这里不仅仅将其重现,更重要的是让读者了解USART的相关知识及使用,这是因为在单片机开发的过程中经常会使用到USART。
4.1 USART简述
对于USART,下面从寄存器和相关电路两方面来进行说明。
4.1.1 USART的寄存器
ATmega88V里只有一个USART,其主要特点为:全双工操作,异步或同步操作,有高精度的波特率发生器及3个独立中断等。USART的方框图如图4.1所示。从图4.1中可以看出它包含4部分,分别是时钟发生器、发送器、接收器和控制及状态寄存器。时钟发生器主要用于对USART的时钟进行设置,与其相关的寄存器是波特率寄存器。发送器与接收器共同使用同一个数据寄存器UDRn,但它们都有各自的移位寄存器,用来处理发送或接收的数据。而控制及状态部分主要是采用3个状态和控制寄存器对整个USART进行设置的。
由上述表述可知,USART包含6个寄存器,分别是USART数据寄存器(UDRn)、USART状态和控制寄存器A/B/C(UCSRnA/B/C)和USART波特率寄存器(UBRRL/H)。
(1)USART数据寄存器(UDRn):UDRn为发送/接收数据缓冲寄存器。当设置为接收模式时,从UDRn读取接收到的数据;而设置为发送模式时,将要发送的数据赋予UDRn即可。
(2)USART状态和控制寄存器A/B/C(UCSRnA/B/C):这3个寄存器用于对USART工作在哪种模式下进行设置并提供状态的读取。
(3)USART波特率寄存器(UBRRL/H,也即UBRRn):UBRRL/H用于对USART的波特率进行设置,在ATmega88V手册中也提供了一些参考值。在指定晶振下,对应波特率,该寄存器有对应的参考值。选择晶体时,一般情况下应使用推荐的晶体。
虽然这几个寄存器很简单,但它们的每个位都需要按照需求来设置,一般使用的是7.3728MHz的晶体,其波特率设置为9600bps。
图4.1 USART的方框图
4.1.2 USART的相关电路
如果要将USART输出的数据显示在计算机上,需要增加一个RS -232及一些相关器件。这是因为USART输出的是TTL电平,而计算机需要的CMOS电平,需要一个转换器件——RS-232对电平进行转换,如图4.2所示。该连接图在很多单片机中都是可以使用的,只是需要将USART的输出/输入端口对应好。
图4.2 RS-232外围电路图
需要注意的是:运用该电路时,应先下载一个RS -232的手册看一下,以对其外围电路有一定的了解。
使用USART传输数据时,需要设置具体的传输速率或通信速率。如果收/发两设备设置的通信速率不一致,则它们不能正常通信。如表4.1所示是在通用振荡器频率下设置UBRRn的例子。
表4.1 在通用振荡器频率下设置UBRRn的例子
注:[1]当波特率最大时,UBRRn=0,误差=0.0%。
RS-232和RS-485的比较如下。
MCU的数据传输到PC时使用的是RS-232,传输到其他MCU时使用的是RS-485或直接连接(如果在同一个板上,MCU的供电电压一致,则可以直接连接);RS-485是一个半双工器件,即同一时间只能发送或接收,两者不可同时进行,而RS-232是一个全双工器件。
RS-485在很多工业设备中被广泛应用。RS -485芯片如图4.3所示,其中RO/DI接单片机,A/B线上挂载多个设备。每次只有一个设备向A/B总线发送数据,其他设备都在接收,若数据格式正确,则对应的设备就进行相应的操作。
图4.3 RS-485芯片
多设备挂接485总线的示意图如图4.4所示。
图4.4 多设备挂接485总线的示意图
4.2 在Proteus中显示“HELLO WORLD”
本节将基于Proteus仿真软件实现在Proteus中显示“HELLO WORLD”。
4.2.1 驱动USART
在驱动USART之前,先来了解一下USART的初始化配置(要想驱动USART,首先需要对其进行初始化):
(1)给对应的I/O设置好方向及电平的高低;
(2)设置USART的波特率;
(3)设置数据的字符长度;
(4)设置是否使能收发,是否中断。
对于上述4个步骤,后3个没有固定的先后顺序。
注意:编写程序时,应尽量使用通用的定义,这样有利于代码的移植;而且代码书写应使用标准风格,以方便代码的维护。
打开AVR Studio 4,首先新建一个工程,命名为USART(该工程的设置与前面介绍的流水灯工程的设置一致),然后在HAL_usart.h中添加以下代码:
/******************************************************************/ /* AVR USART驱动H文件 */ /******************************************************************/ #ifndef _HAL_USART_H_ #define _HAL_USART_H_ //-------------------------------------------------INCLUDE #include "main.h" //包含库文件 //--------------------------------------------------PAR #define fosc 7372800 //晶体值 #define baud 9600 //波特率 //--------------------------------FUNCTION,将外部要调用到的函数书写到此 void USART_init(void); void USART_txrom(const char*data,uint leng); void USART_txram(uchar*data,uint leng); #endif
接着在HAL_usart.c中添加以下代码:
/******************************************************************/ /* AVR USART驱动C文件 */ /******************************************************************/ void USART_init(void) { UBRR0H=(fosc/16/(baud+1))/256; //设置波特率 UBRR0L=(fosc/16/(baud+1))%256; UCSR0B=(1 <<RXEN0)|(1 <<TXEN0); //使能发送接收 UCSR0C=(1 <<UCSZ01)|(1 <<UCSZ00); //8bit }/*------------------------------------------------------ 向USART发送一个字节 ------------------------------------------------------*/ void USART_txbyte(uchar x) { while(!((UCSR0A)&(1 <<UDRE0))); UDR0=x; } /*------------------------------------------------------ 向USART发送常字符串 ------------------------------------------------------*/ void USART_txrom(const char*data,uint leng) { while(leng) { while(!((UCSR0A)&(1 <<UDRE0))); UDR0=*data; data++; leng--; } } /*------------------------------------------------------ 向USART发送字符串 ------------------------------------------------------*/ void USART_txram(uchar*data,uint leng) { while(leng) { while(!((UCSR0A)&(1 <<UDRE0))); UDR0=*data; data++; leng--; } }
驱动USART时,除了需要编写初始化函数外,还需要编写发送与接收函数,后面的4.2.3节将详细介绍双机通信。
4.2.2 显示“HELLO WORLD”
main.c中的main()代码如下:
/********************************************** project:USART工程 IDE:AVR Studio 4+Winavr20070525 device:atmega88 author:lg date:2012-07-22 21:10 goal:重现HELLO WORLD,并熟悉运用USART ***********************************************/ #include "main.h" /************************************************ 设备初始化 ************************************************/ void DEVICE_init(void) { CLOSE_INT(); //关全局中断 //----------PB口 DDRB=0X00; //未使用也定义 PORTB=0X00; //----------PC口 DDRC=0X00; //未使用也定义 PORTC=0X00; //----------PD口 DDRD| =0XFF; //设置PD口的0~7口方向为输出 PORTD=0X00; //设置PD口的0~3口电平为低 USART_init(); //UART初始化 OPEN_INT(); //开全局中断 } //----------------------MAIN----------------------- int main(void) { //------设备初始化 DEVICE_init(); //-----------循环 ----------- while(1) { //----------------------------- _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms _delay_ms(500); //延时500ms USART_txrom("Hello World!",12); LED1_XOR(); } }
仿真电路如图4.5所示,图中使用了虚拟设备中的串口终端,这样在仿真的过程中可以显示USART发送过来的数据。在本仿真电路中,MCU的设置与前述电路相同,不同的是代码编译文件。
图4.5 仿真电路
单击“运行”按钮,可以在弹出的提示界面中看到“Hello World!usart test!”等字符,如图4.6所示。
图4.6 虚拟示波器显示的实验结果1
4.2.3 双机通信
双机通信是指在两个设备之间通过某种协议或关系进行信息传输、交换等操作。
由于我们刚刚接触单片机,所以在这里先做一个简单的实验:主机通过串口发字符到从机后,从机通过串口将其发送到虚拟终端。
主机的代码可以延用4.2.2节中工程的代码。由于从机只有在接收到主机发送过来的字符后才向虚拟终端发送字符,所以这里需要使用到串口中断。AVR提供了很多外设中断,其使用没有什么特殊的,只是需要注意中断的触发条件是什么。
从机HAL_usart.c的代码如下:
/******************************************************** HAL_usart.c /********************************************************/ //-------------------------------------------头文件 #include "HAL_usart.h" //#define fosc 7372800 //#define baud 9600 /*------------------------------------------------------ USART初始化函数 ------------------------------------------------------*/ void USART_init(void) { UBRR0H=(fosc/16/(baud+1))/256; //设置波特率 UBRR0L=(fosc/16/(baud+1))%256; UCSR0B=(1 <<RXCIE0)|(1 <<RXEN0)|(1 <<TXEN0); //使能发送接收 UCSR0C=(1 <<UCSZ01)|(1 <<UCSZ00);//8bit } /*------------------------------------------------------ 向USART发送一个字节 ------------------------------------------------------*/ void USART_txbyte(uchar x) { while(!((UCSR0A)&(1 <<UDRE0))); UDR0=x; } /*------------------------------------------------------ 向USART发送常字符串 ------------------------------------------------------*/ void USART_txrom(const char*data,uint leng) { while(leng) { while(!((UCSR0A)&(1 <<UDRE0))); UDR0=*data; data++; leng--; } } /*------------------------------------------------------ 向USART发送字符串 ------------------------------------------------------*/ void USART_txram(uchar*data,uint leng) { while(leng) { while(!((UCSR0A)&(1 <<UDRE0))); UDR0=*data; data++; leng--; } } //--------------------------------------------------------------------- // 串口接收结束中断 //--------------------------------------------------------------------- ISR(SIG_USART_RECV) { uchar tmp; tmp=UDR0; //获取接收到的字符 USART_txbyte(tmp); //将接收到的字符发送出去 }
在main.h中添加以下几条语句:
#define CLOSE_INT() cli() //关全局中断 #define OPEN_INT() sei() //开全局中断
在DEVICE_init()函数中添加以上语句后,除了需要开启对应的外设中断位外,还需要打开全局中断,否则中断是不运行的或无效的。这个读者可以自己尝试一下。
而从机的main()函数除了初始化设备外,只需要再添加一个while(1);语句,等着主机发送字符即可。
打开Proteus软件,建立双机调试工程,其仿真电路如图4.7所示。
图4.7 双机通信的仿真电路
按照要求分别对主/从机添加HEX文件,然后单击“运行”按钮,结果如图4.8所示。
图4.8 虚拟示波器显示的实验结果2
本实验的效果是:主机向从机发送“Hello World!”,从机接收到后将其转发到虚拟终端进行显示。
这里只给出简单的双机通信,想要更复杂的,读者可以自己去发挥、实践。
4.2.4 USART开发的用处
本章完成了HELLO WORLD的经典重现及双机通信,但是USART的运用远不只于此,如在前面提到的485总线及一些通信上的应用等。在一些开发过程中,当不存在显示设备或指示设备,但是需要查看数据时,也可以使用USART。由于在实际工程中不可能进行仿真,所以需要使用一些小技巧来解决问题,如使用USART将数据发送到界面上显示,或是在需要的观察点附近做上标记,输出设定字符,查看是否运行到此或输出该字符等——这是US-ART在程序调试中的应用。
另外,USART可以制定出一套属于自己的通信协议来,如485协议就是一个很广泛的应用。
4.3 小结
本章介绍了ATmega88V的USART(从其寄存器到相关电路再到驱动USART),并对一些相关开发软件进行了练习和使用。读者在自己的开发过程中应多多积累一些开发技巧。