使用STC8H单片机
IIC通讯时序原理就不再赘述了
STC公司从标准IIC协议中更改了两种机制如下:
◆发送起始信号(START)后不进行仲裁
◆时钟信号(SCL)停留在低电平时不进行超时检测
只有当IIC模块工作在主机模式时,MSSPEED参数设置的等待参数才有效。此等待参数主要用于主机模式的以下几个信号:
例1:当MSSPEED=10时,TSSTA=THSTA=TSSTO=THSTO=THCK=24/FOSC
例2:当24MHz的工作频率下需要400K的I2C总线速度时,MSSPEED=(24M/400K /2-4)/2=13
上图标记的寄存器实例中运用得到
对IIC引脚初始化,我这里选择了P3.3和P3.2引脚分别是IICSDA以及IICSCL。
#define HARDIIC_SDA_IN {P3M1&=~0x08;P3M0&=~0x08;} //P33双向IO口
#define HARDIIC_SCL_IN {P3M1|=0x04;P3M0&=~0x04;} //高阻输入
IIC从机初始化
void IIC_init()
{
EA = 1; // 控制总中断
//IIC初始化--------------
HARDIIC_SCL_IN; //P32双向IO口
HARDIIC_SDA_IN; //P33双向IO口
P_SW2 = ((P_SW2 & (~(1<<7))) | (1<<7)); //开启 扩展 RAM 区特殊功能寄存器(XFR)访问控制寄存器
P_SW2 = ((P_SW2 & (~(3<<4))) | (3<<4)); //外设端口切换控制寄存器
I2CCFG = 0x81; //使能 I2C 从机模式
I2CSLADR = 0xFF; //这里接收所有的设备地址
//下面是对设备地址必须相同的解释
//设置从机设备地址寄存器 I2CSLADR=0101_1010B 最后一位设置为1 接受所有的设备地址
//即 I2CSLADR[7:1]=010_1101B,MA=0B。
//由于 MA 为 0,主机发送的的设备地址必须与
//I2CSLADR[7:1]相同才能访问此 I2C 从机设备。
//主机若需要写数据则要发送 5AH(0101_1010B)
//主机若需要读数据则要发送 5BH(0101_1011B)
I2CSLST = 0x00; //IIC从机状态寄存器清零
I2CSLCR = 0x78; //使能从机模式中断
isda = 1; //用户变量初始化
isma = 1;//寄存器地址
addr = 0;//设备地址
I2CTXD = buffer[addr];
}
IIC中断服务函数
//调压芯片或IIC逻辑 开始信号 -> 设备地址 -> 从机应答 -> 储存地址 -> 从机应答 -> 数据1 -> 从机应答 -> 数据2 -> 从机应答 ->停止信号
//========================================================================
// 描述: IIC通信中断服务函数 包括发送和接收
// 参数: none.
// 返回: none.
//========================================================================
void I2C_Isr() interrupt 24 //IIC中断函数
{
// printf("无条件接收IICRXT:%X\r\n", I2CRXD); //接收到0x44 为开始信号或停止信号的返回值
_push_(P_SW2);//将 切换控制寄存器 进栈处理
P_SW2 |= 0x80;
if (I2CSLST & 0x40) //收到开始信号
{
// printf("START\r\n");
I2CSLST &= ~0x40; //开始信号清零 STAIF 位清零
} else if (I2CSLST & 0x20) //从机模式时接收到 1 字节的数据后的中断请求位
{
I2CSLST &= ~0x20; //1 字节的数据 RXIF 位清零
if (isda) //设备地址
{
isda = 0; //处理 RECV 事件(设备地址)
deviceID_addr = I2CRXD;
// printf("设备地址:%X\r\n",deviceID_addr);
}
else if (isma)//寄存器地址
{
isma = 0; //处理 RECV 事件(寄存器地址)
memory_addr = I2CRXD;
IICdata_addr =0;
// printf("寄存器地址:%X\r\n",memory_addr);
}//由于初始化的时候,IIC接收所有设备地址 ,不过从这里可继续添加 else if 语句进行判断特点指令
else//数据1 和 数据2
{
if(IICdata_addr >= 2) IICdata_addr = 0;
buffer[IICdata_addr++] = I2CRXD; //处理 RECV 事件(数据1 和 数据2)
// printf("打印出_buffer:%X\r\n", buffer[IICdata_addr++]);
}
} else if (I2CSLST & 0x10)//从机模式时发送完成 1 字节的数据后的中断请求位
{
I2CSLST &= ~0x10; //处理 SEND 事件 从机模式时发送完成1字节的数据后的中断请求位
if (I2CSLST & 0x02)//从机模式时,接收到的 ACK 数据
{
// I2CTXD = 0xff; //接收到 NAK 则停止读取数据
}
else
{ //接收到 ACK 则继续读取数据 (重点)可持续发送 数据出去 主机觉得不断开
if(deviceID_addr == 0xC3) //判断是否发出了24个字节 这里是我芯片特定的规律
{
I2CTXD = buffer1_24byte[++Read_organize_one];
if(Read_organize_one >= 24)
{
//printf("打印出_I2CTXD:%X ADD:%d\r\n", buffer1_24byte[Read_organize_one-1],Read_organize_one-1); //打印会影响数据输出准确性
Read_organize_one = 0;
I2CSLST &= ~0x10; //处理 SEND 事件 从机模式时发送完成1字节的数据后的中断请求位
I2CSLST &= ~0x08; //处理 STOP 事件 从机模式时接收到STOP信号后的中断请求位
}
}
if(deviceID_addr == 0xC5) //判断是否发出了24个字节 这里是我芯片特定的规律
{
I2CTXD = buffer2_24byte[++Read_organize_two];
if(Read_organize_two >= 24)
{
Read_organize_two = 0;
I2CSLST &= ~0x10; //处理 SEND 事件 从机模式时发送完成1字节的数据后的中断请求位
I2CSLST &= ~0x08; //处理 STOP 事件 从机模式时接收到STOP信号后的中断请求位
}
}
}
} else if (I2CSLST & 0x08)//从机模式时接收到 STOP 信号后的中断请求位
{
// printf("STOP\r\n");
I2CSLST &= ~0x08; //处理 STOP 事件
isda = 1;//设备地址
isma = 1; //寄存器地址
CBM128S085_start = 1;
Read_organize_one = 0;
Read_organize_two = 0;
}
_pop_(P_SW2);//出栈处理
}
使用printf打印出信息的时候,最好不要在IIC中断里放置会出现打印出来的数据不对,最好是在主程序的死循环中放置
注意变量溢出的问题不然会死机
// printf("设备地址: %X\r\n",deviceID_addr);
// printf("寄存器地址: %X\r\n",memory_addr);
// printf("buffer寄存器:%X\r\n",buffer[0]);
// printf("buffer寄存器:%X\r\n",buffer[1]);
打印16进的时候,%后面用 “x"会发现多了一个字节,但这个字节是不属于其变量的,这样会让人产生错觉。所以要使用"bx”
printf("设备地址: %bX\r\n",deviceID_addr);
printf("寄存器地址: %bX\r\n",memory_addr);
printf("buffer寄存器:%bX\r\n",buffer[0]);
printf("buffer寄存器:%bX\r\n",buffer[1]);