Arduino UNO R3主處理器ATMega328P的串行通訊子系統能夠用於與計算機、外設或其餘微控制器進行通訊,它支持3種串行通訊方式:通用同步/異步收發器,串行外設接口和兩線串行接口。git
在串行通訊中,波特率用來衡量傳輸速率的快慢,同步和異步的對象是波特率的時鐘信號;同步通訊的設備之間須要一條額外的時鐘線,也所以同步方式能夠提供更高的波特率;這裏將以異步爲例。異步
下面的示例可使經過串口發送給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的串口0由5個相關寄存器控制,串口0狀態和控制寄存器A(UCSR0A)的結構以下圖所示:
RXC0 |
TXC0 |
UDRE0 |
FE0 |
DOR0 |
PE0 |
U2X0 |
MPCM0 |
接收完成標誌位RXD和數據寄存器空標誌位UDRE分別在完成一幀數據接收和發送緩衝區爲空時被置爲1,能夠經過向它們寫1清0。
串口狀態和控制寄存器B(UCSR0B)的結構以下圖所示:
RXCIE0 |
TXCIE0 |
UDRIE0 |
RXEN0 |
TXEN0 |
UCSZ02 |
RXB08 |
TXB08 |
向接收使能位RXNE或發送使能位TXNE寫入1能夠分別使能串口0的接收或發送功能。
串口0控制寄存器C(UCSR0C)的結構以下圖所示:
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波特率寄存器(UBRR0H和UBRR0L)的計算公式是:
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,主出從入MOSI和SPI選中SS。
74HC595是一種8位的存儲器,它的結構以下圖所示:
當11(SH_CP)引腳有上升沿產生時,14(DS)引腳上的電平信號會被採樣,並移入8位移位寄存器中,多餘的位將從9(Q7’)引腳移出;當12(ST_CP)引腳上有上升沿產生,而且13(OE)引腳爲低電平時,移位寄存器中的內容會被複制到存儲寄存器中並輸出。
74HC595芯片不是標準的串行外設接口設備,但可使用串行外設接口向它輸入數據,如圖所示鏈接電路,Arduino開發板11(PB3/MOSI)引腳鏈接到74HC595芯片14(DS)引腳,13(PB5/SCK)引腳鏈接到11(SH_CP)引腳;74HC595芯片12(ST_CP)引腳能夠鏈接到任一Arduino數字引腳,這裏是A0(PC0)引腳:
下面的示例代碼可使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 IDE的Wire庫提供了這四種模式的示例,咱們主要關注主機發送模式和主機接收模式,下面是這兩個示例:
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+R或NOT 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 }