CH32V003 I2C 从机实现

/ 0评 / 1

很多厂商,特别是国内的,对I2C从机的实现基本都不怎么在意,但是平时我又很多时候要让单片机做从机,一些简单的低数据量的交互非常方便.而且不额外占用任何IO.

时序分析是写代码的关键,幸好,在文档中有描述.

从机发送

从机接收

首先第一个共同的地方,EVT1,即ADDR匹配,但是一般的I2C通信是包含寄存器+内容的,所以更多的是类似如下的时序.

所以第二触发一定是EVT2(写寄存器地址),如果EVT2发生后,继续发生EVT2,可以理解成写入寄存器,则每次写入,寄存器指针移动1,如果检查到START标志(EVT1),则其实是RESTART,这时候读写方向就反了.

清除标志时候比较特别,基本上每种标志都有自己的清除方法,还真的挺混乱,还有就是收到STOP或者其他不明条件,就要马上停止,免得从机锁死总线.

标志解释:

代码实现,对状态机进行了简化,因此实际代码并不多,更多功能就自己想了.

#include "debug.h"

uint8_t reg_index = 0;
uint8_t reg[255] = {0x00};

#define DEVICE_IDLE 0
#define DEVICE_EVT1 1
#define DEVICE_EVT2 2

void I2C1_EV_IRQHandler(void) __attribute__((interrupt("WCH-Interrupt-fast")));

void I2C1_EV_IRQHandler(void)
{
    static uint8_t status = DEVICE_IDLE;

    if (I2C_GetFlagStatus(I2C1, I2C_FLAG_STOPF))
    {
        I2C1->CTLR1 &= I2C1->CTLR1;
        status = DEVICE_IDLE;
    }
    else if (I2C_GetFlagStatus(I2C1, I2C_FLAG_ADDR))
    {
        (void)(I2C1->STAR2); // 清除ADDR
        if (status == DEVICE_IDLE) // 如果从空闲开始则切换状态,否则只是RESTART!
        {
            status = DEVICE_ADDR_MATCHED;
        }
    }
    else if (I2C_GetFlagStatus(I2C1, I2C_FLAG_RXNE))
    {
        if (status == DEVICE_ADDR_MATCHED)
        {
            reg_index = I2C_ReceiveData(I2C1);
            status = DEVICE_DATA_PROCESS;
        }
        else
        {
            reg[reg_index] = I2C_ReceiveData(I2C1);
            reg_index++;
        }
    }
    else if (I2C_GetFlagStatus(I2C1, I2C_FLAG_TXE))
    {
        I2C_SendData(I2C1, reg[reg_index]);
        reg_index++;
    }
    else if (I2C_GetFlagStatus(I2C1, I2C_FLAG_AF))
    {
        I2C_SendData(I2C1, reg[reg_index]);
        reg_index++;
        I2C1->STAR1 &= ~I2C_FLAG_AF;
        status = DEVICE_IDLE;
    }
}

int main(void)
{
    GPIO_InitTypeDef GPIO_InitStructure={0};
    I2C_InitTypeDef I2C_InitTSturcture={0};
    NVIC_InitTypeDef NVIC_InitStructure = {0};

    RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOC | RCC_APB2Periph_AFIO, ENABLE );
    RCC_APB1PeriphClockCmd( RCC_APB1Periph_I2C1, ENABLE );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; // SCL
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1; // SDA
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init( GPIOC, &GPIO_InitStructure );

    NVIC_InitStructure.NVIC_IRQChannel = I2C1_EV_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    I2C_InitTSturcture.I2C_ClockSpeed = 100000;
    I2C_InitTSturcture.I2C_Mode = I2C_Mode_I2C;
    I2C_InitTSturcture.I2C_DutyCycle = I2C_DutyCycle_16_9;
    I2C_InitTSturcture.I2C_OwnAddress1 = 0x90;
    I2C_InitTSturcture.I2C_Ack = I2C_Ack_Enable;
    I2C_InitTSturcture.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Init( I2C1, &I2C_InitTSturcture );

    I2C_ITConfig(I2C1,I2C_IT_BUF,ENABLE);
    I2C_ITConfig(I2C1,I2C_IT_EVT,ENABLE);

    NVIC_EnableIRQ(I2C1_EV_IRQn);

    I2C_Cmd( I2C1, ENABLE );

    while(1);
}

其实这个芯片功能还算齐全,便宜,但是资料是真的很杂乱,但是看在价格份上,不得不原谅呢.

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注