AVR開發 Arduino方法(四) 串行通訊子系統

  Arduino UNO R3主處理器ATMega328P的串行通訊子系統能夠用於與計算機、外設或其餘微控制器進行通訊,它支持3種串行通訊方式:通用同步/異步收發器,串行外設接口和兩線串行接口。git

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庫函數有:oop

 

  Serial.begin(speed):打開串口0並設置它的波特率ui

  speed:串口0的波特率spa

  Serial.available():判斷串口0的緩衝區內是否有數據3d

  函數返回串口0緩衝區內數據的字節數code

  Serial.read():讀取串口0輸入數據對象

  函數返回串口0輸入數據的一個字節blog

  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 }
相關文章
相關標籤/搜索