本文介紹如何使用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 }