很多人不知道怎么看著時(shí)序圖寫程序,下面結(jié)合一個(gè)非標(biāo)準(zhǔn)的I2C器件,教大家如何寫一個(gè)高效的IO模擬I2C時(shí)序。
觀察該時(shí)序,具備I2C的開始信號(hào),I2C的結(jié)束信號(hào),I2C的應(yīng)答、非應(yīng)答、響應(yīng)應(yīng)答,以及寫字節(jié)和讀字節(jié)的基本操作時(shí)序。
下面,我們一步一步分析。
1、I2C開始信號(hào)
觀察時(shí)序圖,在SCLK高電平的狀態(tài)下,在SDIO產(chǎn)生一個(gè)下降沿是為開始信號(hào)。
void I2C_Start() { //設(shè)置I2C使用的兩個(gè)引腳為輸出模式 pinMode(SCLK_PIN, OUTPUT); pinMode(SDIO_PIN, OUTPUT); //在SCL為高電平的時(shí)候讓SDA產(chǎn)生一個(gè)下降沿是為開始信號(hào) digitalWrite(SDIO_PIN, 1); digitalWrite(SCLK_PIN, 1); digitalWrite(SDIO_PIN, 0); }
上述代碼即先將兩個(gè)引腳設(shè)置為輸出模式,然后在SCLK為高電平的時(shí)候在SDIO引腳輸出一個(gè)下降沿。
2、I2C停止信號(hào)
觀察時(shí)序圖,在SCLK為高電平的時(shí)候在SDIO引腳產(chǎn)生一個(gè)上升沿是為停止信號(hào)。
void I2C_Stop() { pinMode(SDIO_PIN, OUTPUT); //在SCL為高電平的時(shí)候讓SDA產(chǎn)生一個(gè)上升沿是為停止信號(hào) digitalWrite(SDIO_PIN, 0); digitalWrite(SCLK_PIN, 1); digitalWrite(SDIO_PIN, 1); }
這里采用的是Arduino編寫的IO基本操作,你可以替換成任意單片機(jī)的IO操作。
由于整個(gè)過程SCLK引腳一直是輸出狀態(tài),所以僅在開始信號(hào)中對(duì)SCLK初始化為輸出模式,而過程中可能會(huì)修改SDIO的輸入輸出模式,所以其他的函數(shù)開頭都要根據(jù)情況對(duì)SDIO引腳的模式進(jìn)行設(shè)置。
通過三行代碼實(shí)現(xiàn)在SCLK為高電平的時(shí)候在SDIO產(chǎn)生一個(gè)上升沿,實(shí)現(xiàn)停止信號(hào)。
3、寫字節(jié)操作
接下來,按照時(shí)序的順序編寫方便認(rèn)讀
I2C的讀寫字節(jié)是這么定義的:當(dāng)時(shí)鐘線為低電平的時(shí)候,允許修改數(shù)據(jù)線的電平狀態(tài),在時(shí)鐘線為高電平的時(shí)候讀取數(shù)據(jù)線的狀態(tài)。
因?yàn)槭菍懖僮鳎虼宋覀円葘r(shí)鐘線SCLK拉低,再修改SDIO的值,然后拉高時(shí)鐘。拉高后,從機(jī)就會(huì)從總線上讀取SDIO的狀態(tài),接著一位一位的這么發(fā)送。
void I2C_Write(uint8_t dat) { pinMode(SDIO_PIN, OUTPUT); //拉低時(shí)鐘線后可修改數(shù)據(jù)線的狀態(tài) digitalWrite(SCLK_PIN, 0); for(int i=0;i<8;i++) { digitalWrite(SDIO_PIN, (bool)(dat&0x80)); digitalWrite(SCLK_PIN, 1);//在高電平時(shí)候送出數(shù)據(jù) dat=dat<<1; digitalWrite(SCLK_PIN, 0);//拉低準(zhǔn)備下一個(gè)位的數(shù)據(jù)發(fā)送 } }
上述代碼正描述了這一情況:為了保證最后是低電平,這里將SCLK的第一次拉低放到循環(huán)外面,這樣可以用最少的執(zhí)行次數(shù)完成一個(gè)字節(jié)的寫任務(wù);同時(shí),結(jié)束完一個(gè)字節(jié)寫入后時(shí)鐘線是低電平狀態(tài)(時(shí)序圖中寫入的第一個(gè)字節(jié)為DeviceID,第二個(gè)字節(jié)為寄存器地址+讀寫位)。
寫完一個(gè)字節(jié)后,從機(jī)會(huì)對(duì)寫入事件進(jìn)行應(yīng)答,這個(gè)時(shí)候主級(jí)可以從總線上讀取應(yīng)答信號(hào)。
4、讀取從機(jī)應(yīng)答引號(hào)
應(yīng)答信號(hào)在寫入完一個(gè)字節(jié)后的低電平后由從機(jī)送出,在時(shí)鐘為高電平的時(shí)候可以讀取出來,我們注意到寫入自己操作后時(shí)鐘線已經(jīng)是低電平了,因此這個(gè)時(shí)候
只要拉高時(shí)鐘線,接下來就可以讀取應(yīng)答信號(hào),讀取完應(yīng)答信號(hào)根據(jù)時(shí)序圖應(yīng)該拉低時(shí)鐘準(zhǔn)備下一個(gè)字節(jié)的寫入。
bool I2C_RACK() { bool ack; pinMode(SDIO_PIN, INPUT); digitalWrite(SCLK_PIN, 1);//接收應(yīng)答信號(hào),當(dāng)時(shí)鐘拉高時(shí)候,從機(jī)送出應(yīng)答信號(hào) ack = digitalRead(SDIO_PIN); digitalWrite(SCLK_PIN, 0);//讀取完應(yīng)答信號(hào)后拉低時(shí)鐘。 return ack; }
如上代碼所示,即為接收從機(jī)應(yīng)答,拉高時(shí)鐘,讀取應(yīng)答,再拉低,返回應(yīng)答。如果從機(jī)應(yīng)答了,這里會(huì)讀取到一個(gè)低電平。
后面就是再寫入一個(gè)寄存器+讀寫位的地址,參靠上面的寫入操作。
寫入寄存器地址后,緊跟著又一個(gè)接收從機(jī)應(yīng)答信號(hào),然后從機(jī)就會(huì)送出數(shù)據(jù),送出的數(shù)據(jù)分高字節(jié)和低字節(jié),高低字節(jié)間要有一個(gè)主機(jī)發(fā)送給從機(jī)的應(yīng)答信號(hào),這樣從機(jī)酒知道主機(jī)收到了數(shù)據(jù),就會(huì)送出后面的低字節(jié)數(shù)據(jù)。
5、讀字節(jié)操作
注意,前面說過,讀寫都是總線在時(shí)鐘低電平時(shí)候修改數(shù)據(jù)線,在高電平送出。
因此,主機(jī)讀取從機(jī)送來的數(shù)據(jù)仍然是在高電平時(shí)候讀取。
uint8_t I2C_Read() { uint8_t dat=0; pinMode(SDIO_PIN, INPUT); for(int i=0;i<8;i++) { digitalWrite(SCLK_PIN, 1);//讀取數(shù)據(jù)時(shí)候是在時(shí)鐘的高電平狀態(tài)讀取 dat=dat<<1; if(digitalRead(SDIO_PIN)) { dat=dat|1; } digitalWrite(SCLK_PIN, 0);//拉低時(shí)鐘線準(zhǔn)備下一個(gè)位的讀取 } return dat; }
操作過程是將SDIO數(shù)據(jù)線的IO設(shè)置為輸入模式,準(zhǔn)備讀取,然后拉高時(shí)鐘,讀取數(shù)據(jù),移位,拉低循環(huán)讀取8位數(shù)據(jù)。
注意,操作完一個(gè)字節(jié)讀取任務(wù)后,時(shí)鐘線還是低電平。
讀取完一個(gè)字節(jié)后,主機(jī)要給從機(jī)發(fā)送一個(gè)應(yīng)答信號(hào),這樣從機(jī)會(huì)接著發(fā)低字節(jié)數(shù)據(jù)。
6、主機(jī)發(fā)送應(yīng)答信號(hào)給從機(jī)
void I2C_ACK() { pinMode(SDIO_PIN, OUTPUT); digitalWrite(SDIO_PIN, 0);//給從機(jī)發(fā)送應(yīng)答信號(hào),即拉低數(shù)據(jù)線,然后拉高時(shí)鐘讓從機(jī)讀取該應(yīng)答 digitalWrite(SCLK_PIN, 1); digitalWrite(SCLK_PIN, 0);//執(zhí)行完應(yīng)答后拉低時(shí)鐘線,準(zhǔn)備下一步動(dòng)作。 }
拉低數(shù)據(jù)線,然后在高電平的時(shí)候讓從機(jī)去讀取,之后拉低時(shí)鐘線準(zhǔn)備下一步接收動(dòng)作。
當(dāng)再接收一個(gè)字節(jié)后,就讀取完成了,這個(gè)時(shí)候就是產(chǎn)生一個(gè)非應(yīng)答信號(hào),然后發(fā)給總線結(jié)束信號(hào),告訴從機(jī)一個(gè)讀寫周期結(jié)束了。
7、主機(jī)非應(yīng)答信號(hào)
什么是非應(yīng)答信號(hào)呢?
就是接收完了數(shù)據(jù),釋放數(shù)據(jù)線,不去拉低數(shù)據(jù)線。
void I2C_NACK() { //非應(yīng)答信號(hào):即主機(jī)不再對(duì)從機(jī)進(jìn)行應(yīng)答,主機(jī)釋放數(shù)據(jù)線,即拉高數(shù)據(jù)線,然后給時(shí)鐘一個(gè)周期信號(hào)(拉高再拉低) pinMode(SDIO_PIN, OUTPUT); digitalWrite(SDIO_PIN, 1); digitalWrite(SCLK_PIN, 1); digitalWrite(SCLK_PIN, 0); }
將SDIO引腳設(shè)置為輸出,拉高數(shù)據(jù)線,即為釋放數(shù)據(jù)線,然后拉高拉低時(shí)鐘,即在時(shí)鐘線產(chǎn)生一個(gè)時(shí)鐘周期信號(hào)。
然后發(fā)送結(jié)束信號(hào)。結(jié)束信號(hào)在開頭已經(jīng)講明,即在時(shí)鐘線為高電平的狀態(tài)下,在數(shù)據(jù)線產(chǎn)生一個(gè)上升沿。
觀察以上代碼沒一個(gè)多余重復(fù)的操作動(dòng)作,即完美的視線了時(shí)序圖上的所有操作。
接下來就是利用上述的I2C成分進(jìn)行對(duì)寄存器的讀寫操作了。
8、讀寄存器
由于圖中設(shè)備的DeviceID 為0x80,即直接寫進(jìn)來,從機(jī)判斷是讀還是寫的字節(jié)在寄存器地址。
因此,將寄存器的地址左移一位,在末尾補(bǔ)上是讀(1)還是寫(0)。
uint16_t read_reg(uint8_t reg) { uint16_t dat=0; reg=(reg<<1)|1; I2C_Start(); I2C_Write(0x80); I2C_RACK(); I2C_Write(reg); I2C_RACK(); dat=I2C_Read(); dat=dat<<8; I2C_ACK(); dat=dat|I2C_Read(); I2C_NACK(); I2C_Stop(); return dat; }
9、寫寄存器操作
void write_reg(uint8_t reg, uint16_t dat) { reg=(reg<<1); I2C_Start(); I2C_Write(0x80); I2C_RACK(); I2C_Write(reg); I2C_RACK(); I2C_Write(dat>>8); I2C_RACK(); I2C_Write(dat&0xFF); I2C_NACK(); I2C_Stop(); }
最后,對(duì)寄存器讀寫函數(shù)測(cè)試。
void setup() { Serial.begin(115200); Serial.println("Hello I2C"); write_reg(0x02,0x2250); Serial.println(read_reg(0x02),HEX); write_reg(0x02,0x2281); Serial.println(read_reg(0x02),HEX); } void loop() { }
讀取的數(shù)值與寫入的是一樣的。
最后曬出完整的測(cè)試代碼:
#define SCLK_PIN 8 #define SDIO_PIN 9 void I2C_Start() { //設(shè)置I2C使用的兩個(gè)引腳為輸出模式 pinMode(SCLK_PIN, OUTPUT); pinMode(SDIO_PIN, OUTPUT); //在SCL為高電平的時(shí)候讓SDA產(chǎn)生一個(gè)下降沿是為開始信號(hào) digitalWrite(SDIO_PIN, 1); digitalWrite(SCLK_PIN, 1); digitalWrite(SDIO_PIN, 0); } void I2C_Stop() { pinMode(SDIO_PIN, OUTPUT); //在SCL為高電平的時(shí)候讓SDA產(chǎn)生一個(gè)上升沿是為停止信號(hào) digitalWrite(SDIO_PIN, 0); digitalWrite(SCLK_PIN, 1); digitalWrite(SDIO_PIN, 1); } void I2C_Write(uint8_t dat) { pinMode(SDIO_PIN, OUTPUT); //拉低時(shí)鐘線后可修改數(shù)據(jù)線的狀態(tài) digitalWrite(SCLK_PIN, 0); for(int i=0;i<8;i++) { digitalWrite(SDIO_PIN, (bool)(dat&0x80)); digitalWrite(SCLK_PIN, 1);//在高電平時(shí)候送出數(shù)據(jù) dat=dat<<1; digitalWrite(SCLK_PIN, 0);//拉低準(zhǔn)備下一個(gè)位的數(shù)據(jù)發(fā)送 } } uint8_t I2C_Read() { uint8_t dat=0; pinMode(SDIO_PIN, INPUT); for(int i=0;i<8;i++) { digitalWrite(SCLK_PIN, 1);//讀取數(shù)據(jù)時(shí)候是在時(shí)鐘的高電平狀態(tài)讀取 dat=dat<<1; if(digitalRead(SDIO_PIN)) { dat=dat|1; } digitalWrite(SCLK_PIN, 0);//拉低時(shí)鐘線準(zhǔn)備下一個(gè)位的讀取 } return dat; } bool I2C_RACK() { bool ack; pinMode(SDIO_PIN, INPUT); digitalWrite(SCLK_PIN, 1);//接收應(yīng)答信號(hào),當(dāng)時(shí)鐘拉高時(shí)候,從機(jī)送出應(yīng)答信號(hào) ack = digitalRead(SDIO_PIN); digitalWrite(SCLK_PIN, 0);//讀取完應(yīng)答信號(hào)后拉低時(shí)鐘。 return ack; } void I2C_ACK() { pinMode(SDIO_PIN, OUTPUT); digitalWrite(SDIO_PIN, 0);//給從機(jī)發(fā)送應(yīng)答信號(hào),即拉低數(shù)據(jù)線,然后拉高時(shí)鐘讓從機(jī)讀取該應(yīng)答 digitalWrite(SCLK_PIN, 1); digitalWrite(SCLK_PIN, 0);//執(zhí)行完應(yīng)答后拉低時(shí)鐘線,準(zhǔn)備下一步動(dòng)作。 } void I2C_NACK() { //非應(yīng)答信號(hào):即主機(jī)不再對(duì)從機(jī)進(jìn)行應(yīng)答,主機(jī)釋放數(shù)據(jù)線,即拉高數(shù)據(jù)線,然后給時(shí)鐘一個(gè)周期信號(hào)(拉高再拉低) pinMode(SDIO_PIN, OUTPUT); digitalWrite(SDIO_PIN, 1); digitalWrite(SCLK_PIN, 1); digitalWrite(SCLK_PIN, 0); } uint16_t read_reg(uint8_t reg) { uint16_t dat=0; reg=(reg<<1)|1; I2C_Start(); I2C_Write(0x80); I2C_RACK(); I2C_Write(reg); I2C_RACK(); dat=I2C_Read(); dat=dat<<8; I2C_ACK(); dat=dat|I2C_Read(); I2C_NACK(); I2C_Stop(); return dat; } void write_reg(uint8_t reg, uint16_t dat) { reg=(reg<<1); I2C_Start(); I2C_Write(0x80); I2C_RACK(); I2C_Write(reg); I2C_RACK(); I2C_Write(dat>>8); I2C_RACK(); I2C_Write(dat&0xFF); I2C_NACK(); I2C_Stop(); } void setup() { Serial.begin(115200); Serial.println("Hello I2C"); write_reg(0x02,0x2250); Serial.println(read_reg(0x02),HEX); write_reg(0x02,0x2281); Serial.println(read_reg(0x02),HEX); } void loop() { }
看完這篇文章,你學(xué)會(huì)純手工擼IO模擬I2C時(shí)序的代碼了嗎?
審核編輯:黃飛
-
總線
+關(guān)注
關(guān)注
10文章
2903瀏覽量
88399 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4346瀏覽量
62977 -
高電平
+關(guān)注
關(guān)注
6文章
154瀏覽量
21500 -
I2C驅(qū)動(dòng)
+關(guān)注
關(guān)注
0文章
9瀏覽量
7101 -
時(shí)鐘線
+關(guān)注
關(guān)注
0文章
7瀏覽量
3635
原文標(biāo)題:看時(shí)序圖寫I2C驅(qū)動(dòng),教你如何自己手?jǐn)]非標(biāo)I2C驅(qū)動(dòng)函數(shù)
文章出處:【微信號(hào):電子Online,微信公眾號(hào):電子Online】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
利用IO口模擬I2C時(shí)序進(jìn)而實(shí)現(xiàn)I2C通訊的步驟
I2C總線驅(qū)動(dòng)程序
I2C總線驅(qū)動(dòng)程序的實(shí)現(xiàn)
![<b class='flag-5'>I2C</b>總線驅(qū)動(dòng)<b class='flag-5'>程序</b>的實(shí)現(xiàn)](https://file1.elecfans.com//web2/M00/A4/67/wKgZomUMNA6AJpXYAABA6pGemw4550.jpg)
基于GAL的I2C總線時(shí)序模擬
![基于GAL的<b class='flag-5'>I2C</b>總線<b class='flag-5'>時(shí)序模擬</b>](https://file1.elecfans.com//web2/M00/A6/29/wKgZomUMPAqAT7bfAAAPGpxPiXs576.jpg)
軟件模擬i2c實(shí)現(xiàn)io腳時(shí)序電路的技巧
![軟件<b class='flag-5'>模擬</b><b class='flag-5'>i2c</b>實(shí)現(xiàn)<b class='flag-5'>io</b>腳<b class='flag-5'>時(shí)序</b>電路的技巧](https://file1.elecfans.com//web2/M00/A6/DD/wKgZomUMQPGABYGGAAAcrE7f4cY544.png)
深度解析IO模擬時(shí)序(SPI)的注意事項(xiàng)
![深度解析<b class='flag-5'>IO</b><b class='flag-5'>模擬</b><b class='flag-5'>時(shí)序</b>(SPI)的注意事項(xiàng)](https://file.elecfans.com/web1/M00/44/FA/o4YBAFpeqUKAAdemAACXZUW6Hg4505.png)
80C51單片機(jī)模擬I2C總線的主機(jī)程序分享
![80<b class='flag-5'>C</b>51單片機(jī)<b class='flag-5'>模擬</b><b class='flag-5'>I2C</b>總線的主機(jī)<b class='flag-5'>程序</b>分享](https://file.elecfans.com/web1/M00/7C/D4/o4YBAFwHgVKAbHRIAAApfPDO-bA322.jpg)
使用51單片機(jī)IO模擬I2C的程序免費(fèi)下載
![使用51單片機(jī)<b class='flag-5'>IO</b><b class='flag-5'>模擬</b><b class='flag-5'>I2C</b>的<b class='flag-5'>程序</b>免費(fèi)下載](https://file.elecfans.com/web1/M00/A0/42/pIYBAF1DsRyAPyg9AAPEPzUVNp0792.png)
DSP配置I2C通訊(非IO口軟件模擬時(shí)序)
![DSP配置<b class='flag-5'>I2C</b>通訊(非<b class='flag-5'>IO</b>口軟件<b class='flag-5'>模擬</b><b class='flag-5'>時(shí)序</b>)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
嵌入式內(nèi)核及驅(qū)動(dòng)開發(fā)-09IIC子系統(tǒng)框架使用(I2C協(xié)議和時(shí)序,I2C驅(qū)動(dòng)框架,I2C從設(shè)備驅(qū)動(dòng)開發(fā),MPU6050硬件連接
![嵌入式內(nèi)核及驅(qū)動(dòng)開發(fā)-09IIC子系統(tǒng)框架使用(<b class='flag-5'>I2C</b>協(xié)議和<b class='flag-5'>時(shí)序</b>,<b class='flag-5'>I2C</b>驅(qū)動(dòng)框架,<b class='flag-5'>I2C</b>從設(shè)備驅(qū)動(dòng)開發(fā),MPU6050硬件連接](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
硬件I2C與模擬I2C
![硬件<b class='flag-5'>I2C</b>與<b class='flag-5'>模擬</b><b class='flag-5'>I2C</b>](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
經(jīng)過驗(yàn)證的GPIO模擬I2C時(shí)序代碼
![經(jīng)過驗(yàn)證的GPIO<b class='flag-5'>模擬</b><b class='flag-5'>I2C</b><b class='flag-5'>時(shí)序</b>代碼](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
ESP 12E I2c基卡的I2C IO卡設(shè)計(jì)
![ESP 12E <b class='flag-5'>I2c</b>基卡的<b class='flag-5'>I2C</b> <b class='flag-5'>IO</b>卡設(shè)計(jì)](https://file.elecfans.com/web1/M00/D9/4E/pIYBAF_1ac2Ac0EEAABDkS1IP1s689.png)
評(píng)論