适用于初学者的AVR教程。
目标
理解通信协议的定义,实现串口基本通信。理解字符串定义,并通过字符串操作来实现串口通信控制LED变化。
P.S.数据手册上关于UART的讲述非常详细,且简单明了,还有对应的示例代码。本文仅概述常用的8N1 9600参数配置,旨在介绍如何使用手册。
使用到的硬件
Arduino uno
完整代码
UART_96008n1_loop:
1 |
|
UART
先来看看网络百科里怎么介绍通信协议和串行接口,当然有时间的话建议还是点击链接通读一下。
通信协议:
通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。
UART:
通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称作UART。它将要传输的资料在串行通信与并行通信之间加以转换。作为把并行输入信号转成串行输出信号的芯片,UART通常被集成于其他通讯接口的连结上。具体实物表现为独立的模块化芯片,或作为集成于微处理器中的周边设备。一般是RS-232C规格的,与类似Maxim的MAX232之类的标准信号幅度变换芯片进行搭配,作为连接外部设备的接口。在UART上追加同步方式的序列信号变换电路的产品,被称为USART(Universal Synchronous Asynchronous Receiver Transmitter)。
简单来说,通信协议就是一个标准,这个标准用于搭建两个设备间的通信通道。通信通道的建立被形象的称作“握手”,可以理解为两个人之间沟通交流的第一步。如果使用不同的通信协议,是无法成功握手通信的,好比一个普通的中国人是无法和英国人正常交流的。当然,你也可以说这个中国人学了英语,可以和英国人交流,或者英国人学了中文,可以和中国人交流。而这体现在通信上则是协议栈或者说协议族。通常来说一个协议族包含着很多协议,而使用该协议族的设备可以与单独使用某个该族内协议的设备兼容通信。
那么单片机之间是如何通过通信协议来通信的?以UART通信、ASCII编码为例,数据帧的传递是通过接口的电平变化来实现的。想发送字母“a”,那么其实通过接口发送的是数字“97”或者十六进制“0x61”。而映射到物理层,就是将该数字转换成二进制,即“0b0110 0001”,然后“1”为高电平、“0”为低电平。发送端通过改变GPIO-TXD的状态来把数据发送给接收端,接收端通过读取GPIO-RXD的电平变化来接收数据。
通常来说UART有四个引脚,TXD、RXD、GND和VCC,老式的RS232一般有9个引脚。TXD引脚为数据发送引脚,RXD为数据接收引脚,GND是地线,VCC是电源。因此若两个开发板想通过UART连接,则需将发送端的TXD与接收端的RXD相连、将接收端的TXD与发送端的RXD相连。通常地线是必须要接在一起的,这样会形成电气上的回路,使得通信更加稳定。当然现在的技术即使不接地线通信一般也不会丢包,但是最好是要接地。至于电源线,在逻辑电平一致不需使用电平转换器的情况下一般可以不接。而在逻辑电平不一致时需接在电平转换器上以提供标准电平。
上图是Atmega328P的UART的数据帧。开始位是第一位,后面9位是数据位,接着是奇偶校验位,最后是停止位。对于UART通信而言,重要的参数有:数据帧和波特率。数据帧需要设置数据包的格式,比如几位数据位、停止位,比如是否启用奇偶校验。波特率是通信传输的速度,以Atmega328p为例,其计算公式为: BAUD = fOSC/16(UBRRn+1) 。当然通常使用的是它的反公式: UBRRn = (fOSC/16BAUD)-1,因为在Atmega328p上是通过配置寄存器UBRRn来设置波特率大小的。通常数据手册上会有一个典型应用波特率表,可以对表根据波特率来查找对应的UBRRn的值。
Tips:两个设备间的通信不光要使用同样的通信协议,对该协议的配置也必须是一致。比如开发板A使用了9600的波特率,开发板B使用了15200的波特率,那么二者是无法握手通信的。因为不同的波特率决定了每个bit的带宽,即TXD状态改的频率。数据帧也一样,假使A板使用8bit数据位、B板9bit数据位。那么二者通信时,A发送的数据包将被错误解包。A板数据包的奇偶校验位(若启用)或停止位(若禁用奇偶校验)将被B板作为第九位来解包,自然会错误解包。
UART in Arduino
手册P143介绍了Arduino的UART特性。核心内容有:
1.全双工
2.支持同步传输
3.支持主从同步传输(SPI)
4.支持5—9bit数据帧和1/2bit停止位
5.支持硬件奇偶验校
其实因为Atmega328P芯片较小,Pin的数量也较少,在该板上的UART接口只有一组。而该组UART是USART,其SPI功能也是由该接口实现的。
上图是整个USART的结构框图。新手接触这种图一般都会一头雾水无从下手。其实该图某种意义上包含了整个UART,很值得去看。这也是为什么很多老玩家看了结构图就对如何使用该接口有了大概的思路。
首先看第一层,“Clock Generator”意思是时钟发生器。寄存器UBRRn的下面写的是波特率发生器,那么可以看出来是通过该寄存器来配置波特率的。而波特率的产生是基于时钟源的,所以右侧有OSC(系统时钟源)的字样。再往下涉及到同步模式,XCKn是时钟引脚,可以输出或者接收同步时钟。而接受或发送是通过引脚完成的,所以有个引脚控制模块。最后需要同步时钟逻辑模块(Sync Logic)来解析这个时钟。
类似的,在第二层传输层物理引脚为TXD。因为硬件支持奇偶校验,所以有个奇偶校验发生器。上文提到过,ASCII每个字符是由8bit组成的,所以需要移位寄存器来整合这8bit。其原理是每个移位脉冲下或左或右移动一位,直至8bit接收完。UDRn很明显是缓存寄存器,括号里也写了“Transmit”,即将要发送的值赋给该寄存器来完成通信。
第三层接收层物理引脚为RXD。大体与发送一致,区别在于对于接收有一个时钟复写模块来提取时序信息和一个数据复写模块来初步缓存数据。
最下面的是配置UART所需要用到的一些SFR。
所以看得懂这张图基本上就会使用UART了。当然,对于新手,在尝试理解结构图的同时,对于不甚明白的结构可以直接略过。因为很多是硬件方面的东西,在编程中完全用不到。可以在搞明白了UART的概念和使用方法后再回过来看这张图。
如何编程
首先需要统一通信设备间的参数标准。所谓的“9600 8N1”是常用的调试参数,它指的是波特率为9600、数据位为8、禁用奇偶校验和停止位为1。所以需要配置的参数就有波特率、数据位、奇偶校验位和停止位。
UCSRnA/B/C,这三个寄存器全称是“USART Control and Status Register n A/B/C”。是三个关于控制和状态的配置的寄存器。寄存器内每一位都有其对传输过程的定义,需要认真阅读每一位的定义。由于内容过多,具体定义请参考手册p159。
首先是配置波特率发生器,使用的是寄存器UBRR0L/H。为什么分为“L”和“H”两部分呢?因为一个寄存器是8位,其最大值为0xff(256),对于比较大的波特率的值是无法在一个寄存器内储存的。该寄存器的配置方法最简单是直接赋值,比如波特率为9600,从上图中查到对应的寄存器值为103,则:UBRR0=103;
。
数据帧由三个寄存器控制UCSRnB/C中的UCSZn0/1/2来配置,通过上图可以看到八位数据位需要将UCSZn0/1两位置为1。异步传输(普通模式)需要将UMSELn位置0。禁用奇偶校验需要UPMn位置0。选择1bit停止位需要USBS位置0。选择8bit则UCSZn1和UCSZn0位置1。即:
1 | UCSR0B = (1 << RXEN0) | (1 << TXEN0); |
简单封装一下,初始化函数为:
1 | void Init() |
对于发送,需要进行两步操作。第一步:确定所有的数据已经接收/发送完毕,即寄存器UDRn是空的。第二步:将需要发送的值赋给缓冲区。对于第一步的实现,使用while()
结构等待寄存器UCSRnA中的UDREN标志位被清零。对于第二步的实现,则直接将字符数据赋值给缓存寄存器UDRn。
简单封装一下,发送函数为:
1 | void UART_96008n1_send(unsigned char data) |
对于接收,也需要进行两步操作。第一步:确定所有的数据已经接收完毕。第二步:将缓冲区的值赋返回。对于第一步的实现,使用while()
结构等待寄存器UCSRnA中的RXCn标志位被清零。对于第二步的实现,则直接使用return
返回缓存寄存器UDRn的值。需要注意的是,由于调运了return
函数,在定义整个接收函数时需要将该函数的数据类型保持与c return
函数的返回值的数据类型一致。比如现在返回的是ASCII字符,那么的接收函数就应该定义为 unsigned char
类型。
简单封装一下,接收函数为:
1 | unsigned char UART_96008n1_receive(void) |
如何测试
对于一个通信协议的测试,通常来说会使用loop_test来进行测试,也就是将数据自发自收。需要注意的是,由于只有一个UART接口,所以当该接口被使用时(loop_test)是无法通过IDE中的串口监视器来监视数据的。也因此如果在烧录软件之前短接了TXD和RXD两个引脚,程序也无法正常烧录,因为该端口被占用了。所以要先将程序烧录,再短接TXD和RXD两个引脚。
对上文中的代码进行整合:
1 |
|
短接TXD和RXD后,如果通信正常,PB5(GPIO13)上的灯将会被点亮。
通过串口控制LED
1 |
|
上述代码是使用Luna语言完成的,其功能是通过串行监控器来控制LED。通过之前的教程和本章UART的教程,请尝试将其转换成AVR架构。实在解决不了的问题可以联系我:lizhang19933@gmail.com。