通讯协议学习之路(实践部分):IIC开发实践

通讯协议之路主要分为两部分,第一部分从理论上面讲解各类协议的通讯原理以及通讯格式,第二部分从具体运用上讲解各类通讯协议的具体应用方法。

后续文章会同时发表在个人博客(jason1016.club)、CSDN;视频会发布在bilibili(UID:399951374)

本文前缀:

通讯协议专栏:通讯协议_JASON丶LI的博客-CSDN博客

UART理论部分:通讯协议学习之路:IIC协议理论-CSDN博客

硬件IIC

硬件IIC(Inter-Integrated Circuit)是一种串行通信协议,由飞利浦公司(现在的恩智浦半导体)开发。它使用两根线(SDA和SCL)来进行数据传输,其中SDA线用于数据传输,SCL线用于时钟同步。硬件IIC通常由硬件电路实现,例如在微控制器或集成电路中集成的硬件模块。

硬件I2C的效率要远高于软件的,而软件I2C由于不受管脚限制,接口比较灵活。

软件IIC

软件IIC是一种通过软件实现的IIC通信协议。它不依赖于硬件电路,而是通过软件控制GPIO(通用输入输出)引脚来模拟IIC通信。软件IIC通常用于一些资源受限的系统,例如单片机或嵌入式系统,这些系统可能没有硬件IIC模块。

软件IIC和硬件IIC之间的关系是,软件IIC是对硬件IIC的一种软件实现。软件IIC可以在没有硬件IIC模块的情况下实现IIC通信,但由于软件实现的限制,软件IIC的速度和可靠性可能不如硬件IIC。在一些资源受限的系统中,软件IIC是一种常用的替代方案。

  1. 硬件IIC用法比较复杂,模拟IIC的流程更清楚一些。
  2. 硬件IIC速度比模拟快,并且可以用DMA
  3. 模拟IIC可以在任何管脚上,而硬件只能在固定管脚上。

软件i2c是程序员使用程序控制SCL,SDA线输出高低电平,模拟i2c协议的时序。一般较硬件i2c稳定,但是程序较为繁琐,但不难。

硬件i2c程序员只要调用i2c的控制函数即可,不用直接的去控制SCL,SDA高低电平的输出。但是有些单片机的硬件i2c不太稳定,调试问题较多。

参考文章:硬件IIC和软件IIC区别-CSDN博客

开发实践

标准库

软件IIC:

软件IIC的实现核心在于把握IIC的通讯格式,所谓协议通讯,我的理解就是将10的高低电平状态整合成一段段有规律的预警,就像我们规定逗号是停顿,句号是句子结束一样,其实协议通讯同样如此,具体的IIC通讯格式可以移步到另外一篇文章:通讯协议学习之路:IIC协议理论-CSDN博客

接下来是具体配置代码

IIC_Control.c
#include "IIC_Control.h"
#include "IIC_Software.h"

#define IIC_ADDRESS        0xA0

void IIC_Write(uint8_t RegAddress,uint8_t Data)
{
    IIC_Software_Start();
    IIC_Software_SendByte(IIC_ADDRESS);
    IIC_Software_ReadBck();
    IIC_Software_SendByte(RegAddress);
    IIC_Software_ReadBck();
    IIC_Software_SendByte(Data);
    IIC_Software_ReadBck();
    IIC_Software_Stop();
}


uint8_t IIC_Read(uint8_t RegAddress)
{
    uint8_t Data;
    
    IIC_Software_Start();
    IIC_Software_SendByte(IIC_ADDRESS);
    IIC_Software_ReadBck();
    IIC_Software_SendByte(RegAddress);
    IIC_Software_ReadBck();
    IIC_Software_Start();
    IIC_Software_SendByte(IIC_ADDRESS | 0x01);
    IIC_Software_ReadBck();
    Data = IIC_Software_ReadByte();
    IIC_Software_SendBck();
    IIC_Software_Stop();
    
    return Data;
}

IIC_Control.h
#ifndef __IICCONTROL_H
#define __IICCONTROL_H

#include "stm32f10x.h"                  // Device header

void IIC_Write(uint8_t RegAddress,uint8_t Data);

uint8_t IIC_Read(uint8_t RegAddress);


#endif
IIC_Software.c
#include "stm32f10x.h"                  // Device header
#include "IIC_Software.h"
#include "Delay.h"

//PB10-SCL PB11-SDA
//- 常态:SCL==1;SDA==1
//- 工作态:SCL电频周期翻转;SDA电频由数据而定
//- 起始位:SCL==1时:SDA==1-->0
//- 停止位:SCL==1时:SDA==0-->1
//- 逻辑1:SCL==1时:SDA==1
//- 逻辑0:SCL==1时:SDA==0

void IIC_Delay(void)
{
    Delay_us(4);
}    

void IIC_Software_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    
    GPIO_InitTypeDef GPIO_Initstructure;
    GPIO_Initstructure.GPIO_Mode = GPIO_Mode_Out_OD;
    GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_Initstructure);
    
    SDA_H;SCL_H; //设置空闲态
}

void IIC_Software_Start(void)    //起始位
{
    SDA_H;
    SCL_H;
    IIC_Delay();
    SDA_L;
    IIC_Delay();
    SCL_L;
}

void IIC_Software_Stop(void)    //停止位
{
    SDA_L;
    SCL_H;
    IIC_Delay();
    SDA_H;
//    IIC_Delay();
//    SCL_H;    
}

void IIC_Software_SendByte(unsigned char byte)    //发送
{
    int i;
    for(i=0; i<8; i++)
    {
        SCL_L;
        if(byte&1<<(7-i))
        {
            SDA_H;
        }
        else{
            SDA_L;
        }
        SCL_H;
        IIC_Delay();
    }
    SCL_L;

}

unsigned char IIC_Software_ReadBck(void)    //接收应答
{
    u8 ack;
    SDA_L;        //拉低SDA  这里是用来处理半高电平,直接拉低sda总线再接收应答,这样一来应答信号为0时可以直接读取,当应答信号为1时也可以直接报错
//    SDA_H;        //拉高SDA    
    SCL_H;        //拉高SCL
    IIC_Delay();       //等待
    ack = Read_SDA;    //读取 
    SCL_L;      //这里拉低回来是为了对齐时序
    SDA_L;      
    return ack;
}

    
void IIC_Software_SendBck(void)    //发送应答
{
    SCL_L;
    SDA_L;
    SCL_H;
    IIC_Delay();
    SCL_L;
}

unsigned char IIC_Software_ReadByte(void)                //从总线上读一个字节
{
    int i;
    unsigned char byte=0;
    SDA_H;            //释放SDA
    for(i=0; i<8; i++)
    {
        SCL_H;        //拉高,然后读
        IIC_Delay();
        if(Read_SDA)
        {
            byte |= (1<<(7-i));
        }
        SCL_L;
        IIC_Delay();
    }
    return byte;
}
IIC_Software.h
#ifndef __IICSOFTWARE_H
#define __IICSOFTWARE_H


//PB10-SCL PB11-SDA

#define SCL_H GPIO_SetBits(GPIOB, GPIO_Pin_10)
#define SCL_L GPIO_ResetBits(GPIOB, GPIO_Pin_10)
#define SDA_H GPIO_SetBits(GPIOB, GPIO_Pin_11)
#define SDA_L GPIO_ResetBits(GPIOB, GPIO_Pin_11)
#define Read_SDA GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11)

void IIC_Delay(void);

void IIC_Software_Init(void);


void IIC_Software_Start(void);    //起始位


void IIC_Software_Stop(void);    //停止位


void IIC_Software_SendByte(unsigned char byte);    //发送


unsigned char IIC_Software_ReadBck(void);    //接收应答



void IIC_Software_SendBck(void);    //发送应答


unsigned char IIC_Software_ReadByte(void);                //从总线上读一个字节


#endif

硬件IIC:

硬件IIC就不需要像软件配置一样一步步来,只需要使能配置I2Cx外设即可,并且标准库对硬件IIC的各项指令都有对应的函数封装,配置完成后只需要调用封装好的函数即可实现通讯。

标准库IIC函数:
//配置自身设备地址2
void I2C_OwnAddress2Config(I2C_TypeDef* I2Cx, uint8_t Address);

//发送设备地址
void I2C_Send7bitAddress(I2C_TypeDef* I2Cx, uint8_t Address, uint8_t I2C_Direction);

//接收数据
uint8_t I2C_ReceiveData(I2C_TypeDef* I2Cx);

//停止接收
void I2C_AcknowledgeConfig(I2C_TypeDef* I2Cx, FunctionalState NewState);

//IIC外设开始正常工作
void I2C_Cmd(I2C_TypeDef* I2Cx, FunctionalState NewState);
MYIIC.c
#include "MYIIC.h"
#include "delay.h"

#define EERADDRESS        0xA0

void MYIIC_Init(void)
{
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C2, ENABLE);
    
    GPIO_InitTypeDef GPIO_Initstructure;
    GPIO_Initstructure.GPIO_Mode = GPIO_Mode_AF_OD;
    GPIO_Initstructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
    GPIO_Initstructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_Initstructure);
    
    
    I2C_DeInit(I2C2);
    I2C_InitTypeDef I2C_Initstructure;
    I2C_Initstructure.I2C_Mode = I2C_Mode_I2C;
    I2C_Initstructure.I2C_DutyCycle = I2C_DutyCycle_2; //该参数只有在 I2C 工作在快速模式(时钟工作频率高于 100KHz)下才有意义
    I2C_Initstructure.I2C_OwnAddress1 = 0xFF;
    I2C_Initstructure.I2C_Ack = I2C_Ack_Enable;
    I2C_Initstructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
    I2C_Initstructure.I2C_ClockSpeed = 100000;
    I2C_Init(I2C2, &I2C_Initstructure);    
    
    I2C_Cmd(I2C2, ENABLE);
}


//指定超时退出机制,防止下述死循环卡死
void I2C_WaitEvent(I2C_TypeDef* I2Cx, uint32_t I2C_EVENT)
{
    uint32_t Timeout;
    Timeout = 10000;
    while(I2C_CheckEvent(I2Cx, I2C_EVENT) != SUCCESS)
    {
        Timeout --;
        if(Timeout == 0)
        {
            break;
        }
    }
}


void I2C_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr)
{
    //读一个字节
    while(I2C_GetFlagStatus(DEBUG_I2Cx_Port, I2C_FLAG_BUSY));
    //发送Start信号
    I2C_GenerateSTART(DEBUG_I2Cx_Port,ENABLE);
    //等待EV5事件:IIC开始信号已经发出 (I2C_SR1内SB位置1)
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    
    //发送7位“EEPROM地址”
    I2C_Send7bitAddress(DEBUG_I2Cx_Port,EERADDRESS,I2C_Direction_Transmitter);
    //等待EV6事件:表示地址已经发送
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
    
    //写入EEPROM内将要写入的地址数据
    I2C_SendData(DEBUG_I2Cx_Port,WriteAddr);
    //等待EV8事件:返回SET则数据寄存器DR为空    
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
    
    //写入数据
    I2C_SendData(DEBUG_I2Cx_Port,*pBuffer);
    //等待EV8事件:返回SET则数据寄存器DR为空
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
    
    //一个字节发送完成,发送Stop信号
  I2C_GenerateSTOP(DEBUG_I2Cx_Port, ENABLE);
}


/**
  * @brief   从EEPROM里面读取一块数据 
  * @param   
  *        @arg pBuffer:存放从EEPROM读取的数据的缓冲区指针
  *        @arg WriteAddr:接收数据的EEPROM的地址
  * @retval  无
  */
void I2C_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr)
{
    //发送Start信号
    I2C_GenerateSTART(DEBUG_I2Cx_Port,ENABLE);
    //等待EV5事件:IIC开始信号已经发出 (I2C_SR1内SB位置1)
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    
    //发送7位“EEPROM地址”
    I2C_Send7bitAddress(DEBUG_I2Cx_Port,EERADDRESS,I2C_Direction_Transmitter);
    //等待EV6事件:表示地址已经发送
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)==ERROR);
    
    //写入EEPROM内存“单元地址”
    I2C_SendData(DEBUG_I2Cx_Port,ReadAddr);
    //等待EV8事件:数据寄存器DR为空    ,地址数据已经发送
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_BYTE_TRANSMITTED)==ERROR);
    
    //重新发送Start信号
    I2C_GenerateSTART(DEBUG_I2Cx_Port,ENABLE);
    //等待EV5事件
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_MODE_SELECT)==ERROR);
    
    //发送7位“EEPROM地址”
    I2C_Send7bitAddress(DEBUG_I2Cx_Port,EERADDRESS,I2C_Direction_Receiver);//注意方向
    //等待EV6事件(接收):表示地址已经发送
    while(I2C_CheckEvent(DEBUG_I2Cx_Port,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)==ERROR);//注意方向
    
    //产生非应答
    I2C_AcknowledgeConfig(DEBUG_I2Cx_Port, DISABLE);
    //发送Stop信号
    I2C_GenerateSTOP(DEBUG_I2Cx_Port, ENABLE);
    //等待EV7事件, BUSY, MSL and RXNE flags
    while(I2C_CheckEvent(DEBUG_I2Cx_Port, I2C_EVENT_MASTER_BYTE_RECEIVED)==ERROR); 
    
    *pBuffer = I2C_ReceiveData(DEBUG_I2Cx_Port);

    //重新初始化 为下次做准备
    I2C_AcknowledgeConfig(DEBUG_I2Cx_Port, ENABLE);
}
MYIIC.h
#ifndef __MYIIC_H
#define __MYIIC_H

#include "stm32f10x.h"                  // Device header


#define DEBUG_I2Cx_Port I2C2
void MYIIC_Init(void);

void I2C_ByteWrite(uint8_t *pBuffer, uint8_t WriteAddr);

void I2C_ByteRead(uint8_t *pBuffer, uint8_t ReadAddr);

#endif

DMA IIC:

还未调试

HAL库

HAL库IIC接收和发送的轮询、中断、DMA三种模式的函数

/* IO operation functions  ****************************************************/
/******* Blocking mode: Polling */
HAL_StatusTypeDef HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);
HAL_StatusTypeDef HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);

/******* Non-Blocking mode: Interrupt */
HAL_StatusTypeDef HAL_I2C_Master_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Write_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_I2C_Master_Seq_Transmit_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_Master_Seq_Receive_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_Slave_Seq_Transmit_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_Slave_Seq_Receive_IT(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_EnableListen_IT(I2C_HandleTypeDef *hi2c);
HAL_StatusTypeDef HAL_I2C_DisableListen_IT(I2C_HandleTypeDef *hi2c);
HAL_StatusTypeDef HAL_I2C_Master_Abort_IT(I2C_HandleTypeDef *hi2c, uint16_t DevAddress);

/******* Non-Blocking mode: DMA */
HAL_StatusTypeDef HAL_I2C_Master_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Master_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Slave_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Write_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);
HAL_StatusTypeDef HAL_I2C_Mem_Read_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size);

HAL_StatusTypeDef HAL_I2C_Master_Seq_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_Master_Seq_Receive_DMA(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_Slave_Seq_Transmit_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions);
HAL_StatusTypeDef HAL_I2C_Slave_Seq_Receive_DMA(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t XferOptions);

IIC写函数

HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

功能:IIC写数据

参数:

  1. *hi2c 设置使用的是那个IIC 例:&hi2c2
  2. DevAddress 写入的地址 设置写入数据的地址 例 0xA0
  3. *pData 需要写入的数据
  4. Size 要发送的字节数
  5. Timeout 最大传输时间,超过传输时间将自动退出传输函数

IIC读函数

HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

功能:IIC读一个字节

参数:

  1. *hi2c: 设置使用的是那个IIC 例:&hi2c2
  2. DevAddress: 写入的地址 设置写入数据的地址 例 0xA0
  3. *pDat:a 存储读取到的数据
  4. Size: 发送的字节数
  5. Timeout: 最大读取时间,超过时间将自动退出读取函数

举例:

HAL_I2C_Master_Transmit(&hi2c1,0xA1,(uint8_t*)TxData,2,1000) ;//发送两个字节数据

IIC写数据函数

HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t pData, uint16_t Size, uint32_t Timeout);
/*  第1个参数为I2C操作句柄
    第2个参数为从机设备地址
    第3个参数为从机寄存器地址
    第4个参数为从机寄存器地址长度
    第5个参数为发送的数据的起始地址
    第6个参数为传输数据的大小
    第7个参数为操作超时时间   */

功能: IIC写多个数据 该函数适用于IIC外设里面还有子地址寄存器的设备,比方说E2PROM,除了设备地址,每个存储字节都有其对应的地址

参数:

  1. *hi2c: I2C设备号指针,设置使用的是那个IIC 例:&hi2c2
  2. DevAddress: 从设备地址 从设备的IIC地址 例E2PROM的设备地址 0xA0
  3. MemAddress: 从机寄存器地址 ,每写入一个字节数据,地址就会自动+1
  4. MemAddSize: 从机寄存器地址字节长度 8位或16位
    1. 写入数据的字节类型 8位还是16位
    2. I2C_MEMADD_SIZE_8BIT
    3. I2C_MEMADD_SIZE_16BIT
  5. 在stm32f1xx_hal_i2c.h中有定义

  1. *pData: 需要写入的的数据的起始地址
  2. Size: 传输数据的大小 多少个字节
  3. Timeout: 最大读取时间,超过时间将自动退出函数

使用HAL_I2C_Mem_Write等于先使用HAL_I2C_Master_Transmit传输第一个寄存器地址,再用HAL_I2C_Master_Transmit传输写入第一个寄存器的数据。可以传输多个数据

void Single_WriteI2C(uint8_t REG_Address,uint8_t REG_data)
{
    uint8_t TxData[2] = {REG_Address,REG_data};
    while(HAL_I2C_Master_Transmit(&hi2c1,I2C1_WRITE_ADDRESS,(uint8_t*)TxData,2,1000) != HAL_OK)
    {
        if (HAL_I2C_GetError(&hi2c1) != HAL_I2C_ERROR_AF)
                {
                  Error_Handler();
                }
    }
}

在传输过程,寄存器地址和源数据地址是会自加的。

至于读函数也是如此,因此用HAL_I2C_Mem_Write和HAL_I2C_Mem_Read,来写读指定设备的指定寄存器数据是十分方便的,让设计过程省了好多步骤。

举例:

8位:

HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write[i]),8, 1000);
HAL_I2C_Mem_Read(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_8BIT,&(I2C_Buffer_Write[i]),8, 1000);

16位:

HAL_I2C_Mem_Write(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_16BIT,&(I2C_Buffer_Write[i]),8, 1000);
HAL_I2C_Mem_Read(&hi2c2, ADDR, i, I2C_MEMADD_SIZE_16BIT,&(I2C_Buffer_Write[i]),8, 1000);

如果只往某个外设中写数据,则用Master_Transmit。 如果是外设里面还有子地址,例如我们的E2PROM,有设备地址,还有每个数据的寄存器存储地址。则用Mem_Write。Mem_Write是2个地址,Master_Transmit只有从机地址

IIC接收函数(proteus未实现)

在mian.c文件前面声明,AT24C02 写地址和读地址 ,定义写数据数组,和读数据数组

/* USER CODE BEGIN PV */
#include <string.h>

#define ADDR_24LCxx_Write 0xA0
#define ADDR_24LCxx_Read 0xA1
#define BufferSize 256
uint8_t WriteBuffer[BufferSize],ReadBuffer[BufferSize];
uint16_t i;
/* USER CODE END PV */

在main.c中添加

  /* USER CODE BEGIN 2 */
    for(i=0; i<256; i++)
    WriteBuffer[i]=i;    /* WriteBuffer init */


        printf("\r\n***************I2C Example Z小旋测试*******************************\r\n");
            for (int j=0; j<32; j++)
        {
                if(HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, 8*j, I2C_MEMADD_SIZE_8BIT,WriteBuffer+8*j,8, 1000) == HAL_OK)
                {
                                printf("\r\n EEPROM 24C02 Write Test OK \r\n");
                        HAL_Delay(20);
                }
                else
                {
                         HAL_Delay(20);
                                printf("\r\n EEPROM 24C02 Write Test False \r\n");
                }
        }
        /*
        // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码
        for(i=0;i<BufferSize;i++)
        {
            HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer[i],1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入
          HAL_Delay(5);//此处延时必加,与AT24C02写时序有关
        }
        printf("\r\n EEPROM 24C02 Write Test OK \r\n");
        */

        HAL_I2C_Mem_Read(&hi2c1, ADDR_24LCxx_Read, 0, I2C_MEMADD_SIZE_8BIT,ReadBuffer,BufferSize, 0xff);

        for(i=0; i<256; i++)
            printf("0x%02X  ",ReadBuffer[i]);
            
  /* USER CODE END 2 */

注意事项:

AT24C02的IIC每次写之后要延时一段时间才能继续写 每次写之后要delay 5ms左右 不管硬件IIC采用何种形式(DMA,IT),都要确保两次写入的间隔大于5ms;

读写函数最后一个超时调整为1000以上 因为我们一次写8个字节,延时要久一点

AT24C02页写入只支持8个byte,所以需要分32次写入。这不是HAL库的bug,而是AT24C02的限制,其他的EEPROM可以支持更多byte的写入。

当然,你也可以每次写一个字节,分成256次写入,也是可以的 那就用注释了的代码即可

        /*
        // wrinte date to EEPROM   如果要一次写一个字节,写256次,用这里的代码
        for(i=0;i<BufferSize;i++)
        {
            HAL_I2C_Mem_Write(&hi2c1, ADDR_24LCxx_Write, i, I2C_MEMADD_SIZE_8BIT,&WriteBuffer[i],1,0xff);//使用I2C块读,出错。因此采用此种方式,逐个单字节写入
          HAL_Delay(5);//此处延时必加,与AT24C02写时序有关
        }
        printf("\r\n EEPROM 24C02 Write Test OK \r\n");
        */

参考文章:【精选】【STM32】HAL库 STM32CubeMX教程十二---IIC(读取AT24C02 )_hal iic-CSDN博客

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

JASON丶LI

作者码力不足了,卑微的求个打赏

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

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

打赏作者

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

抵扣说明:

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

余额充值