前言

        这一个板块针对开发板上的I2C总线区域的模块,这一条总线同时连接了两个芯片一个是E2PROM(AT24C02),另外一个是MCP4017(可编程电阻)。同时官方给的资料文件中有软件I2C的源码可以直接给我们使用,所以这里就使用软件I2C,就不用芯片自带的硬件I2C了。

        关于I2C通信的详细细节这里就不展开讨论,在代码配置的过程中会简单提到一点,详细的内容大家自行去学习。

        官方的软件I2C的源码放在了文章的最后,有需要自取。

芯片资料

E2PROM(AT24C02)

        简单介绍一下这芯片的功能,AT24C02是一种可擦除和可编程只读存储器,可以实现掉电不丢失,用于保存单片机运行时想要永久保存的数据信息。

        AT24C02/ 2K 串行 EEPROM:内部组织有 32 页,每页 8 字节,2K 需要一个 8 位数据字地址用于随机字寻址。大概意思就是这个芯片能存256个uint8_t大小的数据,数据地址也要是uint8_t的数据类型。

设备地址

        要用I2C通信找到对应的设备就要知道这个设备的地址,这个芯片有多个型号,对应的型号的的设备地址标签对应关系如下,AT24C01/02/04/08/16分别对应1K/2K/4K/8K/16K。A2、A1、A0分别对应电路原理图的E3、E2、E1,在上面的框图中可知这三个引脚都是接地的,所以对应的二进制数就是0。最后一位R/W,就是读写位,分别对应1/0。因此这个芯片的设备地址就是1010000_(即0xA_),最后一位读写位。

通信时序

        我们基本只需要掌握指定地址读和指定地址写即可,其它连续写和连续读进行循环操作即可。

        指定地址写(0xA1)

        指定地址读(0xA0)

        这里的时序图里面虽然写的是随机地址,但是并不随机,因为里面有Word Address(数据地址)的信息,所以他就是指定地址读。(应该是翻译问题)

        从这个时序图中已经能够很清晰的知道每一段时序的作用是什么,下面在写代码的过程中也是调用官方的软件I2C的函数来完成对时序的拼接。

代码部分

E2prom.h
#ifndef __E2PROM_H
#define __E2PROM_H
 
//指定地址写
void E2prom_write(uint8_t Address, uint8_t Data);
//指定地址读
uint8_t E2prom_read(uint8_t Address);
 
#endif 
E2prom.c
#include "i2c_hal.h"
#include "stm32g4xx_hal.h"
 
//指定地址写
void E2prom_write(uint8_t Address, uint8_t Data)
{
    I2CStart();              //1.起始信号(START)
    I2CSendByte(0xA0);       //2.发送设备地址 + 写信号(DEVICE ADDRESS+WRITE)
    I2CWaitAck();            //3.等待确认信号(ACK)
 
    I2CSendByte(Address);    //4.发送数据存储地址(WORD ADDRESS)(可以为0~255,对应256个)
    I2CWaitAck();            //5.等待确认信号(ACK)
    I2CSendByte(Data);       //6.发送数据(DATA)
    I2CWaitAck();            //7.等待确认信号(ACK)
    I2CStop();               //8.结束信号(STOP)
}
 
//指定地址读
uint8_t E2prom_read(uint8_t Address)
{
    uint8_t Data;
 
    I2CStart();              //1.起始信号(START)
    I2CSendByte(0xA0);       //2.发送设备地址+写信号(DEVICE ADDRESS+WRITE)
    I2CWaitAck();            //3.等待确认信号(ACK)
 
    I2CSendByte(Address);    //4.发送数据存储地址(WORD ADDRESS)(可以为0~255,对应256个)
    I2CWaitAck();            //5.等待确认信号(ACK)
    I2CStop();               //下一次发送起始信号前先停止,留足缓冲时间,避免时序混乱
 
    I2CStart();              //6.起始信号(START)
    I2CSendByte(0xA1);       //7.发送设备地址+读信号(DEVICE ADDRESS+READ)
    I2CWaitAck();            //8.等待确认信号(ACK)
    Data = I2CReceiveByte(); //9.接收数据(DATA)
    I2CSendNotAck();        //10.发送非确认信号(NO ACK)
    
    I2CStop();               //11.I2C结束信号(STOP)
 
    return Data;
}

        这一部分的代码在有官方的软件I2C的源码下,只要按照这个芯片的I2C通信时序给他拼接起来即可,非常的简单粗暴。

main.c

        同样我也设置了一个测试代码,检测芯片存储的准确性,也是确认我们时序的拼接没有任何问题。

        最终的效果就是在LCD屏幕上打印出ASCII对应的字符。

#include "lcd.h"
#include "E2prom.h"
#include "i2c_hal.h"

uint8_t str[20];

int main(void)
{
    I2CInit();
    LCD_Init();

    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);
  
    uint8_t i = 0;
    uint8_t Address = 0x00;     //存储起始地址
    uint8_t Data    = 0x20;     //空格的ASCII码
    uint8_t Line    = Line0;    //起始行

    while (1)
    {
	  E2prom_write(Address,Data);
	  
      //要进行合适的延时,防止读写两个数据帧靠的太近导致数据错乱
	  HAL_Delay(200);
	  
	  str[i] = E2prom_read(Address);
	  
	  LCD_DisplayStringLine(Line,(unsigned char*)str);
	  
	  Address++;
	  i++;
	  Data++;
	  
	  if(i >= 19)
	  {
		  Line += 24;
		  i = 0;
	  }
	  
	  if(Data == 127)
	  {
		  break;
	  }

    }
    
}

MCP4017(可编程电阻)

        内部的框图如图所示,对应引脚的连接参考最上面的原理图。

设备地址

        MCP4017对应的设备地址是0x5_(E/F),E是包含了写位,F是包含了读位。

        然后这些是对应电阻位置的数据地址从0x00~0x7F,一共是0~127,七位的数据值。

        对应的电阻值如下,对应这块开发板的芯片,一般情况下的最大电阻是1e6Ω,步进值是Rs,最后经过程序设置得出的电阻值是 R = Rs * (N+1),N是上面的十六进制对应的十进制数。

通信时序

        指定地址写(0x5E)

        这里是连续的写入操作,实际上我们只需要写入一次即可。

        指定地址读(0x5F)

        这里的读时序跟前面的AT芯片有所区别,他没有设置写时序,而是直接写入读时序

代码部分

Mcp4017.h
#ifndef __MCP4017_H
#define __MCP4017_H

//指定地址写
void MCP4017_Write(uint8_t Data);
//指定地址读
uint8_t MCP4017_Read(void);
 
#endif
Mcp4017.c

        时序的拼接照着芯片的标明的时序来拼接即可。

#include "stm32g4xx_hal.h"
#include "i2c_hal.h"

void MCP4017_Write(uint8_t Data)
{
	I2CStart();
	I2CSendByte(0X5E);
	I2CWaitAck();
	
	I2CSendByte(Data);
	I2CWaitAck();
	I2CStop();
}
 
uint8_t MCP4017_Read(void)
{
	uint8_t Data;
	
	I2CStart();
	I2CSendByte(0X5F);
	I2CWaitAck();
	
	Data = I2CReceiveByte();
	I2CSendNotAck();
	I2CStop();
	
	return Data;
}
main.c

        测试的代码是跟ADC模块混用的,不清楚这个模块的可以看看我之前的博客,里面有详细的介绍。

#include "lcd.h"
#include "ADC_read.h"
#include "i2c_hal.h"
#include "Mcp4017.h"

int main(void)
{
    I2CInit();
    ADC_Init();
    LCD_Init();

    LCD_Clear(Black);
    LCD_SetBackColor(Black);
    LCD_SetTextColor(White);

    MCP4017_Write(0x7f);

    while (1)
    {
	  ADC_Show();
    }
}

官方I2C源码

i2c_hal.h

#ifndef __I2C_HAL_H
#define __I2C_HAL_H

#include "stm32g4xx_hal.h"

void I2CStart(void);
void I2CStop(void);
unsigned char I2CWaitAck(void);
void I2CSendAck(void);
void I2CSendNotAck(void);
void I2CSendByte(unsigned char cSendByte);
unsigned char I2CReceiveByte(void);
void I2CInit(void);

#endif

i2c_hal.c

/*
  程序说明: CT117E-M4嵌入式竞赛板GPIO模拟I2C总线驱动程序
  软件环境: MDK-ARM HAL库
  硬件环境: CT117E-M4嵌入式竞赛板
  日    期: 2020-3-1
*/

#include "i2c_hal.h"

#define DELAY_TIME	20

/**
  * @brief SDA线输入模式配置
  * @param None
  * @retval None
  */
void SDA_Input_Mode()
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    GPIO_InitStructure.Pin = GPIO_PIN_7;
    GPIO_InitStructure.Mode = GPIO_MODE_INPUT;
    GPIO_InitStructure.Pull = GPIO_PULLUP;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/**
  * @brief SDA线输出模式配置
  * @param None
  * @retval None
  */
void SDA_Output_Mode()
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    GPIO_InitStructure.Pin = GPIO_PIN_7;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStructure.Pull = GPIO_NOPULL;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
}

/**
  * @brief SDA线输出一个位
  * @param val 输出的数据
  * @retval None
  */
void SDA_Output( uint16_t val )
{
    if ( val )
    {
        GPIOB->BSRR |= GPIO_PIN_7;
    }
    else
    {
        GPIOB->BRR |= GPIO_PIN_7;
    }
}

/**
  * @brief SCL线输出一个位
  * @param val 输出的数据
  * @retval None
  */
void SCL_Output( uint16_t val )
{
    if ( val )
    {
        GPIOB->BSRR |= GPIO_PIN_6;
    }
    else
    {
        GPIOB->BRR |= GPIO_PIN_6;
    }
}

/**
  * @brief SDA输入一位
  * @param None
  * @retval GPIO读入一位
  */
uint8_t SDA_Input(void)
{
	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET){
		return 1;
	}else{
		return 0;
	}
}


/**
  * @brief I2C的短暂延时
  * @param None
  * @retval None
  */
static void delay1(unsigned int n)
{
    uint32_t i;
    for ( i = 0; i < n; ++i);
}

/**
  * @brief I2C起始信号
  * @param None
  * @retval None
  */
void I2CStart(void)
{
    SDA_Output(1);
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    SDA_Output(0);
    delay1(DELAY_TIME);
    SCL_Output(0);
    delay1(DELAY_TIME);
}

/**
  * @brief I2C结束信号
  * @param None
  * @retval None
  */
void I2CStop(void)
{
    SCL_Output(0);
    delay1(DELAY_TIME);
    SDA_Output(0);
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    SDA_Output(1);
    delay1(DELAY_TIME);

}

/**
  * @brief I2C等待确认信号
  * @param None
  * @retval None
  */
unsigned char I2CWaitAck(void)
{
    unsigned short cErrTime = 5;
    SDA_Input_Mode();
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    while(SDA_Input())
    {
        cErrTime--;
        delay1(DELAY_TIME);
        if (0 == cErrTime)
        {
            SDA_Output_Mode();
            I2CStop();
            return ERROR;
        }
    }
    SDA_Output_Mode();
    SCL_Output(0);
    delay1(DELAY_TIME);
    return SUCCESS;
}

/**
  * @brief I2C发送确认信号
  * @param None
  * @retval None
  */
void I2CSendAck(void)
{
    SDA_Output(0);
    delay1(DELAY_TIME);
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    SCL_Output(0);
    delay1(DELAY_TIME);

}

/**
  * @brief I2C发送非确认信号
  * @param None
  * @retval None
  */
void I2CSendNotAck(void)
{
    SDA_Output(1);
    delay1(DELAY_TIME);
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    SCL_Output(0);
    delay1(DELAY_TIME);

}

/**
  * @brief I2C发送一个字节
  * @param cSendByte 需要发送的字节
  * @retval None
  */
void I2CSendByte(unsigned char cSendByte)
{
    unsigned char  i = 8;
    while (i--)
    {
        SCL_Output(0);
        delay1(DELAY_TIME);
        SDA_Output(cSendByte & 0x80);
        delay1(DELAY_TIME);
        cSendByte += cSendByte;
        delay1(DELAY_TIME);
        SCL_Output(1);
        delay1(DELAY_TIME);
    }
    SCL_Output(0);
    delay1(DELAY_TIME);
}

/**
  * @brief I2C接收一个字节
  * @param None
  * @retval 接收到的字节
  */
unsigned char I2CReceiveByte(void)
{
    unsigned char i = 8;
    unsigned char cR_Byte = 0;
    SDA_Input_Mode();
    while (i--)
    {
        cR_Byte += cR_Byte;
        SCL_Output(0);
        delay1(DELAY_TIME);
        delay1(DELAY_TIME);
        SCL_Output(1);
        delay1(DELAY_TIME);
        cR_Byte |=  SDA_Input();
    }
    SCL_Output(0);
    delay1(DELAY_TIME);
    SDA_Output_Mode();
    return cR_Byte;
}

//
void I2CInit(void)
{
    GPIO_InitTypeDef GPIO_InitStructure = {0};

    GPIO_InitStructure.Pin = GPIO_PIN_7 | GPIO_PIN_6;
    GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStructure.Pull = GPIO_PULLUP;
    GPIO_InitStructure.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStructure);
}

        到这里只是MCP4017的基本应用,后续会更新它的一些详细参数以及应用。主要是通过读取的电位值来估算大概的电阻值,这个在芯片手册内也有写,感兴趣的可以去看看。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐