STM32之IO口模擬IIC
本文介紹如何使用STM32標準外設庫的GPIO埠模擬IIC,本例程使用PB6和PB7模擬一路IIC。
本文適合對微控制器及C語言有一定基礎的開發人員閱讀,MCU使用STM32F103VE系列。
1. 簡介
IIC (Inter-Integrated Circuit)匯流排,也可寫作I2C,是PHILIPS 公司開發的兩線式序列匯流排,用於多裝置之間通訊,分為主機Master和從機Slave,主機和從機可以有多個,但一般情況下只有一個主機,從機之間可以通過地址進行區分,不同種類的裝置地址不同,如果同時接入多個相同種類的裝置,可以通過片選訊號對從機進行選擇。通訊只能由主機發起,支援的操作分為讀取和寫入,即主機讀取從機的資料,以及向從機寫入資料。
I2C兩線分別是時鐘線SCL和資料線SDA,其中SCL和SDA均由主機控制,可以設定成開漏輸出模式。
2. 協議說明
2.1. 匯流排傳輸訊號
- 空閒狀態:IIC空閒狀態時SCL和SDA均輸出高電平,初始狀態以及傳送結束訊號之後均為空閒狀態。
- 開始訊號:START,簡寫S,SCL為高電平,SDA由高電平向低電平跳變。
- 結束訊號:STOP,簡寫P,SCL為高電平,SDA由低電平向高電平跳變。
- 從機地址:SLAVE_ADDRESS,每種從機都有一個表示該裝置的地址,地址一般為7位,主機發起通訊時,通過 SDA 訊號線傳送裝置地址(SLAVE_ADDRESS)來查詢從機,緊跟裝置地址的一個數據位用來表示資料傳輸方向(R/W位),資料方向位為“1”時表示主機由從機讀資料,資料方向位為“0”時表示主機向從機寫資料。從機接收到匹配的地址後,會返回一個應答(ACK)訊號,只有接收到應答訊號後,主機才能繼續傳送或接收資料。
- 響應訊號:ACK/NACK,簡寫A,響應包括應答(ACK)和非應答(NACK),當資料接收端時,當裝置(無論主從機)接收到 I2C 傳輸的一個位元組資料或地址後,若希望對方繼續傳送資料,則需要向對方傳送“應答(ACK)”訊號,即SDA為低電平,傳送方會繼續傳送下一個資料;若接收端希望結束資料傳輸,則向對方傳送“非應答(NACK)”訊號,即SDA為高電平,傳送方接收到該訊號後會產生一個停止訊號,結束訊號傳輸。
- 主機寫入:SCL為高電平時,SDA有效,SDA為輸出模式,主機控制SDA輸出高電平時表示寫入1,SDA輸出低電平時表示寫入0。
- 主機讀取:SCL為高電平時,SDA有效,SDA為輸入模式,主機讀取SDA高電平時表示輸入1,SDA低電平時表示輸入0。此時主機釋放對 SDA 訊號線的控制,由從機控制 SDA 訊號線,主機接收訊號。
2.2. 基本讀寫過程
- 寫資料:主機先發一個開始訊號(S),然後傳送從機地址(SLAVE ADDRESS),後續跟上寫訊號(R/W位為0),然後等待從機的應答訊號(ACK位為0),主機向從機傳輸資料(DATA),資料包為1個位元組共8位,從高位到低位依次傳送,主機每傳送完1個位元組資料,都要等待從機的應答訊號(ACK),重複這個過程,可以向從機傳輸 N 個數據,當資料傳輸結束時,主機向從機發送一個停止傳輸訊號(P),表示不再傳輸資料。
- 讀資料:主機先發一個開始訊號(S),然後傳送從機地址(SLAVE ADDRESS),後續跟上讀訊號(R/W位為1),然後等待從機的應答訊號(ACK位為0),主機從從機讀取資料(DATA),資料包為1個位元組共8位,從高位到低位依次傳送,從機每傳送完1個位元組資料,都要等待主機的應答訊號(ACK),重複這個過程,可以從從機讀取 N 個數據,當主機希望停止接收資料時,就向從機返回一個非應答訊號(NACK),則從機自動停止資料傳輸,主機再向從機發送一個停止傳輸訊號(P),表示不再傳輸資料。
- 讀和寫資料:除了基本的讀寫,I2C 通訊還有一種是複合讀寫模式,該傳輸過程有兩次起始訊號(S)。一般在第一次傳輸中,主機通過 SLAVE_ADDRESS 尋找到從裝置後,傳送一段“資料”,這段資料通常用於表示從裝置內部的暫存器或儲存器地址(注意區分它與 SLAVE_ADDRESS 的區別);在第二次的傳輸中,對該地址的內容進行讀或寫。也就是說,第一次通訊是告訴從機讀寫地址,第二次則是讀寫的實際內容。
2.3. 速度模式
標準模式傳輸速率為100kbit/s,即10us可以傳輸一個bit,如果用GPIO模擬I2C時電平變換時需要增加適當的延時。
3. 初始化
初始化跟普通GPIO類似,只是輸出模式設定為開漏輸出。
其中SCL始終輸出訊號,但SDA需要支援輸出訊號和讀取訊號,當設定為開漏輸出時,如果需要輸出訊號,則正常輸出即可,如果需要讀取訊號,則MCU將SDA輸出高電平,此時如果從機輸出低電平,則SDA被拉低,此時MCU可以讀到低電平。即GPIO引腳為開漏輸出模式時,MCU輸出高電平時,即釋放了該引腳的控制,此時該引腳的電平取決於從機的輸出,且MCU仍可以讀取該引腳的電平。
GPIO初始化完成之後,可以將SCL和SDA置為高電平,即釋放該引腳的控制,如果總線上有多個主機,則不會干擾其他裝置的通訊。
4. 訊號模擬
需要按照通訊訊號的時序,實現START、STOP、ACK、NACK、Read、Write和WaitAck訊號。
完整程式碼(僅自己編寫的部分)
1 #define IIC_SCL_1 GPIO_SetBits(GPIOB, GPIO_Pin_6) /* SCL = 1 */ 2 #define IIC_SCL_0 GPIO_ResetBits(GPIOB, GPIO_Pin_6) /* SCL = 0 */ 3 4 #define IIC_SDA_1 GPIO_SetBits(GPIOB, GPIO_Pin_7) /* SDA = 1 */ 5 #define IIC_SDA_0 GPIO_ResetBits(GPIOB, GPIO_Pin_7) /* SDA = 0 */ 6 7 #define IIC_READ_SDA() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7) /* 讀SDA口線狀態 */ 8 9 //初始化IIC 10 void IIC_Init(void) 11 { 12 GPIO_InitTypeDef GPIO_InitStructure; 13 14 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); 15 16 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; 17 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ; //開漏輸出 18 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 19 GPIO_Init(GPIOB, &GPIO_InitStructure); 20 21 IIC_Stop(); 22 } 23 24 //產生IIC起始訊號 25 //SCL為高電平時SDA由高變低 26 /* 27 SCL: ̄ ̄ ̄ ̄ ̄\_ 28 SDA: ̄ ̄\____ 29 */ 30 void IIC_Start(void) 31 { 32 IIC_SDA_1; 33 IIC_SCL_1; 34 delay_us(4); 35 IIC_SDA_0; 36 delay_us(4); 37 IIC_SCL_0; 38 } 39 40 //產生IIC停止訊號 41 //SCL為高電平時SDA由低變高 42 //IIC空閒時SCL和SDA均輸出高電平,這樣不會干擾其他裝置的收發 43 /* 44 SCL: ̄ ̄ ̄ ̄ 45 SDA:__/ ̄ 46 */ 47 void IIC_Stop(void) 48 { 49 IIC_SDA_0; 50 IIC_SCL_1; 51 delay_us(4); 52 IIC_SDA_1; 53 } 54 55 //等待應答訊號到來 56 //返回值:1,接收應答失敗 57 // 0,接收應答成功 58 uint8_t IIC_WaitAck(void) 59 { 60 uint8_t errCount = 0; 61 uint8_t ack = 0; 62 63 IIC_SDA_1; 64 delay_us(4); 65 IIC_SCL_1; 66 delay_us(4); 67 68 while(IIC_READ_SDA()) 69 { 70 errCount++; 71 if(errCount > 250){ 72 ack = 1; 73 break; 74 } 75 } 76 IIC_SCL_0; 77 78 return ack; 79 } 80 81 //產生應答ACK 82 //SCL為高電平時SDA為低電平表示應答 83 /* 84 SCL:  ̄ ̄\____ 85 SDA:_______/ ̄ 86 */ 87 void IIC_Ack(void) 88 { 89 IIC_SDA_0; 90 delay_us(4); 91 IIC_SCL_1; 92 delay_us(4); 93 IIC_SCL_0; 94 delay_us(4); 95 IIC_SDA_1; //釋放SDA 96 } 97 98 //產生非應答NACK 99 //SCL為高電平時SDA為高電平表示非應答 100 /* 101 SCL:  ̄ ̄\__ 102 SDA: ̄ ̄ ̄ ̄ ̄ ̄ ̄ 103 */ 104 void IIC_NAck(void) 105 { 106 IIC_SDA_1; 107 delay_us(4); 108 IIC_SCL_1; 109 delay_us(4); 110 IIC_SCL_0; 111 delay_us(4); 112 } 113 114 //IIC傳送一個位元組 115 /* 116 SCL:_ _/ ̄\__ _/ ̄ ̄\__ _/ ̄ ̄\__ _/ ̄ ̄\__ _/ ̄ ̄\__ _/ ̄ ̄\__ _/ ̄ ̄\__ _/ ̄ ̄\__ 117 SDA:-- ------------ -------------- -------------- -------------- -------------- -------------- -------------- -------------- 118 */ 119 void IIC_WriteByte(uint8_t txd) 120 { 121 uint8_t i; 122 123 IIC_SCL_0; 124 for(i = 0; i < 8; i++) 125 { 126 (txd & 0x80) ? IIC_SDA_1 : IIC_SDA_0; 127 txd <<= 1; 128 129 delay_us(4); 130 IIC_SCL_1; 131 delay_us(4); 132 IIC_SCL_0; 133 delay_us(4); 134 } 135 IIC_SDA_1; 136 } 137 138 //讀1個位元組,ack=1時,傳送ACK,ack=0,傳送NACK 139 /* 140 SCL: ̄ ̄\__ / ̄ ̄\__ / ̄ ̄\__ / ̄ ̄\__ / ̄ ̄\__ / ̄ ̄\__ / ̄ ̄\__ / ̄ ̄\__ 141 SDA:========== ============ ============ ============ ============ ============ ============ ============ 142 */ 143 uint8_t IIC_ReadByte(uint8_t ack) 144 { 145 uint8_t i, rcv = 0; 146 147 for(i = 0; i < 8; i++) 148 { 149 rcv <<= 1; 150 IIC_SCL_1; 151 delay_us(4); 152 if(IIC_READ_SDA()){ 153 rcv++; 154 } 155 IIC_SCL_0; 156 delay_us(4); 157 } 158 159 ack ? IIC_Ack() : IIC_NAck(); 160 161 return rcv; 162 }