嵌入式软件--stm32 DAY7 I2C通讯上

iic协议由飞利浦公司开发,是非常常用的一种通信协议,拥有两条总线的他支持多个设备共用同一条总线。常用于oled屏幕的外设驱动和手机PD协议通信中。了解和掌握iic协议是十分有必要的。

1.IIC概念

1.1 IIC的两条线和协议细节

双向两线制,同步半双工

1.1.1 三种速率模式

标准模式 100kbps

快速模式 400kbps

高速模式 3.4Mbps

不占用总线,上拉电阻把总线拉成高电平。

一般使用开漏模式而不是用推挽输出的原因是,避免多设备间冲突(多个设备共用同一条主线,如果有高有低,就会造成冲突),实现多主仲裁。

1.1.2协议细节

起始信号:SCL保持高电平,SDA下降沿(电平由高到低变换)

数据位:SCL保持高电平,SDA维持稳定。

终止信号:SCL保持高电平,SDA上升沿(电平由低到高变换)

确认信号:IIC以字节为单位传输数据,每传输一个字节,都要向发送方发送一个确认信号。ACK NACK

数据的有效性:SDA高低电平在SCL时钟信号是低电平时才能改变,SDA线上的数据必须在SCL高电平周期保持稳定。

响应:接收方发送数据后要给发送方响应,应答响应和非应答响应。

应答响应ACK:给发送方一个低电平,非应答响应NACK:给发送方一个高电平。

2. iic案例1:软件模拟IIC

2.1 EEPROM

型号:M24C02

M24C02的scl和sda与stm32的iic引脚相连,结合上拉电阻,构成IIC总线,通过iic进行交互。

eeprom芯片的设备地址一共七位,高四位固定1010,低三位则由E3/E2/E1信号线决定eeprom的设备地址。

R/W是读写方向控制位,与地址无关。

从设备7位地址+读写控制位

1 0 1 0 E3 E2 E1 R/W

2.2操作时序图

2.2.1单字节写入

写入一个字节,一个字节一个字节的写入。

start 从设备地址+写操作位 ACK 写入字节地址 ACK 写入字节 ACK stop

2.2.2单字节读出

读出一个字节时序:

start 从设备地址+写操作位 ACK 写入字节地址 ACK start 从设备地址+读操作位 ACK 读字节 NACK(不应答,发送方就不会继续发) stop

单次写入多个字节时序:

start 从设备地址+写操作位 ACK 写入字节地址 ACK 写入字节1 ACK 写入字节2 ACK ····写入字节N ACK stop

多字节写入叫页写,页写一次最多写入16个字节。超过16个字节,eeprom会重新第一个字节覆盖写入。

读的时候,要写一个字节再读一个字节,这叫假写真读。写的字节是地址,真正读的是数据。读字节没有限制,可以读取任意多个。

假写真读多个字节序:发start信号 设备地址+写操作位 ACK 写入内部地址 ACK 发start信号 设备地址加读操作位 ACK 读字节1 ACK 读字节2 ACK... 读字节N NO NACK STOP信号

2.3 代码实战IIC

软件模拟IIC,通过向EEPROM发送一段数据,再读取出来,最后发送到串口,核对是否读写正确。

复制上一个项目文件夹,重命名为iic_software_register,意为iic软件模拟寄存器写法。

删除几个文件,只留下基本文件start,user,hardware和keil源文件.在hardware中添加文件夹IIC,新建两个文件iic.c和iic.h。

涉及到读写的EEPROM放到interface,建立两个文件m24c02.h,m24c02.c。

在keil里面将.c文件和.h文件路径添加到位。

关联vscode:

这是寄存器写法,用了PB10,PB11两个引脚。

通信过程:

在.h文件里写预处理函数和函数名。

加上重定向函数,方便打印看结果。

2.3.1 iic初始化配置

复制代码
//初始化
void I2C_Init(void)
{
    //1.配置时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;

    //2.GPIO配置,PB10\PB11通用开漏输出,MODE-11,CNF-01
    GPIOB->CRH|=(GPIO_CRH_MODE10|GPIO_CRH_MODE11);
    GPIOB->CRH&=~(GPIO_CRH_CNF10_1|GPIO_CRH_CNF11_1);
    GPIOB->CRH|=(GPIO_CRH_CNF10_0|GPIO_CRH_CNF11_0);
}

2.3.2 IIC时序

(1)起始信号 SCL处于高电平,SDA下降沿,先拉高后拉低

复制代码
//主机发出起始信号
void I2C_Start(void)
{
    //1.拉高SCL/SDA
    SCL_HIGH;
    SDA_HIGH;
    I2C_DELAY;
    //2.拉低SDA,产生下降沿
    SCL_LOW;
    I2C_DELAY;

}

(2)SDA低,SCL高,SDA上升沿

复制代码
//主机发出停止信号
void I2C_Stop(void)
{
    //1.拉高SCL,拉低SDA
    SCL_HIGH;
    SDA_LOW;
    I2C_DELAY;
    //2.拉高SDA,产生上升沿
    SDA_HIGH;
    I2C_DELAY;
} 

(3)接收应答非应答信号

《1》拉高SDA,拉低SCL,等待SDA反转

《2》SDA拉低,翻转数据,输出应答信号

《3》SCL拉高,保持SDA信号,做数据采样

《4》SCL拉低,数据采样结束

《5》SDA拉高,应答输出结束,释放SDA

cpp 复制代码
//主机发送应答/非应答信号
void I2C_ACK(void)
{
    //1.拉高SDA/拉低SCL,准备反转SDA
    SDA_HIGH;
    SCL_LOW;
    I2C_DELAY;

//《2》SDA拉低,翻转数据,输出应答信号
SDA_LOW;
I2C_DELAY;
//《3》SCL拉高,保持SDA信号,做数据采样
SCL_HIGH;
I2C_DELAY;
//《4》SCL拉低,数据采样结束
SCL_LOW;
I2C_DELAY;
//《5》SDA拉高,应答输出结束,释放SDA总线
SDA_HIGH;
I2C_DELAY;
}

(4)接收非应答

//1.拉低SCL,拉高SDA,准备发送非应答信号

//2.保持SDA,拉高SCL信号,做数据采样

//3.保持SDA,拉低SCL,数据采样结束

cpp 复制代码
void I2C_NACK(void)
{
    //1.拉低SCL,拉高SDA,准备发送非应答信号
SCL_LOW;
SDA_HIGH;
I2C_DELAY;
//2.保持SDA,拉高SCL信号,做数据采样
SCL_HIGH;
I2C_DELAY;
//3.保持SDA,拉低SCL,数据采样结束
SCL_LOW;
I2C_DELAY;
}

(5)

复制代码
//主机从SDA读取响应信号,等待应答
uint8_t I2C_Wait4ACK(void)
{
     //1.拉高SDA/拉低SCL,准备反转SDA
    SDA_HIGH;
    SCL_LOW;
    I2C_DELAY;

//2.SCL拉高,保持SDA信号,做数据采样
SCL_HIGH;
I2C_DELAY;

//3.读取SDA上的电平
uint8_t ack=READ_SDA;

//4.SCL拉低,数据采样结束
SCL_LOW;
I2C_DELAY;
return ack?NACK:ACK;
}

(6)

复制代码
//主机发送一个字节
void I2C_SendByte(uint8_t byte)
{
    for(uint8_t i=0;i<8;i++)
    {
        //1.SCL拉低,等待可能的反转SDA信号,准备发送数据
        SCL_LOW;
        I2C_DELAY;

        //2.将要发送的位传到SDA上,MSB先行
        if(byte&0x80)// 10000000 取最高位
        {
            SDA_HIGH;
        }
        else
        {
            SDA_LOW;
        }
        I2C_DELAY;

        //3.SCL拉高,保持SDA信号,让从设备做数据采样
        SCL_HIGH;
        I2C_DELAY;

        //4.SCL拉低,数据采样结束
        SCL_LOW;
        I2C_DELAY;

        //5.左移一位,为下一次发送做准备
        byte<<=1;
    }
}

//主机从SDA读取一个字节
uint8_t I2C_ReadByte(void)
{
    //定义变量,用来保存读取的字节
    uint8_t data=0;

    for (size_t i = 0; i < 8; i++)
    {
        //1.拉高SDA,拉低SCL,释放SDA总线
        SDA_HIGH;
        SCL_LOW;
        I2C_DELAY;

        //2.SCL拉高,保持SDA信号,做数据采样
        SCL_HIGH;
        I2C_DELAY;

        //3.先左移一位,为下次接收做准备
        data<<=1;

        //4.读取SDA上的电平,放到数据的最低位
      if (READ_SDA)
      {
        data|=0x01;  
      }
      
       
        //5.SCL拉低,数据采样结束
        SCL_LOW;
        I2C_DELAY;

        

    }
    
}

2.4 接口层

.h文件

2.4.1 初始化

接口层没有什么可以初始化的,只要收发逻辑完成即可,因此只要IIC初始化即可。

2.4.2 写入字节,字写和页写

2.4.3 读取字节

假写真读的过程

连续多个字节读取

复制代码
//连续读取多个字节(连续读)
void M24C02_ReadBytes(uint8_t innerAddr, uint8_t *bytes, uint8_t size)
{
     //1.发出开始信号
    I2C_Start();

    //2.发送写地址(假写)
    I2C_SendByte(W_ADDR);

    //3.等待应答
    I2C_Wait4ACK();

    //4.发送内部地址
    I2C_SendByte(innerAddr);

    //5.等待应答
    I2C_Wait4ACK();

    //6.发出开始信号
    I2C_Start();

    //7.发送读地址(真读)
    I2C_SendByte(R_ADDR);

    //8.等待应答
    I2C_Wait4ACK();

    for (uint8_t i = 0; i < size; i++)
    {
    //9.循环读取一个字节的数据
    bytes[i]= I2C_ReadByte();

    //10.如果没结束,发送一个应答;如果最后一个,就给非应答
    if(i<size-1)
    {
        I2C_ACK();
    }
    else
    {
        I2C_NACK();
    }
    }
    
    //11.发送结束信号
    I2C_Stop();
    return bytes;
}

之前未考虑到,写入和读取字节,EEPROM有自己的耗时,需要5ms.在之前的代码中自行添加。

2.4.4 应用测试:读取单字节

复制代码
#include "usart.h"
#include "delay.h"
#include "m24c02.h"


int main(void)
{
	//1.初始化
    USART_Init();
	M24C02_Init();
	
	printf("WO SHI CHINESE!\n");

	//2.写入单个字符
	M24C02_WriteByte(0x00,'a');
	M24C02_WriteByte(0x01,'b');
	M24C02_WriteByte(0x02,'c');
	
	//3.读取单个字符
	uint8_t byte1=M24C02_ReadByte(0x00);
	uint8_t byte2=M24C02_ReadByte(0x01);
	uint8_t byte3=M24C02_ReadByte(0x02);

	//4.串口输出打印
	printf("byte1=%c\n",byte1);
	printf("byte2=%c\n",byte2);
	printf("byte3=%c\n",byte3);






	while(1)
	{
		
	
	}
	
}

#include "usart.h"
#include "delay.h"
#include "m24c02.h"
#include <string.h>

int main(void)
{
	//1.初始化
    USART_Init();
	M24C02_Init();
	
	printf("WO SHI CHINESE!\n");

	//2.写入单个字符
	M24C02_WriteByte(0x00,'a');
	M24C02_WriteByte(0x01,'b');
	M24C02_WriteByte(0x02,'c');
	
	//3.读取单个字符
	uint8_t byte1=M24C02_ReadByte(0x00);
	uint8_t byte2=M24C02_ReadByte(0x01);
	uint8_t byte3=M24C02_ReadByte(0x02);

	//4.串口输出打印
	printf("byte1=%c\n",byte1);
	printf("byte2=%c\n",byte2);
	printf("byte3=%c\n",byte3);

	//5.页写
	M24C02_WriteBytes(0x00,"123456",6);

	//6.读取多个字符
	uint8_t buff[50];
	M24C02_ReadBytes(0x00,buff,6);

	//7.串口输出打印
	printf("buff=%s\n",buff);

	//清除缓冲区
	memset(buff,0,sizeof(buff));

	//长度超出16的页写
	M24C02_WriteBytes(0x00,"1234567890123456",20);//如果从0X05开始读,就可以读到尾
	M24C02_ReadBytes(0x00,buff,20);
	printf("buff=%s\n",buff);

	while(1)
	{
		
	
	}
	
}
相关推荐
智商偏低5 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen6 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森8 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白8 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D9 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术12 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt12 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘12 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang12 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c
几个几个n15 小时前
STM32-第二节-GPIO输入(按键,传感器)
单片机·嵌入式硬件