阅读小tips!(Look at my eyes!)

本篇基于电子类大学生自主学习嵌入式的学习总结,知识点理解输出较于浅显,只适用于深入学习stm32前的一些理解和引导,或是大学期末前一周速通,若要深入学习嵌入式,可借助阅读本篇来搭建一个入门嵌入式的框架,而后想深入可转战b站等学习网站,有更多的大牛在等待你!

一.揭开iic神秘面纱

IIC通常也写作I2C,是一种双向通信总线,对于初学者来说,词汇的过于专业化,还是不好理解,到底什么叫做通信总线呢?

不妨来这样理解,上篇我们共同探索了串口,(tips:没有看过的小伙伴可以移步至上一篇串口篇),我们对于串口还是比较熟悉,串口是一种通信接口,它让设备与设备之间能够通信,串口的TX和RX通常要占用一组IO口,基础的单片机如stm32f103c8t6只配备了三组串口,当我们只有三组通信接口,少量IO口的情况下,如果我们要进行多设备通信,又该如何呢?

单片机为我们提供了iic总线,这种接口通信,实现了一接口对多设备,大大解决了多设备通信的难题,也解放了多组IO口。

IIC总线的基础电路结构

 IIC通过SCL和SDA两条线建立主机(一般是单片机)与从机(其他设备或单片机)之间的通信。

从机的地址:由7的bit位组成,0000 000 - 1111 111,1byte最后一个bit位0/1代表主机读/写数据。

时钟线与数据线:

SCL:

时钟线,一般由主机发送时钟信号,在时钟信号的每一个周期中,数据线上发送一位数据。

传输频率的快慢决定了数据的传输速度。

SDA:

数据线,传输双向,可以由主机传给从机,也可以由从机传给主机,数据位的高低电平决定了0/1.

逻辑线与:

与运算:Y = 1&1&...1 = 1  Y = 1&1&...0 = 0

tips:所有的主机从机连接SDA和SCL引脚都设置为了开漏输出,所以当引脚为高电平时保持高阻态,逻辑上释放总线,由上拉电阻拉高,有引脚为低电平时接入GND。

上拉电阻:

SDA和SCL线上为高阻态时,将引脚拉为高电平。

数据传输:

主机发送时钟信号:

所有从机连接SCL的引脚保持为高电平,逻辑上释放总线,主机连接SCL的引脚通过写1/0,实现高低电平的转换,发送一个时钟脉冲信号,写0为低电平写1为高电平。

主机发送数据信号:

与发送时钟信号相同,所有从机连接SDA的引脚保持为高电平,逻辑上释放总线,主机连接SDA的引脚通过写1/0,实现高低电平的转换,发送一个脉冲信号。

从机发送数据信号:

由于I2C通信是双向的,从机接收数据信号的同时也能发送数据信号,与主机发送数据相同,主机与其余从机连接SDA的引脚保持为高电平,逻辑上释放总线,选中的从机连接SDA的引脚通过写1/0,实现高低电平的转换,发送一个脉冲信号。

I2C通信协议:

数据帧格式:

起始位与停止位:

空闲状态下保持高电平,当数据传输开始时,由主机拉低发送开始信号,数据传输结束时,由主机拉高释放总线。

RW位:

7位地址为从机地址,R表示读数据写0,W表示写数据写1。

ACK与NAK:

ACK:标志位,0-发送NAK,1-发送ACK。只作用于正在被接受的字节。

应答信号,当主机发送一个字节结束时,主机释放SDA总线,从机发送ACK应答信号,拉低SDA总线,表示成功接收数据,并为下一个字节传输做准备。当从机不想接受数据时,主机释放SDA总线,从机不去拉低,发送NAK应答信号,表示数据传输结束,主机紧接着发送停止位。

二、IIC模块的使用

iic的速度模式选择:

                         一般的IIC通信,选择快速模式。

时钟信号的占空比:

                         一般默认选择2/1的时钟信号占空比。

IIC模块的初始化:

此处默认已初始化过IO口。


RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1,ENABLE);//开启I2C1时RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,ENABLE);//施加复位信号RCC_APB1PeriphResetCmd(RCC_APB1Periph_I2C1,DISABLE);//释放复位信号
	
I2C_InitTypeDef I2C_InitStruct;
	
I2C_InitStruct.I2C_ClockSpeed = 400000;//波特率400K
I2C_InitStruct.I2C_Mode = I2C_Mode_I2C;//标准的I2C
I2C_InitStruct.I2C_DutyCycle = I2C_DutyCycle_2;//占空比2:1
	
I2C_Init(I2C1,&I2C_InitStruct);
	
I2C_Cmd(I2C1,ENABLE);//闭合I2C总开关

I2C写数据:

IIC模块的内部结构框图:

SR:Status  Register 状态寄存器,用来储存标志位。接下来将在编写代码过程中逐一对标志位进行讲解。TxE RxNE 串口章节已详细讲解,本章不做赘述。

定义发送数据函数:

int My_I2C_SendBytes(I2C_TypeDef * I2Cx,//I2C接口名称

                     uint8_t Addr,//从机地址,靠左

                     uint8_t * pData,//要发送的数据

                     uint16_t Size//要发送的数据的数量 字节);

通过I2C向从机发送若干个字节。返回值:0-成功 1-寻址失败 2-发送的数据被拒绝

等待总线空闲:

BUSY -总线忙标志位,0-总线空闲 1-总线忙。

//等待总线空闲
	while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BUSY)== SET);
发送起始位:

START - 发送起始位,向该比特位写1发送起始位。

SB - 起始位发送完成,0-起始位未发送,1-起始位发送完成。

//发送起始位
	I2C_GenerateSTART(I2Cx,ENABLE);
	while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB == RESET));
发送地址:

AF- 应答失败  1- 未收到ACK

ADDR - 寻址成功 1-寻址成功

	//寻址阶段
	I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
	
	I2C_SendData(I2Cx,Addr & 0xfe);//左对齐,最后一位为R/W位
	
	while(1)
	{
			if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
			break;
			if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
			{
				I2C_GenerateSTOP(I2Cx,ENABLE);
				return -1;
			}
			//清空ADDR
			I2C_ReadRegister(I2Cx,I2C_Register_SR1);
			I2C_ReadRegister(I2Cx,I2C_Register_SR2);
	}
	
发送数据:
	//发送数据
	for(uint16_t i=0;i<size;i++)
	{
			while(1)
			{
				if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF)==SET)

				{
                    I2C_GenerateSTOP(I2Cx,ENABLE);
                    return -2;
                }
				if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_TXE)==SET)
					break;
			}
			I2C_SendData(I2Cx,pData[i]);
	}
	

BTF -数据发送完成标志位,1-数据发送完成

//检查数据是否发送完成
	while(1)
	{
	  if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
		{
			I2C_GenerateSTOP(I2Cx,ENABLE);
			return -2;
		}
		if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_BTF) == SET)
			break;
	}
	
发送停止位:

STOP- 发停止位,向该比特位写1发送停止位。

//发送停止位
	I2C_GenerateSTOP(I2Cx,ENABLE);
	return 0;

定义接收数据函数:

int My_I2C_RecieveBytes(I2C_TypeDef * I2Cx,//I2C接口名称

                     uint8_t Addr,//从机地址,靠左

                     uint8_t * pBuffer,//要接收的数据

                     uint16_t Size//要接收的数据的数量 字节);

通过I2C向从机接收若干个字节。返回值:0-成功 1-寻址失败 2-发送的数据被拒绝

发送起始位:

//发送起始位
	I2C_GenerateSTART(I2Cx,ENABLE);
	while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_SB == RESET));

发送地址:

//寻址阶段
	I2C_ClearFlag(I2Cx,I2C_FLAG_AF);
	
	I2C_SendData(I2Cx,Addr | 0x01);//左对齐,最后一位为R/W位
	
	while(1)
	{
			if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_ADDR) == SET)
			break;
			if(I2C_GetFlagStatus(I2Cx,I2C_FLAG_AF) == SET)
			{
				I2C_GenerateSTOP(I2Cx,ENABLE);
				return -1;
			}
	}

接收数据:

//接收数据
	if(Size  == 1)
	{
		//清空ADDR
			I2C_ReadRegister(I2Cx,I2C_Register_SR1);
			I2C_ReadRegister(I2Cx,I2C_Register_SR2);
		//ACK =0 STOP =1
		I2C_AcknowledgeConfig(I2Cx,DISABLE);
		I2C_GenerateSTOP(I2Cx,ENABLE);
		// 等待接收完成
		while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
		
		//读取数据
		pBuffer[0] = I2C_ReceiveData(I2Cx);
	}
    else if(Size  == 2)
	{
		//清空ADDR
			I2C_ReadRegister(I2Cx,I2C_Register_SR1);
			I2C_ReadRegister(I2Cx,I2C_Register_SR2);
		//ACK=1
		I2C_AcknowledgeConfig(I2Cx,ENABLE);
		// 等待接收完成
		while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
		//读取第一个数据
		pBuffer[0] = I2C_ReceiveData(I2Cx);
		
		//ACK =0 STOP =1
		I2C_AcknowledgeConfig(I2Cx,DISABLE);
		I2C_GenerateSTOP(I2Cx,ENABLE);
		// 等待接收完成
		while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
		//读取第二个数据
		pBuffer[1] = I2C_ReceiveData(I2Cx);
	}
    else //读取多个字节
	{
		//清空ADDR
			I2C_ReadRegister(I2Cx,I2C_Register_SR1);
			I2C_ReadRegister(I2Cx,I2C_Register_SR2);
		//ACK=1
		I2C_AcknowledgeConfig(I2Cx,ENABLE);
		for(uint16_t i=0;i<sizeof-1;i++)
		{
			// 等待接收完成
			while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
			//读取数据
			pBuffer[i] = I2C_ReceiveData(I2Cx);
		}
		//ACK =0 STOP =1
		I2C_AcknowledgeConfig(I2Cx,DISABLE);
		I2C_GenerateSTOP(I2Cx,ENABLE);
		// 等待接收完成
		while(I2C_GetFlagStatus(I2Cx,I2C_FLAG_RXNE) == RESET);
		//读取最后一个数据
		pBuffer[1] = I2C_ReceiveData(I2Cx);
	}
	
	

发送停止位:

//发送停止位
	//I2C_GenerateSTOP(I2Cx,ENABLE);接收数据中已写
	return 0;

创作不易,请多多点赞,收藏,转发,您的支持,是我创作最大的动力!

内容若有误,在所难免,还望指出,定会积极订正,不胜感激,还望海涵!

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐