Experiment (4): I2C application: read and write serial FLASH experiment

Experiment purpose and task

Purpose:

1. Learn to use I2C;

2. Master the simulation and debugging of KEIL5.

Task:

1. Write the program according to the requirements, and write the principle notes;

2. Check the results of the program operation and analyze whether it is correct;

3. Complete the verification and commissioning of the built project.

Experimental requirements

This experiment uses the EEPROM reading and writing experiment to explain the use of STM32 I2C. In the experiment, the I2C peripheral of STM32 adopts the master mode, which is used as the main transmitter and the main receiver respectively, and the normal communication is ensured by querying the event.

Experimental content and steps

The SCL and SDA pins of the EEPROM chip (model: AT24C02) in this experiment board are connected to the corresponding I2C pins of the STM32, combined with pull-up resistors, constitute the I2C communication bus, and they interact through the I2C bus. The device address of the EEPROM chip has a total of 7 bits, of which the upper 4 bits are fixed as: 1010 b, and the lower 3 bits are determined by the level of the A0/A1/A2 signal line, see the figure below, the R/W in the figure is read and write Direction bit, independent of address.

According to our connection here, A0/A1/A2 are all 0, so the 7-bit device address of EEPROM is: 1010000b, which is 0x50. Since the address and the read/write direction are often combined to form an 8-digit number during I2C communication, and when the R/W bit is 0, it indicates the write direction, so the 7-bit address is added, and its value is "0xA0", often called This value is the "write address" of the I2C device; when the R/W bit is 1, it indicates the read direction, plus the 7-bit address, the value is "0xA1", which is often called the "read address".

There is also a WP pin in the EEPROM chip, which has a write protection function. When the pin level is high, writing data is prohibited. When the pin is low level, data can be written. We directly ground it and do not use write Protective function.

1. Software Design

① Experimental new file steps:

Run the Keil 5 development environment. Since there is no AT24C02 module, this experiment uses the MPU6050 attitude sensor based on the I2C protocol instead. In order to make the project more organized, we store the codes related to the underlying protocol of I2C communication and MPU6050 functions separately, so as to facilitate future porting. Create new MyI2C.c, MyI2C.h, MPU6050.c, MPU6050.h, MPU6050_Reg.h files.

② Programming points:

  • Configure the target pin used for communication as open-drain mode;

  • Enable the clock of the I2C peripheral;

  • Configure the mode, address, speed and other parameters of the I2C peripheral and enable the I2C peripheral;

  • Write basic I2C functions for sending and receiving bytes;

  • Write functions for reading and writing EEPROM storage content;

  • Write a test program to verify the read and write data.

2. Experimental procedure

(1) Run the Keil uVision5 development environment and create a project project.

(2) Add the main.c file to the project. Because the OLED display needs to be used, the OLED file written in the previous experiment is transplanted into the project, and then called in main.c, as shown in Figure 1.

Figure 1 Migration procedure

(3) Add the I2C underlying driver file to the project, so it is necessary to create the MyI2C.c file and write the I2C communication sequence, as shown in Figure 2.

Figure 2 Write MyI2C.c code

(4) Write the MyI2C.h program to facilitate the porting of project files in the future, so that the project engineering tools are portable, as shown in Figure 3.

Figure 3 MyI2C.h program

(5) Add the function file used by MPU6050 in the project, so you need to create the W25Q64_Ins.h file first to store the instruction code, as shown in Figure 4.

Figure 4 Write MPU6050 instruction code

(6) Create the MPU6050.c file and write the functions to be used, as shown in Figure 5.

Figure 5 Write MPU6050 function function

(7) Write the MPU6050.h program to facilitate the transplantation of project files in the future, so that the project engineering tools can be transplanted, as shown in Figure 6.

Figure 6 Write MPU6050.h program

(8) Write the main.c program, read the device ID, and read the accelerometer data in six directions, as shown in Figure 7.

Figure 7 main.c program

Run and debug successfully without errors and warnings.

3. Debugging verification and results

(1) Connect the development board to the computer, use jumpers to connect the MPU6050 and the MCU on the breadboard, and use STLINK to burn the program into the STM32, as shown in Figure 8.

Figure 8 Line connection

(2) After the program is burned, the experimental phenomenon is shown in Figure 9:

Figure 9 Experimental phenomenon

Experimental Code Analysis

(1)MyI2C.c:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"

/**
  * @brief  封装写SCL
  * @param  
  * @retval 
  */
void MyI2C_W_SCL(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_10, (BitAction)BitValue);
	Delay_us(10);
}

/**
  * @brief  封装写SDA
  * @param  
  * @retval 
  */
void MyI2C_W_SDA(uint8_t BitValue)
{
	GPIO_WriteBit(GPIOB, GPIO_Pin_11, (BitAction)BitValue);
	Delay_us(10);
}

/**
  * @brief  封装读SDA
  * @param  
  * @retval 
  */
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_11);
	Delay_us(10);
	return BitValue;
}

/**
  * @brief  初始化
  * @param  
  * @retval 
  */
void MyI2C_Init(void)
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;//开漏输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10 | GPIO_Pin_11;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB, GPIO_Pin_10 | GPIO_Pin_11);
}
/**
  * @brief  I2C起始条件
  * @param  
  * @retval 
  */
void MyI2C_Start(void)
{
	MyI2C_W_SDA(1);//拉高,释放
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);//拉低
	MyI2C_W_SCL(0);
}

void MyI2C_Stop(void)
{
	MyI2C_W_SDA(0);//拉低
	MyI2C_W_SCL(1);//释放SCL
	MyI2C_W_SDA(1);//释放SDA
}

/**
  * @brief  发送一个字节
  * @param  
  * @retval 
  */
void MyI2C_SendByte(uint8_t Byte)
{
	uint8_t i;
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SDA(Byte & (0x80 >> i));//从最高位开始取
		MyI2C_W_SCL(1);//释放SCL
		MyI2C_W_SCL(0);//拉低SCL
	}
}

/**
  * @brief  读一个字节
  * @param  
  * @retval 
  */
uint8_t MyI2C_ReceiveByte(void)
{
	uint8_t i, Byte = 0x00;
	MyI2C_W_SDA(1);
	for (i = 0; i < 8; i ++)
	{
		MyI2C_W_SCL(1);
		if (MyI2C_R_SDA()) {Byte |= (0x80 >> i);}
		MyI2C_W_SCL(0);
	}
	return Byte;
}

/**
  * @brief  发送应答
  * @param  
  * @retval 
  */
void MyI2C_SendAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}

/**
  * @brief  接受应答
  * @param  
  * @retval 
  */
uint8_t MyI2C_ReceiveAck(void)
{
	uint8_t AckBit;
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);
	return AckBit;
}

(2)MPU6050.c:

#include "stm32f10x.h"                  // Device header
#include "MyI2C.h"
#include "MPU6050_Reg.h"
#include "Delay.h"

#define MPU6050_ADDRESS			0xD0

/**
  * @brief  指定地址写
  * @param  
  * @retval 
  */
void MPU6050_WriteReg(uint8_t RegAddress, uint8_t Data)
{
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);//指定寄存器地址
	MyI2C_ReceiveAck();
	MyI2C_SendByte(Data);//指定写入数据
	MyI2C_ReceiveAck();
	MyI2C_Stop();
}

/**
  * @brief  指定地址读
  * @param  
  * @retval 
  */
uint8_t MPU6050_ReadReg(uint8_t RegAddress)
{
	uint8_t Data;
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS);
	MyI2C_ReceiveAck();
	MyI2C_SendByte(RegAddress);
	MyI2C_ReceiveAck();
	
	MyI2C_Start();
	MyI2C_SendByte(MPU6050_ADDRESS | 0x01);//发送 读 指令
	MyI2C_ReceiveAck();
	Data = MyI2C_ReceiveByte();//接受数据
	MyI2C_SendAck(1);//发送应答
	MyI2C_Stop();
	
	return Data;
}

/**
  * @brief  初始化
  * @param  
  * @retval 
  */
void MPU6050_Init(void)
{
	MyI2C_Init();
	//解除睡眠,选择陀螺仪时钟,6个轴不待机,采样分频为10,滤波参数最大,陀螺仪和加速度计选最大量程
	
	MPU6050_WriteReg(MPU6050_PWR_MGMT_1, 0x01);//电源管理寄存器1,选择陀螺仪时钟
	//Delay_ms(100);
	MPU6050_WriteReg(MPU6050_PWR_MGMT_2, 0x00);//电源管理寄存器2
	//Delay_ms(100);
	MPU6050_WriteReg(MPU6050_SMPLRT_DIV, 0x09);//采样率分频,10分频
	MPU6050_WriteReg(MPU6050_CONFIG, 0x06);//配置寄存器,0000 0110
	MPU6050_WriteReg(MPU6050_GYRO_CONFIG, 0x18);//陀螺仪配置寄存器, 0001 1000 
	MPU6050_WriteReg(MPU6050_ACCEL_CONFIG, 0x18);//加速度计配置寄存器
}

/**
  * @brief  
  * @param  
  * @retval 
  */
uint8_t MPU6050_ReadID(void)
{
	return MPU6050_ReadReg(MPU6050_WHO_AM_I);
}

/**
  * @brief  获取数据寄存器数据
  * @param  
  * @retval 
  */
void MPU6050_ReadData(int16_t *AccX, int16_t *AccY, int16_t *AccZ, 
					  int16_t *GyroX, int16_t *GyroY, int16_t *GyroZ)
{
	//加速度
	*AccX = (MPU6050_ReadReg(MPU6050_ACCEL_XOUT_H) << 8)| MPU6050_ReadReg(MPU6050_ACCEL_XOUT_L);
	
	*AccY = (MPU6050_ReadReg(MPU6050_ACCEL_YOUT_H) << 8)
			| MPU6050_ReadReg(MPU6050_ACCEL_YOUT_L);
	
	*AccZ = (MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_H) << 8)
			| MPU6050_ReadReg(MPU6050_ACCEL_ZOUT_L);
	
	*GyroX = (MPU6050_ReadReg(MPU6050_GYRO_XOUT_H) << 8)
			| MPU6050_ReadReg(MPU6050_GYRO_XOUT_L);
	
	*GyroY = (MPU6050_ReadReg(MPU6050_GYRO_YOUT_H) << 8)
			| MPU6050_ReadReg(MPU6050_GYRO_YOUT_L);
	
	*GyroZ = (MPU6050_ReadReg(MPU6050_GYRO_ZOUT_H) << 8)
			| MPU6050_ReadReg(MPU6050_GYRO_ZOUT_L);
}



(3) main function program:

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "MPU6050.h"

uint8_t ID;
int16_t AX, AY, AZ, GX, GY, GZ;

int main(void)
{
	OLED_Init();
	MPU6050_Init();
	
	Delay_us(1000);
	OLED_ShowString(1, 1, "ID:");
	ID = MPU6050_ReadID();
	OLED_ShowHexNum(1, 4, ID, 2);
	
	while (1)
	{
		MPU6050_ReadData(&AX, &AY, &AZ, &GX, &GY, &GZ);
		OLED_ShowSignedNum(2, 1, AX, 5);
		OLED_ShowSignedNum(3, 1, AY, 5);
		OLED_ShowSignedNum(4, 1, AZ, 5);
		OLED_ShowSignedNum(2, 9, GX, 5);
		OLED_ShowSignedNum(3, 9, GY, 5);
		OLED_ShowSignedNum(4, 9, GZ, 5);
	}
}

Experiment summary

Through this experiment, I have a deeper understanding of the I2C interface of STM32, and mastered the basic methods and skills of using I2C for inter-device communication. The following are some of my experiences:

Careful reading of official documentation and reference manuals is crucial to learning and understanding how a peripheral works. By digging into the documentation, I have a clearer understanding of the functions and registers of the I2C interface, and can better understand and apply related functions and parameters.

When writing code, the importance of comments cannot be ignored. Reasonable comments can make the code more readable, understandable, and easy for others to read and maintain. I have added detailed comments to the code, explaining each function and key operation to make the code more readable.

Debugging is a critical step in problem solving. During the experiment, I encountered some problems, such as communication failure, data error, etc. By carefully examining the code and debugging output, I was able to find and fix the problem. This process deepened my understanding of I2C communication and developed my ability to solve problems.

In experiments, timely recording and analysis of experimental results is crucial to understanding and mastering knowledge. I recorded the results of each experiment in detail and analyzed the correctness and stability of the data. These analyzes helped me deepen my understanding of I2C communication, and found some potential problems and room for improvement.

Overall, through this experiment, I have a deeper understanding of the I2C interface of STM32, and mastered the basic method of using I2C for inter-device communication. I believe that this knowledge and experience will have a positive impact on my future study and project development. I will continue to study and explore other peripheral interfaces of STM32 in order to improve my embedded system development ability.

Source code: Experiment 4 

Guess you like

Origin blog.csdn.net/qq_61228493/article/details/131115094