通讯协议学习之路(实践部分):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博客

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/157960.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

.Net 8正式发布

Net 8是官方号称有史以来性能最快的一个版本了。 .Net 8 增加了数以千计的性能、稳定性和安全性改进&#xff0c;以及平台和工具增强功能&#xff0c;有助于提高开发人员的工作效率和创新速度。 反正就是快&#xff0c;性能好、工作效率更高&#xff01; 这个版本&#xff0c…

防抖-节流-深拷贝-事件总线

一、防抖与节流 1.认识防抖与节流函数 防抖和节流的概念其实最早并不是出现在软件工程中&#xff0c;防抖是出现在电子元件中&#xff0c;节流出现在流体流动中 而JavaScript是事件驱动的&#xff0c;大量的操作会触发事件&#xff0c;加入到事件队列中处理。而对于某些频繁…

MySQL使用Xtrabackup恢复全量备份

1、下载之前的备份的全量压缩包 我使用的是备份到AWS存储桶,实验删数数据之前&#xff0c;我做了一次全量备份。 1.1 解压加密的压缩包到指定的目录 由于备份使用的是加密压缩包&#xff0c;现在解压也要用密码解压 解压格式&#xff1a; openssl enc -aes-256-cbc -d -p…

免费!IDEA插件推荐:Apipost-Helper

今天给大家推荐一款IDEA插件&#xff1a;Apipost-Helper-2.0&#xff0c;写完代码IDEA内一键生成API文档&#xff0c;无需安装、打开任何其他软件&#xff1b;写完代码IDEA内一键调试&#xff0c;无需安装、打开任何其他软件&#xff1b;生成API目录树&#xff0c;双击即可快速…

15项基本SCADA技术技能

1. 人机界面 人机界面是将操作员连接到设备、系统或机器的仪表板或用户界面。 以下是 hmi 在 scada 技术人员简历中的使用方式&#xff1a; 完成了查尔斯湖废水处理厂和提升站的完整 HMI 图形界面。对加油系统、加油车、PLC、HMI、触摸屏进行故障排除和维修。对 Horner HMI …

初识Scrapy:Python中的网页抓取神器

Scrapy是一个基于Python的快速、高层次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站点并从页面中提取结构化的数据。它广泛应用于数据挖掘、监测和自动化测试等领域。Scrapy的强大之处在于它是一个框架&#xff0c;可以根据实际需求进行修改和扩展。 Scrapy的主要特点 …

1.rk3588的yolov5运行:pt_onnx_rknn转换及rknn在rk3588系统python运行

自己有点笨&#xff0c;查资料查了一周才完美的实现了yolov5在rk3588环境下的运行&#xff0c;在这里写具体步骤希望大家少走弯路。具体步骤如下&#xff1a; 一、yolov5的原代码下载及pt文件转换为onnx文件 1.yolov5的原代码下载及环境搭建 在这里一定要下载正确版本的源代码…

国家药品价格查询官网-在线网站查询方法

查询药品上市价格对于个人和机构来说都是非常有必要的&#xff0c;对个人可以很好的验证该药品是否存在虚高的情况&#xff0c;对药企来说可以根据同类药品市场价格指导自产药品的定价&#xff0c;对其它机构来说了解药品价格可以帮助选择价格合理的药品供应商&#xff0c;降低…

upload-labs关卡10(点和空格绕过)通关思路

文章目录 前言一、回顾前几关知识点二、靶场第十关通关思路1、看源代码2、bp抓包绕过3、检查文件是否成功上传 总结 前言 此文章只用于学习和反思巩固文件上传漏洞知识&#xff0c;禁止用于做非法攻击。注意靶场是可以练习的平台&#xff0c;不能随意去尚未授权的网站做渗透测…

轻松搭建短域名短链接服务系统,可选权限认证,并自动生成证书认证把nginx的http访问转换为https加密访问,完整步骤和代码

轻松搭建短域名短链接服务系统&#xff0c;可选权限认证&#xff0c;并自动生成证书认证把nginx的http访问转换为https加密访问&#xff0c;完整步骤和代码。 在互联网信息爆炸的时代&#xff0c;网址复杂而冗长&#xff0c;很难在口头告知他人&#xff0c;也难以分享到社交媒体…

快速搭建本地的chatgpt

快速搭建本地的chatgpt 参考&#xff1a;一篇文章教你使用Docker本地化部署Chatgpt&#xff08;非api&#xff0c;速度非常快&#xff01;&#xff01;&#xff01;&#xff09;及裸连GPT的方式&#xff08;告别镜像GPT&#xff09;-CSDN博客 前提是linux下 已安装docker 命…

基于SpringBoot的SSMP整合案例(消息一致性处理与表现层开发)

消息一致性处理 在后端执行完相应的操作后&#xff0c;我们需要将执行操作后的结果与数据返回前端&#xff0c;前端 调用我们传回去的数据&#xff0c;前端是如何知道我们传回去的数据名称的&#xff1f; 答&#xff1a;前后端遵循了同一个"协议"。这个协议就是定义…

深度学习_14_单层|多层感知机及代码实现

单层感知机&#xff1a; 功能&#xff1a; 能完成二分类问题 问题&#xff1a; 模型训练采用X*W b训练出模型&#xff0c;对数据只进行了一层处理&#xff0c;也就是说训练出来的模型仅是个线性模型&#xff0c;它无法解决XOR问题&#xff0c;所以模型在训练效果上&#xf…

竞赛 题目:基于机器视觉opencv的手势检测 手势识别 算法 - 深度学习 卷积神经网络 opencv python

文章目录 1 简介2 传统机器视觉的手势检测2.1 轮廓检测法2.2 算法结果2.3 整体代码实现2.3.1 算法流程 3 深度学习方法做手势识别3.1 经典的卷积神经网络3.2 YOLO系列3.3 SSD3.4 实现步骤3.4.1 数据集3.4.2 图像预处理3.4.3 构建卷积神经网络结构3.4.4 实验训练过程及结果 3.5 …

torch - 张量Tensor常见的形式

1.Scalar 通常就是一个数值 x tensor(42.) 输出x&#xff1a; 2.Vector 特征向量 例如&#xff1a;[-5., 2., 0.]在深度学习中通常表示特征&#xff0c;如词向量特征&#xff0c;某一维度特征等 3.Matrix 一般计算的都是矩阵&#xff0c;通常都是多维的。 可以做矩阵的…

MySQL优化的底层逻辑

文章目录 前言索引的底层结构数据与索引的关系聚簇索引的数据存储普通索引的数据存储 索引的命中逻辑怎么理解索引失效总结 前言 去年刚开始写博客的时候写了一篇《MySQL性能调优参考》&#xff0c;文章中提到优化的几个技巧&#xff0c;比如数据类型的使用、范式和反范式的合…

CI/CD --git版本控制系统

目录 一、git简介 二、git使用 三、github远程代码仓库 一、git简介 Git特点&#xff1a; 速度简单的设计对非线性开发模式的强力支持&#xff08;允许成千上万个并行开发的分支&#xff09;完全分布式有能力高效管理类似 Linux 内核一样的超大规模项目&#xff08;速度和数…

【C++学习手札】模拟实现vector

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;くちなしの言葉—みゆな 0:37━━━━━━️&#x1f49f;──────── 5:28 &#x1f504; ◀️ ⏸ ▶️ ☰…

微信小程序Error: ENOENT: no such file

问题描述 当编译的时候 会提示找不到这个文件&#xff08;index&#xff09;&#xff0c;但是确信项目目录里已经删除了该页面路径&#xff0c;并且app.json的pages列表中也 导致这个问题应该是&#xff1a;新添加了文件&#xff0c;后面删除了或者修改了文件名&#xff0c;导…

MySQL初始化报错。VCRUNTIME140_1.dll缺失

从自己电脑内搜索此dll&#xff0c;粘贴到服务器的以下位置即可。