在本阶段的工作中,需要实现一个由GPIO模拟的I2C从机工程设计,以前只使用GPIO模拟I2C设计过主机,对于从机的设计,还是首次。下面就讲本次工作中从机设计思想做简要记录。
IIC简介
对于IIC的详解网上很多就不复述了贴一个连接
这里是引用 https://blog.csdn.net/qq_39829913/article/details/104718185
程序设计及分析
头文件一些相关弘定义,IO初始化是统一初始化的
#ifndef __IIC_DRIVE_SLAVE_H__
#define __IIC_DRIVE_SLAVE_H__
#include "SYSCFG.h"
#define ADDMAX 20
#define SLAVE_ADDR 0xf8
//i2c对应的 GPIO 引脚宏定义P10 P11 SDA中断触发
#define IIC_SCL RA7
#define IIC_SDA RA6//从机数据输出脚
#define SET_SDA_LOW RA6 = 0
#define SET_SDA_HIGH RA6 = 1
#define GET_SCL_DAT RA7//从机时钟接收脚输入
#define GET_SDA_DAT RA6//从机数据输入
#define SDA_OUT TRISA6=0 //从机数据口为输出
#define SDA_IN TRISA6=1 //从机数据口为输入
#define WAIT_IIC_SCL_HIGH if(!IIC_timeout(1)) {goto IIC_TOUT;} //等待时钟拉高
#define WAIT_IIC_SCL_LOW if(!IIC_timeout(0)) {goto IIC_TOUT;} //等待时钟拉低
//#define WAIT_IIC_SDA_HIGH if(!IIC_timeout(!GET_SDA_DAT)) {goto IIC_TOUT;} //等待数据拉高
//#define WAIT_IIC_SDA_LOW if(!IIC_timeout(GET_SDA_DAT)) {goto IIC_TOUT;} //等待数据拉低
#define IIC_SLAVE_SEND_LOW WAIT_IIC_SCL_LOW; SDA_OUT; SET_SDA_LOW; WAIT_IIC_SCL_HIGH;
#define IIC_SLAVE_SEND_HIGH WAIT_IIC_SCL_LOW; SDA_OUT; SET_SDA_HIGH; WAIT_IIC_SCL_HIGH;
#define IIC_SLAVE_SEND_ACK IIC_SLAVE_SEND_LOW //从机读取结束发送ACK
#define IIC_SLAVE_SEND_NAK IIC_SLAVE_SEND_HIGH //从机读取结束发送NACK
extern volatile unsigned char START_F; //起始信号
extern volatile unsigned char STOP_F; //停止信号
void IIC_SLAVE(void);
#endif
IIC从机C文件
#include "iic_drive_slave.h"
volatile unsigned char registeradd_data[ADDMAX] = {0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0,0,0,0,0,0,0,0,0,0,0,0}; //IIC数据地址可读写 如果需要掉电不丢失需要在通信结束后写入EEPROM 上电再读出来
volatile unsigned char recFinish=0;//这个可以不要没用 循环直接break了
volatile unsigned char recwriteadd=0;//当前IIC读写地址这里是指内部寄存器数据地址0-19
volatile unsigned char recIICadd=0;//接收的IIC地址
volatile unsigned char START_F=0; //起始信号
volatile unsigned char STOP_F=0; //停止信号
unsigned int time = 1000;
unsigned char bitcount,iic_slv_addr,iic_master_rw,receive_BYTE;
/*
PIN 等待引脚 time 超时时间 出现起始和停止信号也会定义超时退出
*/
unsigned int IIC_timeout(unsigned char PIN)//CLK
{
time = 10000;
if(PIN == 1)
{
while((!GET_SCL_DAT) && time)
{
time--;
if(START_F||STOP_F) time = 0;
}
}else
{
while(GET_SCL_DAT && time)
{
time--;
if(START_F||STOP_F) time = 0;
}
}
return time;
}
//进入后需清除其它中断 只允许SDA中断 来判断是START STOP标志 这里只有起始信号后才能进入
void IIC_SLAVE(void)
{
START_F = 0;//起始信号标志清除
STOP_F = 0;//停止信号标志清除
{//START收到起始信号后的 接收从机地址
iic_slv_addr = 0;
for(bitcount = 0; bitcount < 7; bitcount ++)
{
WAIT_IIC_SCL_LOW;
WAIT_IIC_SCL_HIGH;
iic_slv_addr <<= 1; //先移位,再读数
if(GET_SDA_DAT)
iic_slv_addr |= 0x01;
else
iic_slv_addr |= 0x00;
}
//iic_slv_addr <<= 1;
recIICadd = iic_slv_addr;
// 已经读取前7位地址 xxxx xxx0
WAIT_IIC_SCL_LOW;
WAIT_IIC_SCL_HIGH;
if(GET_SDA_DAT)
iic_master_rw = 1;
else
iic_master_rw = 0;
}
if (iic_slv_addr == SLAVE_ADDR)
{
IIC_SLAVE_SEND_ACK; // 地址正确,从机发送ACK信号
if(iic_master_rw)//读IIC信息
{
for(; recwriteadd < ADDMAX; recwriteadd ++)//ADDMAX
{
unsigned char txmask = 0x80;
for(bitcount = 0; bitcount < 8; bitcount ++)
{
WAIT_IIC_SCL_LOW;
SDA_OUT;
if ( registeradd_data[recwriteadd] & txmask )
IIC_SDA = 1;
else
IIC_SDA = 0;
WAIT_IIC_SCL_HIGH;
txmask = txmask >> 1;
}
WAIT_IIC_SCL_LOW;
SDA_IN;
WAIT_IIC_SCL_HIGH;
if ( GET_SDA_DAT )//主机发送NACK 读取结束
break;
}//超过读取地址无响应
}else//写IIC内容
{
receive_BYTE = 0;
for(bitcount = 0; bitcount < 8; bitcount ++)
{
WAIT_IIC_SCL_LOW;
SDA_IN;//输入
WAIT_IIC_SCL_HIGH;
receive_BYTE <<= 1; //先移位,再读数
if(GET_SDA_DAT)
receive_BYTE |= 0x01;
else
receive_BYTE |= 0x00;
}
recwriteadd = receive_BYTE;//写需要读写的地址
if(recwriteadd <ADDMAX)
{
IIC_SLAVE_SEND_ACK; //地址正确成功,从机发送ACK信号
while(!recFinish)//不为1则一直写入地址由上面自加
{
receive_BYTE = 0;
for(bitcount = 0; bitcount < 8; bitcount ++)
{
WAIT_IIC_SCL_LOW;
SDA_IN;
WAIT_IIC_SCL_HIGH;
receive_BYTE <<= 1; //先移位,再读数
if(GET_SDA_DAT)
receive_BYTE |= 0x01;
else
receive_BYTE |= 0x00;
}
registeradd_data[recwriteadd++] = receive_BYTE;//连续写入寄存器
registeradd_data[2] = registeradd_data[0]+registeradd_data[1];//特殊情况校验和
if(recwriteadd < ADDMAX)
{
IIC_SLAVE_SEND_ACK; //地址正确成功,从机发送ACK信号
}
else
{
IIC_SLAVE_SEND_NAK; //超出地址,从机发送NACK信号;
break;
}
}
}
else
{
}//不正确处理
}
}
IIC_TOUT://IIC超时处理
STOP_F = 0;//停止信号标志清除
SDA_IN;
}
函数调用测试
用SDA的起始和停止判断通信的进行状态(开始通信与结束通信),超时用于处理异常情况,避免堵塞,调用时不能在其它地方用延时占用处理时间,通信速率尽量低一些
void interrupt ISR(void)
{
if(EPIF0 & 0x40)//PA6中断进入
{
EPIF0 = 0x40; //写1清零标志位
SDA = GET_SDA_DAT; //调试用
SCL = GET_SCL_DAT; //调试用
if(GET_SCL_DAT)
{
if(GET_SDA_DAT)
{
STOP_F = 1;
}else
{
START_F = 1;
}
}
}
}
void main(void)
{
POWER_INITIAL(); //系统初始化
IO_INT_INITIAL();
while(1)
{
if(START_F)
{
IIC_SLAVE();
}
}
}
这里采用了超时跳出
控制超时如下 具体超时时间因时钟而异需要调试 我是8M时钟这里只是随便填一下超时时间,WAIT_IIC_SCL_HIGH/WAIT_IIC_SCL_LOW超时后会goto到**IIC_TOUT:**用以处理结束通信
/*
PIN 等待引脚 time 超时时间 出现起始和停止信号也会定义超时退出
*/
unsigned int IIC_timeout(unsigned char PIN)//CLK
{
time = 10000;
if(PIN == 1)
{
while((!GET_SCL_DAT) && time)
{
time--;
if(START_F||STOP_F) time = 0;
}
}else
{
while(GET_SCL_DAT && time)
{
time--;
if(START_F||STOP_F) time = 0;
}
}
return time;
}
为0时则超时或出现了停止位和起始位
连续读写初步测试如下
有问题希望提出完善共勉!