主模式:

我们今天来讲I2C通信。那I2C通信的特点是什么能。我们一般使用的串口 (半双工异步串行通信)与I2C 有什么区别呢。

串口(半双工异步串行通信):就是好像朋友在对话。我可以主动和你讲话,你也可以主动和我讲话。

I2C:就好像上下级对话。一个领导面对一个或者多个员工。只有领导主动说话的份儿,下面的员工不能主动说话。只有领导问了,员工才能答。

I2C通信

I2C通信只需要两个引脚 一个数据线,一个时钟线。 数据线顾名思义就是用来传递数据的。时钟线是来决定数据传输的速度。当时钟线为高电平时,数据线上的数据才会被认为是有效的。

数据线的 数据有四种状态 : 高电平,低电平,下降沿(高电平变低电平),上升沿(低电平变高电平)。

当时钟线为高电平时候这四种状态分别代表:1,0,起始位,停止位。

如果我们发送的数据为十六进制的0x88即是二进制为10001000的数据是怎么发送的呢?我们就以此为例一步步讲解。

0t3YzkRIo4OMpf5IuzlP0IC0K8YbNgssEfY4pHdGYcvDXpxBCfy5PkidreTQLIGJxN2gqO4ChuLFiQmR7b44uErZCLDOsx1BhYy6lopIxGg

1,常态

在不发送任何数据的时候数据线和时钟线都为高电平。所以I2C通信在硬件设计,需要在数据线和时钟线上分别加上两个上拉电阻。

2,起始

当开始发送数据的时候 时钟线为高同时数据线从 高电平变低电平,代表开始发送数据。

3,发送数据

发送完起始位后时钟线变为低电平,在发送每一位的数据之前时钟线有一段低电平,主要的作用是给数据线做电平变化用的。

我们现在要发送的第一个位是 1。

1、时钟线为低,同时数据线从低电平变成高电平。

2、接着时钟线变为高电平,此时接收方得知时钟线为高,便查看数据线为高电平 说明数据为 “1”。

3、我们要发送的下一个位为0。时钟线再变为低,同时数据线从高电平变成低电平。

4、接着时钟线再变为高电平,此时接收方得知时钟线为高,便查看数据线为低电平 说明数据为"0"。

5、再下一个为还为0。时钟线再变为低,同时数据线一直保持低电平不变。

6、接着时钟线再变为高电平,此时接收方得知时钟线为高,便查看数据线为低电平 说明数据为”0“。

以此类推 直到发送完所有的位。

4,应答(ACK)

当接收方接收完一个字节的数据就要告诉对方我收到了。接收方如果接收到数据则控制数据线输出低电平。否则为高电平。

5,停止

没有下一个字节要发送,最后时钟线变为高电平后,数据线从低电平变为高电平。代表数据发送停止。

实例讲解:使用单片机使用 RSM2257 电子音量控制芯片来控制音量。一个按键按下,声音变大,一个按键按下,声音变小。在加上一个按键,控制一个LED亮灭的程序。而且音量掉电保存。

介绍RSM2257.

子地址

在I2C通信中每一个从设备都有个子地址,因为I2C支持一主多从,也就是说有一个主机可以连接多个从机。每个从机,都有个地址。就好像每个人的名字一样来区分不同的设备。下面是RSM2257接口协议,首先先发送RSM2257 设备地址 10001000.然后再发送数据。

W8BGpm+ysoI0Q

数据

RSM2257的数据是用来表示音量大小的。我们控制两个音频通道,以10dB为单位降低或增加音量。从功能设置位表格中可知数据为 11100B2B1B0.

B2B1B0的数值决定了音量。请详见 衰减设置位。

KSwQg2tgR8dUA79bD0tRaaysrjyZFmkzS44nofHbmelw3jAjPgUWAvl1bO114YYV5jrtOU9NcfOUlH8rsqmf1xar+W87C28vq+Lutg

单片机I2C通信初始化设置

1、设置端口为输入

TRISC0 = input;

TRISC1 = input;

2、设置模式

我们设置ssp1控制寄存器的 SSP1M<3:0>.我们需要的是I2C主模式。设置如下

SSP1CON1bits.SSPM0 = 0;

SSP1CON1bits.SSPM1 = 0;

SSP1CON1bits.SSPM2 = 0;

SSP1CON1bits.SSPM3 = 1;// I2C Master mode ,clock=Fosc/(4*(SSPxADD+1))

3、设置时钟线频率RSM2257最大为100KHZ,我选择设置为50KHZ.

使用计算公式clock=Fosc/(4*(SSPxADD+1)) 计算出SSP1ADD的值为0x9F;

SSP1ADD=0x9F;

4、开启I2C通信

SSP1CON1bits.SSPEN = 1;

单片机I2C发送程序

1、发送起始位

SSP1CON2bits.SEN = 1;//Start condition

while(SSP1CON2bits.SEN == 1);//waiting for Start condition completed.

2、发送地址

PIR1bits.SSP1IF = 0;

SSP1BUF = 0x88;//Device Address

while(PIR1bits.SSP1IF == 0);

PIR1bits.SSP1IF = 0;

// ~ACK 我们不理会接收方有没有应答。

3、发送 10db音量控制的数据

SSP1BUF = tx_data;//Data 10db level

while(PIR1bits.SSP1IF == 0);

PIR1bits.SSP1IF = 0;

4、发送1db音量控制的数据

// ~ACK

SSP1BUF = 0xD0;//Data 1db level

while(PIR1bits.SSP1IF == 0);

PIR1bits.SSP1IF = 0;

5,发送停止位

// ~ACK

SSP1CON2bits.PEN = 1;//Stop condition

关于I2C通信协议,RSM2257,PIC MSSP 模块设置成I2C,更详细的内容就必须去看数据手册了。

实例程序:程序分为main.c 和 define.h两个文件 芯片PIC16LF1823,开发环境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这样掉电后数据也不会丢失。

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; //将高三位设置为1。表示两个音频通道,以10dB为单位降低或增加音量

i2c_master_tx(tx_db);//I2C发送数据程序

}

/*

*

*/

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)//如果之前没有设置过则音量不衰减

{

DB_VALUE = 0;

}

tx_pro(DB_VALUE);//用I2C通信设置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;

}

}

}

从模式:

网上有许多讲解单片机 实现I2C主模式,但是从模式的很少。我现在就来讲讲PIC单片机使用MSSP模块实现I2C从模式。

有关I2C协议的具体介绍可以看 《PIC单片机之I2C(主模式)》,我们这里直接讲解实例

实例讲解:我们模仿 AT24C02 EEPROM 的协议。让一个主模式的单片机,来读取从模式单片机的数据。

下面为AT24C02的随机地址读取的协议。

第一个字节 :输入7位地址和一位的写状态位,

第二个字节:然后写入EEPROM数据地址,

第三个字节:输入7位地址和一位的读状态位,

第四~N个字节:读出的EEPROM的数据。

UvKWoCdT8OUmIexojg

我们来讲解下程序的基本思路:我们使能了MSSP中断,即是I2C接收中断,当PIC单片机接收到一个数据后就会产生中断。那是接收到设备地址,还是接收到数据,由SSP1STAT寄存器的状态位来判断。

需要判断的状态位分别是 :

数据和地址: 用来判断接收到是地址还是数据

启动位: 用来判断是否接收到启动位

读写: 用来判断是写状态还是读状态。

缓存满: 用来判断缓冲区是否满

我们以随机地址读取为例:讲讲程序执行的过程

1,从单片机接收到启示位和设备地址中断:我们判断SSP1STAT的状态位为(写状态,地址,缓存满,接收到启示位) 然后读取缓存中的设备地址, 接着在读取 需要读/写的数据地址。

2,单片机再次接收到设备地址:我们判断是SSP1STAT的状态为(读状态)然后从设备就输出数据

xw

我们以写字节数据为例:

1,从单片机接收到启示位和设备地址中断:我们判断SSP1STAT的状态位为(写状态,地址,缓存满,接收到启示位) 然后读取缓存中的设备地址, 接着在读取 需要读/写的数据地址。

2,单片机判断SSP1STAT的状态位为(写状态,数据,缓存满)那么单片机就接收输入的数据。

初始化设置:

1,设置I2C通信的两引脚为CLK SCL为输入,

TRISB6 = input;

TRISB4 = input;

2,将MSSP设置为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,设置从设备地址为 0xA0

SSP1ADD =0xA0; //slave address is 0xa0

5,开启I2C

SSP1CONbits.SSPEN=1;//enable I2c

6,清楚状态标志

SSPSTAT=0;

7,使能I2C中断

PIE1bits.SSP1IE = 1;//Enabe interrupt MSSP

INTCONbits.PEIE = 1;

INTCONbits.GIE = 1;

如果你要使用PIC单片机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;

}

}

将初始化函数init_i2c_slave();放到主函数中

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;

}

}

}

Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐