打印
[经验分享]

单片机硬件IIC从机应用

[复制链接]
1900|11
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
tpgf|  楼主 | 2024-11-5 14:25 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
// 这里中断和轮询还没有做管脚初始化区分
IIC_SLVAE_STATUS IIC_Slave_Gpio_Init(void)
{
    // 使用PA04--SCK PA03--SDA  输入上拉
    SDA_SCK_GPIO_EN;
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    REGBITS_SET(SDA_GPIO_PROT->PUR, SDA_GPIO_PIN);
//    REGBITS_CLR(SDA_GPIO_PROT->PDR, SDA_GPIO_PIN);

    REGBITS_SET(SCK_GPIO_PORT->PUR, SCK_GPIO_PIN);
//    REGBITS_CLR(SCK_GPIO_PORT->PDR, SCK_GPIO_PIN);

#if IIC_SLAVE_POLLING_MODE
    // 使用PB04--SCK PB03--SDA  输入上拉
    // 开漏模式
    GPIO_InitStruct.IT = GPIO_IT_NONE;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pins = SDA_GPIO_PIN;
    SDA_WRITE_H;
    GPIO_Init(SDA_GPIO_PROT, &GPIO_InitStruct);

    // 输入模式
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_InitStruct.Pins = SCK_GPIO_PIN;  // 防止有问题,单个初始化
    GPIO_Init(SCK_GPIO_PORT, &GPIO_InitStruct);

#elif IIC_SLAVE_INTTERUPT_MODE

    // 下降沿中断模式
    GPIO_InitStruct.IT = GPIO_IT_FALLING;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_InitStruct.Pins = SDA_GPIO_PIN;
    SDA_WRITE_H;
    GPIO_Init(SDA_GPIO_PROT, &GPIO_InitStruct);

    // 输入模式
    GPIO_InitStruct.IT = GPIO_IT_NONE;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP;
    GPIO_InitStruct.Pins = SCK_GPIO_PIN;  // 防止有问题,单个初始化
    GPIO_Init(SCK_GPIO_PORT, &GPIO_InitStruct);

    //清除中断标志并使能NVIC
    SDA_GPIO_CLEAR_IT_FLAG;
    SCK_GPIO_CLEAR_IT_FLAG;
    NVIC_SetPriority(SDA_SCK_IRQn, 0);  // 最高优先级
    NVIC_EnableIRQ(SDA_SCK_IRQn);
#endif

    if (SDA_READ == GPIO_Pin_RESET || SDA_READ == GPIO_Pin_RESET) {  // 此时的SDA 和SCK要为高为状态
        return STATUS_NO;
    }

#if IIC_SLAVE_TEST_EN
    // 测试脚 PB5--输出上拉
    __RCC_GPIOB_CLK_ENABLE();
    GPIO_InitStruct.IT = GPIO_IT_NONE;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pins = GPIO_PIN_5;
    GPIO_Init(CW_GPIOB, &GPIO_InitStruct);
    TEST_PIN_L;
#endif

    return STATUS_OK;
}

#if IIC_SLAVE_POLLING_MODE

static inline IIC_SLVAE_STATUS IIC_Slave_Start(void)
{
    if (SDA_READ == GPIO_Pin_RESET && SCK_READ == GPIO_Pin_SET) { // 接收到起始位
        TEST_PIN_L;
        iicSlaveStruct.startFlag = 1;
        iicSlaveStruct.rxDataCnt = 0;
        iicSlaveStruct.startRxDataCntRecord = iicSlaveStruct.rxDataCnt;  // 记录当前起始位的第一个数据位置
    } else {
        return STATUS_NO;
    }

    // 消耗掉起始位,等待位低
    uint32_t overCnt = 0;
    while(1) {
        if(SCK_READ == GPIO_Pin_RESET) {
            break; // 进入起始位 开始接收数据
        }
        overCnt++;
        if (overCnt > IIC_RX_OVERTIME_CNT) {
            return IIC_START_OVERTIME;
        }
    };
    // 开始接收数据
    iicSlaveStruct.startFlag = 0;  // 清除标志
    iicSlaveStruct.addressWriteReadFlag = 0;  // 第一次默认地址写,从机接收数据

    iicSlaveStruct.rxByteBitCnt = 0;
    iicSlaveStruct.rxDataCnt = 0;
    iicSlaveStruct.rxTempData = 0;
    iicSlaveStruct.ackMasterFlag = 0;
#if DJI_Drone
    iicSlaveStruct.ackMasterFlagEn = 0;
    iicSlaveStruct.rxDataBuf[1] = 0;  // 这个特殊处理一下
#endif
    return STATUS_OK;
}
#endif

inline void IIC_Slave_Mater_Read_Tx_Handle_Register(void(*txDataHandle)(uint8_t *buf, uint8_t size, IIC_SlaveReadTxDataHandleStruct *txStruct))
{
    iicSlaveStruct.txDataHandle = txDataHandle;
}

inline void IIC_Slave_Rx_Handle_Register(void(*rxDataHandle)(uint8_t *buf, uint8_t size))
{
    iicSlaveStruct.rxDataHandle = rxDataHandle;
}

static inline IIC_SLVAE_STATUS IIC_Slave_Send_Data(void)
{
    // 待会发送的数量接上
    if (iicSlaveStruct.addressWriteReadFlag == 1 && iicSlaveStruct.ackMasterFlag == 0) {
         iicSlaveStruct.txByteBitCnt = 0;
        iicSlaveStruct.txDataCnt = 0;

        if (iicSlaveStruct.txDataHandle != NULL) {  // 填充发送数据
            //回调slaveTxDataHandle这个函数,根据命令号控制是否发送数据,赋值相应的buff数据和size字节数
            iicSlaveStruct.txDataHandle(iicSlaveStruct.rxDataBuf, iicSlaveStruct.rxDataCnt, &iicSlaveStruct.txStruct);
        }
        iicSlaveStruct.rxDataCnt = 0;
        while(1) {
            if (iicSlaveStruct.txByteBitCnt == 8) {  // 数据发送完毕,开始接收应答
                SDA_WRITE_H; // 1个字节发送完毕,开始释放数据线来接收主机应答
                iicSlaveStruct.readMasterAckFlag = 1;

                iicSlaveStruct.txByteBitCnt = 0;
                iicSlaveStruct.txDataCnt++;
            } else {
                if (iicSlaveStruct.txDataCnt < iicSlaveStruct.txStruct.size) { //iicSlaveStruct.txStruct.size不发数据是0
                    if (iicSlaveStruct.txStruct.txBuf[iicSlaveStruct.txDataCnt] & (0x80 >> iicSlaveStruct.txByteBitCnt)) {  // 发送高位在先
                        SDA_WRITE_H;
                    } else {
                        SDA_WRITE_L;
                    }
                } else {
                    SDA_WRITE_H;  // 数据超过就发送0xFF
                }
                iicSlaveStruct.txByteBitCnt++;
            }

            IIC_SLVAE_STATUS result = IIC_SCK_L() ;
            if (result != STATUS_OK) {
                return result;
            }

            result = IIC_SCK_H();
            if (result != STATUS_OK) {
                return result;
            }
        }
    }
    return STATUS_OK;
}

static inline void IIC_Slave_Receive_Data(void)
{
    // sck为高,开始读取数据
    if (iicSlaveStruct.addressWriteReadFlag == 0 && iicSlaveStruct.ackMasterFlag == 0) {  //如果为1就是应答,主机读取
        if (SDA_READ == GPIO_Pin_SET) {
            iicSlaveStruct.rxTempData |= 0x80 >> iicSlaveStruct.rxByteBitCnt; // 高位发送在先
        }
        iicSlaveStruct.rxByteBitCnt++;

        if (iicSlaveStruct.rxByteBitCnt == 8) {
            iicSlaveStruct.ackMasterFlag = 1;   // 开始回复应答

            iicSlaveStruct.rxDataBuf[iicSlaveStruct.rxDataCnt] = iicSlaveStruct.rxTempData;
            iicSlaveStruct.rxByteBitCnt = 0; // 重新计数
            iicSlaveStruct.rxTempData = 0;
            iicSlaveStruct.rxDataCnt++;
            if (iicSlaveStruct.rxDataCnt >= sizeof(iicSlaveStruct.rxDataBuf)) {
                iicSlaveStruct.rxDataCnt = sizeof(iicSlaveStruct.rxDataBuf) - 1;
            }

            if (iicSlaveStruct.rxDataBuf[1] == 0x2f) {
                PB03_SETHIGH(); //打开IO给66供电
                PB04_SETHIGH();
            }

            if (iicSlaveStruct.startFlag == 1) { // 判断读写和命令进行应答
                iicSlaveStruct.startFlag = 0;
                if (iicSlaveStruct.rxDataBuf[iicSlaveStruct.startRxDataCntRecord] == IIC_Slave_Read_Adress) { //主机读状态
                    iicSlaveStruct.addressWriteReadFlag = 1; // 进入写的状态
#if DJI_Drone
                    if (iicSlaveStruct.rxDataBuf[1] == 0x2f) {
                        CLOSE_BMS_IIC;
                        PB03_SETLOW();   //关闭IO口输出,认证芯片断电(降低功耗)
                        PB04_SETLOW();
                    }
#endif
                } else {  // 从新进入接收状态
                    iicSlaveStruct.addressWriteReadFlag = 0;  // 第一次默认地址写,从机接收数据

                    iicSlaveStruct.rxByteBitCnt = 0;
                    iicSlaveStruct.rxDataCnt = 0;
                    iicSlaveStruct.rxTempData = 0;
                }
            }
        }
    } else {
        iicSlaveStruct.ackMasterFlag = 0;
    }
}

static inline void IIC_Slave_Send_Ack(void)
{
    // 开始发送数据
    // 发送的时候不处理
    if (iicSlaveStruct.ackMasterFlag == 1) {  // 应答主机
#if DJI_Drone
        if (iicSlaveStruct.rxDataBuf[1] == 0xC2 || iicSlaveStruct.ackMasterFlagEn == 1) {
            iicSlaveStruct.ackMasterFlagEn = 0;
            CLOSE_BMS_IIC;
            SDA_WRITE_L;
        }
#else
        SDA_WRITE_L;
#endif
    } else {
        SDA_WRITE_H;
    }
}

static inline IIC_SLVAE_STATUS IIC_SCK_L(void)
{
    uint32_t overCnt = IIC_RX_OVERTIME_CNT;
    while(overCnt--) {  // 低电平准备数据循环
        if(SCK_READ == GPIO_Pin_SET) {
            return STATUS_OK; // 进入数据读取区
        }
    }
    return IIC_WAIT_H_OVERTIME;
}

static inline IIC_SLVAE_STATUS IIC_SCK_H(void)
{
    uint32_t overCnt = IIC_RX_OVERTIME_CNT;
    uint8_t endReadFlag = 0;
    if (SDA_READ == GPIO_Pin_RESET) {  // 这里是SCK电平高时,等待位低的过程中检测SDA是否有变化,来判断起始位和终止位
        endReadFlag = 1;
    } else {
        endReadFlag = 0;
    }

    while(overCnt--) { // 高电平读取数据循环
        if (iicSlaveStruct.readMasterAckFlag == 1) {  // 接收应答
            iicSlaveStruct.readMasterAckFlag = 0;
            if (SDA_READ == GPIO_Pin_SET) {
                return IIC_MASTER_ACK_FAIL;
            }
        }

        if(SCK_READ == GPIO_Pin_RESET) {
            return STATUS_OK; // 进入数据准备区
        } else if (endReadFlag == 1 && SDA_READ == GPIO_Pin_SET) {  // 检测到结束标志
            iicSlaveStruct.endFlag = 1;
            return IIC_END_SIGNLE;
        } else if (endReadFlag == 0 && SDA_READ == GPIO_Pin_RESET) { // 检测到起始标志
            iicSlaveStruct.startFlag = 1;
            iicSlaveStruct.startRxDataCntRecord = iicSlaveStruct.rxDataCnt;

            iicSlaveStruct.rxByteBitCnt = 0; // 清除位接收
            iicSlaveStruct.rxTempData = 0;
            iicSlaveStruct.txByteBitCnt = 0;
        }
    }
    return IIC_WAIT_L_OVERTIME;
}

static inline void IIC_Slave_RX_TX_Handle(void)
{
    while(1) {
        if (IIC_Slave_Send_Data() != STATUS_OK) {
            SDA_WRITE_H; // 释放SDA
            break;
        }
        IIC_Slave_Send_Ack();
        iicSlaveStruct.result = IIC_SCK_L();
        if (iicSlaveStruct.result != STATUS_OK) {
            break;
        }

        IIC_Slave_Receive_Data();

        iicSlaveStruct.result = IIC_SCK_H();  //接收完整一帧数据,IIC_END_SIGNLE返回后退出本次接收
        if (iicSlaveStruct.result == IIC_END_SIGNLE) { // 搞成一个宏,结束这次接收
            break;
        } else if (iicSlaveStruct.result != STATUS_OK) {
            break;
        }
    }
}

#if IIC_SLAVE_POLLING_MODE
// 待会接收和发送数据要内联函数封装分开
// 先轮询接收
static IIC_SLVAE_STATUS IIC_Slave_Rx_Data(void)
{
    IIC_SLVAE_STATUS result;
    result = IIC_Slave_Start();
    if (result != STATUS_OK) {
        if (result == STATUS_NO) {
            return STATUS_OK;  // 没有起始位,也可以正常
        }
        return IIC_START_OVERTIME;
    }

    // 开始接收数据
    // sck为低
    IIC_Slave_RX_TX_Handle();
    OPEN_BMS_IIC;
    SDA_WRITE_H; // 释放SDA
    return iicSlaveStruct.result;
}
#endif

#if IIC_SLAVE_INTTERUPT_MODE

void IIC_SDA_Inttrupt_Handle(void)
{
    TEST_PIN_L;
    iicSlaveStruct.startFlag = 1;
    iicSlaveStruct.startRxDataCntRecord = iicSlaveStruct.rxDataCnt;  // 记录当前起始位的第一个数据位置
    iicSlaveStruct.addressWriteReadFlag = 0;  // 第一次默认地址写
    SCK_INTERRUPT_MODE;
}


void IIC_SCK_Intterupt_Handle(void)
{
    SDA_OPEN_LEAK_MODE;
    iicSlaveStruct.startFlag = 0;  // 清除标志
    iicSlaveStruct.addressWriteReadFlag = 0;  // 第一次默认地址写,从机接收数据

    iicSlaveStruct.rxByteBitCnt = 0;
    iicSlaveStruct.rxDataCnt = 0;
    iicSlaveStruct.rxTempData = 0;
    iicSlaveStruct.ackMasterFlag = 0;
#if DJI_Drone
    iicSlaveStruct.ackMasterFlagEn = 0;
    iicSlaveStruct.rxDataBuf[1] = 0;  // 这个特殊处理一下
#endif

    // 开始接收数据
    IIC_Slave_RX_TX_Handle();

    OPEN_BMS_IIC;

    SCK_INTPU_MODE;
    SDA_WRITE_H; // 释放SDA
    SDA_INTERRUPT_MODE;  // 接收完数据要开启中断模式
}

// 放在管脚中断里面跑
inline void IIC_Slave_IRQ_Handle(void)
{
    if (SCK_READ == GPIO_Pin_SET && SDA_READ == GPIO_Pin_RESET && iicSlaveStruct.startFlag == 0) {  // 接收到起始位
        IIC_SDA_Inttrupt_Handle();
    } else if (SCK_READ == GPIO_Pin_RESET) {  // 开始处理数据
        if (SDA_READ == GPIO_Pin_RESET && iicSlaveStruct.startFlag == 1) {
            IIC_SCK_Intterupt_Handle();
            iicSlaveStruct.rxTxFlag = 1;
        }
        iicSlaveStruct.startFlag = 0; // 接收发送完数据或者错误进来都要清楚这个标志位
    }
    SDA_GPIO_CLEAR_IT_FLAG;
    SCK_GPIO_CLEAR_IT_FLAG;
}
#endif

uint8_t Get_IIC_Slave_RxTxFlag(void)
{
    return iicSlaveStruct.rxTxFlag; //  该标志为1 代表iicSlave有接收或者发送数据标志, iicSlave只会置位,不会清除该标志
}

void Set_IIC_Slave_RxTxFlag(uint8_t flag)
{
    if (flag) {
        iicSlaveStruct.rxTxFlag = 1;
    } else {
        iicSlaveStruct.rxTxFlag = 0;
    }
}

void IIC_Slave_Disable(void)
{
    //清除中断标志并使能NVIC
    SDA_GPIO_CLEAR_IT_FLAG;
    SCK_GPIO_CLEAR_IT_FLAG;

    __disable_irq();  

    NVIC_DisableIRQ(SDA_SCK_IRQn);

    __enable_irq();


    SDA_OPEN_LEAK_MODE;
    SDA_WRITE_H; // 释放SDA

    SCK_INTPU_MODE;
    iicSlaveStruct.startFlag = 0;
}

void IIC_Slave_Enable(void)
{
    SDA_GPIO_CLEAR_IT_FLAG;
    SCK_GPIO_CLEAR_IT_FLAG;
    SDA_INTERRUPT_MODE;

    __disable_irq();

    NVIC_EnableIRQ(SDA_SCK_IRQn);

    __enable_irq();
}

// 看函数说明
// 函数参数:
// pin: 0位SDA管脚, 1位SCK管脚
// mode 管脚模式,可以位下面三个值:GPIO_MODE_INPUT_PULLUP、GPIO_MODE_INPUT_PULLDOWN、GPIO_MODE_INPUT
// ItEdge 唤醒中断边沿,可以位下面三个值: GPIO_IT_RISING、GPIO_IT_FALLING 、GPIO_IT_RISING | GPIO_IT_FALLING

void IIC_Slave_Sleep_Entry(uint32_t pin, uint32_t mode, uint32_t ItEdge)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    if (pin == 0) {
        GPIO_InitStruct.IT = ItEdge;
        GPIO_InitStruct.Mode = mode;
        GPIO_InitStruct.Pins = SDA_GPIO_PIN;
        GPIO_Init(SDA_GPIO_PROT, &GPIO_InitStruct);
        SDA_GPIO_CLEAR_IT_FLAG;
    } else {
        GPIO_InitStruct.IT = ItEdge;
        GPIO_InitStruct.Mode = mode;
        GPIO_InitStruct.Pins = SCK_GPIO_PIN;
        GPIO_Init(SCK_GPIO_PORT, &GPIO_InitStruct);
        SCK_GPIO_CLEAR_IT_FLAG;
    }
    //清除中断标志并使能NVIC

    NVIC_SetPriority(SDA_SCK_IRQn, 0);  // 最高优先级
    NVIC_EnableIRQ(SDA_SCK_IRQn);
}

void IIC_Slave_WakeUp_Handle(void)
{
    IIC_Slave_Gpio_Init();
}

void IIC_Slave_Task(void)
{
#if IIC_SLAVE_POLLING_MODE

    IIC_SLVAE_STATUS result = IIC_Slave_Rx_Data();
    if (result != STATUS_OK) {
        SDA_WRITE_H; // 释放SDA
    }

#endif

    //接收完整一帧数据,退出一帧数据的接收,取一帧数据
    if (iicSlaveStruct.rxDataCnt != 0) {  
        if (iicSlaveStruct.rxDataHandle != NULL) {
            iicSlaveStruct.rxDataHandle(iicSlaveStruct.rxDataBuf, iicSlaveStruct.rxDataCnt);
        }
        iicSlaveStruct.rxDataCnt = 0;
    }
}
————————————————

                            版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_50707044/article/details/141261783

使用特权

评论回复
沙发
caigang13| | 2024-11-6 08:13 | 只看该作者
MCU很少做从机,一般都是主机应用吧。

使用特权

评论回复
板凳
lzbf| | 2024-11-8 20:44 | 只看该作者
在SCL和SDA线上正确配置上拉电阻

使用特权

评论回复
地板
sdlls| | 2024-11-9 16:36 | 只看该作者
使用软件模拟I2C通信              

使用特权

评论回复
5
louliana| | 2024-11-10 10:08 | 只看该作者
正确读取或发送数据后,处理中断标志位

使用特权

评论回复
6
wengh2016| | 2024-11-10 11:45 | 只看该作者
避免多个设备使用相同的地址。              

使用特权

评论回复
7
mnynt121| | 2024-11-10 20:04 | 只看该作者
在I2C通信过程中,可能会遇到各种错误情况,如总线冲突、超时等

使用特权

评论回复
8
uiint| | 2024-11-10 22:26 | 只看该作者
使用适当的上拉电阻 来确保信号稳定。

使用特权

评论回复
9
minzisc| | 2024-11-10 22:53 | 只看该作者
从机需要能够跟随主机的时钟信号。

使用特权

评论回复
10
pixhw| | 2024-11-11 10:15 | 只看该作者
注意时钟信号的稳定性,避免由于时钟抖动或不准确导致的通信错误

使用特权

评论回复
11
febgxu| | 2024-11-13 14:57 | 只看该作者
单片机IIC模块的硬件限制,如最大传输速率、最大数据包长度等。

使用特权

评论回复
12
zerorobert| | 2024-11-14 21:49 | 只看该作者
及时处理数据接收和发送,避免阻塞其他中断。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

2028

主题

15903

帖子

13

粉丝