一、IIC基本原理

定义

在这里插入图片描述

  1. IIC全称两线式串行总线,它由数据线SDA和时钟SCL构成的串行总线,可发送和接收数。在CPU与被控IC之间,IC与IC之间进行双向传送,高速I2C总线一般可达400kpbs以上。
    IIC是半双工通信方式。
  2. IIC总线是不同的IC或模板之间的双向两线通信。这两条线是串行数据线(SDA)和串行时钟线(SCL)。这两条线必须通过上拉电路连接至正电源(芯片内部很多电路都采用开漏输出,是浮空的,因此我们需要给电路一个初试的默认值)。且数据传输只能在总线不忙时启动。

开始/停止信息

在这里插入图片描述
条件:数据和时钟线在总线不忙是保持高电平。
在时钟为高电平时,数据线上一个由高到低的变化为开始信号,数据线上由低到高的变化为停止信号。

观察图形可以注意到,开始后一段时间SCL才变为低电平,停止后一段时间SDA才变为高电平。这是因为在通信过程中往往会有外界或设备问题产生干扰通过增加短暂等待期可以等这些外界因素减弱后再继续执行后续指令,从而提高整个系统的稳定性和抗干扰能力。

应答信号ACK

在这里插入图片描述

定义:发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器没有接收成功。
过程:

  1. IIC等待确认信号:首先将SDA设置为输入模式,然后SCL变成高电平。如果SDA为低电平,则SDA变成输出模式,SCL变为低电平,输出成功;如果SDA为高电平,则进入循环,一段时间后SDA变成输出模式,通信停止,输出失败。4
  2. IIC发送确定信号:SDA为低电平,SCL先变为高电平,再变为低电平,然后发送确认信号。
  3. IIC发送非确定信号:SDA为高电平,SCL先变为高电平,再变为低电平,然后发送非确认信号。

数据信息

在这里插入图片描述

定义:一个数据位在每个时钟脉冲期间传输。SDA线上的数据必须在时钟脉冲的高电压期间保持稳定,这个期间数据线上的改变将被当作控制信号。
过程:
IIC发送一个字节:首先确定字节长度,然后进入循环。在循环中,首先将SCL变为低电平,然后输出最高位的数据,字节左移一位,最后将SCL变为高电平,进入下一次循环。循环结束后将SCL设置为低电平。
IIC接收一个字节:首先确定字节长度,定义一个新的字节,然后将SDA设置为输入模式,进入循环。在循环中,首先将SCL变为低电平,延迟两次后变为高电平,然后将SDA字节中的最高位输入到新字节中,最后新字节左移一位,进入下一次循环。循环结束后将SCL设置为低电平,并将SDA调为初始的输出模式。
空闲模式:IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个期间的输出级场效应管均处于截止状态,即释放总线,由两条信号线各自的上拉电阻把电压拉高。
注意:SCL变为低电平后延迟两次后再才变为高电平的原因可能是要同时顾及到设备的数据处理延迟和RC延时效应。

二、24C02存储器基本原理

定义

24C01/02/04/08/16是低工作电压的1k/2k/4k/8k/16k位串行电可擦除只读存储器,内部组织为128/256/512/1024/2048个字节,每个字节8位。

主要特性:工作电压1.8V~5.5V,输入/输出引脚兼容5V。

引脚定义:

引脚名称 引脚功能
A0~A2 器件地址输入
SDA 串行数据输入输出
SCL 串行时钟输入
WC# 写保护
VCC 电源
GND 接地

24C02的芯片地址为1010(A2 A1 A0)(READ = 1,WRITE = 0)
其中24C02芯片地址的高四位必须为1010,(A2 A1 A0)由E1,E2,E3是否接地决定,而在24C02中它们一般都接地,最后一位由读写来决定,读为1,写为2。
所以24C02的芯片地址一般为10100000(写 0xa0)或10100001(读 0xa1)
在这里插入图片描述

写操作时序

  1. 写字节时序:AT24C02存储器写操作需要在给出开始态、操作地址和应答后,紧跟着给出一个8位数据地址。收到地址后,EEPROM通过SDA发出应答信号,并随时钟输入8位数据。在收到8位数据后EEPROM将再次应答,数据传送结束后必须用停止状态来终止写操作。这时,EEPROM进入一个内定时固定存储器写入周期,在该周期内,所有输入被禁止,EEPROM直到写完后才应答。
    在这里插入图片描述
  2. 写页面时序:1k/2kEEPROM能进行8字节页面写入,4k/8k/16k设备能进行16字节页面写入。写页面与写字节相同,只是数据传输设备无需在第一个字节随时钟输入之后发出一个停止状态。在EEPROM确认收到第一个数据之后,数据传输设备能再传送7个或15个数据,每次收到数据之后EEPROM都将通过SDA回送一个应答信号,最后数据传输设备通过停止状态来终止写页面时序。
    在这里插入图片描述
    注意:数据字地址的低三位或低四位在收到每个数据之后,内部自动加1,数据字地址1的高位字节保持不变,以保持存储器页地址不变。如果传送到EEPROM中的数据字超过8或16字节,数据字地址将重复滚动,以前的数据将被覆盖。

读操作时序

类型:立即地址读取时序,随机地址读取时序,顺序地址读取时序。

  1. 立即地址读取时序:内部数据字地址指针保持在读写操作中最后访问的地址,按“1”递增。只要芯片保持上电,该地址在两个操作之间一直有效,如果最后一个操作是在地址n处读取或写入,则立即地址是n+1。
    在这里插入图片描述
  2. 随机地址读取时序:随机读取需要一个空字节写序列来载入数据地址,一旦器件地址和数据地址输入并被EEPROM确认,数据传输设备就必须产生另一个开始条件。读/写选择位处于高电平时,通过送出一个器件地址,数据传输设备激发出一个立即寻址读取,EEPROM确定器件地址,并随时钟串行输出数据。器件读数据不通过确认应答,而是通过产生一个停止条件应答。
    在这里插入图片描述
  3. 顺序地址读取时序:顺序读取由立即地址读取或随机地址读取激发,在读数据器件收到一个数据码之后,通过确认应答,EEPROM收到确认之后,便会继续增加数据码地址及串行输出数据码。当达到存储器地址极限时,数据码地址将重复滚动,顺序读取将继续。当读数据不通过确认应答,而是通过产生一个停止条件应答时,顺序读取操作被终止。
    在这里插入图片描述
    在读操作程序中,随机地址读取时序和顺序地址读取时序往往一起使用。

三、MCP4017可编程电阻原理

定义

  1. MCP4017可编程电阻,也被称为数字电位器或电子电位器,它可以通过内部的电阻阵列和电子开关网络,使电阻可以通过编程方式在多个预设值之间进行切换。
    其中,W通过引脚PB14接到电源上,B接地,A一般为浮空状态(A在下图中没有标出)。
    在这里插入图片描述2. 电阻网:由W,B,A连接组成的网络。如下图所示,Rwb由N值决定,N越高,电阻越高。
    Rwb = Rs*N/127 + Rw。 其中N = 0~127。
    在这里插入图片描述

MCP4017工作原理

  1. MCP4017的地址为01011110(写 0x5E)和01011111(读 0x5F)。
  2. WRITE 1 Byte:在给出开始、操作地址和应答后,写入当前的数据,应答后通过停止状态终止写入。
  3. READ 1 Byte:在给出开始、操作地址和应答后,读取当前的数据,然后通过公式0.7874*读取的数据来计算电阻。数据读取结束后不通过确认应答,而是通过产生一个停止条件应答。

四、24C02程序和MCP4017程序的移植与调试

1. main.c

#include "main.h"
#include "lcd\bsp_lcd.h"
#include "usart\bsp_usart.h"
#include "iic\bsp_iic.h"

//变量创建区
__IO uint32_t uwTick_Lcd_Set_Point = 0;//控制Lcd_Proc的执行速度
unsigned char i;

//LCD显示专用变量
unsigned char Lcd_Disp_String[21];//最多显示20个字符

//EEPROM的相关变量
unsigned char EEPROM_String_1[4] = {0x11,0x22,0x33,0x44};
unsigned char EEPROM_String_2[4] = {0};

//4017相关变量
uint8_t RES_4017;

//子函数声明区
void SystemClock_Config(void);
void Lcd_Proc(void);

//主函数
int main(void)
{
	//内核和时钟的初始化
  HAL_Init();
  SystemClock_Config();

	//外设函数的初始化	
	LCD_Init();
	LCD_Clear(White);
  LCD_SetBackColor(White);
  LCD_SetTextColor(Blue);	
	
	USART1_Init();
	I2CInit();
	
	//EEPROM测试
	iic_24c02_write(EEPROM_String_1, 0, 5);
	HAL_Delay(1);	
	iic_24c02_read(EEPROM_String_2, 0, 5);

  //MCP4017测试
	wirte_resistor(0x11);
	RES_4017 = read_resistor();


  while (1)
  {	
		Lcd_Proc();	
  }
}



void Lcd_Proc(void)
{
	if((uwTick -  uwTick_Lcd_Set_Point)<1000)	return;//减速函数
	uwTick_Lcd_Set_Point = uwTick;
	i++;

	sprintf((char *)Lcd_Disp_String, "EE:%x%x%x%x R:%x",EEPROM_String_2[0],EEPROM_String_2[1],EEPROM_String_2[2],EEPROM_String_2[3],RES_4017);
	LCD_DisplayStringLine(Line2, Lcd_Disp_String);
	sprintf((char *)Lcd_Disp_String, "RES_K:%5.2fK",0.7874*RES_4017);
	LCD_DisplayStringLine(Line3, Lcd_Disp_String);	
	sprintf((char *)Lcd_Disp_String, "VOLTAGE:%6.3fV",3.3*((0.7874*RES_4017)/(0.7874*RES_4017+10)));
	LCD_DisplayStringLine(Line4, Lcd_Disp_String);	
	
}

void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  HAL_PWREx_ControlVoltageScaling(PWR_REGULATOR_VOLTAGE_SCALE1);
  
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLM = RCC_PLLM_DIV3;
  RCC_OscInitStruct.PLL.PLLN = 20;
  RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
  RCC_OscInitStruct.PLL.PLLQ = RCC_PLLQ_DIV2;
  RCC_OscInitStruct.PLL.PLLR = RCC_PLLR_DIV2;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }
  
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

void Error_Handler(void)
{
  __disable_irq();
  while (1)
  {
  }
}

#ifdef  USE_FULL_ASSERT

void assert_failed(uint8_t *file, uint32_t line)
{

}
#endif 

2. bsp_iic.c

#include "iic\bsp_iic.h"

#define DELAY_TIME	20

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);
}

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);
}

void SDA_Output( uint16_t val )
{
    if ( val )
    {
        GPIOB->BSRR |= GPIO_PIN_7;
    }
    else
    {
        GPIOB->BRR |= GPIO_PIN_7;
    }
}

void SCL_Output( uint16_t val )
{
    if ( val )
    {
        GPIOB->BSRR |= GPIO_PIN_6;
    }
    else
    {
        GPIOB->BRR |= GPIO_PIN_6;
    }
}

uint8_t SDA_Input(void)
{
	if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) == GPIO_PIN_SET){
		return 1;
	}else{
		return 0;
	}
}

static void delay1(unsigned int n)
{
    uint32_t i;
    for ( i = 0; i < n; ++i);
}

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);
}

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);

}

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;
        }
    }
    SCL_Output(0);
    delay1(DELAY_TIME);
		SDA_Output_Mode();
    return SUCCESS;
}

void I2CSendAck(void)
{
    SDA_Output(0);
    delay1(DELAY_TIME);
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    SCL_Output(0);
    delay1(DELAY_TIME);

}

void I2CSendNotAck(void)
{
    SDA_Output(1);
    delay1(DELAY_TIME);
    delay1(DELAY_TIME);
    SCL_Output(1);
    delay1(DELAY_TIME);
    SCL_Output(0);
    delay1(DELAY_TIME);

}

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);
}

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);
}

//24C02的相关代码
void iic_24c02_write(unsigned char* pucBuf,unsigned char ucAdder,unsigned char ucNum)
{
	I2CStart();
	I2CSendByte(0xa0);//1010 0000
	I2CWaitAck();
	
	I2CSendByte(ucAdder);
	I2CWaitAck();
	
	while(ucNum--)
	{
		I2CSendByte(*pucBuf++);
		I2CWaitAck();
	}
	
	I2CStop();
	delay1(500);
}

void iic_24c02_read(unsigned char* pucBuf,unsigned char ucAdder,unsigned char ucNum)
{
	I2CStart();
	I2CSendByte(0xa0);//1010 0000
	I2CWaitAck();
	
	I2CSendByte(ucAdder);
	I2CWaitAck();
	
	I2CStart();
	I2CSendByte(0xa1);//1010 0001
	I2CWaitAck();
	
	while(ucNum--)
	{
		*pucBuf++ = I2CReceiveByte();
		if(ucNum)
			I2CSendAck();
		else
			I2CSendNotAck();
	}
	
	I2CStop();
}

//MCP4017的相关代码
void wirte_resistor(uint8_t value)
{
	I2CStart();
	I2CSendByte(0x5E);//0101 1110
	I2CWaitAck();
	
	I2CSendByte(value);
	I2CWaitAck();
	I2CStop();
}

uint8_t read_resistor(void)
{
	uint8_t value;
	
	I2CStart();
	I2CSendByte(0x5F);//0101 1111
	I2CWaitAck();
	
	value = I2CReceiveByte();
	I2CSendNotAck();
	I2CStop();
	
	return value;

}

3. bsp_iic.h

#include "main.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);

//24C02的相关代码
void iic_24c02_write(unsigned char* pucBuf,unsigned char ucAdder,unsigned char ucNum);
void iic_24c02_read(unsigned char* pucBuf,unsigned char ucAdder,unsigned char ucNum);

//MCP4017的相关代码
void wirte_resistor(uint8_t value);
uint8_t read_resistor(void);
Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐