【STM32】Initial use of IIC

Introduction to IIC

physical layer

Insert image description here

Connect multiple devices

It is a bus that supports devices. "Bus" refers to a signal line shared by multiple devices. In an I2C communication bus, multiple I2C communication devices can be connected, supporting multiple communication masters and multiple communication slaves.

two wires

An I2C bus uses only two bus lines, one 双向串行数据线(SDA), 一条串行时钟线(SCL). The data line is used to represent data, and the clock line is used to synchronize data transmission and reception.

Device address

Each device connected to the bus has an independent address, and the host can use this address to access different devices.

Pull-up resistors and high-impedance states

The bus is connected to the power supply through a pull-up resistor. When the I2C device is idle, it will output a high-impedance state. When all devices are idle and all output a high-impedance state, the pull-up resistor will pull the bus to a high level.

arbitration

When multiple hosts use the bus at the same time, in order to prevent data conflicts, arbitration is used to determine which device occupies the bus.

Three speed modes

It has three transmission modes: the standard mode transmission rate is 100kbit/s, the fast mode is 400kbit/s, and the high-speed mode can reach 3.4Mbit/s. However, most I2C devices currently do not support high-speed mode.

Maximum devices limit

The number of ICs connected to the same bus is limited by the bus's maximum capacitance of 400pF.

Protocol layer

The I2C protocol defines communication start and stop signals, data validity, response, arbitration, clock synchronization and address broadcast.

I2C basic reading and writing process

Insert image description here
The master writes data to the slave.
Insert image description here
The master reads data from the slave.

Insert image description here
I2C communication composite format

Insert image description hereData is transferred from the master to the slave
Insert image description hereData is transferred from the slave to the master

S: Transmission start signal
R/W: Transmission direction selection bit, 1 for reading, 0 for writing
A/ A: Response (ACK) or non-response (NACK) signal
Insert image description hereto stop transmission signal

1.S is the start signal, generated by the host. All devices mounted on the bus will receive it and prepare to receive the next data from the host.
2. The device listens to the address information and selects the slave: After the start signal, the host will broadcast the slave Address, on the I2C bus, the address of each device is unique. When the address broadcast by the host is the same as the address of a device, the device is selected. Devices that are not selected will ignore subsequent data signals. Depending on the I2C protocol, this slave address can be 7 or 10 bits.
3. Transmission direction: host broadcast transmission direction, 0 is host to slave (host writes to slave), 1 is slave to host (slave writes to host) 4. Slave response: reply ack and nack, only
receive After receiving the ack, the host continues to send or receive data.

write data

Insert image description here
The master first sends a start signal on the IIC bus, then all slaves on the bus will wait to receive data sent by the master. The host then sends 8-bit data consisting of the slave's ground + 0 (write operation). After receiving the 8-bit data, all slaves will check whether it is the address of their own device. If it is the address of their own device, then the slave will send Reply signal. Only after the master receives a response signal on the bus can it continue to send data to the slave. Note: The data signals transmitted on the IIC bus are broad, including both address signals and real data signals.

Read data

Insert image description here
If the configured direction transmission bit is the "read data" direction, that is, the situation in the second picture, after broadcasting the address and receiving the response signal, the slave begins to return data (DATA) to the host, and the data packet size is also 8 bits. Every time the slave sends a piece of data, it will wait for the response signal (ACK) from the master. Repeating this process, it can return N pieces of data. There is no size limit for this N. When the host wants to stop receiving data, it returns a non-acknowledge signal (NACK) to the slave, and the slave automatically stops data transmission.

signal level

start stop signal

Insert image description here
When the SCL line is high and the SDA line switches from high to low, this condition indicates the start of communication. When SCL is high level, the SDA line switches from low level to high level, indicating the stop of communication. Start and stop signals are generally generated by the host.

Data validity SCL high

Insert image description here

When SCL is high level, the data represented by SDA is valid, and when SCL is low level, the data is converted.

Address and data direction

Each device on the I2C bus has its own independent address. When the host initiates communication, it sends the device address (SLAVE_ADDRESS) through the SDA signal line to find the slave. The I2C protocol stipulates that the device address can be 7 bits or 10 bits. In practice, 7-bit addresses are widely used. A data bit immediately following the device address is used to indicate the direction of data transfer.
When reading data direction, the host will release control of the SDA signal line, the slave controls the SDA signal line, and the host receives the signal. When writing data direction, SDA is controlled by the host and the slave receives the signal.
Insert image description here
Big endian (highly significant bits exist in low addresses), high bits of the waveform go first

ACK NACK

The master station releases the SDA control right after sending the start signal, address, read and write signals, and the slave station starts to automatically control the SDA signal to send ACK NACK

overall control logic

The overall control logic is responsible for coordinating the entire I2C peripheral. The working mode of the control logic changes according to the parameters of the "control register (CR1/CR2)" we configure. When the peripheral is working, the control logic will modify the "status register (SR1 and SR2)" according to the working status of the peripheral. We only need to read the register bits related to these registers to understand the working status of I2C. In addition, the control logic is also responsible for controlling the generation of I2C interrupt signals, DMA requests and various I2C communication signals (start, stop, response signals, etc.) according to requirements.

IIC uses reading and writing EEPROM as an example

Main points

(1) Configure the target pin used for communication to open-drain mode;
(2) Enable the clock of the I2C peripheral;
(3) Configure the mode, address, rate and other parameters of the I2C peripheral and enable the I2C peripheral;
(4) ) Write basic I2C functions for sending and receiving bytes;
(5) Write functions for reading and writing EEPROM storage contents;

Hardware IIC

Configure IIC multiplexed 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. Configure IIC mode

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 As the host, send data

That is, when serving as the host of I2C communication, the process of sending data to the outside

Take writing an E2ROM as an example
Insert image description here
(1) Control the generation of the start signal (S). When the start signal occurs, it generates the event "EV5" and sets the "SB" bit of the SR1 register to 1, indicating that the start signal has been sent. ;

(2) Then send the device address and wait for the response signal. If the slave responds, events "EV6" and "EV8" will be generated. At this time, the "ADDR" bit and "TXE" bit of the SR1 register are set to 1, and ADDR is 1 means the address has been sent, TXE is 1 means the data register is empty;

(3) After the above steps are executed normally and the ADDR bit is cleared, we write the data to be sent to the I2C "data register DR". At this time, the TXE bit will be reset to 0, indicating that the data register is not empty and the I2C peripheral After one bit of data is sent out through the SDA signal line, an "EV8" event will occur, that is, the TXE bit is set to 1. Repeat this process to send multiple bytes of data;

(4) After we finish sending the data, the control I2C device generates a stop signal§. At this time, the EV8_2 event will be generated. The TXE bit and BTF bit of SR1 are both set to 1, indicating the end of the communication.

If we enable the I2C interrupt, when all the above events occur, an I2C interrupt signal will be generated, and the same interrupt service function will be entered. After reaching the I2C interrupt service routine, the register bits will be checked to determine which event it is.

IIC STM32 firmware library function link: IIC STM32 firmware library function link

1.1.1Send single byte data

Insert image description here
The single-byte timing of AT24C02 stipulates that when writing data to it, the first byte is the memory address, and the second byte is the data content to be written. After sending the device address, you need to send the internal storage address of eeprom and the data that needs to be stored.

#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 Sending multi-byte data

Wait for the storage device to be ready

The main implementation of this function is to send its device address to EEPROM and detect the response of EEPROM. If the EEPROM returns a response signal after receiving the address, it means that the EEPROM is ready and can start the next communication. The detection response in the function is achieved by reading the ADDR bit and AF bit of the SR1 register of STM32. When the I2C device responds to the address, the ADDR will be set to 1. If the response fails, the AF bit will be set to 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);
}
Circular single byte transmission
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 page writing

EEPROM defines a page writing sequence. Just tell EEPROM the first memory address address1, and the subsequent data will
be written to address2, address3... in order. This can save communication time and speed up the communication.
Insert image description here
According to the page writing timing, the first data is interpreted as the memory address address1 to be written, and n pieces of data can be sent continuously, and these data will be written to the memory in sequence. Among them, the chip page write timing of the AT24C02 model can send up to 8 data at a time (i.e. n = 8). This value is also called the page size. Some types of chips can transmit up to 16 data per page write timing.

However, this method is still relatively slow and limited to the page size.

//在 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 page writing plus

Using the page writing method of EEPROM, the previous "multi-byte writing" function can be improved to speed up the transmission speed.

 // AT24C01/02 每页有 8 个字节
 #define I2C_PageSize 8
 
 void 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 As the host, receive data

That is, when serving as the host side of I2C communication, the process of receiving data from the outside
Insert image description here
(1) is the same as the main sending process. The start signal (S) is generated by the host side. After the start signal is controlled, it generates the event "EV5". And will set the "SB" bit of the SR1 register to 1, indicating that the start signal has been sent;
(2) Then send the device address and wait for the response signal. If there is a slave response, the event "EV6" will be generated. At this time, the " The ADDR" bit is set to 1, indicating that the address has been sent.
(3) After receiving the address, the slave end starts sending data to the host end. When the host receives these data, an "EV7" event will be generated. RXNE in the SR1 register is set to 1, indicating that the receiving data register is not empty. After we read this register, we can clear the data register to receive the next data. At this time we can control I2C to send a response signal (ACK) or a non-response signal (NACK). If it responds, repeat the above steps to receive data. If it does not respond, stop transmission; (4) After
sending a non-response signal, a stop signal P is generated. , end the transmission.

 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 As a slave, receive data

Insert image description here

1.4 As a slave, receive data

Insert image description here

GPIO analog IIC

I used this method to read an external RTC module before. Please refer to the routine of punctual atoms, so I won’t go into details here. Or maybe I’ll post the routine later if I have a chance.

1. Configure GPIO

2. Write microsecond soft delay

3. Write each function according to timing

DMA and IIC

DMA can be used to transfer data to be transmitted or received in the DR of the IIC. DMA requests (when enabled) are only used for data transmission. When the data register becomes empty during transmission or the data register becomes full during reception, a DMA request is generated. The DMA request must be responded before the current byte transfer is completed . When the data transfer amount set for the corresponding DMA channel has been completed, the DMA controller sends the transfer end signal ETO to the I2C interface and generates a transfer completion interrupt when the interrupt is enabled.

Main transmitter: In the EOT interrupt service routine, DMA requests need to be disabled, and then the stop condition is set after waiting for the BTF event.

Main receiver: When the number of data to be received is greater than or equal to 2, the DMA controller sends a hardware signal EOT_1, which corresponds to DMA transmission (number of bytes - 1). If the LAST bit is set in the I2C_CR2 register, the hardware will automatically send a NACK after sending the next byte after EOT_1. With interrupts enabled, the user can generate a stop condition in the interrupt service routine upon completion of the DMA transfer.

This means that when the DMA generates the EOT flag, (if the EOT-related interrupt is enabled, enter the interrupt program, and if it is not enabled, perform software query for subsequent processing) close the DMA request, then wait for the BTF event, and then perform the STOP operation. The BTF event here is an event that determines whether the data byte transmission is completed during the I2C data sending and receiving process .
IIC communication process flag bit

Configure DMA

Configure DMA

Enable DMA in the IIC configuration

DMA mode can be activated by setting the DMAEN bit in the I2C_CR2 register. As long as the TxE bit is set, data will be loaded into the I2C_DR register from the preset storage area by DMA. To allocate a DMA channel to I2C, the following steps must be performed (x is the channel number).

Send using DMA

  1. Set the I2C_DR register address in the DMA_CPARx register. Data will be transferred from memory to this address after each TxE event
    .

  2. Set the memory address in the DMA_CMARx register. Data is transferred from this memory area to I2C_DR after each TxE event
    .

  3. Set the required number of bytes to transfer in the DMA_CNDTRx register. After each TxE event, this value will be decremented.

  4. Configure the channel priority using the PL[0:1] bits in the DMA_CCRx register.

  5. Set the DIR bit in the DMA_CCRx register.

  6. Depending on the application requirements, it can be configured to issue an interrupt request when half or all of the entire transfer is completed.

  7. The channel is activated by setting the EN bit on the DMA_CCTx register.

When the number of data transfers set in the DMA controller has been completed, the DMA controller sends an EOT/EOT_1 signal of the end of the transfer to the I2C interface. When interrupts are enabled, a DMA interrupt will be generated.
If using DMA to send, do not set the ITBUFEN bit of the I2C_CR2 register.

Use DMA to receive

The DMA receive mode can be activated by setting the DMAEN bit in the I2C_CR2 register. Every time a data byte is received, the DMA will transfer the data of the I2C_DR register to the set storage area (refer to DMA description). To set the DMA channel for I2C reception, the following steps must be performed (x is the channel number):

  1. Set the address of the I2C_DR register in the DMA_CPARx register.
    Data will be transferred from this address to memory after each RxNE event .
  2. Set the memory area address in the DMA_CMARx register. Data will be transferred from the I2C_DR register to this memory area after each RxNE event
    .
  3. Set the desired number of transfer bytes in the DMA_CNDTRx registers. This value will be decremented after each RxNE event
    .
  4. Configure the channel priority with PL[0:1] in the DMA_CCRx register.
  5. Clear the DIR bit in the DMA_CCRx register, and according to the application requirements, an
    interrupt request can be set when the data transfer is half or fully completed.
  6. Setting the EN bit in the DMA_CCRx register activates the channel.

When the number of data transfers set in the DMA controller has been completed, the DMA controller sends an end-of-transmission
EOT/EOT_1 signal to the I2C interface. With interrupts enabled, a DMA interrupt will be generated.
Note: If using DMA for reception, do not set the ITBUFEN bit of the I2C_CR2 register

Guess you like

Origin blog.csdn.net/apythonlearner/article/details/132543977