【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.mzph.cn/news/69397.shtml

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

相关文章

Android Studio 汉化

一、汉化&#xff1a; 查看版本号&#xff0c;查看Android Studio版本&#xff0c;根据版本下载对应的汉化包。例如我的是223。 下载汉化包&#xff1a; 中文语言包下载地址 找到对应的版本 回到Android Studio 1、进入设置 2、从磁盘安装插件 3、选择下载好的包点击OK 4、…

介绍OpenCV

OpenCV是一个开源计算机视觉库&#xff0c;可用于各种任务&#xff0c;如物体识别、人脸识别、运动跟踪、图像处理和视频处理等。它最初由英特尔公司开发&#xff0c;目前由跨学科开发人员社区维护和支持。OpenCV可以在多个平台上运行&#xff0c;包括Windows、Linux、Android和…

AJAX学习笔记6 JQuery对AJAX进行封装

AJAX学习笔记5同步与异步理解_biubiubiu0706的博客-CSDN博客 AJAX请求相关的代码都是类似的&#xff0c;有很多重复的代码&#xff0c;这些重复的代码能不能不写&#xff0c;能不能封装一个工具类。要发送ajax请求的话&#xff0c;就直接调用这个工具类中的相关函数即可。 用J…

深圳-海岸城购物中心数据分析

做数据分析的时候&#xff0c;如果要对商场进行分析&#xff0c;可以从这些数据纬度进行分析&#xff0c;如下图所示&#xff1a; 截图来源于数位观察&#xff1a;https://www.swguancha.com/

如何炒伦敦金

由于疫情的影响&#xff0c;目前世界上多个国家降低存款利率&#xff0c;以推动经济发展&#xff0c;由此也引发了比较严重的通胀问题&#xff0c;尤其是在俄乌冲突之后&#xff0c;国际油价不断上涨&#xff0c;加大了这种通货膨胀的影响。进行伦敦金投资是一种规避通胀的好方…

Python爬虫-某网酒店数据

前言 本文是该专栏的第5篇,后面会持续分享python爬虫案例干货,记得关注。 本文以某网的酒店数据为例,实现根据目标城市获取酒店数据。具体思路和方法跟着笔者直接往下看正文详细内容。(附带完整代码) 正文 地址:aHR0cHM6Ly93d3cuYnRoaG90ZWxzLmNvbS9saXN0L3NoYW5naGFp …

Python 中下划线详解(_、_xx、xx_、__xx、__xx__)

文章目录 1 概述2 演示2.1 _&#xff1a;不重要的变量2.2 _xx&#xff1a;声明私有&#xff0c;仅内部调用2.3 xx_&#xff1a;区分关键字2.4 __xx&#xff1a;声明私有&#xff0c;仅当前类中可用2.5 __xx__&#xff1a;内置函数 1 概述 2 演示 2.1 _&#xff1a;不重要的变…

jmeter setUp Thread Group

SetUp Thread Group 是一种特殊类型的线程组&#xff0c;它用于在主测试计划执行之前执行一些初始化任务。 SetUp Thread Group 通常用于以下几种情况&#xff1a; 用户登录&#xff1a;在模拟用户执行实际测试之前&#xff0c;模拟用户登录到系统以获取访问权限。 创建会话&a…

OpenWrt编译自己的应用程序

编译OpenWrt的应用程序可以参考OpenWrt内部其他应用程序的例程&#xff0c;来编写成自己的应用程序 一、OpenWrt源代码获取与编译 1.1、搭建环境 下载OpenWrt的官方源码&#xff1a; git clone https://github.com/openwrt/openwrt.git1.2、安装编译依赖项 sudo apt update…

Linux下的系统编程——进程(八)

前言&#xff1a; 程序是指储存在外部存储(如硬盘)的一个可执行文件, 而进程是指处于执行期间的程序, 进程包括 代码段(text section) 和 数据段(data section), 除了代码段和数据段外, 进程一般还包含打开的文件, 要处理的信号和CPU上下文等等.下面让我们开始对Linux进程的学…

使用【宝塔+docker】在云服务器上部署基于SpringBoot 和 Dubbo RPC 的项目:踩坑记录

待部署的项目包括&#xff1a;前端front&#xff0c;服务提供者backend&#xff0c;服务消费者gateway&#xff0c;注册中心nacos 服务器信息&#xff1a;腾讯云入门级服务器2核2G&#xff08;后续有对服务器进行升级&#xff09; 部署工具&#xff1a;前端使用宝塔部署&#x…

IDEA中debug调试模拟时显示不全(不显示null)的解决

IDEA中debug调试模拟时显示不全&#xff08;不显示null&#xff09;的解决 1、在IDEA中找到File&#xff08;文件&#xff09;->Settings&#xff08;设置&#xff09; 2、依次找到以下内容进行设置&#xff08;原版、汉化版&#xff09;&#xff1a; 打开Build, Executio…

2.2 Vector<T> 动态数组(模板语法)

C数据结构与算法 目录 本文前驱课程 1 C自学精简教程 目录(必读) 2 动态数组 Vector&#xff08;难度1&#xff09; 其中&#xff0c;2 是 1 中的一个作业。2 中详细讲解了动态数组实现的基本原理。 本文目标 1 学会写基本的C类模板语法&#xff1b; 2 为以后熟练使用 S…

【STL】模拟实现map和set {map和set的封装;核心结构;插入和查找;红黑树的迭代器;STL中的红黑树结构}

模拟实现map和set map和set是红黑树的两种不同封装形式&#xff0c;底层使用同一颗泛型结构的红黑树&#xff0c;只是存储类型不同。set是红黑树的K模型&#xff0c;存储key&#xff1b;map是红黑树的KV模型&#xff0c;存储pair<key,value>。 下面的代码和讲解着重体现…

【漏洞复现】网御ACM上网行为管理系统bottomframe.cgi接口存在SQL注入漏洞

漏洞描述 网御上网行为管理系统(简称Leadsec ACM)是网御为互联网接入用户在信息内容安全、网络应用管理、组织运营效率、网络资源利用、法律风险规避及网络投资回报等方面提供的全方位解决方案。网御上网行为管理系统存在SQL注入漏洞。 网御 ACM上网行为管理系统 bottomfram…

python实现pdf双页文档转png图片,png图片裁剪为左右两等分,再合并为新的pdf单页文档

一、问题引入 现有pdf双页文档如下&#xff1a; 现按照以下页码次序对pdf双页文档进行裁剪和拼接&#xff0c;其中有两点需要特别注意&#xff0c;一是封面页只裁剪中间部分&#xff0c;二是文档是从右往左的顺序排版的 二、python程序 import os import office from PIL …

Android中的view绘制流程,简单理解

简单理解 Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类&#xff0c;widgets是我们通常用于创建和用户交互的组件&#xff0c;比如按钮、文本输入框等等。子类ViewGroup是所有布局&…

【网络安全带你练爬虫-100练】第23练:文件内容的删除+写入

目录 0x00 前言&#xff1a; 0x02 解决&#xff1a; 0x00 前言&#xff1a; 本篇博文可能会有一点点的超级呆 0x02 解决&#xff1a; 你是不是也会想&#xff1a; 使用pyrhon将指定文件夹位置里面的1.txt中数据全部删除以后---->然后再将参数req_text的值写入到1.txt …

HDFS HA 高可用集群搭建详细图文教程

目录 一、高可用&#xff08;HA&#xff09;的背景知识 1.1 单点故障 1.2 如何解决单点故障 1.2.1 主备集群 1.2.2 Active、Standby 1.2.3 高可用 1.2.4 集群可用性评判标准&#xff08;x 个 9&#xff09; 1.3 HA 系统设计核心问题 1.3.1 脑裂问题 1.3.2 数据状…

Pytorch从零开始实战01

Pytorch从零开始实战——MNIST手写数字识别 文章目录 Pytorch从零开始实战——MNIST手写数字识别环境准备数据集模型选择模型训练可视化展示 环境准备 本系列基于Jupyter notebook&#xff0c;使用Python3.7.12&#xff0c;Pytorch1.7.0cu110&#xff0c;torchvision0.8.0&…