关于IIC的一些理解

 

简单低速通信

简单的双向两线制总线协议标准,支持同步串行半双工通讯。

标准模式

100kbit/s

快速模式

400kbit/s

高速模式

3.4Mbit/s(大多数 IIC 设备不支持)

开漏模式允许总线上多个设备的输出线连接在一起,实现“线与”功能。在 IIC 总线中,多个主设备或从设备会连接到同一组 SDA 线(数据线)和 SCL(时钟线)上。当任意一个设备将信号线拉低时,整个总线的电平就会拉低;只有所有设备都释放总线(输出高阻态)时,总线才会在外部上拉电阻的作用下呈现高电平。这种特性使得多个设备可以共享总线进行通信,避免信号冲突。

 

物理层

SCL:时钟线,用于数据收发同步;

SDA:串行数据总线,用高低电平表示数据;

支持一主多从,也支持多主多从

每个设备都有唯一的地址,主机通过这个地址与从设备通信;

总线通过上拉电阻接到电源,设备空闲时,输出高阻态,当所有设备都空闲时,都输出高阻态,由上拉电阻把总线拉成高电平;

协议层

位传输:高位先行;

起始信号:SDA 由高变低,SCL 保持高电平;

停止信号:SDA 由低变高,SCL 保持高电平;

传输地址:主机通过 SDA 线发送设备地址来查找从机;IIC 规定设备地址可以是 7 位或 10 位,实际中 7 位地址应用更为广泛;

 

数据有效性:SCL 高电平范围内去读取 SDA 线上的数据,只有在 SCL 处于低电平时,SDA 才能改变;SDA 线上的数据必须在时钟的高电平周期保持稳定。

响应:接收方接收到数据后,要给发送方发送响应,有两种响应

1.应答响应:给发送方发送一个低电平;

2.非应答响应:给发送方发送一个高电平;

 

 

代码实现

 

 

 

 

1.软件模拟 IIC

 

IIC 操作配置

/* 宏定义:应答与非应答 */
#define ACK 0
#define NACK 1

/* 宏定义:时钟线拉高拉低 */
#define SCL_HIGH (GPIOB->ODR |= GPIO_ODR_ODR10)
#define SCL_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR10)

/* 宏定义:数据线拉高拉低 */
#define SDA_HIGH (GPIOB->ODR |= GPIO_ODR_ODR11)
#define SDA_LOW (GPIOB->ODR &= ~GPIO_ODR_ODR11)

/* 宏定义:数据线读取 */
#define SDA_READ (GPIOB->IDR & GPIO_IDR_IDR11)

/* 宏定义:操作延时 */
#define IIC_DELAY Com_Delay_Us(5)

IIC 实际操作声明


/**
 * @brief IIC初始化
 *
 */
void Dri_IIC_Init(void);

/**
 * @brief 发出起始信号
 *
 */
void Dri_IIC_Start(void);

/**
 * @brief 发送停止信号
 *
 */
void Dri_IIC_Stop(void);

/**
 * @brief 主机发送应答信号
 *
 */
void Dri_IIC_sendACK(void);

/**
 * @brief 主机发送非应答信号
 *
 */
void Dri_IIC_sendNACK(void);

/**
 * @brief 主机等待从设备发来的应答信号
 *
 * @return uint8_t 0:从机应答 1:从机非应答
 */
uint8_t Dri_IIC_wait4ACK(void);

/**
 * @brief IIC发送一个字节
 *
 * @param byte 要发送的字节
 */
void Dri_IIC_sendByte(uint8_t byte);

/**
 * @brief 主机从从设备接收一个字节
 *
 * @return uint8_t 从机发送的字节
 */
uint8_t Dri_IIC_RceiveByte(void);

IIC 实际操作实现


/**
 * @brief IIC初始化
 *
 */
void Dri_IIC_Init(void)
{

    /* 使能IIC2时钟 */
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

    /* GPIO工作模式配置 通用开漏输出 MODE:11 CNF:01 */
    GPIOB->CRH |= (GPIO_CRH_MODE10 | GPIO_CRH_MODE11);
    GPIOB->CRH &= ~(GPIO_CRH_CNF10_1 | GPIO_CRH_CNF11_1);
    GPIOB->CRH |= (GPIO_CRH_CNF10_0 | GPIO_CRH_CNF11_0);
}

/**
 * @brief 发出起始信号
 *
 */
void Dri_IIC_Start(void)
{
    SCL_HIGH;
    SDA_HIGH;
    IIC_DELAY;

    SDA_LOW;
    IIC_DELAY;

    // SCL_LOW;
    // IIC_DELAY;
}

/**
 * @brief 发送停止信号
 *
 */
void Dri_IIC_Stop(void)
{

    SCL_HIGH;
    SDA_LOW;
    IIC_DELAY;

    SDA_HIGH;
    IIC_DELAY;
}

/**
 * @brief 主机发送应答信号
 *
 */
void Dri_IIC_sendACK(void)
{

    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;

    SDA_LOW;
    IIC_DELAY;

    SCL_HIGH;
    IIC_DELAY;

    SCL_LOW;
    IIC_DELAY;

    SDA_HIGH;
    IIC_DELAY;
}

/**
 * @brief 主机发送非应答信号
 *
 */
void Dri_IIC_sendNACK(void)
{
    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;

    SCL_HIGH;
    IIC_DELAY;

    SCL_LOW;
    IIC_DELAY;
}

/**
 * @brief 主机等待从设备发来的应答信号
 *
 * @return uint8_t 0:从机应答 1:从机非应答
 */
uint8_t Dri_IIC_wait4ACK(void)
{

    //  ack;

    SCL_LOW;
    SDA_HIGH;
    IIC_DELAY;

    SCL_HIGH;
    IIC_DELAY;

    uint16_t ack = SDA_READ;

    SCL_LOW;
    IIC_DELAY;

    return ack ? NACK : ACK;
}

/**
 * @brief IIC发送一个字节
 *
 * @param byte 要发送的字节
 */
void Dri_IIC_sendByte(uint8_t byte)
{

    for (uint8_t i = 0; i < 8; i++)
    {
        SCL_LOW;
        SDA_LOW;
        IIC_DELAY;

        if (byte & 0x80) // 判断最高位是否为1
        {
            SDA_HIGH;
        }
        else
        {
            SDA_LOW;
        }
        IIC_DELAY;

        SCL_HIGH;
        IIC_DELAY;

        SCL_LOW;
        IIC_DELAY;
        /* byte左移 */
        byte <<= 1;
    }
}

/**
 * @brief 主机从EEPROM接收一个字节
 *
 * @return uint8_t 从机发送的字节
 */
uint8_t Dri_IIC_RceiveByte(void)
{
   
    uint8_t data = 0;

    for (uint8_t i = 0; i < 8; i++)
    {
        SCL_LOW;
        IIC_DELAY;

        SCL_HIGH;
        IIC_DELAY;

        data <<= 1;   // 先左移一位,防止左移溢出
        if (SDA_READ) // 主机发送应答信号
        {
            data |= 0x01;
        }
        
        SCL_LOW;
        IIC_DELAY;
    }

    return data;
}

M24C02 操作声明

#include "Dri_IIC.h"
#include "Dri_UART.h"

/* 宏定义:地址读写 */
#define W_ADDR 0xA0
#define R_ADDR 0xA1

/**
 * @brief EEPROM初始化
 *
 */
void Int_EEPROM_Init(void);

/**
 * @brief 向EEPROM中写入一个字节
 *
 * @param byte 待写入字节
 * @param inner_addr 写入字节的位置
 */
void Int_EEPROM_WriteByte(uint8_t byte, uint8_t inner_addr);

/**
 * @brief 从EEPROM中读取一个字节
 *
 * @param inner_addr 读取字节的内部地址
 * @return uint8_t 返回值
 */
uint8_t Int_EEPROM_ReadByte(uint8_t inner_addr);

/**
 * @brief 页写
 *
 * @param inner_addr 起始地址
 * @param bytes 待写入数据
 * @param size 待写入数据长度
 */
void Int_EEPROM_PageWrite(uint8_t inner_addr, uint8_t *bytes, uint8_t size);

/**
 * @brief 页读
 *
 * @param inner_addr 起始地址
 * @param buffer 缓冲区
 * @param size 缓冲区长度

 */
void Int_EEPROM_PageRead(uint8_t inner_addr, uint8_t *buffer, uint8_t size);

M24C02 操作实现

/**
 * @brief EEPROM初始化
 *
 */
void Int_EEPROM_Init(void)
{
    Dri_IIC_Init();
}

/**
 * @brief 向EEPROM中写入一个字节
 *
 * @param byte 待写入字节
 * @param inner_addr 写入字节的位置
 */
void Int_EEPROM_WriteByte(uint8_t byte, uint8_t inner_addr)
{

    Dri_IIC_Start();

    /* 发送写地址 */
    Dri_IIC_sendByte(W_ADDR);

    uint8_t ack = Dri_IIC_wait4ACK();
    if (ack == ACK)
    {
        Dri_IIC_sendByte(inner_addr);

        Dri_IIC_wait4ACK();

        /* 发送具体数据 */
        Dri_IIC_sendByte(byte);
        Dri_IIC_wait4ACK();
        printf("写入成功\r\n");
        Dri_IIC_Stop();
    }

    /* 延时5ms:等待写入周期结束 */
    Com_Delay_Ms(5);
}
/**
 * @brief 从EEPROM中读取一个字节
 *
 * @param inner_addr 读取字节的内部地址
 * @return uint8_t 返回值
 */
uint8_t Int_EEPROM_ReadByte(uint8_t inner_addr)
{
    uint8_t data = 0;
    Dri_IIC_Start();
    Dri_IIC_sendByte(W_ADDR); // 假写
    Dri_IIC_wait4ACK();

    Dri_IIC_sendByte(inner_addr);
    Dri_IIC_wait4ACK();

    Dri_IIC_Start();
    Dri_IIC_sendByte(R_ADDR); // 真读
    Dri_IIC_wait4ACK();

    data = Dri_IIC_RceiveByte();
    printf("读取成功\r\n");
    Dri_IIC_sendNACK(); // 发送一个非应答

    Dri_IIC_Stop();
    return data;
}

/**
 * @brief 页写
 *
 * @param inner_addr 起始地址
 * @param bytes 待写入数据
 * @param size 待写入数据长度
 */
void Int_EEPROM_PageWrite(uint8_t inner_addr, uint8_t *bytes, uint8_t size)
{

    Dri_IIC_Start();
    Dri_IIC_sendByte(W_ADDR);

    uint8_t ack = Dri_IIC_wait4ACK();
    if (ack == ACK)
    {
        Dri_IIC_sendByte(inner_addr);
        Dri_IIC_wait4ACK();

        for (uint8_t i = 0; i < size; i++)
        {
            Dri_IIC_sendByte(bytes[i]);
            Dri_IIC_wait4ACK();
        }
        Dri_IIC_Stop();
    }

    Com_Delay_Ms(5);
}
/**
 * @brief 页读
 *
 * @param inner_addr 起始地址
 * @param buffer 缓冲区
 * @param size 缓冲区长度

 */
void Int_EEPROM_PageRead(uint8_t inner_addr, uint8_t *buffer, uint8_t size)
{

    Dri_IIC_Start();
    Dri_IIC_sendByte(W_ADDR); // 假写
    Dri_IIC_wait4ACK();
    Dri_IIC_sendByte(inner_addr);
    Dri_IIC_wait4ACK();

    Dri_IIC_Start();
    Dri_IIC_sendByte(R_ADDR);
    Dri_IIC_wait4ACK();
    for (uint8_t i = 0; i < size; i++)
    {

        buffer[i] = Dri_IIC_RceiveByte();
        if (i < size - 1)
        {
            Dri_IIC_sendACK();
        }
        else
        {
            Dri_IIC_sendNACK();
        }
    }
    Dri_IIC_Stop();
}

主函数测试

int main()
{
	Dri_UART_Init();
	Int_EEPROM_Init();

	printf("STM32‘s EEPROM exampl...\r\n");

	// 向EEPROM依次写入数据
	Int_EEPROM_WriteByte('g', 0x00);
	Int_EEPROM_WriteByte('h', 0x01);
	Int_EEPROM_WriteByte('r', 0x02);

	// 读取字符
	uint8_t byte1 = Int_EEPROM_ReadByte(0x00);
	uint8_t byte2 = Int_EEPROM_ReadByte(0x01);
	uint8_t byte3 = Int_EEPROM_ReadByte(0x02);

	printf("byte1=%c\r\n", byte1);
	printf("byte2=%c\r\n", byte2);
	printf("byte3=%c\r\n", byte3);

	/* 写入多个字符 */
	Int_EEPROM_PageWrite(0x000, "高浩然", 9);

	/* 读取多个字符 */
	uint8_t buffer[100] = {0};
	Int_EEPROM_PageRead(0x00, buffer, 9);
	printf("buffer=%s\r\n", buffer);

	/* 验证写入和读取的字符串是否一致 */
	char *str = "高浩然";
	if (memcmp(str, buffer, 9) == 0)
	{
		printf("写入和读取的字符串一致\r\n");
	}
	else
	{
		printf("写入和读取的字符串不一致\r\n");
	}
	while (1)
	{
	};
}

结果

https://p.sda1.dev/24/fd6873fdc9d45d38696f2300c74198a4/Snipaste_2025-06-16_00-22-52.png

 

 

硬件模拟

 

posted @ 2025-06-26 13:50  拉格朗日_欧拉  阅读(36)  评论(0)    收藏  举报