【STM32】IIC的初步使用

IIC简介

物理层

在这里插入图片描述

连接多个devices

它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。

两根线

一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA)一条串行时钟线(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。

设备地址

每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之间的访问。

上拉电阻和高阻态

总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

仲裁

多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用总线。

三种速度模式

具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式下可达 3.4Mbit/s,但目前大多 I2C 设备尚不支持高速模式。

最大devices限制

连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

协议层

I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地址广播等环节。

I2C 基本读写过程

在这里插入图片描述
主机写数据到从机
在这里插入图片描述
主机由从机中读数据

在这里插入图片描述
I2C 通讯复合格式

在这里插入图片描述 数据由主机传输至从机
在这里插入图片描述数据由从机传输至主机

S : 传输开始信号
R/W: 传输方向选择位,1 为读,0 为写
A/ A: 应答(ACK)或非应答(NACK)信号
在这里插入图片描述停止传输信号

1.S是起始信号,由主机产生,挂载在总线上的设备都会收到,准备接收主机下一个数据
2.设备聆听地址信息,选择从机: 起始信号之后,主机会广播从机地址,在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这个设备就被选中了,没被选中的设备将会忽略之后的数据信号。根据 I2C 协议,这个从机地址可以是 7 位或 10 位。
3.传输方向:主机广播传输方向,0是主机到从机(主机往从机写),1为从机到主机(从机往主机写)
4.从机应答:回复ack与nack,只有收到ack之后,主机继续发送或者接收数据

写数据

在这里插入图片描述
主机首先在 IIC 总线上发送起始信号,那么这时总线上的从机都会等待接收由主机发出的数据。主机接着发送从机地+0(写操作)组成的 8bit 数据,所有从机接收到该 8bit 数据后,自行检验是否是自己的设备的地址,假如是自己的设备地址,那么从机就会发出应答信号。主机在总线上接收到有应答信号后,才能继续向从机发送数据。注意:IIC 总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。

读数据

在这里插入图片描述
若配置的方向传输位为“读数据”方向,即第二幅图的情况,广播完地址,接收到应答信号后,从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数据,都会等待主机的应答信号(ACK),重复这个过程,可以返回 N 个数据,这个 N 也没有大小限制。当主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。

信号电平

起始 停止信号

在这里插入图片描述
当 SCL 线是高电平时 SDA 线从高电平向低电平切换,这个情况表示通讯的起始。当 SCL 是高电平时 SDA 线由低电平向高电平切换,表示通讯的停止。起始和停止信号一般由主机产生。

数据有效性 SCL的高电平

在这里插入图片描述

SCL为高电平的时候 SDA表示的数据有效,SCL为低电平时数据变换

地址及数据方向

I2C 总线上的每个设备都有自己的独立地址,主机发起通讯时,通过 SDA 信号线发送设备地址(SLAVE_ADDRESS)来查找从机。I2C 协议规定设备地址可以是 7 位或 10 位,实际中 7 位的地址应用比较广泛。紧跟设备地址的一个数据位用来表示数据传输方向。
读数据方向时,主机会释放对 SDA 信号线的控制,由从机控制 SDA 信号线,主机接收信号,写数据方向时,SDA 由主机控制,从机接收信号。
在这里插入图片描述
大端(高有效位存在低地址),波形高位先行

ACK NACK

主站发送起始信号,地址,读写信号之后释放SDA控制权,从站开始自动控制SDA信号发送ACK NACK

整体控制逻辑

整体控制逻辑负责协调整个 I2C 外设,控制逻辑的工作模式根据我们配置的“控制寄存器(CR1/CR2)”的参数而改变。在外设工作时,控制逻辑会根据外设的工作状态修改“状态寄存器(SR1 和 SR2)”,我们只要读取这些寄存器相关的寄存器位,就可以了解 I2C的工作状态。除此之外,控制逻辑还根据要求,负责控制产生 I2C 中断信号、DMA 请求及各种 I2C 的通讯信号(起始、停止、响应信号等)。

IIC使用 读写EEPROM为例

要点

(1) 配置通讯使用的目标引脚为开漏模式;
(2) 使能 I2C 外设的时钟;
(3) 配置 I2C 外设的模式、地址、速率等参数并使能 I2C 外设;
(4) 编写基本 I2C 按字节收发的函数;
(5) 编写读写 EEPROM 存储内容的函数;

硬件IIC

配置IIC复用的GPIO

#配置 GPIO 复用
GPIO_InitTypeDef GPIO_InitStructure;/* 使能与 I2C 有关的时钟 */
RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB)/* 配置GPIO为开漏输出模式 */
//配置Gpio初始化结构体略
GPIO_Init(GPIOB, &GPIO_InitStructure);

1.配置IIC模式

I2C_InitTypeDef I2C_InitStructure;/* I2C 配置 */
I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;/*!< 指定工作模式,可选 I2C 模式及 SMBUS 模式 *//* 高电平数据稳定,低电平数据变化 SCL 时钟线的占空比 */
I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2; /*指定时钟占空比,可选 low/high = 2:1 及 16:9 模式*//*!< 指定自身的 I2C 设备地址 */
I2C_InitStructure.I2C_OwnAddress1 =I2Cx_OWN_ADDRESS7;/*!< 指定自身的 I2C 设备地址 */I2C_InitStructure.I2C_Ack = I2C_Ack_Enable ; /*!< 使能或关闭响应(一般都要使能) *//* I2C 的寻址模式 */
I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit; /*!< 指定地址的长度,可为 7 位及 10 位 */I2C_InitStructure.I2C_ClockSpeed = I2C_Speed; /*!< 设置 SCL 时钟频率,此值要低于 400000*/I2C_Init(I2C1, &I2C_InitStructure);
I2C_Cmd(I2C1, ENABLE);

1.1作为主机端,进行数据发送

即作为 I2C 通讯的主机端时,对外部发送数据的过程

以写一个E2ROM为例
在这里插入图片描述
(1) 控制产生起始信号(S),当发生起始信号后,它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;

(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”及“EV8”,这时 SR1 寄存器的“ADDR”位及“TXE”位被置 1,ADDR 为 1 表示地址已经发送,TXE 为 1 表示数据寄存器为空;

(3) 以上步骤正常执行并对 ADDR 位清零后,我们往 I2C 的“数据寄存器 DR”写入要发送的数据,这时TXE位会被重置0,表示数据寄存器非空,I2C外设通过SDA信号线一位位把数据发送出去后,又会产生“EV8”事件,即 TXE 位被置 1,重复这个过程,就可以发送多个字节数据了;

(4) 当我们发送数据完成后,控制 I2C 设备产生一个停止信号§,这个时候会产生EV8_2 事件,SR1 的 TXE 位及 BTF 位都被置 1,表示通讯结束。

假如我们使能了 I2C 中断,以上所有事件产生时,都会产生 I2C 中断信号,进入同一个中断服务函数,到 I2C 中断服务程序后,再通过检查寄存器位来判断是哪一个事件。

IIC STM32固件库函数链接: IIC STM32固件库函数链接

1.1.1发送单字节数据

在这里插入图片描述
AT24C02的单字节时序规定,向它写入数据的时候,第一个字节为内存地址,第二个字节是要写入的数据内容。在发送device地址之后,需要发送eeprom的内部存储地址和需要存的数据

#define EEPROM_I2Cx      I2C1/* 具体修改的寄存器位置,查看固件库函数*/uint32_t I2C_EE_ByteWrite(u8* pBuffer, u8 WriteAddr){/* 产生 I2C 起始信号 *///I2Cx->CR1 |= I2C_CR1_START; 操作I2C中的CR1寄存器I2C_GenerateSTART(I2C1, ENABLE);/*设置超时等待时间*/I2CTimeout = I2CT_FLAG_TIMEOUT;//I2CTimeout 常数/* 检测 EV5 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){/* 超时,报错*/if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(0);}/* 发送 EEPROM 设备地址 */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,I2C_Direction_Transmitter);/* 检测 EV6 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(1);}/* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) *//*  EEPROM 内部存储器读写的规则是第一个数据为要读写的地址,第二个数据是具体读写的数据 */I2C_SendData(EEPROM_I2Cx, WriteAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV8 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);}/* 发送一字节要写入的数据 */I2C_SendData(EEPROM_I2Cx, *pBuffer);I2CTimeout = I2CT_FLAG_TIMEOUT;I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV8 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(2);}/* 发送停止信号 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);}

1.1.2发送多字节数据

等待存储设备准备好

这个函数主要实现是向 EEPROM 发送它设备地址,检测 EEPROM 的响应,若EEPROM 接收到地址后返回应答信号,则表示 EEPROM 已经准备好,可以开始下一次通讯。函数中检测响应是通过读取 STM32 的 SR1 寄存器的 ADDR 位及 AF 位来实现的,当I2C 设备响应了地址的时候,ADDR 会置 1,若应答失败,AF 位会置 1。

	//等待 EEPROM 到准备状态void I2C_EE_WaitEepromStandbyState(void){vu16 SR1_Tmp = 0;do {/* 发送起始信号 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);/* 读 I2C1 SR1 寄存器 */SR1_Tmp = I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1);/* 发送 EEPROM 地址 + 写方向 */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS,I2C_Direction_Transmitter);}// SR1 位 1 ADDR:1 表示地址发送成功,0 表示地址发送没有结束// 等待地址发送成功while (!(I2C_ReadRegister(EEPROM_I2Cx, I2C_Register_SR1) & 0x0002));/* 清除 AF 位 */I2C_ClearFlag(EEPROM_I2Cx, I2C_FLAG_AF);/* 发送停止信号 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}
循环单字节发送
uint8_t I2C_EE_ByetsWrite(uint8_t* pBuffer,uint8_t WriteAddr,uint16_t NumByteToWrite)
{uint16_t i;uint8_t res;/*每写一个字节调用一次 I2C_EE_ByteWrite 函数,前文的单字节写入*/for (i=0; i<NumByteToWrite; i++){/*等待 EEPROM 准备完毕*/I2C_EE_WaitEepromStandbyState();/*按字节写入数据*/res = I2C_EE_ByteWrite(pBuffer++,WriteAddr++);}return res;
}

1.1.3 EEPROM 的页写入

EEPROM 定义了一种页写入时序,只要告诉 EEPROM 第一个内存地址 address1,后面的数
据按次序写入到 address2、address3… 这样可以节省通讯的时间,加快速度。
在这里插入图片描述
根据页写入时序,第一个数据被解释为要写入的内存地址 address1,后续可连续发送 n个数据,这些数据会依次写入到内存中。其中 AT24C02 型号的芯片页写入时序最多可以一次发送 8 个数据(即 n = 8 ),该值也称为页大小,某些型号的芯片每个页写入时序最多可传输 16 个数据。

但是这种方式还是比较慢,拘泥于页的大小。

//在 EEPROM 的一个写循环中可以写多个字节,但一次写入的字节数不能超过 EEPROM 页的大小,AT24C02 //每页有 8 个字节
//NumByteToWrite:要写的字节数要求 NumByToWrite 小于页大小uint8_t I2C_EE_PageWrite(uint8_t* pBuffer, uint8_t WriteAddr,uint8_t NumByteToWrite)
{	I2CTimeout = I2CT_LONG_TIMEOUT;while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(4);}/* 产生 I2C 起始信号 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV5 事件并清除标志 */while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(5);}/* 发送 EEPROM 设备地址 */I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV6 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(6);}/* 发送要写入的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) */I2C_SendData(EEPROM_I2Cx, WriteAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV8 事件并清除标志*/while (! I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(7);}/* 循环发送 NumByteToWrite 个数据 */while (NumByteToWrite--){/* 发送缓冲区中的数据 */I2C_SendData(EEPROM_I2Cx, *pBuffer);/* 指向缓冲区中的下一个数据 */pBuffer++;I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV8 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(8);}}/* 发送停止信号 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);
}

1.1.4 EEPROM 的页写入plus

利用 EEPROM 的页写入方式,可以改进前面的“多字节写入”函数,加快传输速度

 // AT24C01/02 每页有 8 个字节#define I2C_PageSize 8void I2C_EE_BufferWrite(u8* pBuffer, u8 WriteAddr,u16 NumByteToWrite){u8 NumOfPage=0,NumOfSingle=0,Addr =0,count=0,temp =0;/*mod 运算求余,若 writeAddr 是 I2C_PageSize 整数倍,运算结果 Addr 值为 0*//*判断目标地址(起始地址)是否为一页的开始*/Addr = WriteAddr % I2C_PageSize;/*差 count 个数据值,刚好可以对齐到页地址*/count = I2C_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage = NumByteToWrite / I2C_PageSize;/*mod 运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % I2C_PageSize;// Addr=0,则 WriteAddr 刚好按页对齐 aligned,也就是从这页的第一个byte开始// 这样就很简单了,直接写就可以,写完整页后, 把剩下的不满一页的写完即可if (Addr == 0) {//从某页的第一位写起/* 如果 NumByteToWrite < I2C_PageSize *//* 总字数还不满一页 */if (NumOfPage == 0) {//总页数不满一页I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}/* 如果 NumByteToWrite > I2C_PageSize */else {/*先把整数页都写了*/while (NumOfPage--) {I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);//写一页I2C_EE_WaitEepromStandbyState();//等待rom准备好WriteAddr += I2C_PageSize;	//写完一页之后,下一页的地址pBuffer += I2C_PageSize;	//写完一页之后,要传输的源地址也要加}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle!=0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}}}从某页的第一位写起// 如果 WriteAddr 不是按 I2C_PageSize 对齐// 那就算出对齐到页地址还需要多少个数据,先把这几个数据写完,剩下开始的地址就已经对齐else{//不是从某页的第一位写起/* 如果 NumByteToWrite < I2C_PageSize */if (NumOfPage== 0){//总数不满一页if (NumOfSingle > count) {//count:还需要写多少个字节才能页面对齐//NumOfSingle:当前页面空闲的字节数// temp 的数据要写到写一页temp = NumOfSingle - count;I2C_EE_PageWrite(pBuffer, WriteAddr, count);//写count数目的到当前页I2C_EE_WaitEepromStandbyState();//等待准备完毕WriteAddr += count;//目标地址移动pBuffer += count;I2C_EE_PageWrite(pBuffer, WriteAddr, temp);//在下一页写完剩下的I2C_EE_WaitEepromStandbyState();}else{ /*若 count 比 NumOfSingle 大*/I2C_EE_PageWrite(pBuffer, WriteAddr, NumByteToWrite);I2C_EE_WaitEepromStandbyState();}}/* 如果 NumByteToWrite > I2C_PageSize *//* 如果 不止写一页 */else {	/* 如果 不止写一页 *//*地址不对齐多出的 count 分开处理,不加入这个运算*/NumByteToWrite -= count;NumOfPage = NumByteToWrite / I2C_PageSize;NumOfSingle = NumByteToWrite % I2C_PageSize;/*先把 WriteAddr 所在页的剩余字节写了*/if (count != 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, count);I2C_EE_WaitEepromStandbyState();/*WriteAddr 加上 count 后,地址就对齐到页了*/WriteAddr += count;pBuffer += count;}/*把整数页都写了*/while (NumOfPage--) {I2C_EE_PageWrite(pBuffer, WriteAddr, I2C_PageSize);I2C_EE_WaitEepromStandbyState();WriteAddr += I2C_PageSize;pBuffer += I2C_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle != 0) {I2C_EE_PageWrite(pBuffer, WriteAddr, NumOfSingle);I2C_EE_WaitEepromStandbyState();}}/*把整数页都写了*/}/* 如果 不止写一页 */	}

1.2作为主机端,进行数据接收

即作为 I2C 通讯的主机端时,从外部接收数据的过程
在这里插入图片描述
(1) 同主发送流程,起始信号(S)是由主机端产生的,控制发生起始信号后,它产生事件“EV5”,并会对 SR1 寄存器的“SB”位置 1,表示起始信号已经发送;
(2) 紧接着发送设备地址并等待应答信号,若有从机应答,则产生事件“EV6”这时SR1 寄存器的“ADDR”位被置 1,表示地址已经发送。
(3) 从机端接收到地址后,开始向主机端发送数据。当主机接收到这些数据后,会产生“EV7”事件,SR1 寄存器的 RXNE被置 1,表示接收数据寄存器非空,我们读取该寄存器后,可对数据寄存器清空,以便接收下一次数据。此时我们可以控制I2C 发送应答信号(ACK)或非应答信号(NACK),若应答,则重复以上步骤接收数据,若非应答,则停止传输;
(4) 发送非应答信号后,产生停止信号P,结束传输。

 uint8_t I2C_EE_BufferRead(uint8_t* pBuffer, uint8_t ReadAddr,u16 NumByteToRead){I2CTimeout = I2CT_LONG_TIMEOUT;while (I2C_GetFlagStatus(EEPROM_I2Cx, I2C_FLAG_BUSY)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(9);}/* 产生 I2C 起始信号 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV5 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(10);}/* 发送 EEPROM 设备地址 */I2C_Send7bitAddress(EEPROM_I2Cx,EEPROM_ADDRESS,I2C_Direction_Transmitter);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV6 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(11);}	/*通过重新设置 PE 位清除 EV6 事件 */I2C_Cmd(EEPROM_I2Cx, ENABLE);/* 发送要读取的 EEPROM 内部地址(即 EEPROM 内部存储器的地址) *//*  EEPROM 内部存储器读写的规则是第一个数据为要读写的地址,第二个数据是具体读写的数据 */I2C_SendData(EEPROM_I2Cx, ReadAddr);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV8 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_BYTE_TRANSMITTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(12);}/* 产生第二次 I2C 起始信号 */I2C_GenerateSTART(EEPROM_I2Cx, ENABLE);I2CTimeout = I2CT_FLAG_TIMEOUT;	/* 检测 EV5 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_MODE_SELECT)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(13);}/* 发送 EEPROM 设备地址 */I2C_Send7bitAddress(EEPROM_I2Cx, EEPROM_ADDRESS, I2C_Direction_Receiver);I2CTimeout = I2CT_FLAG_TIMEOUT;/* 检测 EV6 事件并清除标志*/while (!I2C_CheckEvent(EEPROM_I2Cx,I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED)){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(14);}/* 读取 NumByteToRead 个数据*/while (NumByteToRead){/*若 NumByteToRead=1,表示已经接收到最后一个数据了,发送非应答信号,结束传输*/if (NumByteToRead == 1){/* 发送非应答信号 */I2C_AcknowledgeConfig(EEPROM_I2Cx, DISABLE);/* 发送停止信号 */I2C_GenerateSTOP(EEPROM_I2Cx, ENABLE);}I2CTimeout = I2CT_LONG_TIMEOUT;while (I2C_CheckEvent(EEPROM_I2Cx, I2C_EVENT_MASTER_BYTE_RECEIVED)==0){if ((I2CTimeout--) == 0) return I2C_TIMEOUT_UserCallback(3);}{/*通过 I2C,从设备中读取一个字节的数据 */*pBuffer = I2C_ReceiveData(EEPROM_I2Cx);/* 存储数据的指针指向下一个地址 */pBuffer++;/* 接收数据自减 */NumByteToRead--;}	}	/* 读取 NumByteToRead 个数据*//* 使能应答,方便下一次 I2C 传输 */I2C_AcknowledgeConfig(EEPROM_I2Cx, ENABLE);}

1.3作为从机端,进行数据接收

在这里插入图片描述

1.4作为从机端,进行数据接收

在这里插入图片描述

GPIO模拟IIC

我之前使用这套方法读取一个外置的RTC模块,参考正点原子的例程,这边就不赘述了。或者之后有机会把例程贴出来哈

1.配置GPIO

2.编写微秒级软延时

3.根据时序编写各个函数

DMA 与IIC

可以使用DMA,对IIC的DR中要传输或者接收的数据进行搬运,DMA请求(当被使能时)仅用于数据传输。发送时数据寄存器变空或接收时数据寄存器变满,则产生DMA请求。DMA请求必须在当前字节传输结束之前被响应。当为相应DMA通道设置的数据传输量已经完成时,DMA控制器发送传输结束信号ETO到I2C接口,并且在中断允许时产生一个传输完成中断。

主发送器:在EOT中断服务程序中,需禁止DMA请求,然后在等到BTF事件后设置停止条件。

主接收器:当要接收的数据数目大于或等于2时,DMA控制器发送一个硬件信号EOT_1,它对应DMA传输(字节数-1)。如果在I2C_CR2寄存器中设置了LAST位,硬件在发送完EOT_1后的下一个字节,将自动发送NACK。在中断允许的情况下,用户可以在DMA传输完成的中断服务程序中产生一个停止条件。

意思是说当DMA产生EOT标志后,(如果开启了EOT相关中断就进中断程序,没有开启就进行软件查询做后续处理)关闭DMA请求,然后等待BTF事件,之后执行STOP操作。 这里的BTF事件就是I2C数据收发过程中的数据字节是否传输完成的的事件
IIC通讯过程 标志位

配置DMA

对DMA进行配置

在IIC的配置中开启DMA

通过设置I2C_CR2寄存器中的DMAEN位可以激活DMA模式。只要TxE位被置位,数据将由DMA从预置的存储区装载进I2C_DR寄存器。为I2C分配一个DMA通道,须执行以下步骤(x是通道号)。

利用DMA发送

  1. 在DMA_CPARx寄存器中设置I2C_DR寄存器地址。数据将在每个TxE事件后从存储器传
    送至这个地址。

  2. 在DMA_CMARx寄存器中设置存储器地址。数据在每个TxE事件后从这个存储区传送至
    I2C_DR。

  3. 在DMA_CNDTRx寄存器中设置所需的传输字节数。在每个TxE事件后,此值将被递减。

  4. 利用DMA_CCRx寄存器中的PL[0:1]位配置通道优先级。

  5. 设置DMA_CCRx寄存器中的DIR位。

  6. 根据应用要求可以配置在整个传输完成一半或全部完成时发出中断请求。

  7. 通过设置DMA_CCTx寄存器上的EN位激活通道。

当DMA控制器中设置的数据传输数目已经完成时,DMA控制器给I2C接口发送一个传输结束的EOT/ EOT_1信号。在中断允许的情况下,将产生一个DMA中断。
如果使用DMA进行发送时,不要设置I2C_CR2寄存器的ITBUFEN位。

利用DMA接收

通过设置I2C_CR2寄存器中的DMAEN位可以激活DMA接收模式。每次接收到数据字节时,将由DMA把I2C_DR寄存器的数据传送到设置的存储区(参考DMA说明)。设置DMA通道进行I2C接收,须执行以下步骤(x是通道号):

  1. 在DMA_CPARx寄存器中设置I2C_DR寄存器的地址。数据将在每次RxNE事件后从此地
    址传送到存储区。
  2. 在DMA_CMARx寄存器中设置存储区地址。数据将在每次RxNE事件后从I2C_DR寄存器
    传送到此存储区。
  3. 在DMA_CNDTRx寄存器中设置所需的传输字节数。在每个RxNE事件后,此值将被递
    减。
  4. 用DMA_CCRx寄存器中的PL[0:1]配置通道优先级。
  5. 清除DMA_CCRx寄存器中的DIR位,根据应用要求可以设置在数据传输完成一半或全部
    完成时发出中断请求。
  6. 设置DMA_CCRx寄存器中的EN位激活该通道。

当DMA控制器中设置的数据传输数目已经完成时,DMA控制器给I2C接口发送一个传输结束的
EOT/ EOT_1信号。在中断允许的情况下,将产生一个DMA中断。
注: 如果使用DMA进行接收时,不要设置I2C_CR2寄存器的ITBUFEN位

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/119853.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

说说Omega架构

分析&回答 Omega架构我们暂且称之为混合数仓。 什么是ECS设计模式 在谈我们的解法的时候&#xff0c;必须要先提ECS的设计模式。 简单的说&#xff0c;Entity、Component、System分别代表了三类模型。 实体(Entity)&#xff1a;实体是一个普通的对象。通常&#xff0c…

pandas数据分析之数据绘图

一图胜千言&#xff0c;将信息可视化&#xff08;绘图&#xff09;是数据分析中最重要的工作之一。它除了让人们对数据更加直观以外&#xff0c;还可以帮助我们找出异常值、必要的数据转换、得出有关模型的想法等等。pandas 在数据分析、数据可视化方面有着较为广泛的应用。本文…

python中super()用法

super关键字的用法 一、概述二、作用三、语法四、使用示例1.通过super() 来调用父类的__init__ 构造方法&#xff1a;2.通过supper() 来调用与子类同名的父类方法2.1 单继承2.2 多继承 一、概述 super() 是python 中调用父类&#xff08;超类&#xff09;的一种方法&#xff0…

iPhone 隔空投送使用指南:详细教程

本文介绍了如何在iPhone上使用隔空投送,包括如何在iOS 11到iOS 14的iPhone上启用它、发送文件以及接受或拒绝AirDrop发送给你的文件。对于iOS 7以上的旧款iPhone,提供了另一种方法。 如何打开隔空投送 你可以通过以下两种方式之一启动隔空投送功能:在“设置”应用程序或控…

C#安装“Windows 窗体应用(.NET Framework)”

目录 背景: 第一步: 第二步: 第三步&#xff1a; 总结: 背景: 如下图所示:在Visual Studio Installer创建新项目的时候&#xff0c;想要添加windows窗体应用程序&#xff0c;发现里面并没有找到Windows窗体应用(.NET Framework)模板&#xff0c;快捷搜索也没有发现&#…

解决小程序中textarea ios端样式不兼容的方法

问题描述 &#xff0c;今天在调试小程序的时候有个需求需要textarea与标题对其&#xff0c;微信开发工具和安卓系统都没有问题 但是ios系统textarea存在内边距。出现不兼容的情况 解决方法&#xff1a;我们看官网的textarea的属性 textarea | uni-app官网 disable-default-p…

路径规划 | 图解Lazy Theta*算法(附ROS C++/Python/Matlab仿真)

目录 0 专栏介绍1 Theta*算法局限性2 Lazy Theta*算法原理3 Theta* VS. Lazy Theta*4 仿真实现4.1 ROS C实现4.2 Python实现4.3 Matlab实现 0 专栏介绍 &#x1f525;附C/Python/Matlab全套代码&#x1f525;课程设计、毕业设计、创新竞赛必备&#xff01;详细介绍全局规划(图…

计算机竞赛 基于深度学习的人脸专注度检测计算系统 - opencv python cnn

文章目录 1 前言2 相关技术2.1CNN简介2.2 人脸识别算法2.3专注检测原理2.4 OpenCV 3 功能介绍3.1人脸录入功能3.2 人脸识别3.3 人脸专注度检测3.4 识别记录 4 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 基于深度学习的人脸专注度…

苹果Mac系统如何优化流畅的运行?提高运行速度

Mac系统的稳定性和流畅性一直备受大家称赞&#xff0c;这也是大多数人选择Mac的原因&#xff0c;尽管如此&#xff0c;我们仍不时地对Mac进行优化、调整&#xff0c;以使其比以前更快、更流畅地运行。以下是小编分享给各位的Mac优化方法&#xff0c;记得保存哦~ 一、释放被过度…

Java 中数据结构HashSet的用法

Java HashSet HashSet 基于 HashMap 来实现的&#xff0c;是一个不允许有重复元素的集合。 HashSet 允许有 null 值。 HashSet 是无序的&#xff0c;即不会记录插入的顺序。 HashSet 不是线程安全的&#xff0c; 如果多个线程尝试同时修改 HashSet&#xff0c;则最终结果是…

React原理 - React Reconciliation-上

目录 扩展学习资料 React Reconciliation Stack Reconciler【15版本、栈协调】 Stack Reconciler-事务性 事务性带来的弊端&#xff1a; 扩展学习资料 名称 链接 备注 官方文档 Reconciliation – React 英文 stack reconciler Implementation Notes – React 英文…

spark支持深度学习批量推理

背景 在数据量较大的业务场景中&#xff0c;spark在数据处理、传统机器学习训练、 深度学习相关业务&#xff0c;能取得较明显的效率提升。 本篇围绕spark大数据背景下的推理&#xff0c;介绍一些优雅的使用方式。 spark适用场景 大数据量自定义方法处理、类sql处理传统机器…

环保环卫行业案例 | 燕千云助力高能环境搭建数智化IT服务管理体系及平台

当前环境卫生问题在全球已引起前所未有的关注&#xff0c;而促进健康又成为环境与发展所关注的核心问题。随着数字化时代的到来&#xff0c;环保环卫行业呈现出多个发展趋势&#xff0c;随着业务系统规模的不断扩大&#xff0c;信息系统的运维问题也日益突出&#xff0c;需要得…

『Swift社区赠书第 1 期』- 『循序渐进 Vue.js 3.x 前端开发实战』

文章目录 关于作者内容介绍评论区抽三位小伙伴送书活动时间&#xff1a;截止到 2023-08-24 20:00:00 获奖名单 ps. 文末送书&#xff0c;送书为 Swift社区 额外福利 《循序渐进 Vue.js 3.x 前端开发实战》本书包含 42 集视频教学&#xff0c;完整源代码 PPT 课件。 Vue.js 3…

睿思BI实现杜邦分析

杜邦分析法&#xff08;DuPont analysis&#xff09;是一种分析企业财务状况的方法&#xff0c;得名于美国杜邦公司。该方法可以应用于销售业绩分析。 睿思BI实现杜邦分析效果如下&#xff1a; 效果演示地址&#xff1a;https://www.ruisitech.com/rsbi-ultimate/#/dashboard/…

Zookeeper 入门

第 1 章 Zookeeper 入门 1.1概述 Zookeeper从设计模式角度来理解&#xff1a;是一个基于观察者模式设计的分布式服务管理框架&#xff0c;它负责存储和管理大家都关心的数据&#xff0c;然后接受观察者的注册&#xff0c;一旦这些数据的状态发生变化&#xff0c;Zookeeper就将…

Kubernetes技术--k8s核心技术Service服务

1.service概述 Service 是 Kubernetes 最核心概念,通过创建 Service,可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。 2.service存在的意义 -1:防止pod失联(服务发现) 我们先说一下什么叫pod失联。 -2:

肖sir __linux__面试题和考核05

面试题 1、查看linux中第11行到第20行的数据&#xff08;比如文档a 有30行&#xff09; 方法1&#xff1a;tail -n 11 mm |head -n10 n 表示从第10行开始&#xff0c;取前10行 方法2&#xff1a;head -n -10 mm| tail -n 10 表示从末尾第10行开始&#xff0c;最后10行 方法3&am…

金融风控数据分析-信用评分卡建模(附数据集下载地址)

本文引用自&#xff1a; 金融风控&#xff1a;信用评分卡建模流程 - 知乎 (zhihu.com) 在原文的基础上加上了一部分自己的理解&#xff0c;转载在CSDN上作为保留记录。 本文涉及到的数据集可直接从天池上面下载&#xff1a; Give Me Some Credit给我一些荣誉_数据集-阿里云…

OceanBase安全审计之传输加密

上一期我们讲了关于 OceanBase 安全审计的《身份鉴别》和《用户管理与访问控制》 两个部分&#xff0c;OceanBase 的安全机制介绍其支持传输加密&#xff0c;今天我们主要来实践一下如何配置传输加密以及验证是否真的加密。 作者&#xff1a;金长龙 爱可生测试工程师&#xff0…