I2C的工程应用
本工程基于LM3S8962的I2C总线来模拟EEPROM(CAT24C02)传输协议的操作过程,系统简单实用,可以移植到所有的LM3Sxxx系列的MCU上。
在PROGRAM中,采用INT的方式进行数据传输过程的处理和实现,工程的功能实现都是在中断函数中进行的。
1.cat24c02是EEPROM芯片,从机地址为0x50,可存储256BYTE的数据,支持100KBIT/S或者400KBIT/S的速度。
2.cat24c02的总线协议
由于24c02是MEMORY DIVICE,所以存储从机子地址,即数据地址。所以无论是读还是写,主机首先要在发送“从机子地址+写”后的第一个字节发送“数据地址”。若主机此时要向24C02写入多个数据,那么此时便可以直接操作了,24C02会自增地址值;如果主机要向从机读多个数据,那个就必须重新发送起始信号,再发送“从机地址+读”后才能从24C02中读取数据。
主机向从机写数据采用突发发送时序:起始地址->从机地址/写控制命令->从机ACK->数据地址->从机ACK->发送数据->从机ACK->...->STOP
主机从从机读数据采用突发发送后突发读时序:起始地址->从机地址/写控制命令->从机ACK->数据地址->从机ACK->起始地址->从机地址/读控制命令->ACK->从机发送数据->主机ACK...->非应答->STOP
3.从机在数据传输过程中的实际操作
从机在接收或发送一个字节后,都会把从机原始状态寄存器I2CSRIS的RIS位置位。从机通过查询或者中断的方式检测到该位后,读取并判断I2CSCSR控制/状态寄存器的3个位FBR TREQ RREQ的状态,然后完成相应的操作,下面是这三个位的组合情况,除了下面的情况外,其它的组合情况是不存在的:
状态编号:1 2 3
FBR: 1 0 0
TREQ: 0 0 1
RREQ: 1 1 0
状态描述:接收请求,查手册 接收请求 发送请求
主机操作从机的过程中完全都是根据上表来实现的。当主机向从机写数据时,从机首先读到状态1,接收到的是数据地址,将这个地址赋给一个数组下标或者是指针,之后每次都读到是状态2,读到的都是主机所写的数据,将这些数据存储到数组中,下标地址自动加1;当主机从从机读取数据时,从机会先读到状态1,读到的是数据地址,将这个地址赋给一个数组下标或者是指针,之后每次都读到是状态3,读到的都是从机所发送的数据,将这些数据存储到数组中,下标地址自动加1;
下面是工程的实现代码:
#include "hw_memmap.h" #include "hw_ints.h" #include "hw_types.h" #include "hw_i2c.h" #include "gpio.h" #include "sysctl.h" #include "i2c.h" #include "interrupt.h"
#include "I2CINT.H"
/*-----------------------------------------------------------------------------*/
#define LED_PORT GPIO_PORTB_BASE //数据判断口的定义 #define LED_PIN GPIO_PIN_1 // 数据判断的LED指示 /****************************************************************** - 功能描述:主函数 - 隶属模块:------ - 函数属性:------ - 参数说明:void - 返回说明:嵌入式中的主函数的返回类型必须是Int型的 - 函数说明:------ ******************************************************************/
int main(void) { unsigned char WDATA[5] = {0xAA,0x55,0xAA,0x55,0xAA}; //主机向24C64写入的数据 unsigned char RDATA[5]; //主机从24C64读出的数据 unsigned long ulIdx; // 设置晶振为系统时钟 SysCtlClockSet(SYSCTL_SYSDIV_1 | SYSCTL_USE_OSC | SYSCTL_OSC_MAIN | SYSCTL_XTAL_6MHZ); // 使能本例所使用的外设。 SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); GPIODirModeSet(LED_PORT, LED_PIN, GPIO_DIR_MODE_OUT); I2CInit(400000, 1); // 初始化I2C驱动 while(1) // 将一串数据写入EEPROM的前8个字节。 { ISendStr(0xA0, 0x00, 1, WDATA, 5); // 向24C02中写入数据 Delays(5); // 等待主机向24C02中写入数据延时。 IRcvStr(0xA0, 0x00, 1, RDATA, 5); // 从24C02中读出数据 Delays(5); // 等待主机从24C02中读出数据延时。 for(ulIdx = 0; ulIdx < 5; ulIdx++) { // 判断接收到的数据是否正确. if(WDATA[ulIdx] != RDATA[ulIdx]) { //如果接收的数据出错,则点亮LED1指示. GPIOPinWrite(LED_PORT,LED_PIN,~LED_PIN); while(1); } } // 如果接收到的数据正确,则点亮LED2指示. GPIOPinWrite(LED_PORT,LED_PIN,~LED_PIN); Delays(3); GPIOPinWrite(LED_PORT,LED_PIN,LED_PIN); while(1); } }
/* --------------------------------------------------------------------------------------------------------- **************************************************************************************************
: I2CINT.c ** 功能说明 : Luminary硬件I2C中断方式软件包。 ** 使用说明 : 主程序要配置好I2C总线接口(I2C引脚功能和I2C中断,并已使能I2C主模式) *************************************************************************************************** --------------------------------------------------------------------------------------------------------- */ #include "hw_ints.h" #include "hw_memmap.h" #include "hw_types.h" #include "hw_i2c.h" #include "gpio.h" #include "sysctl.h" #include "i2c.h" #include "interrupt.h"
#define uchar unsigned char #define ulong unsigned long
/******************************************************************************************* ** 中断中的状态 *******************************************************************************************/ #define STATE_IDLE 0 /* 总线空闲状态 */ #define STATE_WRITE_ONE 1 /* 写单个字节状态 */ #define STATE_WRITE_NEXT 2 /* 写下一个字节状态 */ #define STATE_WRITE_FINAL 3 /* 写最后一个字节状态 */ #define STATE_READ_ONE 4 /* 读单个字节状态 */ #define STATE_READ_FIRST 5 /* 读第一个字节状态 */ #define STATE_READ_NEXT 6 /* 读下一个字节状态 */ #define STATE_READ_FINAL 7 /* 读最后一个字节状态 */ #define STATE_READ_WAIT 8 /* 读字节等待状态 */
/******************************************************************************************* ** 全局变量定义 *******************************************************************************************/ static volatile uchar I2C_sla; /* I2C器件从地址 */ static volatile ulong I2C_suba; /* I2C器件内部子地址 */ static volatile uchar I2C_suba_num; /* I2C子地址字节数 */ static volatile uchar *I2C_buf; /* 数据缓冲区指针 */ static volatile ulong I2C_num; /* 要读取/写入的数据个数 */ static volatile uchar I2C_opt; /* I2c读写操作,0为读操作,1为写操作 */ static volatile uchar I2C_state = STATE_IDLE; /* 中断服务程序的当前状态 */
/* ****************************************************************************************** ** 函数名称: Delays() ** 函数功能: 延时 ** 入口参数: dly ** 出口参数: 无 ****************************************************************************************** */ void Delays (ulong dly) { int i,j; for (; dly>0; dly--) { for (i=0; i<150; i++) for (j=0; j<255; j++); } }
/* ********************************************************************************************************* ** 函数名称:I2CInit() ** 函数功能:I2C接口初始化。 ** 入口参数:spd 总线速度100K(参数值为100000)或400K(参数值为4000000) ** pri 中断优先级0~7 ** 出口参数:返回值为false时表示操作出错,为true时表示操作正确。 ** 说明: ********************************************************************************************************* */ int I2CInit(ulong spd, uchar pri) { if ((spd == 400000) || (spd == 100000)) { // 使能所使用的外设 SysCtlPeripheralEnable(SYSCTL_PERIPH_I2C); SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOB); // 配置相关引脚,以进行I2C操作 GPIOPinTypeI2C(GPIO_PORTB_BASE, GPIO_PIN_2 | GPIO_PIN_3);
// 初始化I2C主机,设置主机为低速. if(spd == 400000) I2CMasterInit(I2C_MASTER_BASE, true); else I2CMasterInit(I2C_MASTER_BASE, false); // 使能处理器中断 IntMasterEnable(); // 使能I2C中断 IntEnable(INT_I2C);
// 使能I2C主机中断 I2CMasterIntEnable(I2C_MASTER_BASE); // 设置I2C中断的优先级 IntPrioritySet(INT_I2C, (pri << 5)); return(true); } else return(false); }
/* ********************************************************************************************************* ** 函数名称:ISendByte() ** 函数功能:向无子地址器件发送1字节数据。 ** 入口参数:sla 器件地址 ** c 要发送的数据 ** 出口参数:返回值为false时表示操作出错,为true时表示操作正确。 ** 说明: 使用前要初始化好I2C引脚功能和I2C中断,并已使能I2C主模式 ********************************************************************************************************* */ int ISendByte(uchar sla, uchar c) { I2C_sla = sla >> 1; I2C_buf = &c; I2C_state = STATE_WRITE_ONE; I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, false); // 主机写操作 I2CMasterDataPut(I2C_MASTER_BASE, *I2C_buf); // 写一个数据 I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_SINGLE_SEND); // 启动单次发送 while(I2C_state != STATE_IDLE); if(true == I2CMasterBusy(I2C_MASTER_BASE)) return (false); else return (true); }
/* ********************************************************************************************************* ** 函数名称 :ISendStr() ** 函数功能 :向有子地址器件任意地址开始写入N字节数据 ** 入口参数 : sla 器件从地址 ** suba_type 子地址结构 0-8+X结构 1-单字节地址 2-双字节地址 3-三字节地址 ** suba 器件子地址 ** s 数据发送缓冲区指针 ** no 写入的个数 ** 出口参数 : TRUE 操作成功 ** FALSE 操作失败 ********************************************************************************************************* */ int ISendStr(uchar sla, ulong suba, uchar sub_type, uchar *s, ulong no) { I2C_sla = sla >> 1; I2C_buf = s; I2C_num = no; I2C_suba = suba; switch(sub_type) { // 子地址为“8+x”类型 case 0: I2C_sla = I2C_sla | ((suba >> 8) & 0x07); I2C_suba_num = 1; break; // 子地址为1个字节 case 1: I2C_suba_num = 1; break; // 子地址为2个字节 case 2: I2C_suba_num = 2; break; // 子地址为3个字节 case 3: I2C_suba_num = 3; break; default: break; } if((no == 1) && (I2C_suba_num == 1)) I2C_state = STATE_WRITE_FINAL; // 单字节地址或8+x结构地址发送1个字节数据 else I2C_state = STATE_WRITE_NEXT; // 多字节地址或发送多个字节数据 I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, false); // 主机写操作 I2C_suba_num--; I2CMasterDataPut(I2C_MASTER_BASE, (I2C_suba >> (8 * I2C_suba_num))); // 写子地址高字节 I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);// 突发写操作启动 while(I2C_state != STATE_IDLE); if(true == I2CMasterBusy(I2C_MASTER_BASE)) return (false); else return (true); }
/* ********************************************************************************************************* ** 函数名称:ISendStrExt() ** 函数功能:向无子地址器件发送多字节数据。 ** 入口参数:sla 器件地址 ** s 要发送的数据 ** no 发送的字节个数 ** 出口参数:返回值为false时表示操作出错,为true时表示操作正确。 ** 说明: 发送数据字节至少2个字节 ********************************************************************************************************* */ int ISendStrExt(uchar sla, uchar *s, uchar no) { I2C_sla = sla >> 1; I2C_buf = s; I2C_num = no; if (no > 1) // 发送的字节参数过滤 { if(no == 2) I2C_state = STATE_WRITE_FINAL; else I2C_state = STATE_WRITE_NEXT; I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, false); // 主机写操作 I2CMasterDataPut(I2C_MASTER_BASE, *I2C_buf++); I2C_num--; I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);// 突发写操作启动 while(I2C_state != STATE_IDLE); if(true == I2CMasterBusy(I2C_MASTER_BASE)) return (false); else return (true); } else return(false); }
/* ********************************************************************************************************* ** 函数名称:IRcvByte() ** 函数功能:向无子地址器件读取1字节数据。 ** 入口参数:sla 器件地址 ** c 接收数据的变量指针 ** 出口参数:返回值为false时表示操作出错,为true时表示操作正确。 ** 说明: 使用前要初始化好I2C引脚功能和I2C中断,并已使能I2C主模式 ********************************************************************************************************* */ int IRcvByte(uchar sla, uchar *c) { I2C_sla = sla >> 1; I2C_buf = c; I2C_state = STATE_READ_WAIT; I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, true); // 主机读操作 I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);// 启动单次读取 while(I2C_state != STATE_IDLE); if(true == I2CMasterBusy(I2C_MASTER_BASE)) return (false); else return (true); }
/* ********************************************************************************************************* ** 函数名称 :IRcvStr() ** 函数功能 :向有子地址器件读取N字节数据 ** 入口参数 : sla 器件从地址 ** suba_type 子地址结构 0-8+X结构 1-单字节地址 2-双字节地址 3-三字节地址 ** suba 器件内部物理地址 ** *s 将要读取的数据的指针 ** no 将要读取的数据的个数 ** 出口参数 : TRUE 操作成功 ** FALSE 操作失败 ********************************************************************************************************* */ int IRcvStr(uchar sla, ulong suba, uchar sub_type, uchar *s, uchar no) { I2C_sla = sla >> 1; I2C_buf = s; I2C_num = no; I2C_suba = suba; I2C_opt = 1; switch(sub_type) { // 子地址为“8+x”类型 case 0: I2C_sla = I2C_sla | ((suba >> 8) & 0x07); I2C_suba_num = 1; break; // 子地址为1个字节 case 1: I2C_suba_num = 1; break; // 子地址为2个字节 case 2: I2C_suba_num = 2; break; // 子地址为3个字节 case 3: I2C_suba_num = 3; break; default: break; } if(I2C_suba_num == 1) { if(I2C_num == 1) I2C_state = STATE_READ_ONE; else I2C_state = STATE_READ_FIRST; } else I2C_state = STATE_WRITE_NEXT; I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, false); // 主机写操作 I2C_suba_num--; I2CMasterDataPut(I2C_MASTER_BASE, (I2C_suba >> (8 * I2C_suba_num))); // 写子地址高字节 I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_START);// 突发写操作启动 while(I2C_state != STATE_IDLE); if(true == I2CMasterBusy(I2C_MASTER_BASE)) return (false); else return (true); }
/* ********************************************************************************************************* ** 函数名称:IRvcStrExt() ** 函数功能:向无子地址器件读取N字节数据。 ** 入口参数:sla 器件地址 ** s 接收数据的变量指针 ** no 将要读取的数据的个数 ** 出口参数:使用前要初始化好I2C引脚功能和I2C中断,并已使能I2C主模式 ** 说明: 至少要读取2个字节 ********************************************************************************************************* */ int IRvcStrExt(uchar sla, uchar *s, uchar no) { I2C_sla = sla >> 1; I2C_buf = s; I2C_num = no; if(I2C_num > 1) { if(I2C_num == 2) I2C_state = STATE_READ_FINAL; else I2C_state = STATE_READ_NEXT; I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, true); // 主机读操作 I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START);// 突发读操作启动 while(I2C_state != STATE_IDLE); if(true == I2CMasterBusy(I2C_MASTER_BASE)) return (false); else return (true); } else return (false); }
/******************************************************************************************* ** 函数名称 :I2C_ISR() ** 函数功能 :中断读写数据 ** 入口参数 :无 ** 出口参数 :无 *******************************************************************************************/ void I2C_ISR (void) { I2CMasterIntClear(I2C_MASTER_BASE); // 清除I2C中断标志 switch(I2C_state) { // 空闲状态 case STATE_IDLE: { break; } // 写完单个字节状态 case STATE_WRITE_ONE: { I2C_state = STATE_IDLE; break; } // 写下一个数据 case STATE_WRITE_NEXT: { // 将下一个字节写入寄存器 if(I2C_suba_num != 0) { I2C_suba_num--; I2CMasterDataPut(I2C_MASTER_BASE, (I2C_suba >> (8 * I2C_suba_num))); if((I2C_suba_num == 0) && I2C_opt == 1) { if(I2C_num == 1) I2C_state = STATE_READ_ONE; else I2C_state = STATE_READ_FIRST; } } else { I2CMasterDataPut(I2C_MASTER_BASE, *I2C_buf++); I2C_num--; if(I2C_num == 1) { I2C_state = STATE_WRITE_FINAL; } } // 继续执行块写操作(run=1) I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_CONT); break; } // 写最后一个数据 case STATE_WRITE_FINAL: { I2CMasterDataPut(I2C_MASTER_BASE, *I2C_buf); I2C_num--; // 发送停止 I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_SEND_FINISH); // 下一个状态为块写完成 I2C_state= STATE_IDLE; break; } // 读取一个字节 case STATE_READ_ONE: {
I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, true);
I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_SINGLE_RECEIVE);
I2C_state= STATE_READ_WAIT;
break; } // 读取多个字节开始 case STATE_READ_FIRST: {
I2CMasterSlaveAddrSet(I2C_MASTER_BASE, I2C_sla, true);
I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_START);
if(I2C_num == 2) I2C_state = STATE_READ_FINAL; else I2C_state = STATE_READ_NEXT;
break; } // 读取下一个字节 case STATE_READ_NEXT: {
*I2C_buf++ = I2CMasterDataGet(I2C_MASTER_BASE); I2C_num--;
I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_CONT);
if(I2C_num == 2) I2C_state = STATE_READ_FINAL;
break; } // 读取最后一个字节 case STATE_READ_FINAL: {
*I2C_buf++ = I2CMasterDataGet(I2C_MASTER_BASE); I2C_num--;
I2CMasterControl(I2C_MASTER_BASE, I2C_MASTER_CMD_BURST_RECEIVE_FINISH);
I2C_state= STATE_READ_WAIT; break; } // 等待读取一个字节 case STATE_READ_WAIT: { *I2C_buf++ = I2CMasterDataGet(I2C_MASTER_BASE); // 读取数据 I2C_num--;
I2C_state= STATE_IDLE; break; } } }
|