使用GPIO实现IIC SLAVE的方法讨论(IIC 从机)

在本阶段的工作中,需要实现一个由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时则超时或出现了停止位和起始位

连续读写初步测试如下
在这里插入图片描述

有问题希望提出完善共勉!

  • 7
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
使用GPIO实现IIC SLAVE之前,需要先了解IIC SLAVE的工作原理。IIC SLAVE是指设备作为IIC总线上的从设备,需要响应主设备的读写请求。 GPIO是通用输入输出引脚,可以通过软件控制引脚的电平状态。在实现IIC SLAVE时,可以使用GPIO模拟IIC总线上的时钟线(SCL)和数据线(SDA)。 以下是使用GPIO实现IIC SLAVE的简单流程: 1. 初始化GPIO引脚,将SCL和SDA引脚设置为输入或输出模式。 2. 等待主设备发送起始信号(S)。 3. 接收主设备发送的设备地址和读写标志(R/W)。 4. 判断接收到的地址是否与当前设备地址匹配,如果匹配则发送应答信号(ACK)。 5. 根据读写标志,执行读或写操作。如果是写操作,接收主设备发送的数据并进行处理;如果是读操作,发送需要读取的数据。 6. 等待主设备发送停止信号(P)。 以下是使用Python代码实现IIC SLAVE的简单示例: ```python import RPi.GPIO as GPIO # IIC SLAVE地址 SLAVE_ADDR = 0x50 # GPIO引脚初始化 GPIO.setmode(GPIO.BOARD) GPIO.setup(SCL_PIN, GPIO.OUT) GPIO.setup(SDA_PIN, GPIO.OUT) # 等待主设备发送起始信号 def wait_start(): GPIO.setup(SDA_PIN, GPIO.IN) while GPIO.input(SDA_PIN) == GPIO.LOW: pass GPIO.setup(SDA_PIN, GPIO.OUT) # 判断起始信号是否合法 if GPIO.input(SCL_PIN) == GPIO.LOW: return True else: return False # 接收设备地址和读写标志 def receive_addr(): addr = 0 for i in range(7): GPIO.setup(SCL_PIN, GPIO.IN) addr = (addr << 1) | GPIO.input(SDA_PIN) GPIO.setup(SCL_PIN, GPIO.OUT) rw = GPIO.input(SDA_PIN) return (addr, rw) # 发送应答信号 def send_ack(): GPIO.output(SDA_PIN, GPIO.LOW) GPIO.setup(SCL_PIN, GPIO.IN) GPIO.setup(SCL_PIN, GPIO.OUT) GPIO.output(SDA_PIN, GPIO.HIGH) # 接收数据 def receive_data(): data = 0 for i in range(8): GPIO.setup(SCL_PIN, GPIO.IN) data = (data << 1) | GPIO.input(SDA_PIN) GPIO.setup(SCL_PIN, GPIO.OUT) return data # 发送数据 def send_data(data): for i in range(8): GPIO.setup(SCL_PIN, GPIO.OUT) GPIO.output(SDA_PIN, (data & 0x80) >> 7) data = data << 1 GPIO.setup(SCL_PIN, GPIO.IN) # 等待主设备发送停止信号 def wait_stop(): GPIO.setup(SDA_PIN, GPIO.IN) while GPIO.input(SDA_PIN) == GPIO.HIGH: pass GPIO.setup(SDA_PIN, GPIO.OUT) # 主函数 def main(): # 等待起始信号 if wait_start() == False: return # 接收设备地址和读写标志 addr, rw = receive_addr() # 判断地址是否匹配 if addr != SLAVE_ADDR: return # 发送应答信号 send_ack() # 根据读写标志执行操作 if rw == 0: # 接收主设备发送的数据并进行处理 data = receive_data() # 处理数据 ... else: # 发送需要读取的数据 send_data(data) # 等待停止信号 wait_stop() # 主程序入口 if __name__ == '__main__': main() ``` 注意:以上示例仅为演示实现IIC SLAVE的基本原理,具体实现还需要根据具体应用场景进行调整和优化。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值