(HAL)STM32F407ZGT6——24-1 IIC实验

目录

一、I2C简介

1、为何SDA与SCL使用开漏输出模式?

2、AT24C02通讯地址

3、软件I2C与硬件I2C的区别

4、EEPROM是什么?

5、I2C仲裁机制?

二、配置文件及相关函数

1、myiic.c

2、24cxx.c

(1)写一个字节

 (2)读一个字节

(3)连续读写(均是在读写一个字节的基础上实现)。


一、I2C简介

对比串口通信,从全双工转为半双工,有应答,一根线可以同时接多个模块,单片机可以选择与特定模块通信,并且不会相互干扰。
简而言之,I2C为同步、串行、半双工的通信总线协议。

1、为何SDA与SCL使用开漏输出模式?

SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平。即可同时作为输入输出。
硬件模式:复用开漏输出、既不上拉也不下拉。(快速模式:400 Kbit/s)
软件模拟:推挽输出、配置上拉电阻。
        

2、AT24C02通讯地址

根据不同地址访问不同芯片模块,若有同样地址的芯片模块挂载在同一条总线上时,通过修改AD0(举例)引脚上的电平,可以改变芯片地址的最后一位。可通过修改A0、A1、A2同时挂载多个相同地址的芯片模块。

3、软件I2C与硬件I2C的区别

容易用软件模拟。软件I2C,可直接软件模拟,任意引脚均可用,比较灵活。
硬件I2C具有资源限制,需要使用特定的引脚,不同stm32核心板的资源不同。

4、EEPROM是什么?

EEPROM是一种掉电数据不丢失的存储器,常用来存储配置信息,系统重新上电时可以加载。写完需要等5ms。

5、I2C仲裁机制?

        I2C 仲裁机制,总线具有 线“与”(Wired-AND)的逻辑功能,即只要有一个节点发送低电平时,总线上就表现为低电平;当所有节点都表现为高点平时,总线才表现为高电平。简单说,它遵循“低电平优先”的原则,即谁先发送低电平谁就会掌握对总线的控制权。SDA只允许一个主节点占据总线。
        正是由于这种“线”与逻辑功能,当多个节点同时发送时钟信号时,在总线上表现的是统一的时钟信号。这就是SCL的同步原理。
        SDA仲裁与SCL时钟同步处理过程没有先后关系,而是同步进行的。

二、配置文件及相关函数

1、myiic.c

#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"


/**
 * @brief       初始化IIC
 * @param       无
 * @retval      无
 */
void iic_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    IIC_SCL_GPIO_CLK_ENABLE();  /* SCL引脚时钟使能 */
    IIC_SDA_GPIO_CLK_ENABLE();  /* SDA引脚时钟使能 */

    gpio_init_struct.Pin = IIC_SCL_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_PP;        /* 推挽输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_VERY_HIGH; /* 快速 */
    HAL_GPIO_Init(IIC_SCL_GPIO_PORT, &gpio_init_struct);/* SCL */

    gpio_init_struct.Pin = IIC_SDA_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;        /* 开漏输出 */
    HAL_GPIO_Init(IIC_SDA_GPIO_PORT, &gpio_init_struct);/* SDA */
    /* SDA引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */

    iic_stop();     /* 停止总线上所有设备 */
}

/**
 * @brief       IIC延时函数,用于控制IIC读写速度
 * @param       无
 * @retval      无
 */
static void iic_delay(void)
{
    delay_us(2);    /* 2us的延时, 读写速度在250Khz以内 */
}

/**
 * @brief       产生IIC起始信号
 * @param       无
 * @retval      无
 */
void iic_start(void)
{
    IIC_SDA(1);
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(0);     /* START信号: 当SCL为高时, SDA从高变成低, 表示起始信号 */
    iic_delay();
    IIC_SCL(0);     /* 钳住I2C总线,准备发送或接收数据 */
    iic_delay();
}

/**
 * @brief       产生IIC停止信号
 * @param       无
 * @retval      无
 */
void iic_stop(void)
{
    IIC_SDA(0);     /* STOP信号: 当SCL为高时, SDA从低变成高, 表示停止信号 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(1);     /* 发送I2C总线结束信号 */
    iic_delay();
}

/**
 * @brief       等待应答信号到来
 * @param       无
 * @retval      1,接收应答失败
 *              0,接收应答成功
 */
uint8_t iic_wait_ack(void)
{
    uint8_t waittime = 0;
    uint8_t rack = 0;

    IIC_SDA(1);     /* 主机释放SDA线(此时外部器件可以拉低SDA线) */
    iic_delay();
    IIC_SCL(1);     /* SCL=1, 此时从机可以返回ACK */
    iic_delay();

    while (IIC_READ_SDA)    /* 等待应答 */
    {
        waittime++;

        if (waittime > 250)
        {
            iic_stop();
            rack = 1;
            break;
        }
    }

    IIC_SCL(0);     /* SCL=0, 结束ACK检查 */
    iic_delay();
    return rack;
}

/**
 * @brief       产生ACK应答
 * @param       无
 * @retval      无
 */
void iic_ack(void)
{
    IIC_SDA(0);     /* SCL 0 -> 1 时 SDA = 0,表示应答 */
    iic_delay();
    IIC_SCL(1);     /* 产生一个时钟 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
    IIC_SDA(1);     /* 主机释放SDA线 */
    iic_delay();
}

/**
 * @brief       不产生ACK应答
 * @param       无
 * @retval      无
 */
void iic_nack(void)
{
    IIC_SDA(1);     /* SCL 0 -> 1  时 SDA = 1,表示不应答 */
    iic_delay();
    IIC_SCL(1);     /* 产生一个时钟 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
}

/**
 * @brief       IIC发送一个字节
 * @param       data: 要发送的数据
 * @retval      无
 */
void iic_send_byte(uint8_t data)
{
    uint8_t t;
    
    for (t = 0; t < 8; t++)
    {
        IIC_SDA((data & 0x80) >> 7);    /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
        data <<= 1;     /* 左移1位,用于下一次发送 */
    }
    IIC_SDA(1);         /* 发送完成, 主机释放SDA线 */
}

/**
 * @brief       IIC读取一个字节
 * @param       ack:  ack=1时,发送ack; ack=0时,发送nack
 * @retval      接收到的数据
 */
uint8_t iic_read_byte(uint8_t ack)
{
    uint8_t i, receive = 0;

    for (i = 0; i < 8; i++ )    /* 接收1个字节数据 */
    {
        receive <<= 1;  /* 高位先输出,所以先收到的数据位要左移 */
        IIC_SCL(1);
        iic_delay();

        if (IIC_READ_SDA)
        {
            receive++;
        }
        
        IIC_SCL(0);
        iic_delay();
    }

    if (!ack)
    {
        iic_nack();     /* 发送nACK */
    }
    else
    {
        iic_ack();      /* 发送ACK */
    }

    return receive;
}

2、24cxx.c

(1)写一个字节

void at24cxx_write_one_byte(uint16_t addr,uint8_t data)            /* 指定地址写入一个字节 */
{
    /* 1、发送起始信号 */
    iic_start();
    
    /* 2、发送通信地址(写操作地址) */
    iic_send_byte(0xA0);
    
    /* 3、等待应答信号 */
    iic_wait_ack();
    
    /* 4、发送内存地址 */
    iic_send_byte(addr);
    
    /* 5、等待应答信号 */
    iic_wait_ack();
    
    /* 6、发送写入数据 */
    iic_send_byte(data);

    /* 7、等待应答信号 */
    iic_wait_ack();
    
    /* 8、发出停止信号 */
    iic_stop();
    
    /* 等待EEPROM写入完成 */
    delay_ms(10);
    
    
}

 (2)读一个字节

uint8_t at24cxx_read_one_byte(uint16_t addr)                       /* 指定地址读取一个字节 */
{
    uint8_t rec = 0;
    
    /* 1、发送起始信号 */
    iic_start();
    
    /* 2、发送通信地址(写操作地址) */
    iic_send_byte(0xA0);
    
    /* 3、等待应答信号 */
    iic_wait_ack();
    
    /* 4、发送内存地址 */
    iic_send_byte(addr);
    
    /* 5、等待应答信号 */
    iic_wait_ack();
    
    /* 6、发送起始信号 */
    iic_start();

    /* 7、发送通讯地址(读通讯地址) */
    iic_send_byte(0xA1);
    
    /* 8、等待应答信号 */
    iic_wait_ack();
    
   /* 9、等待接收数据 */
   rec = iic_read_byte(0);
   /* 10、发送非应答nack(上述0)(即获取该地址而已) */
   
   /* 11、发送停止信号 */
   iic_stop();
   
   return rec;
   
}

(3)连续读写(均是在读写一个字节的基础上实现)。

/**
 * @brief       在AT24CXX里面的指定地址开始读出指定个数的数据
 * @param       addr    : 开始读出的地址 对24c02为0~255
 * @param       pbuf    : 数据数组首地址(读的数据放的位置)
 * @param       datalen : 要读出数据的个数
 * @retval      无
 */
void at24cxx_read(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
    while (datalen--)
    {
        *pbuf++ = at24cxx_read_one_byte(addr++);
    }
}

/**
 * @brief       在AT24CXX里面的指定地址开始写入指定个数的数据
 * @param       addr    : 开始写入的地址 对24c02为0~255
 * @param       pbuf    : 数据数组首地址
 * @param       datalen : 要写入数据的个数
 * @retval      无
 */
void at24cxx_write(uint16_t addr, uint8_t *pbuf, uint16_t datalen)
{
    while (datalen--)
    {
        at24cxx_write_one_byte(addr, *pbuf);
        addr++;
        pbuf++;
    }
}

写时序,每次发完一个字节注意需要等待应答
读时序,第一次起始信号先写地址,第二次起始信号读,从机返回 word address的数据
发送不成功波形检验,首先查看是否有应答ACK,第二查看波形的波峰是否过低导致被识别为低电平。
正点原子例程考虑了7位与10位收发,if进行判断AT24C16,分两个字节判断。
### STM32F407ZGT6 IIC编程教程 对于STM32F407ZGT6微控制器而言,IIC(Inter-Integrated Circuit)总线是一种用于连接低速外围设备到微处理器及其相关电路的简单双向二线制同步串行总线。为了实现有效的IIC通信,在初始化阶段需配置SCL(时钟线)和SDA(数据线),并设定相应的波特率。 #### 配置指南 1. **库文件准备** 使用标准外设库或HAL库可以简化开发过程。推荐采用最新的HAL库版本,因为其提供了更高级别的抽象接口以及更好的兼容性和稳定性[^1]。 2. **引脚分配** SDA 和 SCL 应该被映射至具有相应功能的GPIO引脚上。通常情况下,默认的选择如下所示: | 功能 | GPIO Pin | | --------|-------------| | SCL | PB8 | | SDA | PB9 | 3. **初始化设置** ```c // 初始化I2C外设结构体定义 static void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; // 设置传输速率 (Hz) hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } ``` 这段代码展示了如何利用CubeMX工具自动生成的基础框架来进行基本的I2C模块初始化操作。 #### 常见问题解决方案 1. **启动失败** 如果遇到无法成功发送起始条件的情况,可能是由于线路阻抗不匹配或是拉电流不足引起。建议检查外部上拉电阻是否合适,并确认电源电压稳定。 2. **读写超时错误** 当发生此类异常时,应首先排查目标器件地址是否正确无误;其次考虑是否存在干扰因素影响信号质量;最后尝试调整时序参数以适应不同速度等级的要求。 3. **ACK/NACK 错误** 这种情况往往是因为接收方未能及时响应所致。确保从属设备处于工作状态并且能够正常回应主机请求非常重要。另外也要注意软件层面的数据校验逻辑是否有缺陷。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成为不掉头发的工程师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值