AVR开发 Arduino方法(四) 串行通信子系统

  Arduino UNO R3主处理器ATMega328P的串行通信子系统可以用于与计算机、外设或其他微控制器进行通信,它支持3种串行通信方式:通用同步/异步收发器,串行外设接口和两线串行接口。

1. 通用同步/异步收发器

  在串行通信中,波特率用来衡量传输速率的快慢,同步和异步的对象是波特率的时钟信号;同步通信的设备之间需要一条额外的时钟线,也因此同步方式可以提供更高的波特率;这里将以异步为例。

  下面的示例可以使通过串口发送给Arduino的数据回显到串口监视器上:

 1 // SerialEcho.ino
 2 char data;
 3 
 4 void setup() {
 5   Serial.begin(9600);
 6 }
 7 
 8 void loop() {
 9   if (Serial.available() > 0) {
10     data = Serial.read();
11     Serial.print(data);
12   }
13 }

   与通用同步/异步收发器相关的Arduino库函数有:

 

  Serial.begin(speed):打开串口0并设置它的波特率

  speed:串口0的波特率

  Serial.available():判断串口0的缓冲区内是否有数据

  函数返回串口0缓冲区内数据的字节数

  Serial.read():读取串口0输入数据

  函数返回串口0输入数据的一个字节

  Serial.print(val):向串口0打印数据

  val:打印的数据

  Serial.println(val):向串口0打印数据并换行

  val:打印的数据

 

  ATMega328P的串口05个相关寄存器控制,串口0状态和控制寄存器AUCSR0A的结构如下图所示:

RXC0

TXC0

UDRE0

FE0

DOR0

PE0

U2X0

MPCM0

接收完成标志位RXD和数据寄存器空标志位UDRE分别在完成一帧数据接收和发送缓冲区为空时被置为1,可以通过向它们写10

  串口状态和控制寄存器BUCSR0B的结构如下图所示:

RXCIE0

TXCIE0

UDRIE0

RXEN0

TXEN0

UCSZ02

RXB08

TXB08

向接收使能位RXNE或发送使能位TXNE写入1可以分别使能串口0的接收或发送功能。

  串口0控制寄存器CUCSR0C的结构如下图所示:

URSEL01

UMSEL00

UPM01

UPM00

USBS0

UCSZ01

UCSZ00

UCPOL0

向终止位选择控制位USBS0位写入0,则只有1个停止位,写入1则有2个停止位。数据帧长度控制位UCSZ0[2:0]同时存在UCSR0B寄存器和UCSR0C寄存器中,它和奇偶校验模式控制位UPM0[1:0]位的设置如下表所示:

UCSZ0[2:0]

数据帧长度

 

UPM0[1:0]

奇偶校验模式

000

5

 

00

无奇偶校验

001

6

 

010

7

 

01

(保留)

011

8

 

100

(保留)

 

10

偶校验

101

(保留)

 

110

(保留)

 

11

奇校验

111

9

 

  串口0波特率寄存器(UBRR0HUBRR0L的计算公式是:

  Arduino UNO R3开发板使用8位数据帧长度,1个停止位,无奇偶校验,通过直接访问寄存器改写以上程序为:

 1 // SerialEcho_reg.ino
 2 unsigned char USART0_Receive();
 3 void USART0_Transmit(unsigned char val);
 4 
 5 void setup() {
 6   UCSR0A = 0x20;
 7   UCSR0B = 0x18;
 8   UCSR0C = 0x06;
 9 
10   UBRR0H = 0x00;
11   UBRR0L = 0x67;
12 }
13 
14 void loop() {
15   USART0_Transmit(USART0_Receive());
16 }
17 
18 unsigned char USART0_Receive() {
19   while (!(UCSR0A & (1 << RXC0)));
20   return UDR0;
21 }
22 
23 void USART0_Transmit(unsigned char val) {
24   while (!(UCSR0A & (1 << UDRE0)));
25   UDR0 = val;
26 }

 

2. 串行外设接口

  串行外设接口是一种同步的串行通信方式,因此它需要比通用同步/异步收发器多一条时钟线。此外,串行外设接口还引入了主机和从机的概念,通信中使用的时钟信号由主机产生,从机只有在被主机选中时才能与其进行通信;因此,一个串行外设接口设备一般需要连接4条信号线:SPI时钟SCK,主入从出MISO,主出从入MOSISPI选中SS

74HC595是一种8位的存储器,它的结构如下图所示:

11SH_CP)引脚有上升沿产生时,14DS)引脚上的电平信号会被采样,并移入8位移位寄存器中,多余的位将从9Q7’)引脚移出;当12ST_CP)引脚上有上升沿产生,并且13OE)引脚为低电平时,移位寄存器中的内容会被复制到存储寄存器中并输出。

  74HC595芯片不是标准的串行外设接口设备,但可以使用串行外设接口向它输入数据,如图所示连接电路,Arduino开发板11PB3/MOSI)引脚连接到74HC595芯片14DS)引脚,13PB5/SCK)引脚连接到11SH_CP)引脚;74HC595芯片12ST_CP)引脚可以连接到任一Arduino数字引脚,这里是A0PC0)引脚:

  下面的示例代码可以使74HC595芯片连接的LED呈现明暗交替的图案:

 1 // ShiftOutLed.ino
 2 const int DS = 11;
 3 const int SH_CP = 13;
 4 const int ST_CP = A0;
 5 
 6 void setup() {
 7   pinMode(DS, OUTPUT);
 8   pinMode(SH_CP, OUTPUT);
 9   pinMode(ST_CP, OUTPUT);
10 
11   digitalWrite(ST_CP, LOW);
12   shiftOut(DS, SH_CP, MSBFIRST, B10101010);
13   digitalWrite(ST_CP, HIGH);
14 }
15 
16 void loop() {
17 }

   与串行外设接口相关的Arduino库函数有:

 

  shiftOut(dataPin, clockPin, bitOrder, value):作为主机移位输出

  dataPin:指定移位输出的引脚

  clockPin:指定同步时钟信号的引脚

  bitOrder:从高位开始发送数据(MSBFIRST)或从低位开始发送数据(LSBFIRST

  val:移位输出的数据

 

  ATMega328P的串行外设接口由2个相关寄存器控制,SPI控制寄存器SPCR的结构如下图所示:

SPIE

SPE

DORD

MSTR

CPOL

CPHA

SPR1

SPR0

SPI使能位SPE写入1则启用串行外设接口,写入0则禁用;数据序列位DORD位写入1则从SPI数据寄存器SPDR的高位开始发送,写入0则从低位开始发送;时钟相位位CPHA写入1则数据在上升沿采样,写入0则在下降沿采样。此外,Arduino作为主机,则主/从选择位MSTR需写入1

  SPI状态寄存器SPSR的结构如下图所示:

SPIF

WCOL

 

 

 

 

 

SPI2X

SPI2X位与SPCR寄存器中的SPR[1:0]位共同设定SPI的分频系数,如下表所示:

SPI2X

SPR[1:0]

时钟源

0

00

系统时钟4分频

0

01

系统时钟16分频

0

10

系统时钟64分频

0

11

系统时钟128分频

1

00

系统时钟2分频

1

01

系统时钟8分频

1

10

系统时钟32分频

1

11

系统时钟64分频

  通过直接访问寄存器改写以上程序为:

 1 // ShiftOutLed_reg.ino
 2 void setup() {
 3   DDRB |= (1 << PB3) | (1 << PB5);
 4   DDRC |= (1 << PC0);
 5   
 6   PORTC &= ~(1 << PC0);
 7   SPCR = 0x77;
 8   SPDR = 0xaa;
 9   PORTC |= (1 << PC0);
10 }
11 
12 void loop() {
13 }

3. 两线串行接口

  两线串行接口同样也是一种同步的串行通信方式,它的读和写时序如下图所示:

  

  

由于主机先发送从机地址,从机应答后再发送其他数据,因此两线串行接口不需要类似于串行外设接口的选择信号线;又因为采用半双工的通信方式,两线串行接口只需要一条数据线,所以一个两线串行接口设备一般只需要2条信号线,即时钟信号线SCL和数据信号线SDA

  两线串行接口可以工作在主机发送模式,主机接收模式,从机发送模式或从机接收模式,Arduino IDEWire库提供了这四种模式的示例,我们主要关注主机发送模式和主机接收模式,下面是这两个示例:

 1 // master_writer.ino
 2 #include <Wire.h>
 3 
 4 void setup() {
 5   Wire.begin();
 6 }
 7 
 8 byte x = 0;
 9 
10 void loop() {
11   Wire.beginTransmission(8);
12   Wire.write("x is ");
13   Wire.write(x);
14   Wire.endTransmission();
15 
16   x++;
17   delay(500);
18 }
19 
20 // master_reader.ino
21 #include <Wire.h>
22 
23 void setup() {
24   Wire.begin();
25   Serial.begin(9600);
26 }
27 
28 void loop() {
29   Wire.requestFrom(8, 6);
30 
31   while (Wire.available()) {
32     char c = Wire.read();
33     Serial.print(c);
34   }
35 
36   delay(500);
37 }

   与两线串行接口主机相关的Arduino库函数有:

 

  Wire.begin():作为主机打开两线串行接口

  Wire.beginTransmission(address):开始向指定地址从机传输数据

  address:指定从机的地址

  Wire.write(val):向从机发送数据

  val:发送的数据

  Wire.endTransmission():结束向从机发送数据

  Wire.requestFrom(address, quantity):向指定地址从机请求指定字节数的数据

  address:指定从机的地址

  quantity:指定请求的字节数

  Wire.available():判断两线串行接口的缓冲区内是否有数据

  函数返回两线串行接口缓冲区内数据的字节数

  Wire.read():读取两线串行接口输入的数据

  函数返回两线串行接口输入数据的一个字节

 

  ATMega328P的两线串行接口的主机模式由3个相关寄存器控制。两线串行接口波特率寄存器TWBR的计算公式是:


其中,TWPS是预分频系数,它由两线串行接口状态寄存器TWSR中的TWPS[1:0]位设置,寄存器的结构如下图所示:

TWS7

TWS6

TWS5

TWS4

TWS3

 

TWPS1

TWPS0

  两线串行接口控制寄存器TWCR的结构如下图所示:

TWINT

TWEA

TWSTA

TWSTO

TWWC

TWEN

 

TWIE

TWI使能位TWEN写入1则启用两线串行接口,写入0则禁用;向起始信号使能位TWSTA或停止信号使能位TWSTO写入1,则会产生起始信号或停止信号,停止条件产生后,TWSTO位会自动清零。

  TWI中断标志位被置1表示产生了相关事件的中断,通过判断TWSR寄存器高5位的值可以判断中断事件,如下表所示:

主机发送模式(TWPS[1:0] = 00

主机接收模式(TWPS[1:0] = 00

状态码

状态

状态码

状态

0x08

START已发送

0x08

SATRT已发送

0x10

重复START已发送

0x10

重复START已发送

0x18

SLA+W已发送,接收到ACK

0x38

SLA+RNOT ACK仲裁失败

0x20

SLA+W已发送,接收到NOT ACK

0x40

SLA+R已发送,接收到ACK

0x28

数据已发送,接收到ACK

0x48

SLA+R已发送,接收到NOT ACK

0x30

数据已发送,接收到NOT ACK

0x50

接收到数据,已产生ACK

0x38

SLA+W或数据的仲裁失败

0x58

接收到数据,已产生NOT ACK

  通过直接访问寄存器改写以上程序为:

  1 // master_writer_reg.ino
  2 #define READ        0x01
  3 #define WRITE        0x00
  4 void twi_write(byte address, byte val);
  5 
  6 void setup() {
  7   TWSR &= ~(1 << TWPS1) & ~(1 << TWPS0);
  8   TWBR = 0x48;
  9 }
 10 
 11 byte x = 0;
 12 
 13 void loop() {
 14   twi_write(8, x);
 15 
 16   x++;
 17   delay(500);
 18 }
 19 
 20 void twi_write(byte address, byte val) {
 21   // 发送开始
 22   TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
 23   while (!(TWCR & (1 << TWINT)));
 24   if ((TWSR & 0xf8) != 0x08) {
 25     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 26     return;
 27   }
 28 
 29   // 发送从机地址
 30   TWDR = (address << 1) | WRITE;
 31   TWCR = (1 << TWINT) | (1 << TWEN);
 32   while (!(TWCR & (1 << TWINT)));
 33   if ((TWSR & 0xf8) != 0x18) {
 34     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 35     return;
 36   }
 37 
 38   // 发送数据
 39   TWDR = val;
 40   TWCR = (1 << TWINT) | (1 << TWEN);
 41   while (!(TWCR & (1 << TWINT)));
 42   if ((TWSR & 0xf8) != 0x28) {
 43     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 44     return;
 45   }
 46 
 47   // 发送停止
 48   TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 49 }
 50 
 51 // master_reader_reg.ino
 52 #define READ        0x01
 53 #define WRITE        0x00
 54 
 55 byte data;
 56 void twi_read(byte address);
 57 
 58 void setup() {
 59   TWSR &= ~(1 << TWPS1) & ~(1 << TWPS0);
 60   TWBR = 0x48;
 61 
 62   Serial.begin(9600);
 63 }
 64 
 65 void loop() {
 66   twi_read(8);
 67   Serial.print(data);
 68 
 69   delay(500);
 70 }
 71 
 72 void twi_read(byte address) {
 73   // 发送开始
 74   TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
 75   while (!(TWCR & (1 << TWINT)));
 76   if ((TWSR & 0xf8) != 0x08) {
 77     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 78     return;
 79   }
 80 
 81   // 发送从机地址(伪写)
 82   TWDR = (address << 1) | WRITE;
 83   TWCR = (1 << TWINT) | (1 << TWEN);
 84   while (!(TWCR & (1 << TWINT)));
 85   if ((TWSR & 0xf8) != 0x18) {
 86     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 87     return;
 88   }
 89 
 90   // 发送重复开始
 91   TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
 92   while (!(TWCR & (1 << TWINT)));
 93   if ((TWSR & 0xf8) != 0x10) {
 94     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
 95     return;
 96   }
 97 
 98   // 发送从机地址(读)
 99   TWDR = (address << 1) | READ;
100   TWCR = (1 << TWINT) | (1 << TWEN);
101   while (!(TWCR & (1 << TWINT)));
102   if ((TWSR & 0xf8) != 0x40) {
103     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
104     return;
105   }
106 
107   // 读取数据
108   TWCR = (1 << TWINT) | (1 << TWEN);
109   while (!(TWCR & (1 << TWINT)));
110   if ((TWSR & 0xf8) != 0x58) {
111     TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
112     return;
113   }
114   data = TWDR;
115 
116   // 发送停止
117   TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
118 }
posted @ 2017-09-15 10:37  Lets_Blu  阅读(2810)  评论(1编辑  收藏  举报