1.理论部分
1.IIC概念
中文叫集成电路总线,是一种串行通信总线,使用多主从架构,由飞利浦公司1980年代初设计,方便主板、嵌入式系统或手机与周边组件通讯。因为简单被广泛用于微控制器与传感器阵列,显示器等之间的通信。
I2C总线支持任何IC生成过程。每个器件都有唯一的地址识别,都可以作为一个发送或接收器。
主设备生成总线时钟,所有组件之间都存在简单的主/从关系,连接到总线的每个设备均可以通过唯一的地址进行软件寻址。
I2C是真正的多主,多从设备总线,可提供仲裁和冲突检测。在一般项目中一般使用一主多从的模式。
I2C传输速度只有三种模式:标准模式(100kbit/s);快速模式(400kbit/s)和高速模式(3.4Mbit/s)。
SDA是串行数据总线,而SCL是串行时钟线的缩写。都要接收上拉电阻。
使用I2C可以将多个从机连接到单个主设备,也可以用其他的连接。
I2C分为软件I2C和硬件I2C,软件是指用单片机的两个IO端口模拟的I2C,用软件控制管脚状态以模拟I2C通信波形,软件模拟jicunqi的工作方式。硬件I2C指一块硬件电路,硬件I2C对应芯片上的外设,有对应的I2C驱动电路,所使用的I2C管脚也是专用的,硬件I2C直接调用内部寄存器进行配置。硬件I2C的效率要高于软件,而软件则不受管脚限制,接口比较灵活
2.IIC物理层
I2C一共有两根总线:一条是双向的串行数据线SDA,一条是串行时钟线SCL。SDA是数据线,D表示DATA。SCL是时钟线,C代表Clock,就是控制数据发送的时序的。
所有连接到I2C总线设备上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL都接到总线的SCL上。I2C总线上的每一个设备都有自己的一个唯一的地址,来确保不同设备之间访问的准确性。
通常将I2C设备分为主设备和从设备,谁控制时钟线SCL谁就是主设备。I2C主设备功能是产生时钟信号,产生起始信号和停止信号。I2C从设备功能是可编程的I2C地址检测,停止位检测。
I2C的一个优点是支持多主控,其中任何一个能够进行发送接收的设备都可以成为主总线。一个主控能够控制信号的传输和时钟频率。任何时间点上只能有一个主控。
SCL和SDA都需要借上拉电阻(一般3.3k到10k之间)保证数据稳定性,减少干扰。
I2C是半双工,同一时间只可以单向通信。
为避免总线信号混乱,I2C空闲状态只能有外部上拉,此时空闲设备被拉到高组态,相当于断路,只有开启了设备才会正常进行通信,不会干扰其他设备。
每一个I2C器件都有一个器件地址,有的器件地址出厂就设定好了,不可更改,比如OV7670的地址为0x42;有的器件例如EEPROM,前四个地址已经确定好为1010,后三个地址由硬件连接决定,所以I2C总线最多能连8个EEPROM芯片。
3.IIC的协议层
I2C总线在传播数据的过程中共有三种信号(开始信号,结束信号,应答信号)。
开始信号:SCL为高电平,SDA由高电平向低电平跳变,开始传送数据
结束信号:SCL为高电平,SDAy有低电平向高电平跳变,结束传送数据
应答信号:接收数据的IC在接受到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已经收到数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据情况作出是否继续传递信号的判断。没有收到应答信号,则判读为受控单元故障。
这些信号中,起始信号是必需的,结束信号和应答信号,都可以不要。
I2C的通信协议流程:
- 开始信号
SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。
2. 停止信号
SCL保持高电平,SDA由低电平变为高电平。在起始条件后,总线处于忙状态,由本次数据传输的主从设备独占,其他I2C无法访问总线;而在停止条件产生后,本次数据传输的主从设备将释放总线,总线再次处于空闲状态。
3. 数据有效性
I2C信号在数据传输过程中,当SCL=1高电平时,SDA必须保持稳定状态,不允许有电平跳变,只有在时钟线上的信号为低电平时,数据线上的高低电平在允许变化。
SCL=1时,数据线SDA的任何电平变换会看做是总线的起始信号或者停止信号。也就是在I2C传输数据的过程中SCL时钟会频繁的切换电平,以保证数据的传输。
4. 应答信号
每当主机向从机发完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据。
应答信号:主机SCL拉高,读取从机SDA的电平,为低电平表示产生应答。
应答信号为低电平时,规定为有效应答位(ACK),表示接收器已经成功接收到了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
每发送一个字节(8bit)在一个字节传输的8个时钟后的第九个时钟期间,接收器接收数据后必须返回一个ACK应答信号给发送器,这样才能进行数据传输。
应答出现在每一次主机完成8个数据位传输后紧跟的时钟周期,低电平0表示应答,1表示非应答。
5. 数据传送格式
当一个字节数据位从高位到低位的顺序传输完成之后,紧接着从设备拉低SDA线,回传给主设备一个应答位,此时才认为一个字节真正的被传输完成,如果一段时间内没有收到从机的应答信号,则自动认为从机已经正确接收到数据。
I2C每一帧数据由9bit组成,如果是发送数据,则包含8bit+1bitACK;如果是设备地址,则8bit设备地址+1bit方向。
2.STM32CUBeMX
3.Keil编程
1.OLED
1.移植oled的驱动程序
2.编写oled的写函数
void OLED_Write(uint8_t uaddr,uint8_t udata)
{
uint8_t pbuf[2];
pbuf[0] = uaddr;
pbuf[1] = udata;
HAL_I2C_Master_Transmit(&hi2c1,0x78,pbuf,2,10);
}
2.24C02
编写24c02的读写函数
void write_24c02(uint8_t addr,uint8_t *data,uint8_t len)
{
HAL_I2C_Mem_Write(&hi2c1, 0xa0, addr,I2C_MEMADD_SIZE_8BIT, data, len, len*100);
}
void read_24c02(uint8_t addr,uint8_t *data,uint8_t len)
{
HAL_I2C_Mem_Read(&hi2c1, 0xa1, addr,I2C_MEMADD_SIZE_8BIT, data, len, len*100);
}
//24c02.c
#include "24c02.h"
#include "i2c.h"
// 写单个字节到指定位置
HAL_StatusTypeDef EEPROM_Write(uint8_t devAddr, uint16_t memAddr, uint8_t *data, uint16_t count) {
return HAL_I2C_Mem_Write(&hi2c1, devAddr, memAddr, 1, data, count, 100);
}
// 从指定位置读取多个字节
HAL_StatusTypeDef EEPROM_Read(uint8_t devAddr, uint16_t memAddr, uint8_t *data, uint16_t count) {
return HAL_I2C_Mem_Read(&hi2c1, devAddr, memAddr, 1, data, count, 100);
}
//24c02.h
#ifndef _24C02_H_
#define _24C02_H_
#include "main.h"
// 定义设备地址
#define EEPROM_I2C_ADDRESS 0xA0 // 24C02 的7位地址 (A0-A2接地)
// 写单个字节到指定位置
HAL_StatusTypeDef EEPROM_Write(uint8_t devAddr, uint16_t memAddr, uint8_t *data, uint16_t count);
// 从指定位置读取多个字节+
HAL_StatusTypeDef EEPROM_Read(uint8_t devAddr, uint16_t memAddr, uint8_t *data, uint16_t count);
#endif
//main.c
#include "main.h"
#include "i2c.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include "24c02.h"
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
uint8_t writeData[] = {0x55, 0xAA, 0x12, 0x34};
uint8_t readData[4];
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
int fputc(int ch,FILE *p)
{
HAL_UART_Transmit(&huart2,(uint8_t *)&ch,1,0xffff);
return ch;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
MX_USART2_UART_Init();
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
/* USER CODE BEGIN 2 */
printf("this is i2c 24c02 init\r\n");
// 向地址0x00写入数据
EEPROM_Write(EEPROM_I2C_ADDRESS, 0x00, writeData, sizeof(writeData));
HAL_Delay(100);
// 从地址0x00读回数据
EEPROM_Read(EEPROM_I2C_ADDRESS, 0x00, readData, sizeof(readData));
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
printf("24c02:%#x,%#x,%#x,%#x\r\n",readData[0],readData[1],readData[2],readData[3]);
HAL_Delay(500);
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}