STM32-Jiangsu University of Science and Technology

New Construction

import startup file

Start is the startup file, which is the most basic file in STM32. It does not need to be modified, just added.
There are many types of startup files, which need to be selected according to the chip model:
Please add a picture description

If you choose the premium series, then use the boot file with VL, if you choose the normal version, choose the one without VL,
and then choose LD (lower density), MD, HD or XL according to the size of the Flash

stm32f10x.h is the STM32 peripheral register description file, which is used to describe which registers STM32 has and the system file with its corresponding address
is used to configure the clock

Since STM32 is composed of the core and the peripheral devices of the core, and the core register description ( CoreSupport) and the peripheral register description file ( DeviceSupport) are not together, it is necessary to add the "core register" description file.

The two CM3 are register descriptions of the core

import library function file

When the library is not introduced in the project, that is, there is no library function, the programming is performed by directly operating the register. Therefore, it is necessary to introduce the library function of STM32.

1. Introduce header files and source files

STM32F10x_StdPeriph_Driver is the STM32 standard peripheral driver folder, where
inc is the header file of the library function
and src is the source file of the library function, among them: misc is the library function of the kernel, and the others are peripheral library functions outside the kernel

Copy all the header files and source files under these two folders to the Library under the project folder.
Add the file to the project in keil5.

2. Import configuration files

STM32F10x_StdPeriph_Template is a sample project folder, where
the stm32f10x_conf.h (configuration) file is used to configure the inclusion relationship of the library function header file, as well as the function definition for parameter checking, which is required for all library functions.
The two files ending with it are used to store interrupt functions.

Copy these 3 files to the User of the project.
Add the file to the project in keil5.

4. Keil software configuration
Open the first header file stm32f10x.h introduced by the main function, and find it in the bottom part

#ifdef USE_STDPERIPH_DRIVER
  #include "stm32f10x_conf.h"
#endif

This is a conditional compilation, which means: if the USE_STDPERIPH_DRIVER statement is defined (using the standard peripheral driver), the statement of the imported header function is valid.

Therefore, copy the statement USE_STDPERIPH_DRIVERto the magic wand of keil5 software → C/C++ → Define:.

Then add Library and user to the header file path below.

New project completed

After the above two steps, the startup file and library function file are introduced and placed in Start and Library respectively. After the configuration is completed, the files in these two folders do not need to be modified (the permissions given by others are also read-only ).
Only the code under the User folder needs to be modified.

GPIOC refers to a GPIO port in a single-chip microcomputer (such as STM32, Arduino, etc.), where GPIO is the abbreviation of General Purpose Input Output (General Purpose Input Output), and C indicates that this port belongs to Group C.
In microcontrollers, GPIO can be used to control the input and output of digital signals. For example, GPIOC can be configured as an output mode, and its high and low levels can be controlled in the program to control the state of external devices or perform certain operations. In addition, GPIOC can also be configured as an input mode to read digital signals sent by external devices.

GPIO

Introduction

  • GPIO (General Purpose Input Output) general purpose input and output port
  • 8 kinds of input and output modes can be configured
  • Pin level: 0V~3.3V, some pins can tolerate 5V (for details, please refer to the pin definition of STM32, the one with FT is 5V tolerant)
  • In the output mode, the port can be controlled to output high and low levels to drive LEDs, control buzzers, simulate communication protocol output timing, etc.
  • In the input mode, the high and low level or voltage of the port can be read, which is used to read key input, external module level signal input (pressure sensitive), ADC voltage acquisition, analog communication protocol receiving data (MQTT), etc.

basic structure

  • In STM32, all GPIOs are mounted on the APB2 peripheral bus. The names of GPIO peripherals are named according to GPIOA, GPIOB, and GPIOC. Each GPIO peripheral has a total of 16 pins, numbered is 0 to 15.
  • Register: STM32 is a 32-bit microcontroller, so the registers are all 32-bit, and each bit of the register corresponds to a pin. But the port has only 16 bits, so only the lower 16 bits of the register have corresponding ports, and the upper 16 bits are not used.
  • Driver: It is used to increase the driving ability of the signal. The register is only responsible for storing data. If you want to perform operations such as lighting, you need a driver to increase the driving ability.
    Please add a picture description

GPIO bit structure

Data entry (read from right to left):

  • Protection diode: protect the internal circuit
    When the input voltage is higher than 3.3V, the upper protection diode is turned on, and the current generated by the input voltage will flow into VDD V_{DD}VDD, and will not flow into the internal circuit, which can avoid excessive voltage from causing damage to the internal circuit.
    When the input voltage is lower than 0V (compared to VSS V_{SS}VSS, so there can be a negative voltage), the lower diode will be turned on, and the current will flow from VSS to V_{SS}VSSIt flows out directly and does not draw current from the inside, which protects the internal circuit.
  • Pull-up resistors and pull-down resistors ( configuration parameters ): Provide a default input level for the input (the input pin is not connected to high and low levels), and it is
    turned on, and it is turned off at the bottom——pull-up input mode, at this time, the pin defaults to high The level (1)
    is turned on at the lower level, and the upper level is disconnected - pull-down input mode, at this time the pin defaults to low level (0)
    and both are disconnected - floating input mode, at this time the input level of the pin is very high Vulnerable to change(?)
  • Schmitt Trigger: Shaping the input voltage
    Given an upper threshold and a lower threshold, when the input voltage is higher than the upper threshold, the output is high. Of course, when the input voltage is lower than the lower threshold, the output is low. In this way, the noisy analog signal can be shaped into a stable signal, and the output jitter phenomenon caused by signal fluctuation can be effectively avoided.

Thus, the signal enters the input data register, and then the program reads the data in the register to obtain the input level of the port.
It can also be input to on-chip peripherals, which have analog input and alternate function input respectively.

Please add a picture description

Data output (from left to right):
There are two sources of data output: output data registers and on-chip peripherals.

  • Bit setting/clearing register: set 1 or 0 for a single bit.
    Since the output data register can only be operated as a whole, if you want to process a certain bit individually, you can only read it out, change it, and then write This can be done more efficiently by using a "bit set/clear register".
  • Output control ( configuration parameter ): Select whether the data source is a register or an on-chip peripheral.
  • MOS electronic switch ( configuration parameters ):
    • Push-pull output mode: both P-MOS and N-MOS are valid.
      When the data in the register is 1, the upper transistor is turned on, the lower transistor is disconnected, and the output is connected to VDD V_{DD}VDD, the output is high.
      When the data in the register is 0, the upper tube is disconnected, the lower tube is turned on, and the output is connected to VSS V_{SS}VSS, the output is low.
      In this mode, both high and low levels have strong drive capability, so this mode is also called forced output mode.
    • Open-drain output mode: N-MOS is valid, P-MOS is invalid (the upper tube is disconnected).
      When the data in the register is 1, the lower tube is disconnected, and the output is equivalent to disconnecting, high-impedance mode. Unknown
      When the data in the register is 0, the lower switch is turned on and the output is connected to VSS V_{SS}VSS, the output is low.
      In this mode, only the low level has the driving ability, so this mode can be used as the driving mode of the communication protocol, or used to output the level signal of 5V.
    • Off mode: both P-MOS and N-MOS are invalid (when the pin is configured as input mode)

8 working modes of GPIO

Please add a picture description

GPIO common library functions

initialization function

void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
During initialization, the second parameter is a structure:
GPIO_InitTypeDef GPIO_InitStruct;
3 attribute values ​​need to be set, namely GPIO_Mode(working mode), GPIO_Pin(pin selection), GPIO_Speed(output speed)

  1. GPIO_ModeThere are the following 8 parameters, corresponding to the 8 working modes of GPIO mentioned in the previous section
{
    
     GPIO_Mode_AIN = 0x0,  //模拟输入
  GPIO_Mode_IN_FLOATING = 0x04,  //浮空输入
  GPIO_Mode_IPD = 0x28, //下拉输入
  GPIO_Mode_IPU = 0x48, //上拉输入
  GPIO_Mode_Out_OD = 0x14, //开漏输出
  GPIO_Mode_Out_PP = 0x10, //推挽输出
  GPIO_Mode_AF_OD = 0x1C, //复用开漏
  GPIO_Mode_AF_PP = 0x18 //复用推挽
}GPIOMode_TypeDef;
  1. GPIO_PinThe macro definition is used for naming, and the variable name is directly copied as the attribute value. When using multiple pins, you can use or operation |(as can be seen from the definition below, different pins are 1 at different binary positions, or operation, which can be selected)
    GPIO_InitStruct.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_13
#define GPIO_Pin_0                 ((uint16_t)0x0001)  /*!< Pin 0 selected */
#define GPIO_Pin_1                 ((uint16_t)0x0002)  /*!< Pin 1 selected */
#define GPIO_Pin_2                 ((uint16_t)0x0004)  /*!< Pin 2 selected */
#define GPIO_Pin_3                 ((uint16_t)0x0008)  /*!< Pin 3 selected */
#define GPIO_Pin_4                 ((uint16_t)0x0010)  /*!< Pin 4 selected */
#define GPIO_Pin_5                 ((uint16_t)0x0020)  /*!< Pin 5 selected */
#define GPIO_Pin_6                 ((uint16_t)0x0040)  /*!< Pin 6 selected */
#define GPIO_Pin_7                 ((uint16_t)0x0080)  /*!< Pin 7 selected */
#define GPIO_Pin_8                 ((uint16_t)0x0100)  /*!< Pin 8 selected */
#define GPIO_Pin_9                 ((uint16_t)0x0200)  /*!< Pin 9 selected */
#define GPIO_Pin_10                ((uint16_t)0x0400)  /*!< Pin 10 selected */
#define GPIO_Pin_11                ((uint16_t)0x0800)  /*!< Pin 11 selected */
#define GPIO_Pin_12                ((uint16_t)0x1000)  /*!< Pin 12 selected */
#define GPIO_Pin_13                ((uint16_t)0x2000)  /*!< Pin 13 selected */
#define GPIO_Pin_14                ((uint16_t)0x4000)  /*!< Pin 14 selected */
#define GPIO_Pin_15                ((uint16_t)0x8000)  /*!< Pin 15 selected */
#define GPIO_Pin_All               ((uint16_t)0xFFFF)  /*!< All pins selected */
  1. GPIO_SpeedIn general, choose 50MHz
{
    
     
  GPIO_Speed_10MHz = 1,
  GPIO_Speed_2MHz, 
  GPIO_Speed_50MHz
}GPIOSpeed_TypeDef;

To sum up, the following is an initialization example:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE); // 初始化GPIOC的外设时钟,若使用的是PB或PA就改成GPIOB,GPIA

GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP; //工作模式设置为推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13; //初始化引脚PA13,一般在STM32板子上是自带的LED
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //输出速度选择为50MHz
GPIO_Init(GPIOC, &GPIO_InitStruct);

8 read and write functions

uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadInputData(GPIO_TypeDef* GPIOx);
uint8_t GPIO_ReadOutputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);
uint16_t GPIO_ReadOutputData(GPIO_TypeDef* GPIOx);
void GPIO_SetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin); //可把指定的端口设置为高电平
void GPIO_ResetBits(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin);//可把指定的端口设置为低电平
void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pin, BitAction BitVal);
void GPIO_Write(GPIO_TypeDef* GPIOx, uint16_t PortVal); // 可同时对16个端口i进行写入操作

gpio_writebit() is used to set the value of a GPIO pin, it can only be set to 0 or 1.
gpio_setbits() and gpio_resetbits() can set the state of multiple pins at the same time. gpio_setbits() can set the specified pins to 1, and gpio_resetbits() can set them to 0. Therefore, these two functions are more suitable than gpio_writebit() for controlling multiple GPIO pins at the same time (bitwise OR).

serial communication

Communication Interface

  • The purpose of communication: to transfer the data of one device to another device for communication between STM32 and other modules
  • Communication protocol: Formulate the rules of communication, and the two parties in communication will send and receive data according to the rules of the agreement.
    Please add a picture description
  • USART serial port: TX (Transmit Exchange) is the data sending pin; RX (Receive Exchange) is the data receiving pin.
  • I2C: SCL (Serial Clock) is the clock; SDA (Serial Data) is the data
  • SPI: SCLK (Serial Clock) is the clock; OSI (Master output Slave Input) is the host output data pin; MISO (Master Input Slave output) is the host input data pin; CS (Chip Select) is the chip select, used to specify the communication object
  • CAN: CAN_H, CAN_L are differential data pins, using two pins to represent a differential data
  • USB: DP (Data Positive D+), DM (Data Minus D-) is also a pair of differential data pins

USART serial protocol

The specific details are not recorded, watch the video.
Summary: The TX pin outputs the high and low levels that are flipped regularly, and the RX pin regularly reads the high and low levels of the pins. The data of each byte plus the start bit, stop bit, and optional parity bit are packaged into a data frame, output on the TX pin once, and the RX pin on the other end receives it in sequence, thus completing the transfer of byte data .

USART serial port peripherals

  • USART is a hardware peripheral integrated in STM32. It can automatically generate data frame timing according to a byte of data in the data register, send it from the TX pin, and automatically receive the data frame timing of the RX pin, and splice it into a byte of data. , stored in the data register
  • USART resources of STM32F103C8T6: USART1 (on APB2 bus), USART2 (on APB1 bus), USART3 (on APB1 bus)

Serial code

driver layer

In the section of introducing library functions here , we know that Librarythe folder stores the library functions of STM32, and the library functions abstract a layer at the register operation level, providing a rich function interface for hardware operations.
But in the main function, in many cases we only want a simple and clear function to perform a desired operation. For example, when using a serial port, when I write the main function, I just want to simply, I give the data, and it is responsible for the transmission. I don't want to worry about what clock I have to initialize, which port to use, and what messy settings to configure. Therefore, we need to build a bridge between the library function and the main function, use it to encapsulate the details of using the library function to operate the hardware, and provide a simple and easy-to-use new function interface to the main function.
Therefore, we abstract one more layer up at the library function level, which is the so-called driver. Here, we create a new folder Hardwareto store these hardware drivers (eg: key driver, LED driver), and the serial port driver to be written.

code writing

Serial.cCreate new files in the Hardware folder Serial.h. The Hardware folder is used to store hardware drivers (eg: button drivers, LED drivers), and the Library folder is to use library functions to implement our own logic. First, check the serial port
library What functions are included in the function, open it Library/stm32f10x_usart.c, and find that there are many functions, but when we write the driver, we only need to use the commonly used ones, such as:

The following is the specific code:
the following functions are the convenient and easy-to-use new function interfaces that we encapsulate the library functions and abstract upwards to the main function, which can be directly called in the main function.

USART serial port initialization

The initialization of the serial port is encapsulated as a function Serial_Init, with the following steps

  1. turn on the clock
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //开启USART1的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE); //开启GPIOA的时钟
  1. To initialize GPIO pins, you can refer to the section GPIO initialization function . Here, PA9 is (STM32引脚定义中,PA9不仅可以作为GPIO口,也可以复用作为 USART1_TX 即串口输出 来使用)configured as a multiplexed push-pull output, and PA10 is PA10复用为 USART1_RX 即串口接收输入 来使用configured as a pull-up input.
    (The input and output here are relative to the hardware where this interface is located. For example, here is STM32, USART1_TX is used as a serial port output, which means that data is output from STM32 via PA9 as a serial port TX; USART1_RX is used as a serial port to receive input , means that the data is input from the outside to the STM32 via PA10 as the serial port RX)
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; //上拉输入
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
  1. Initialize USART
USART_InitTypeDef USART_InitStructure;
USART_InitStructure.USART_BaudRate = 9600; //波特率:9600
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不使用流控
USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx; //串口模式:TX发送、接收双模式
USART_InitStructure.USART_Parity = USART_Parity_No; //校验位:无校验
USART_InitStructure.USART_StopBits = USART_StopBits_1; //停止位:1位
USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长:8位

USART_Init(USART1, &USART_InitStructure);
  1. Turn on the output of the RXNE flag bit to NVIC
    To use the interrupt to realize the receiving function of the serial port, it is necessary to connect the RXNE flag bit to the NVIC interrupt controller. Specifically: Once the RXNE flag is set to 1, it will apply for an interrupt to the NVIC, and then the data can be received in the interrupt function. The name of the interrupt function can startup_stm32f10x_md.sbe found in the startup file -- USART1_IRQHandler.
    Please add a picture description
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 
  1. Initialize NVIC, //TODO I don't know much about NVIC yet
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //分组???

NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; //中断通道:
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //开启
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //优先级:
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;

NVIC_Init(&NVIC_InitStructure);
  1. serial port enable
USART_Cmd(USART1, ENABLE);
Serial sending
  • Send byte Serial_SendByte
    Call this function to send a byte of data from the TX pin
void Serial_SendByte(uint8_t Byte)
{
    
    
	USART_SendData(USART1, Byte); \\直接调用库函数SendData来将Byte写入TDR
	while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET); \\等待标志位,避免数据覆盖
}

  • send arraySerial_SendArray
\*
 * Array : 数组指针
 * Length :数组长度
*\
void Serial_SendArray(uint8_t *Array, uint16_t Length)
{
    
    
	uint16_t i;
	for (i = 0; i < Length; i ++)
	{
    
    
		Serial_SendByte(Array[i]); //依次取出数组Array的每一项,利用Serial_SendByte进行发送
	}
}
  • send string functionSerial_SendString
void Serial_SendString(char *String)
{
    
    
	uint8_t i;
	for (i = 0; String[i] != '\0'; i ++) //字符串会以'\0'结束
	{
    
    
		Serial_SendByte(String[i]);
	}
}
  • Send number function Serial_SendNumber
    //TODO explains the reason.
    When sending a string, it is essentially sending a number, and finally the number in the form of a string is displayed on the computer.
void Serial_SendNumber(uint32_t Number, uint8_t Length)
{
    
    
	uint8_t i;
	for (i = 0; i < Length; i ++)
	{
    
    
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');
	}
}

  • printf redirection
    Under normal circumstances, the printf function is to send data to the screen, but for a single-chip microcomputer without a screen, it can only import the data flow to the computer through the serial port and display it in the serial port assistant. This is an indirect way to achieve Print.
    Therefore, it is necessary to rewrite the underlying function of printf fputc(why it is defined directly in the c file, and the printf function in the library is changed? There is no rewrite override operation similar to java here)
int fputc(int ch, FILE *f)
{
    
    
	Serial_SendByte(ch);
	return ch;
}
Serial port receiving

For serial port reception, there are two methods of query and interrupt .

  • Query: Constantly judge the RXNE flag bit in the main function. If it is set to 1, it means that the data has been received. Call again ReceiveDatato read the DR register.
while(1){
    
    
  if(USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){
    
     //判断RXNE标志位
    RXData = USART_ReceiveData(USART1); //接收一个字节,放在变量RXData中
    OLED_ShowHexNum(1, 1, RXData, 2); //若硬件连有OLED显示屏,可以显示在其上
  }
}
  • Interrupt: It is necessary to add the code to enable interrupt in the initialization. In addition, write an interrupt function USART1_IRQHandlerto receive data.
void USART1_IRQHandler(void)
{
    
    
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) //判断RXNE标志位
	{
    
    
		Serial_RxData = USART_ReceiveData(USART1); //Serial_RxData 为定义在该文件中的全局变量,用来暂存数据
		Serial_RxFlag = 1;  //Serial_RxFlag 为定义在该文件中的全局变量,用来暂存标志位
		USART_ClearITPendingBit(USART1, USART_IT_RXNE); //以防没读取DR时,不能自动清除标志位,此处咱们手动清除标志位
	}
}

In order to complete the upward packaging, two functions are provided here, respectively for the main function to judge whether the reception is complete, and to obtain the Serial_RxData data temporarily stored in the driver file

uint8_t Serial_GetRxFlag(void)
{
    
    
	if (Serial_RxFlag == 1)
	{
    
    
		Serial_RxFlag = 0;
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)
{
    
    
	return Serial_RxData;
}
Serial send and receive main function
#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "OLED.h"
#include "Serial.h"

uint8_t RxData;

int main(void)
{
    
    
	OLED_Init();
	OLED_ShowString(1, 1, "RxData:");
	
	Serial_Init();

	//串口发送
	Serial_SendByte(0x41);

	uint8_t MyArray[] = {
    
    0x42, 0x43, 0x44, 0x45};
	Serial_SendArray(MyArray, 4);

	Serial_SendString("\r\nNum1=");
	
	Serial_SendNumber(111, 3);
	
	printf("\r\nNum2=%d", 222);
	
	char String[100];
	sprintf(String, "\r\nNum3=%d", 333);
	Serial_SendString(String);
	
	Serial_Printf("\r\nNum4=%d", 444);
	Serial_Printf("\r\n");

	while (1)
	{
    
     
    //串口接收
		if (Serial_GetRxFlag() == 1) //判断标志位
		{
    
    
			RxData = Serial_GetRxData(); //接收一个字节的数据
			Serial_SendByte(RxData); //回传显示
			OLED_ShowHexNum(1, 8, RxData, 2);
		}
	}
}
Serial data packet sending and receiving
HEX packet

Different packets are divided by specifying dedicated flag data at the header and tail of the packet.
Question: What should I do when the payload data is duplicated with the header and tail of the packet?

  1. Limit the range of payload data so that the header and tail of the packet are not in the data range
  2. Use a fixed-length data packet, so that you can use the header and tail for data alignment
    Data alignment: When receiving the data at the payload data position, it will not be judged as the header and tail. But when receiving the data at the header and tail of the packet, it will judge whether it is indeed the header and the tail of the packet.
  3. Increase the number of packet headers and tails to make it appear that the payload data cannot appear

Advantages: The transmission is the most direct, the data analysis is simple, and it is suitable for the module to send the original data. For example, gyroscopes using serial communication, temperature and humidity sensors
Disadvantages: Insufficient flexibility, easy to repeat the load with the header and tail

text packet

In the text data packet, each byte has undergone a layer of encoding and decoding.
Advantages: The data is intuitive and easy to understand, and it is suitable for inputting instructions in the occasion of human-computer interaction. For example, the AT command used by the Bluetooth module, the G code commonly used by CNC and 3D printers.
Disadvantages: low parsing efficiency

the code
  1. Send HEX data packet Serial_SendPacket
    Call this function, and the 4 data of TxPacket will be sent out automatically with the header and tail. (The global variable Serial_TxPacket defined in Serial.c, if you want to use it in the main function. You can use the get and set functions to pass pointers, or directly declare it in the file, because in many cases embedded programming is not so high-level Serial.hdesignextern requirements, so many uses of global variables and this kind of way that is not conducive to software engineering design philosophy)
void Serial_SendPacket(void)
{
    
    
	Serial_SendByte(0xFF);
	Serial_SendArray(Serial_TxPacket, 4);
	Serial_SendByte(0xFE);
}
  1. Receiving HEX data packets USART1_IRQHandler
    In the receiving interrupt function, the state machine is used to execute the receiving logic. Receive a packet and store it in Serial_RxPacketthe receive array.
    ( staticStatic variables are similar to global variables, and will only be initialized when the function is entered for the first time. After the function exits, the data is still valid, but unlike global variables, static variables can only be used in this function.)
uint8_t Serial_RxPacket[4]; // 接收缓冲区
uint8_t Serial_RxFlag; //接收标志位

void USART1_IRQHandler(void)
{
    
    
	static uint8_t RxState = 0; //使用静态变量作为状态标志
	static uint8_t pRxPacket = 0; //指示接收到哪一个了
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
    
    
		uint8_t RxData = USART_ReceiveData(USART1);
		
		if (RxState == 0)
		{
    
    
			if (RxData == 0xFF)
			{
    
    
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if (RxState == 1)
		{
    
    
			Serial_RxPacket[pRxPacket] = RxData;
			pRxPacket ++;
			if (pRxPacket >= 4)
			{
    
    
				RxState = 2;
			}
		}
		else if (RxState == 2)
		{
    
    
			if (RxData == 0xFE)
			{
    
    
				RxState = 0;
				Serial_RxFlag = 1; //接收完成
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}
  1. To send a text packet
    you can use Serial_SendStringor重定向printf

  2. Receive text data packets
    Here, in order to prevent the mismatch between the main function and the hardware processing speed, resulting in data packet misalignment , it is necessary to Serial.hdeclare extern uint8_t Serial_RxFlag;it, so that both the main function and the main function Serial.ccan read and write the variable, so that the main function can complete the processing. After the data packet, Serial.cthe next data packet is received.
    In this way, in the main function, if (Serial_RxFlag == 1)it means that the data packet is received, and the main function starts processing; after the processing is completed, the receiving part Serial_RxFlag = 0;is notified Serial.cthat the main function has finished processing, and the next data packet can be received.

char Serial_RxPacket[100]; //接收缓冲区

void USART1_IRQHandler(void)
{
    
    
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
	{
    
    
		uint8_t RxData = USART_ReceiveData(USART1);
		
		if (RxState == 0)
		{
    
    
			if (RxData == '@' && Serial_RxFlag == 0) // 遇到包头且主函数处理完毕上一个数据包
			{
    
    
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if (RxState == 1)
		{
    
    
			if (RxData == '\r') //接收时遇到第一个包尾'\r'
			{
    
    
				RxState = 2;
			}
			else
			{
    
    
				Serial_RxPacket[pRxPacket] = RxData;
				pRxPacket ++;
			}
		}
		else if (RxState == 2)
		{
    
    
			if (RxData == '\n')
			{
    
    
				RxState = 0;
				Serial_RxPacket[pRxPacket] = '\0'; // 给字符串加入结束字符'\0'
				Serial_RxFlag = 1;
			}
		}
		
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);
	}
}

Guess you like

Origin blog.csdn.net/qq_41168765/article/details/130568947