PIC單片機(jī)之I2C通信-主-從模式
主模式:
我們今天來(lái)講I2C通信。那I2C通信的特點(diǎn)是什么能。我們一般使用的串口 (半雙工異步串行通信)與I2C 有什么區(qū)別呢。
串口(半雙工異步串行通信):就是好像朋友在對(duì)話(huà)。我可以主動(dòng)和你講話(huà),你也可以主動(dòng)和我講話(huà)。
I2C:就好像上下級(jí)對(duì)話(huà)。一個(gè)領(lǐng)導(dǎo)面對(duì)一個(gè)或者多個(gè)員工。只有領(lǐng)導(dǎo)主動(dòng)說(shuō)話(huà)的份兒,下面的員工不能主動(dòng)說(shuō)話(huà)。只有領(lǐng)導(dǎo)問(wèn)了,員工才能答。
I2C通信
I2C通信只需要兩個(gè)引腳 一個(gè)數(shù)據(jù)線(xiàn),一個(gè)時(shí)鐘線(xiàn)。 數(shù)據(jù)線(xiàn)顧名思義就是用來(lái)傳遞數(shù)據(jù)的。時(shí)鐘線(xiàn)是來(lái)決定數(shù)據(jù)傳輸?shù)乃俣?。?dāng)時(shí)鐘線(xiàn)為高電平時(shí),數(shù)據(jù)線(xiàn)上的數(shù)據(jù)才會(huì)被認(rèn)為是有效的。
數(shù)據(jù)線(xiàn)的 數(shù)據(jù)有四種狀態(tài) : 高電平,低電平,下降沿(高電平變低電平),上升沿(低電平變高電平)。
當(dāng)時(shí)鐘線(xiàn)為高電平時(shí)候這四種狀態(tài)分別代表:1,0,起始位,停止位。
如果我們發(fā)送的數(shù)據(jù)為十六進(jìn)制的0x88即是二進(jìn)制為10001000的數(shù)據(jù)是怎么發(fā)送的呢?我們就以此為例一步步講解。
1,常態(tài)
在不發(fā)送任何數(shù)據(jù)的時(shí)候數(shù)據(jù)線(xiàn)和時(shí)鐘線(xiàn)都為高電平。所以I2C通信在硬件設(shè)計(jì),需要在數(shù)據(jù)線(xiàn)和時(shí)鐘線(xiàn)上分別加上兩個(gè)上拉電阻。
2,起始
當(dāng)開(kāi)始發(fā)送數(shù)據(jù)的時(shí)候 時(shí)鐘線(xiàn)為高同時(shí)數(shù)據(jù)線(xiàn)從 高電平變低電平,代表開(kāi)始發(fā)送數(shù)據(jù)。
3,發(fā)送數(shù)據(jù)
發(fā)送完起始位后時(shí)鐘線(xiàn)變?yōu)榈碗娖?,在發(fā)送每一位的數(shù)據(jù)之前時(shí)鐘線(xiàn)有一段低電平,主要的作用是給數(shù)據(jù)線(xiàn)做電平變化用的。
我們現(xiàn)在要發(fā)送的第一個(gè)位是 1。
1、時(shí)鐘線(xiàn)為低,同時(shí)數(shù)據(jù)線(xiàn)從低電平變成高電平。
2、接著時(shí)鐘線(xiàn)變?yōu)楦唠娖?,此時(shí)接收方得知時(shí)鐘線(xiàn)為高,便查看數(shù)據(jù)線(xiàn)為高電平 說(shuō)明數(shù)據(jù)為 “1”。
3、我們要發(fā)送的下一個(gè)位為0。時(shí)鐘線(xiàn)再變?yōu)榈?,同時(shí)數(shù)據(jù)線(xiàn)從高電平變成低電平。
4、接著時(shí)鐘線(xiàn)再變?yōu)楦唠娖?,此時(shí)接收方得知時(shí)鐘線(xiàn)為高,便查看數(shù)據(jù)線(xiàn)為低電平 說(shuō)明數(shù)據(jù)為"0"。
5、再下一個(gè)為還為0。時(shí)鐘線(xiàn)再變?yōu)榈停瑫r(shí)數(shù)據(jù)線(xiàn)一直保持低電平不變。
6、接著時(shí)鐘線(xiàn)再變?yōu)楦唠娖剑藭r(shí)接收方得知時(shí)鐘線(xiàn)為高,便查看數(shù)據(jù)線(xiàn)為低電平 說(shuō)明數(shù)據(jù)為”0“。
以此類(lèi)推 直到發(fā)送完所有的位。
4,應(yīng)答(ACK)
當(dāng)接收方接收完一個(gè)字節(jié)的數(shù)據(jù)就要告訴對(duì)方我收到了。接收方如果接收到數(shù)據(jù)則控制數(shù)據(jù)線(xiàn)輸出低電平。否則為高電平。
5,停止
沒(méi)有下一個(gè)字節(jié)要發(fā)送,最后時(shí)鐘線(xiàn)變?yōu)楦唠娖胶?,?shù)據(jù)線(xiàn)從低電平變?yōu)楦唠娖?。代表?shù)據(jù)發(fā)送停止。
實(shí)例講解:使用單片機(jī)使用 RSM2257 電子音量控制芯片來(lái)控制音量。一個(gè)按鍵按下,聲音變大,一個(gè)按鍵按下,聲音變小。在加上一個(gè)按鍵,控制一個(gè)LED亮滅的程序。而且音量掉電保存。
介紹RSM2257.
子地址
在I2C通信中每一個(gè)從設(shè)備都有個(gè)子地址,因?yàn)镮2C支持一主多從,也就是說(shuō)有一個(gè)主機(jī)可以連接多個(gè)從機(jī)。每個(gè)從機(jī),都有個(gè)地址。就好像每個(gè)人的名字一樣來(lái)區(qū)分不同的設(shè)備。下面是RSM2257接口協(xié)議,首先先發(fā)送RSM2257 設(shè)備地址 10001000.然后再發(fā)送數(shù)據(jù)。
數(shù)據(jù)
RSM2257的數(shù)據(jù)是用來(lái)表示音量大小的。我們控制兩個(gè)音頻通道,以10dB為單位降低或增加音量。從功能設(shè)置位表格中可知數(shù)據(jù)為 11100B2B1B0.
B2B1B0的數(shù)值決定了音量。請(qǐng)?jiān)斠?jiàn) 衰減設(shè)置位。
單片機(jī)I2C通信初始化設(shè)置
1、設(shè)置端口為輸入
TRISC0 = input;
TRISC1 = input;
2、設(shè)置模式
我們?cè)O(shè)置ssp1控制寄存器的 SSP1M<3:0>.我們需要的是I2C主模式。設(shè)置如下
SSP1CON1bits.SSPM0 = 0;
SSP1CON1bits.SSPM1 = 0;
SSP1CON1bits.SSPM2 = 0;
SSP1CON1bits.SSPM3 = 1;// I2C Master mode ,clock=Fosc/(4*(SSPxADD+1))
3、設(shè)置時(shí)鐘線(xiàn)頻率RSM2257最大為100KHZ,我選擇設(shè)置為50KHZ.
使用計(jì)算公式clock=Fosc/(4*(SSPxADD+1)) 計(jì)算出SSP1ADD的值為0x9F;
SSP1ADD=0x9F;
4、開(kāi)啟I2C通信
SSP1CON1bits.SSPEN = 1;
單片機(jī)I2C發(fā)送程序
1、發(fā)送起始位
SSP1CON2bits.SEN = 1;//Start condition
while(SSP1CON2bits.SEN == 1);//waiting for Start condition completed.
2、發(fā)送地址
PIR1bits.SSP1IF = 0;
SSP1BUF = 0x88;//Device Address
while(PIR1bits.SSP1IF == 0);
PIR1bits.SSP1IF = 0;
// ~ACK 我們不理會(huì)接收方有沒(méi)有應(yīng)答。
3、發(fā)送 10db音量控制的數(shù)據(jù)
SSP1BUF = tx_data;//Data 10db level
while(PIR1bits.SSP1IF == 0);
PIR1bits.SSP1IF = 0;
4、發(fā)送1db音量控制的數(shù)據(jù)
// ~ACK
SSP1BUF = 0xD0;//Data 1db level
while(PIR1bits.SSP1IF == 0);
PIR1bits.SSP1IF = 0;
5,發(fā)送停止位
// ~ACK
SSP1CON2bits.PEN = 1;//Stop condition
關(guān)于I2C通信協(xié)議,RSM2257,PIC MSSP 模塊設(shè)置成I2C,更詳細(xì)的內(nèi)容就必須去看數(shù)據(jù)手冊(cè)了。
實(shí)例程序:程序分為main.c 和 define.h兩個(gè)文件 芯片PIC16LF1823,開(kāi)發(fā)環(huán)境MPLAB X IDE.
define.h文件
/**********RA*********/
//B'1111,1000'H F8
#define LED_SW RA5//IN
#define UP_SW RA4//IN
#define DOWN_SW RA3//IN
#define LED RA2//OUT
//RA1
//RA0
/**********RC***********/
//H FF
//RC0 SCL
//RC1 SDA
#define input 1
#define LED_VALUE 1
#define UP_VALUE 2
#define DOWN_VALUE 3
#define key_delay 300
main.c文件
#include
#include"define.h"
__CONFIG(FOSC_INTOSC&WDTE_OFF&PWRTE_ON&MCLRE_OFF&CP_ON&CPD_OFF&BOREN_ON
&CLKOUTEN_OFF&IESO_ON&FCMEN_ON);
__CONFIG(PLLEN_OFF&LVP_OFF) ;
void tx_pro(unsigned char tx_db);
unsigned char DB_VALUE;
void init_fosc(void)
{
OSCCON = 0xF0;//32MHZ
}
void init_gpio(void)
{
PORTA=0;
LATA =0;
ANSELA=0x00;
TRISA =0xF8;
PORTC=0;
LATC=0;
ANSELC = 0x00;
TRISC =0xFF;
}
void init_i2c_master()
{
TRISC0 = input;
TRISC1 = input;
SSP1CON1bits.SSPM0 = 0;
SSP1CON1bits.SSPM1 = 0;
SSP1CON1bits.SSPM2 = 0;
SSP1CON1bits.SSPM3 = 1;// I2C Master mode ,clock=Fosc/(4*(SSPxADD+1))
SSP1STATbits.SMP = 1;
SSP1ADD = 0x9F;//SCL CLOCK Frequency 50KHZ
SSP1CON1bits.SSPEN = 1;
}
void i2c_master_tx(unsigned char tx_data)
{
SSP1CON2bits.SEN = 1;//Start condition
while(SSP1CON2bits.SEN == 1);//waiting for Start condition completed.
PIR1bits.SSP1IF = 0;
SSP1BUF = 0x88;//Device Address
while(PIR1bits.SSP1IF == 0);
PIR1bits.SSP1IF = 0;
// ~ACK
SSP1BUF = tx_data;//Data 10db level
while(PIR1bits.SSP1IF == 0);
PIR1bits.SSP1IF = 0;
// ~ACK
SSP1BUF = 0xD0;//Data 1db level
while(PIR1bits.SSP1IF == 0);
PIR1bits.SSP1IF = 0;
// ~ACK
SSP1CON2bits.PEN = 1;//Stop condition
}
void delay(unsigned int n)
{
while(n--);
}
unsigned char key_board(void)
{
if(LED_SW==1)
{
delay(key_delay);
if(LED_SW==1)
{
while(LED_SW==1);
return LED_VALUE;
}
}
if(UP_SW==1)
{
delay(key_delay);
if(UP_SW==1)
{
while(UP_SW==1);
return UP_VALUE;
}
}
if(DOWN_SW==1)
{
delay(key_delay);
if(DOWN_SW==1)
{
while(DOWN_SW==1);
return DOWN_VALUE;
}
}
return 0;
}
void DB_INC(void)
{
if(DB_VALUE < 7)
{
DB_VALUE++;
eeprom_write(0x00, DB_VALUE);//將音量值保存到EEPROM這樣掉電后數(shù)據(jù)也不會(huì)丟失。
tx_pro(DB_VALUE);
}
}
void DB_DEC(void)
{
if(DB_VALUE > 0)
{
DB_VALUE --;
eeprom_write(0x00, DB_VALUE);
tx_pro(DB_VALUE);
}
}
void tx_pro(unsigned char tx_db)
{
tx_db |= 0xE0; //將高三位設(shè)置為1。表示兩個(gè)音頻通道,以10dB為單位降低或增加音量
i2c_master_tx(tx_db);//I2C發(fā)送數(shù)據(jù)程序
}
/*
*
*/
int main(int argc, char** argv) {
unsigned char keyvalue;
init_fosc();
init_gpio();
init_i2c_master();
LED=0;
DB_VALUE= eeprom_read(0x00);//讀eeprom 中保存的音量值
if(DB_VALUE > 7)//如果之前沒(méi)有設(shè)置過(guò)則音量不衰減
{
DB_VALUE = 0;
}
tx_pro(DB_VALUE);//用I2C通信設(shè)置RSM2257的音量
while(1)
{
keyvalue=key_board();//判斷按鍵程序,
switch(keyvalue)
{
case LED_VALUE://LED按鍵按下
{
LED = ~LED;
};break;
case UP_VALUE://音量加
{
DB_INC();
};break;
case DOWN_VALUE://音量減
{
DB_DEC();
};break;
}
}
}
從模式:
網(wǎng)上有許多講解單片機(jī) 實(shí)現(xiàn)I2C主模式,但是從模式的很少。我現(xiàn)在就來(lái)講講PIC單片機(jī)使用MSSP模塊實(shí)現(xiàn)I2C從模式。
有關(guān)I2C協(xié)議的具體介紹可以看 《PIC單片機(jī)之I2C(主模式)》,我們這里直接講解實(shí)例
實(shí)例講解:我們模仿 AT24C02 EEPROM 的協(xié)議。讓一個(gè)主模式的單片機(jī),來(lái)讀取從模式單片機(jī)的數(shù)據(jù)。
下面為AT24C02的隨機(jī)地址讀取的協(xié)議。
第一個(gè)字節(jié) :輸入7位地址和一位的寫(xiě)狀態(tài)位,
第二個(gè)字節(jié):然后寫(xiě)入EEPROM數(shù)據(jù)地址,
第三個(gè)字節(jié):輸入7位地址和一位的讀狀態(tài)位,
第四~N個(gè)字節(jié):讀出的EEPROM的數(shù)據(jù)。
我們來(lái)講解下程序的基本思路:我們使能了MSSP中斷,即是I2C接收中斷,當(dāng)PIC單片機(jī)接收到一個(gè)數(shù)據(jù)后就會(huì)產(chǎn)生中斷。那是接收到設(shè)備地址,還是接收到數(shù)據(jù),由SSP1STAT寄存器的狀態(tài)位來(lái)判斷。
需要判斷的狀態(tài)位分別是 :
數(shù)據(jù)和地址: 用來(lái)判斷接收到是地址還是數(shù)據(jù)
啟動(dòng)位: 用來(lái)判斷是否接收到啟動(dòng)位
讀寫(xiě): 用來(lái)判斷是寫(xiě)狀態(tài)還是讀狀態(tài)。
緩存滿(mǎn): 用來(lái)判斷緩沖區(qū)是否滿(mǎn)
我們以隨機(jī)地址讀取為例:講講程序執(zhí)行的過(guò)程
1,從單片機(jī)接收到啟示位和設(shè)備地址中斷:我們判斷SSP1STAT的狀態(tài)位為(寫(xiě)狀態(tài),地址,緩存滿(mǎn),接收到啟示位) 然后讀取緩存中的設(shè)備地址, 接著在讀取 需要讀/寫(xiě)的數(shù)據(jù)地址。
2,單片機(jī)再次接收到設(shè)備地址:我們判斷是SSP1STAT的狀態(tài)為(讀狀態(tài))然后從設(shè)備就輸出數(shù)據(jù)
我們以寫(xiě)字節(jié)數(shù)據(jù)為例:
1,從單片機(jī)接收到啟示位和設(shè)備地址中斷:我們判斷SSP1STAT的狀態(tài)位為(寫(xiě)狀態(tài),地址,緩存滿(mǎn),接收到啟示位) 然后讀取緩存中的設(shè)備地址, 接著在讀取 需要讀/寫(xiě)的數(shù)據(jù)地址。
2,單片機(jī)判斷SSP1STAT的狀態(tài)位為(寫(xiě)狀態(tài),數(shù)據(jù),緩存滿(mǎn))那么單片機(jī)就接收輸入的數(shù)據(jù)。
初始化設(shè)置:
1,設(shè)置I2C通信的兩引腳為CLK SCL為輸入,
TRISB6 = input;
TRISB4 = input;
2,將MSSP設(shè)置為I2C從模式,七位從地址
SSP1CONbits.SSPM0 = 0;
SSP1CONbits.SSPM1 = 1;
SSP1CONbits.SSPM2 = 1;
SSP1CONbits.SSPM3 = 0;// I2C slave mode ,7bit address
3,使能CLK時(shí)鐘
SSP1CONbits.CKP = 1; // enable clock
4,設(shè)置從設(shè)備地址為 0xA0
SSP1ADD =0xA0; //slave address is 0xa0
5,開(kāi)啟I2C
SSP1CONbits.SSPEN=1;//enable I2c
6,清楚狀態(tài)標(biāo)志
SSPSTAT=0;
7,使能I2C中斷
PIE1bits.SSP1IE = 1;//Enabe interrupt MSSP
INTCONbits.PEIE = 1;
INTCONbits.GIE = 1;
如果你要使用PIC單片機(jī)I2C從模式只要使用下面的代碼:
將void i2c_salve_interrupt_tx();void i2c_salve_interrupt_rx();放到中斷程序中,如下:
void interrupt isr(void)
{
if(SSP1IE && SSP1IF)
{
i2c_salve_interrupt_tx();
i2c_salve_interrupt_rx();
SSP1IF=0;
}
}
將初始化函數(shù)init_i2c_slave();放到主函數(shù)中
void main()
{
init_i2c_slave();
}
頭文件 :i2c_salve.h
#ifndef _I2C_SALVE_H
#define _I2C_SALVE_H
void init_i2c_slave();
void i2c_salve_interrupt_tx();
void i2c_salve_interrupt_rx();
#endif
代碼:i2c_salve.c
#include
#define input 1
#define RX_BUF_LEN 29
#define while_delay 6000
unsigned char i2c_address,word_address,Register[29];
unsigned char RANDOM_READ,i2c_counter;
extern unsigned char A_readflag;
/*I2C SALVE */
void init_i2c_slave()
{
TRISB6 = input;
TRISB4 = input;
SSP1CONbits.SSPM0 = 0;
SSP1CONbits.SSPM1 = 1;
SSP1CONbits.SSPM2 = 1;
SSP1CONbits.SSPM3 = 0;// I2C slave mode ,7bit address
SSP1CONbits.CKP = 1; // enable clock
SSP1ADD =0xA0; //slave address is 0xa0
SSP1CONbits.SSPEN=1;//enable I2c
SSPSTAT=0;
PIE1bits.SSP1IE = 1;//Enabe interrupt MSSP
INTCONbits.PEIE = 1;
INTCONbits.GIE = 1;
}
/*I2C salve mode interrupt */
void i2c_salve_interrupt_tx()//master read
{
unsigned char Temp;
unsigned int timercounter;
Temp=SSP1STAT;
Temp &= 0x2D;
if(SSP1STATbits.R_nW ==1)//Read operation.
{
A_readflag=0;
SSP1IF = 0;
i2c_address = SSP1BUF;
i2c_counter = word_address;
while(i2c_counter < RX_BUF_LEN)
{
SSP1BUF=Register[i2c_counter];//send data
SSP1CONbits.CKP=1;// enable colck
timercounter=while_delay;
while(PIR1bits.SSP1IF == 0)
{
timercounter--;
if(timercounter==0)
{
return;
}
}//waiting for ~ACK
SSP1IF = 0;
if(SSP1CON2bits.ACKSTAT == 1)
{
return ; //NOACK
}
else
{
i2c_counter++;//ACK
}
}
SSP1IF = 0;
}
}
void i2c_salve_interrupt_rx()//master writer
{
unsigned char rx_status;
unsigned char Temp;
unsigned int timercounter;
rx_status=false;
Temp=SSP1STAT;
Temp &= 0x2D;
if(Temp==0x09)//Write operation,last byte was an address,buffer is full
{
SSP1IF = 0;
i2c_address = SSP1BUF;
timercounter=while_delay;
while(PIR1bits.SSP1IF == 0)
{
timercounter--;
if(timercounter==0)
{
return ;
}
}//waiting for send ~ACK
SSP1IF = 0;
word_address = SSP1BUF;
return ;
}
if(Temp==0x29)//Write operation,last byte was data,buffer is full
{
SSP1IF=0;
Register[word_address]=SSP1BUF;
word_address++;
if(word_address>=RX_BUF_LEN)
{
word_address=0;
}
}
}