STM32CubeMX series 07 - IIC communication (AT24C02, OLED screen)

====>>> Article summary (with code summary) <<<====

1. Preparations

1.1. Hardware used

STM32F103 Puzhong-Quasi-end-Z100, master STM32F103ZET6.

1.2. Introduction to IIC

IIC(Inter Integrated Circuit)

  1. Developed by PHILIPS company
  2. Requires two wires ( 数据线SDAand 时钟SCL)
  3. two-way transmission
  4. High-speed IIC up to 400kbps or more

Three types of signals (just look at it, the hardware IIC used here, and you don’t need to write it yourself)

  • Start signal: When SCL is high level, SDA jumps from high level to low level, and starts to transmit data;
  • End signal: When SCL is high level, SDA jumps from low level to high level, and the data transmission ends;
  • Response signal: After receiving 8bit data, the IC that receives data sends a specific low-level pulse to the IC that sends data, indicating that the data has been received. After the CPU sends a signal to the controlled unit, it waits for the controlled unit to send a response signal. After receiving the response signal, the CPU makes a judgment on whether to continue to transmit the signal according to the actual situation. If no response signal is received, it is judged that the controlled unit is faulty.

The start signal is required, and both the end signal and the response signal are optional.

1.2. Generate project

1.2.1. Create project selection master

insert image description here

1.2.2. System configuration

Configure the clock source
insert image description here
Configure the debug mode (you can check it if you need ST-Link download and debug)
insert image description here
Configure the clock tree (you can directly enter 72 in HCLK, and then press Enter to automatically configure)
insert image description here

1.2.3. Configuration project directory

insert image description here
insert image description here

1.2.4. IIC configuration

insert image description here
Then generate code.

2. Experiment of reading and writing EEPROM (AT24C02)

EEPROM (Electrically Erasable Programmable read only memory) refers to electrically erasable programmable read-only memory. It is a memory chip that does not lose data after power failure.

2.1. Introduction to AT24C02

2.1.1. AT24C02 Schematic

insert image description here

  • A0, A1, A2: hardware address pins
  • WP: Write protection pin, connected to high level read only, grounded to allow reading and writing
  • SCL and SDA: IIC bus

The 02 behind 24C02 means that 2Kbit data can be stored, and the storage capacity converted into bytes is 2*1024/8 = 256byte; 256 bytes are divided into 32 pages, each page has 8 bytes.

2.1.2. Device address

On page 9 of the reference manual of AT24C02, you can see
insert image description here
the address of the AT24C device in the figure below:

  • The first four digits are fixed 1010
  • A2, A1, and A0 are determined by the pin level. The schematics here are all grounded, which is 000 by default.
  • The last bit indicates whether the operation is a read operation or a write operation. The read address is 0xA1, and the write address is 0xA0.

in conclusion:

  • When writing 24C02, the slave address is 10100000 (0xA0)
  • When reading 24C02, the slave address is 10100001 (0xA1)

On-chip addressing:

Chip addressing can read/write any one of the internal 256 bytes, and its addressing range is 00~FF, a total of 256 addressing units.

2.2. Read and write timing

Write a byte As
insert image description here
can be seen from the timing diagram (the signal above is the signal of the MCU, and the signal below is the signal of the memory chip), the sequence of operations for writing a byte of data is:

  1. MCU first sends a start signal (START) to the chip
  2. After the first byte after the start letter, send the device address (DEVICE ADDRESS) to be written (note, because there may be multiple devices on the bus, different devices are selected according to the device address), and then send the write data command (0xA0), and then wait for the acknowledgment signal (ACK)
  3. The storage address of the sent data. There is a total of 256 bytes of storage space, the address is from 0x00~0xFF, which address you want to store, you can send it to which address
  4. Send the data to be stored, and the MCU will receive a response signal after the sending is completed
  5. After the data transmission is completed, the transmission end signal (STOP) stops the bus.

Read a byte As
insert image description here
can be seen from the timing diagram (the signal above is the signal of the MCU, and the signal below is the signal of the memory chip), the sequence of operations for reading a byte of data is:

  1. MCU first sends a start signal (START) to the chip
  2. The first byte after the start signal, send the device address to be read (DEVICE ADDRESS) (note, because there may be multiple devices on the bus, different devices are selected according to the device address), and then send the write data command ( 0xA0), and send the data address to be read (WORD ADDRESS), and then wait for the response signal (ACK)
  3. Send the start signal again (START)
  4. The first byte after the start signal, send the device address to be read (DEVICE ADDRESS), send the read data command (0xA1)
  5. At this time, 24C02 will automatically send data to MCU.
  6. The MCU sends an end signal (STOP) to stop the bus.

Writing a page of data
insert image description here
The timing diagram is similar to writing a single byte, except that the memory will send a response signal to the MCU after each byte is written, and then continue to send the next byte. After writing, the MCU will send a stop signal.

The 256 bytes are divided into 32 pages with 8 bytes per page . AT24C02 page writing only supports 8 bytes, so it needs to be written in 32 times. If 8 bytes are written continuously according to the above timing sequence, data will be repeatedly written to the page. (Of course, you can also write a byte to each of the 256 addresses...)


insert image description here
The timing diagram of continuous read data is similar to that of a single byte. After the memory sends each byte to the MCU, the MCU will send a response signal to the memory until the MCU sends a stop signal. And there is no 8-byte limit for reading data.

2.3. Code implementation

Although the timing looks complicated, don't worry, many of them have already been implemented.
In the generated project, open it , and you can see that the codes for polling, interrupt and DMA control methods stm32f1xx_hal.hhave been generated . We only look at polling, and the others are similar, but the application scenarios are different.
insert image description here

// 作为主机 发送数据
// 参数:iic接口、设备地址、发送的数据、数据长度、超时时间
HAL_I2C_Master_Transmit(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 作为主机 接收数据
// 参数:iic接口、设备地址、存储读取到的数据、数据长度、超时时间
HAL_I2C_Master_Receive(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 作为从机 发送数据
// 参数:iic接口、设备地址、发送的数据、数据长度、超时时间
HAL_I2C_Slave_Transmit(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 作为从机 接收数据
// 参数:iic接口、设备地址、存储读取到的数据、数据长度、超时时间
HAL_I2C_Slave_Receive(I2C_HandleTypeDef *hi2c, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 直接发送两个字节数据(就用于我们现在的情况,发送命令 + 发送数据)
// 参数:iic接口、设备地址、发送的数据1、发送的数据2、数据长度、超时时间
HAL_I2C_Mem_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 直接发送两个字节数据,并接受数据(就用于我们现在的情况,发送命令 + 接收数据)
// 参数:iic接口、设备地址、发送的数据1、发送的数据2、存储读取到的数据、数据长度、超时时间
HAL_I2C_Mem_Read(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint16_t MemAddress, uint16_t MemAddSize, uint8_t *pData, uint16_t Size, uint32_t Timeout);

// 查询设备是否就绪
HAL_I2C_IsDeviceReady(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint32_t Trials, uint32_t Timeout);

We can directly look at HAL_I2C_Mem_Write, HAL_I2C_Mem_Read, just enough to meet 指令 + 地址the situation where we need to send , and the address can be increased by itself during the process of sending or reading data, which is very convenient.

i2c.hDeclare the following code in

/* USER CODE BEGIN Prototypes */

#define    	AT24C02_ADDR_WRITE  		0xA0	// 写命令
#define    	AT24C02_ADDR_READ   		0xA1	// 读命令
uint8_t At24c02_Write_Byte(uint16_t addr, uint8_t* dat);
uint8_t At24c02_Read_Byte(uint16_t addr, uint8_t* read_buf);
uint8_t At24c02_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size);
uint8_t At24c02_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size);

/* USER CODE END Prototypes */

i2c.cAdd the following code in

/* USER CODE BEGIN 1 */

#include <string.h>

/**
 * @brief        AT24C02任意地址写一个字节数据
 * @param        addr —— 写数据的地址(0-255)
 * @param        dat  —— 存放写入数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Write_Byte(uint16_t addr, uint8_t* dat)
{
    
    
	HAL_StatusTypeDef result;
	result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, 1, 0xFFFF);
	HAL_Delay(5);	// 写一个字节,延迟一段时间,不能连续写
    return result;
}

/**
 * @brief        AT24C02任意地址读一个字节数据
 * @param        addr —— 读数据的地址(0-255)
 * @param        read_buf —— 存放读取数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Read_Byte(uint16_t addr, uint8_t* read_buf)
{
    
    
    return HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDR_READ, addr, I2C_MEMADD_SIZE_8BIT, read_buf, 1, 0xFFFF);
}

/**
 * @brief        AT24C02任意地址连续写多个字节数据
 * @param        addr —— 写数据的地址(0-255)
 * @param        dat  —— 存放写入数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Write_Amount_Byte(uint16_t addr, uint8_t* dat, uint16_t size)
{
    
    
    uint8_t i = 0;
    uint16_t cnt = 0;        // 写入字节计数
	HAL_StatusTypeDef result;	// 返回是否写入成功

    /* 对于起始地址,有两种情况,分别判断 */
    if(0 == addr % 8)
    {
    
    
        /* 起始地址刚好是页开始地址 */
        /* 对于写入的字节数,有两种情况,分别判断 */
        if(size <= 8)
        {
    
    
            // 写入的字节数不大于一页,直接写入
			result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, size, 0xFFFF);
            HAL_Delay(20);	// 写完八个字节(最多八个字节),延迟久一点
			return result;
        }
        else
        {
    
    
            // 写入的字节数大于一页,先将整页循环写入
            for(i = 0; i < size/8; i++)
            {
    
    
                HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], 8, 0xFFFF);
                // 一次写入了八个字节,延迟久一点
				HAL_Delay(20);	// 写完八个字节,延迟久一点
				addr += 8;
                cnt += 8;
            }
            // 将剩余的字节写入
			result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], size - cnt, 0xFFFF);
            HAL_Delay(20);	// 写完八个字节(最多八个字节),延迟久一点
			return result;
        }
    }
    else
    {
    
    
        /* 起始地址偏离页开始地址 */
        /* 对于写入的字节数,有两种情况,分别判断 */
        if(size <= (8 - addr%8))
        {
    
    
            /* 在该页可以写完 */
			result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, size, 0xFFFF);
			HAL_Delay(20);	// 写完八个字节(最多八个字节),延迟久一点
            return result;
        }
        else
        {
    
    
            /* 该页写不完 */
            // 先将该页写完
            cnt += 8 - addr%8;
            HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, dat, cnt, 0xFFFF);
            HAL_Delay(20);	// 写完八个字节(最多八个字节),延迟久一点
			addr += cnt;

            // 循环写整页数据
            for(i = 0;i < (size - cnt)/8; i++)
            {
    
    
                HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], 8, 0xFFFF);
                HAL_Delay(20);	// 写完八个字节,延迟久一点
				addr += 8;
                cnt += 8;
            }
            // 将剩下的字节写入
			result = HAL_I2C_Mem_Write(&hi2c1, AT24C02_ADDR_WRITE, addr, I2C_MEMADD_SIZE_8BIT, &dat[cnt], size - cnt, 0xFFFF);
            HAL_Delay(20);	// 写完八个字节(最多八个字节),延迟久一点
			return result;
        }            
    }
}

/**
 * @brief        AT24C02任意地址连续读多个字节数据
 * @param        addr —— 读数据的地址(0-255)
 * @param        dat  —— 存放读出数据的地址
 * @retval       成功 —— HAL_OK
*/
uint8_t At24c02_Read_Amount_Byte(uint16_t addr, uint8_t* recv_buf, uint16_t size)
{
    
    
    return HAL_I2C_Mem_Read(&hi2c1, AT24C02_ADDR_READ, addr, I2C_MEMADD_SIZE_8BIT, recv_buf, size, 0xFFFF);
}

/* USER CODE END 1 */

main.cAdd the following code in

int main(void)
{
    
    
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* Configure the system clock */
  SystemClock_Config();

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();

	// 单个字节 读写测试
	uint8_t simple_write_dat = 0xa5;	// 一个字节
	uint8_t simple_recv_buf = 0;

	if(HAL_OK == At24c02_Write_Byte(10, &simple_write_dat)){
    
    
		printf("Simple data write success \r\n");
	} else {
    
    
		printf("Simple data write fail \r\n");
	}
	
	HAL_Delay(50);      // 写一次和读一次之间需要短暂的延时
	
	if(HAL_OK == At24c02_Read_Byte(10, &simple_recv_buf)){
    
    
		printf("Simple data read success, recv_buf = 0x%02X \r\n", simple_recv_buf);
	} else {
    
    
		printf("Simple data read fail \r\n");
	}
	printf("--------------------- \r\n");
	// 单个字节读写 测试结束
	
	// 浮点数 读写测试
	union float_union{
    
    
		float float_write_dat;		// 浮点数占4个字节
		double double_write_dat;	// 双精度浮点数占8个字节
		uint8_t buf[8];				// 定义 8个字节 的空间
	};
	union float_union send_float_data;	// 用来发送
	union float_union rev_float_data;	// 用来接收
	
	// 先测试第一个 浮点数
	send_float_data.float_write_dat = 3.1415f;
	if(HAL_OK == At24c02_Write_Amount_Byte(20, send_float_data.buf, 4)){
    
    
		printf("Float data write success \r\n");
	} else {
    
    
		printf("Float data write fail \r\n");
	}
	HAL_Delay(50);
	if(HAL_OK == At24c02_Read_Amount_Byte(20, rev_float_data.buf, 4)){
    
    
		// 默认输出六位小数
		printf("Float data read success, recv_buf = %f \r\n", rev_float_data.float_write_dat);
	} else {
    
    
		printf("Float data read fail \r\n");
	}
	// 测试第二个 双精度浮点数
	send_float_data.double_write_dat = 3.1415f;
	if(HAL_OK == At24c02_Write_Amount_Byte(20, send_float_data.buf, 8)){
    
    
		printf("Double data write success \r\n");
	} else {
    
    
		printf("Double data write fail \r\n");
	}
	HAL_Delay(50);
	if(HAL_OK == At24c02_Read_Amount_Byte(20, rev_float_data.buf, 8)){
    
    
		// 最多15位小数
		printf("Double data read success, recv_buf = %.15f \r\n", rev_float_data.double_write_dat);
	} else {
    
    
		printf("Double data read fail \r\n");
	}
	printf("--------------------- \r\n");
	// 浮点数读写测试 测试结束
  
	// 连续数据读写测试
	uint8_t write_dat[22] = {
    
    0};		// 22个字节
	uint8_t recv_buf[22] = {
    
    0};
	
	printf("正在往数组中填充数据... \r\n");
	for(int i = 0; i < 22; i++){
    
    
		write_dat[i] = i;
		printf("%02X ", write_dat[i]);
	}
	printf("\r\n 数组中数据填充完毕... \r\n");
	
	if(HAL_OK == At24c02_Write_Amount_Byte(0, write_dat, 22)){
    
    
		printf("24c02 write success \r\n");
	} else {
    
    
		printf("24c02 write fail \r\n");
	}
	
	HAL_Delay(50);      // 写一次和读一次之间需要短暂的延时
	
	if(HAL_OK == At24c02_Read_Amount_Byte(0, recv_buf, 22)){
    
    
		printf("read success \r\n");
		for(int i = 0; i < 22; i++) {
    
    
			printf("0x%02X ", recv_buf[i]);
		}
	} else {
    
    
		printf("read fail\r\n");
	}
	// 连续数据读写 测试结束
  while (1)
  {
    
    
    /* USER CODE END WHILE */
    /* USER CODE BEGIN 3 */
  }

Because each sending or receiving can only be performed in units of one byte, there is no problem with integers of type uint8_t. But for floating-point numbers, etc., which occupy more than one byte, it can be done in the form of a union.
This method is applicable in many application scenarios, such as sending floating-point numbers through a serial port, which can also be done in this way.

Effect verification
Compile and burn
Use the serial port assistant to observe the phenomenon.
insert image description here

3. Realization of 0.96-inch OLED display

3.1. Introduction to OLED (SSD1306)

The four-line OLED uses IIC communication. This module is integrated SSD1306存储芯片.

You can see the figure below from the module data sheet. GDDRAM is a bit-mapped static RAM that holds the bit patterns to be displayed. The size of the RAM is 128 x 64 bits, and the RAM is divided into eight pages, from page 0 to page 7, for a monochrome 128x64 dot matrix display.
insert image description here
The storage size just corresponds to the resolution size of our screen (128*64). To put it simply, as long as the effective level is stored in the corresponding storage bit, the corresponding pixel can be lighted up.

3.2. Code implementation

The specific display method is similar to that of the previous chapter, just put the code here, just copy it and use it.
Because there is a lot of code here, we create another folder to put our code.

  1. In the project directory, create icodea folder to store our own code.
  2. Under icodethe folder, create OLEDa folder to store oled related codes.
  3. Under OLEDthe folder, create the following files: oled.c, oled.h, oledfont.h, bmp.h.

oled.c: source file
oled.h: header file
oledfont.h: font file
bmp.h: picture library file

insert image description here
Add the source file into it
insert image description here
Add the header file path into it
insert image description here

The code is as follows (I always feel that there is something wrong with what I have in hand, and I will write it when there is a reliable one)
oled.h


oled.c


oledfont.h


bmp.h


Guess you like

Origin blog.csdn.net/weixin_46253745/article/details/127834961