1、参照原理《单片机模拟iic从设备-原理说明》使用中断引脚协助SDA脚进行iic起始信号和结束信号的判断。
2、本次代码从机工作在多个从设备的情况下,选择方案一。
? ? ? ? 2.1 本代码涉及到的算法:对于iic发送数据或者发送应答后用到iic超时释放SDA,避免拉死iic、拉死主机。
? ? ? ? 2.2?本代码使用外部中断引脚,考虑iic特性,故外部引脚监测SDA下降沿触发,然后确认是否为iic启动信号,进而进行通信。
? ? ? ? 2.3?如果iic通信速率快的话,单片机处理不过来,会导致错误,本iic时钟为150Khz
? ? ? ? 2.4?基于150khz的时钟,有时单片机会误进入起始信号接收iic地址,从而错过真正的起始信号,本代码加入了接收数据异常后退出iic模式。
? ? ? ? 2.5?单片机接收数据很好,但是发送数据比较慢。注意。
? ? ? ? 2.6 单片机基于松翰SN8P2708A,很老的芯片了。编译器需要用官方提供的开发工具和烧录器。
? ? ? ? 2.7 主设备通信格式为:发送起始信号、发送从设备iic地址、发送1字节数据、发送1字节数据、发送结束信号。对于主发送。对于主读取,发送起始信号、发送从设备iic地址、发送1字节数据、发送0xff、发送结束信号、发送起始信号、发送从设备iic地址、读取数据。
3、iic_drive.h
#ifndef __IIC_DRIVE_H__
#define __IIC_DRIVE_H__
#include "SN8P2708A.h"
#include "typedef.h"
//i2c对应的 GPIO 引脚宏定义P10 P11
#define IIC_SCL FP10
#define IIC_SDA FP11
#define iic_sda FP02 //中断引脚外加辅助IIC_SDA输入引脚
#define iic_delay_time 6//最大延迟等待时常为6us,对于时钟频率150Khz
extern uint8_t IIC_ADDR;//由开机上电ADC采集判断IIC_ADDR的地址是多少(8位哈)
extern uint8_t KEY_Value;//按键反馈回来的结果
extern uint8_t led_row_data[10];//led每一行对应的值
#define device_ID_ADDR 0x6f //器件的ID地址
#define device_ID 0x14 //器件的ID地址值
#define device_CODE_ADDR 0x70 //器件的软件地址
#define device_CODE 0x40 //器件的软件值
#define device_KEY_ADDR 0x50 //上层读取单片机按键值的地址
#define device_NO_KEY 0x80 //没有检测到按键按下
这是每一行的按键地址///
#define Addr_ROW1 0x59
#define Addr_ROW2 0x5a
#define Addr_ROW3 0x5b
#define Addr_ROW4 0x5c
#define Addr_ROW5 0x5d
#define Addr_ROW6 0x5e
#define Addr_ROW7 0x5f
#define Addr_ROW8 0x60
#define Addr_ROW9 0x61
#define Addr_ROW10 0x62
//iic的初始化配置
void iic_init_config(void);
#endif /*__IIC_DRIVE_H__*/
4、iic_deive.c
/*
//以下代码作者:金丝草
//本代码功能:软件模拟iic从设备进行通信
*/
#include "iic_drive.h"
中断函数要是调用函数的话,在函数定义的前面必须加上__interrupt
static uint8_t iic_RX_BUF[3] = { 0 };//iic接收缓存区2个就够了
static uint8_t get_iic_addr=0x00; //获取到的iic地址
static uint8_t flag = 0; //设定为全局变量
static uint8_t iic_Count = 8; //iic的计数器
static uint8_t iic_delay=iic_delay_time;//初始化iic等待时长
//iic初始化配置
void iic_init_config(void)
{
///iic对应的引脚配置工作模式
FP10M=0;//时钟配置为输入模式
FP11M=1;//数据配置为输出模式
P1OC= 0x03;//P10为开漏模式,P11为开漏模式
//默认拉高状态
IIC_SDA = 1;
// IIC_SCL = 1;//作为输入引脚无需赋值,也付不了值
//注:P01的中断触发方向为下降沿。是不可更改的哈
FP02M = 0; //引脚配置为输入模式
// P0UR =0x02; //配置为上拉模式//外部有上拉电阻了
FP02IRQ = 0;//先清除中断标志位
FP02IEN = 1;//开启外部中断P01使能
FGIE = 1; //开启总中断
}
************************此器件工作在iic从模式下*********************************/
void __interrupt [0x08] ISR(void)
{
static uint8_t temp = 0x01;
if(IIC_SCL)//表示确实来开始信号了
{
FGIE = 0;//关闭所有中断
get_iic_addr = 0x00;
while(IIC_SCL);//等待时钟拉低
for(iic_Count=0x80;iic_Count!=0;iic_Count>>=1)
{
while(!IIC_SCL);//等待时钟拉高
if(iic_sda)
{
get_iic_addr |=iic_Count;//将读取的数据保存在缓存区里
temp = iic_sda;//临时存放SDA的当前值
}
if(iic_Count == 0x01)
{
break;
}
if(temp == 0) //判断是否为IIC结束信号
{
while(IIC_SCL)//等待时钟拉低,且判断是否为IIC结束信号
{
if(iic_sda == 1)//结束信号来了
{
if(IIC_SCL == 1)//再次确认是否为结束信号
goto exit_iic;//结束本次通信
else
break;
}
}
}
else
while(IIC_SCL);//等待时钟拉低
}
//比较地址是否为自己且是否为读或写
if ((get_iic_addr & 0xfe) == IIC_ADDR)//如果接收到的地址匹配了
{
//发送ack应答信号//
while(IIC_SCL);//等待时钟拉低
IIC_SDA = 0;//发送应答信号
while (!IIC_SCL)//等待时钟拉高
{
iic_delay--;
if(!iic_delay)
{IIC_SDA=1;iic_delay=iic_delay_time;goto exit_iic;} //超时等待处理
}
iic_delay=iic_delay_time;
//发送ack应答信号//
if (get_iic_addr & 0x01)/如果是读单片机
{
switch(iic_RX_BUF[0])
{
case device_ID_ADDR: temp=device_ID;break;
case device_CODE_ADDR :temp=device_CODE;break;
case device_KEY_ADDR :temp=KEY_Value;KEY_Value=device_NO_KEY;break;
}
for(iic_Count=0x80;iic_Count!=0;iic_Count>>=1)//结束之后时钟是低电平
{
while(IIC_SCL);//时钟拉低的话
if(temp & iic_Count)//判断要发送的数据是高还是低
{
IIC_SDA=1;
}
else
{
IIC_SDA=0;
}
if(iic_sda == 0)
{
while (!IIC_SCL)//等待时钟拉高
{
iic_delay--;
if(!iic_delay)
{IIC_SDA=1;iic_delay=iic_delay_time;goto exit_iic;} //超时等待处理
}
iic_delay=iic_delay_time;
}
else
while(!IIC_SCL);//等待时钟拉高
}
if(iic_sda == 0) //如果最后数据发送的为0
{
while(IIC_SCL)//等待时钟拉低
{
iic_delay--;
if(!iic_delay)
{IIC_SDA=1;iic_delay=iic_delay_time;goto exit_iic;} //超时等待处理
}
IIC_SDA = 1;//释放SDA线
iic_delay=iic_delay_time;
}
}
else/写单片机
{
RX_DATA:
接收数据
iic_RX_BUF[flag]=0x00;//缓存区清零
while(IIC_SCL)//等待时钟拉低
{
iic_delay--;
if(!iic_delay)
{IIC_SDA=1;iic_delay=iic_delay_time;goto exit_iic;} //超时等待处理
}
IIC_SDA=1;//释放总线
iic_delay=iic_delay_time;
for(iic_Count=0x80;iic_Count!=0;iic_Count>>=1)
{
while(!IIC_SCL);//等待时钟拉高
if(iic_sda)
{
iic_RX_BUF[flag] |=iic_Count;//将读取的数据保存在缓存区里
}
if(iic_Count==0x01)
{
break;
}
while(IIC_SCL);//等待时钟拉低
}
接收数据
/发送ack应答信号//
while(IIC_SCL);//等待时钟拉低
IIC_SDA = 0;//发送应答信号
while (!IIC_SCL)//等待时钟拉高
{
iic_delay--;
if(!iic_delay)
{IIC_SDA=1;iic_delay=iic_delay_time;goto exit_iic;} //超时等待处理
}
iic_delay=iic_delay_time;
发送ack应答信号//
if(flag)
{
flag=0;
switch( iic_RX_BUF[0] )//led控制地址及结果
{
case Addr_ROW1: led_row_data[0]=iic_RX_BUF[1]<<2;break;
case Addr_ROW2: led_row_data[1]=iic_RX_BUF[1]<<2;break;
case Addr_ROW3: led_row_data[2]=iic_RX_BUF[1]<<2;break;
case Addr_ROW4: led_row_data[3]=iic_RX_BUF[1]<<2;break;
case Addr_ROW5: led_row_data[4]=iic_RX_BUF[1]<<2;break;
case Addr_ROW6: led_row_data[5]=iic_RX_BUF[1]<<2;break;
case Addr_ROW7: led_row_data[6]=iic_RX_BUF[1]<<2;break;
case Addr_ROW8: led_row_data[7]=iic_RX_BUF[1]<<2;break;
case Addr_ROW9: led_row_data[8]=iic_RX_BUF[1]<<2;break;
case Addr_ROW10: led_row_data[9]=iic_RX_BUF[1]<<2;break;
}
while(IIC_SCL)//等待时钟拉低
{
iic_delay--;
if(!iic_delay)
{IIC_SDA=1;iic_delay=iic_delay_time;goto exit_iic;} //超时等待处理
}
IIC_SDA=1;//释放总线
iic_delay=iic_delay_time;
}
else
{
flag=1;
goto RX_DATA;
}
}
}
}
exit_iic:
FP02IRQ = 0;//中断标志清零
FGIE = 1;//开启所有中断
}
|