PIC單片機(jī)之I2C通信-主-從模式
主模式:
我們今天來講I2C通信。那I2C通信的特點(diǎn)是什么能。我們一般使用的串口 (半雙工異步串行通信)與I2C 有什么區(qū)別呢。
串口(半雙工異步串行通信):就是好像朋友在對話。我可以主動和你講話,你也可以主動和我講話。
I2C:就好像上下級對話。一個領(lǐng)導(dǎo)面對一個或者多個員工。只有領(lǐng)導(dǎo)主動說話的份兒,下面的員工不能主動說話。只有領(lǐng)導(dǎo)問了,員工才能答。
I2C通信
I2C通信只需要兩個引腳 一個數(shù)據(jù)線,一個時鐘線。 數(shù)據(jù)線顧名思義就是用來傳遞數(shù)據(jù)的。時鐘線是來決定數(shù)據(jù)傳輸?shù)乃俣?。?dāng)時鐘線為高電平時,數(shù)據(jù)線上的數(shù)據(jù)才會被認(rèn)為是有效的。
數(shù)據(jù)線的 數(shù)據(jù)有四種狀態(tài) : 高電平,低電平,下降沿(高電平變低電平),上升沿(低電平變高電平)。
當(dāng)時鐘線為高電平時候這四種狀態(tài)分別代表:1,0,起始位,停止位。
如果我們發(fā)送的數(shù)據(jù)為十六進(jìn)制的0x88即是二進(jìn)制為10001000的數(shù)據(jù)是怎么發(fā)送的呢?我們就以此為例一步步講解。
1,常態(tài)
在不發(fā)送任何數(shù)據(jù)的時候數(shù)據(jù)線和時鐘線都為高電平。所以I2C通信在硬件設(shè)計,需要在數(shù)據(jù)線和時鐘線上分別加上兩個上拉電阻。
2,起始
當(dāng)開始發(fā)送數(shù)據(jù)的時候 時鐘線為高同時數(shù)據(jù)線從 高電平變低電平,代表開始發(fā)送數(shù)據(jù)。
3,發(fā)送數(shù)據(jù)
發(fā)送完起始位后時鐘線變?yōu)榈碗娖?,在發(fā)送每一位的數(shù)據(jù)之前時鐘線有一段低電平,主要的作用是給數(shù)據(jù)線做電平變化用的。
我們現(xiàn)在要發(fā)送的第一個位是 1。
1、時鐘線為低,同時數(shù)據(jù)線從低電平變成高電平。
2、接著時鐘線變?yōu)楦唠娖?,此時接收方得知時鐘線為高,便查看數(shù)據(jù)線為高電平 說明數(shù)據(jù)為 “1”。
3、我們要發(fā)送的下一個位為0。時鐘線再變?yōu)榈停瑫r數(shù)據(jù)線從高電平變成低電平。
4、接著時鐘線再變?yōu)楦唠娖?,此時接收方得知時鐘線為高,便查看數(shù)據(jù)線為低電平 說明數(shù)據(jù)為"0"。
5、再下一個為還為0。時鐘線再變?yōu)榈停瑫r數(shù)據(jù)線一直保持低電平不變。
6、接著時鐘線再變?yōu)楦唠娖剑藭r接收方得知時鐘線為高,便查看數(shù)據(jù)線為低電平 說明數(shù)據(jù)為”0“。
以此類推 直到發(fā)送完所有的位。
4,應(yīng)答(ACK)
當(dāng)接收方接收完一個字節(jié)的數(shù)據(jù)就要告訴對方我收到了。接收方如果接收到數(shù)據(jù)則控制數(shù)據(jù)線輸出低電平。否則為高電平。
5,停止
沒有下一個字節(jié)要發(fā)送,最后時鐘線變?yōu)楦唠娖胶?,?shù)據(jù)線從低電平變?yōu)楦唠娖?。代表?shù)據(jù)發(fā)送停止。
實(shí)例講解:使用單片機(jī)使用 RSM2257 電子音量控制芯片來控制音量。一個按鍵按下,聲音變大,一個按鍵按下,聲音變小。在加上一個按鍵,控制一個LED亮滅的程序。而且音量掉電保存。
介紹RSM2257.
子地址
在I2C通信中每一個從設(shè)備都有個子地址,因?yàn)镮2C支持一主多從,也就是說有一個主機(jī)可以連接多個從機(jī)。每個從機(jī),都有個地址。就好像每個人的名字一樣來區(qū)分不同的設(shè)備。下面是RSM2257接口協(xié)議,首先先發(fā)送RSM2257 設(shè)備地址 10001000.然后再發(fā)送數(shù)據(jù)。
數(shù)據(jù)
RSM2257的數(shù)據(jù)是用來表示音量大小的。我們控制兩個音頻通道,以10dB為單位降低或增加音量。從功能設(shè)置位表格中可知數(shù)據(jù)為 11100B2B1B0.
B2B1B0的數(shù)值決定了音量。請?jiān)斠?衰減設(shè)置位。
單片機(jī)I2C通信初始化設(shè)置
1、設(shè)置端口為輸入
TRISC0 = input;
TRISC1 = input;
2、設(shè)置模式
我們設(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è)置時鐘線頻率RSM2257最大為100KHZ,我選擇設(shè)置為50KHZ.
使用計算公式clock=Fosc/(4*(SSPxADD+1)) 計算出SSP1ADD的值為0x9F;
SSP1ADD=0x9F;
4、開啟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 我們不理會接收方有沒有應(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ù)手冊了。
實(shí)例程序:程序分為main.c 和 define.h兩個文件 芯片PIC16LF1823,開發(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ù)也不會丟失。
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。表示兩個音頻通道,以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)//如果之前沒有設(shè)置過則音量不衰減
{
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)在就來講講PIC單片機(jī)使用MSSP模塊實(shí)現(xiàn)I2C從模式。
有關(guān)I2C協(xié)議的具體介紹可以看 《PIC單片機(jī)之I2C(主模式)》,我們這里直接講解實(shí)例
實(shí)例講解:我們模仿 AT24C02 EEPROM 的協(xié)議。讓一個主模式的單片機(jī),來讀取從模式單片機(jī)的數(shù)據(jù)。
下面為AT24C02的隨機(jī)地址讀取的協(xié)議。
第一個字節(jié) :輸入7位地址和一位的寫狀態(tài)位,
第二個字節(jié):然后寫入EEPROM數(shù)據(jù)地址,
第三個字節(jié):輸入7位地址和一位的讀狀態(tài)位,
第四~N個字節(jié):讀出的EEPROM的數(shù)據(jù)。
我們來講解下程序的基本思路:我們使能了MSSP中斷,即是I2C接收中斷,當(dāng)PIC單片機(jī)接收到一個數(shù)據(jù)后就會產(chǎn)生中斷。那是接收到設(shè)備地址,還是接收到數(shù)據(jù),由SSP1STAT寄存器的狀態(tài)位來判斷。
需要判斷的狀態(tài)位分別是 :
數(shù)據(jù)和地址: 用來判斷接收到是地址還是數(shù)據(jù)
啟動位: 用來判斷是否接收到啟動位
讀寫: 用來判斷是寫狀態(tài)還是讀狀態(tài)。
緩存滿: 用來判斷緩沖區(qū)是否滿
我們以隨機(jī)地址讀取為例:講講程序執(zhí)行的過程
1,從單片機(jī)接收到啟示位和設(shè)備地址中斷:我們判斷SSP1STAT的狀態(tài)位為(寫狀態(tài),地址,緩存滿,接收到啟示位) 然后讀取緩存中的設(shè)備地址, 接著在讀取 需要讀/寫的數(shù)據(jù)地址。
2,單片機(jī)再次接收到設(shè)備地址:我們判斷是SSP1STAT的狀態(tài)為(讀狀態(tài))然后從設(shè)備就輸出數(shù)據(jù)
我們以寫字節(jié)數(shù)據(jù)為例:
1,從單片機(jī)接收到啟示位和設(shè)備地址中斷:我們判斷SSP1STAT的狀態(tài)位為(寫狀態(tài),地址,緩存滿,接收到啟示位) 然后讀取緩存中的設(shè)備地址, 接著在讀取 需要讀/寫的數(shù)據(jù)地址。
2,單片機(jī)判斷SSP1STAT的狀態(tài)位為(寫狀態(tài),數(shù)據(jù),緩存滿)那么單片機(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時鐘
SSP1CONbits.CKP = 1; // enable clock
4,設(shè)置從設(shè)備地址為 0xA0
SSP1ADD =0xA0; //slave address is 0xa0
5,開啟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;
}
}
}