stm32 serial port custom protocol receives a string of hexadecimal data (convert two bytes into decimal data) + some basic knowledge of serial port

Posted by soloslinger on Tue, 04 Jan 2022 22:58:52 +0100

1, Basic storage unit

  • bit:

    A digit in a binary number, which can be 0 or 1. It is the smallest unit of data in a computer.

  • Byte s:

    The basic unit of data in a computer. Every 8 bits form a byte. All kinds of information need at least one byte to be stored and processed in the computer.

    For example, an ASCII code is represented by one byte and a Chinese character is represented by two bytes.

  • Word:

    Two bytes are called a word. The storage unit of Chinese characters is one word.

1 byte = 8-bit data
A Chinese character is a word, that is, two bytes, 16 bit data

2, Communication protocol

Communication mode - block diagram - reference link

(1) Parallel communication and serial communication

  • Parallel communication

    8-bit data is transmitted in parallel. 8 lines are required to transmit an 8-bit data.

    For example: SDIO, FSMC(16 bit) – all data bits are transmitted at the same time

  • serial communication

    8-bit data can be transmitted bit by bit with only one cable.

    For example, USART, IIC and SPI are all serial methods - when sending data, they send data bit by bit

  • Characteristics of serial communication and parallel communication

(2) Three working modes

  • full duplex

    There are two data lines, one for receiving data and the other for transmitting data. They do not interfere with each other and can send and receive data at the same time.

    For example: usart (half duplex or full duplex communication), SPI (half duplex or full duplex communication)

  • Half duplex

    There are two data lines, but you can't send data at the same time. You can send and receive data time-sharing

  • Simplex

    Only one data line, only one-way communication (only one direction)

    Example: IIC

000 - definition, difference and application of simplex, half duplex and full duplex of serial communication-

(3) Send and receive data synchronously or asynchronously

  • Synchronous communication

    In the data synchronization mode, the clock signals of the two devices are the same (those with clock signals are synchronized).

    In order to ensure the accuracy of data transmission during data transmission:
    (1) When the clock is at high level, the data is valid
    (2) The clock signal is invalid when the data is at low level
    (3) High requirements for clock (if the clock has spikes or clutter, the data transmission is inaccurate)

    For example: SPI, IIC communication interface.

  • asynchronous communication

    No clock signal: in order to ensure the accuracy of data transmission, some auxiliary identifiers are added

    For example: UART (universal asynchronous transceiver), single bus.

  • Comparison of synchronous communication and asynchronous communication

    (1) In synchronous communication, most of the content transmitted by data signal is effective data.
    (2) In asynchronous communication, the transmitted data will contain various identifiers of frames.
    (3) Therefore, the efficiency of synchronous communication is better, but the allowable error for clock is small, and the allowable error for clock is large for asynchronous communication.

(4) Communication rate

  • Bit rate: binary number transmitted per second unit: bit/s

    IIC,SPI (synchronous communication, one data is transmitted under one clock and controlled by the clock)

  • Baud rate: number of symbols transmitted per second (serial port)

    A binary bit represents a symbol (in special cases)

3, Serial communication protocol

The physical layer dictates whether we communicate with our mouth or limbs
The protocol layer stipulates whether we communicate in Chinese or English

(1) RS232

1. RS232 and TTL are the difference in level

TTL level is directly from the single chip microcomputer (or chip): the high level is represented by 3.3V or 5v, and the low level is represented by 0
In RS232, 1 is represented by - 15V and 0 by + 15V. The logic is just the opposite, and the gap between low level and high level is very large


(2) USB to serial port (TTL standard)

(3) Serial port to serial port (TTL - > TTL)

(4) Basic composition of serial data packet

  • Odd check

  • Parity check

4, Register

(1) Status register: USART_SR

  • TXE - transmit data register empty

    When the data in the TDR register is transferred to the shift register by hardware, the TXE bit is set by hardware.
    If USART_ If TXEIE in CR1 register is 1, an interrupt is generated. For USART_ Clear this bit for Dr write operation.

    0 - the data has not been transferred to the shift register
    1 - the data has been transferred to the shift register

  • TC send complete

    When the transmission of a frame containing data is completed and TXE=1, the hardware will set TC position '1'.
    If USART_ If tcie in CR1 = 1, an interrupt is generated. This bit is cleared by the software sequence (read USART_SR and then write USART_DR). The TC bit can also be cleared by writing '0', which is recommended only in multi cache communication.

    0 - sending has not been completed
    1 - sending completed

  • RXNE - read data register is not empty

    When the data in the RDR shift register is transferred to USART_ In the Dr register, this bit (RXNE) is set by hardware.
    If USART_ If rxneie in CR1 register = 1, an interrupt is generated. For USART_ The read operation of DR can clear this bit. RXNE bit can also be cleared by writing 0, which is recommended only in multi cache communication.

    0 - data not received
    1 - the received data can be read out

(2) Data register USART_DR

(3) Control register 1 (USART_CR1)

  • UE is an enable serial port (1-module enable)
  • TE is transmit enable (0-disable transmit, 1-enable transmit)
  • RE is receive enable (0-disable receive, 1-enable receive)
  • PEIE is PE interrupt enable (0-disable interrupt generation, 1-generate USART interrupt when PE of USART_SR is 1)
  • TCIE is transmit completion interrupt enable (0 - interrupt generation is prohibited, 1 - USART interrupt is generated when TC in USART_SR is' 1 ')
  • RXNEIE: receive buffer non air interrupt enable (0 - interrupt generation is prohibited, 1 - USART interrupt is generated when ORE or RXNE in USART_SR is' 1 ')

(4) Serial port receiving and sending data

  • Send data flow

At this time, UE=1,TE=1
The sequence corresponding to the above figure is 1 - > 2 - > 3. The data comes from CPU or DMA. After the data comes, it is first put into the transmission register (TDR) and then put into the transmission data shift register. Since the data is 8 bits, it will be transmitted bit by bit (using TX pin).

  • When the data is transferred from the transmit register to the transmit data shift register, TXE will be set to 1, that is, TXE=1, which means that the transmit data register is empty, but it does not mean that the data has been sent.
  • Because it is necessary to send data bit by bit through the transmission shift register, if all the transmission shift register data are sent out, TC will be set to 1, that is, TC=1 indicates that the transmission of data is completed.

**

  • Receiving data flow

**


At this time, UE=1,RX=1
Data comes in from RX pin and is received bit by bit.

  • First put it into the data receiving shift register
  • Then transfer the data to the data receiving register (RDR). At this time, RXNE will be set to 1, that is, RXNE=1, indicating that the data receiving register is not empty (the received data can be read out)

5, STM32 firmware library functions


Several common firmware library functions

  • Serial port initialization function

    void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct);Pin, baud rate, digit, check, clock, etc
    
  • Serial port enable

     USART_Cmd(USART1, ENABLE); //Enable serial port - UE bit is configured
    
  • Interrupt enable

     void USART_ITConfig(USART_TypeDef* USARTx, uint16_t USART_IT,FunctionalState NewState)
    
  • send data

    STM32 library function operation USART_ The function of sending data to Dr register is to send data to serial port register USART through this function_ Dr writes a data.

    void USART_SendData(USART_TypeDef* USARTx, uint16_t Data);
    
  • receive data

    STM32 library function operation USART_ The function of Dr register to read the data received by the serial port is: the data received by the serial port can be read through this function.

     uint16_t USART_ReceiveData(USART_TypeDef* USARTx);
    
  • Get flag bit

    This function only judges the flag bit. When the corresponding interrupt function is not enabled, it is usually used to judge whether the flag bit is set to 1

     Flagstatus USART_GetFlagStatus(USARTx,USART_FLAG)
    
  • Interrupt status bit acquisition function

    It will not only judge whether the flag bit is set to 1, but also judge whether the corresponding interrupt is enabled. Therefore, in the serial port interrupt function, if you want to obtain the interrupt flag bit, you usually use this function.

     ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t  USART_IT)
    
  • Clear interrupt flag bit

     Void  USART_Flag_Clear(USARTx,USART_FLAG)
    

Distinguishing description of several flag bit functions - link

6, USART application

Serial port initialization function

//Initialize IO serial port 1 
//bound: baud rate
void uart_init(u32 bound){
    //GPIO port settings
		GPIO_InitTypeDef GPIO_InitStructure;//GPIO structure pointer
		USART_InitTypeDef USART_InitStructure;//Serial port structure pointer
		NVIC_InitTypeDef NVIC_InitStructure;//Interrupt packet structure pointer
		//1. Enable serial port clock, serial port pin clock 
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//Enable USART1, GPIOA clock
		
	//2. Reset serial port	
		USART_DeInit(USART1);  //Reset serial port 1
	
	//3. Setting of transmit and receive pins
	 //USART1_ TX pa.9 (it can be seen from the figure that it is set as push-pull multiplex output)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//Multiplexed push-pull output
    GPIO_Init(GPIOA, &GPIO_InitStructure); //Initialize PA9
   
    //USART1_RX 	   PA.10 (floating input can be seen from the figure)
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//Floating input
    GPIO_Init(GPIOA, &GPIO_InitStructure);  //Initialize PA10


   //4. USART initialization settings

		USART_InitStructure.USART_BaudRate = bound;//Generally set to 9600;
		USART_InitStructure.USART_WordLength = USART_WordLength_8b;//The word length is in 8-bit data format
		USART_InitStructure.USART_StopBits = USART_StopBits_1;//A stop bit
		USART_InitStructure.USART_Parity = USART_Parity_No;//No parity bit
		USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//No hardware data flow control
		USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;	//Transceiver mode

    USART_Init(USART1, &USART_InitStructure); //Initialize serial port
		
#if EN_USART1_RX 		  // If reception is enabled  
   //5. Usart1 NVIC configuration
		NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//Preemption priority 3
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//Sub priority 3
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ channel enable
		NVIC_Init(&NVIC_InitStructure);	//Initializes the VIC register according to the specified parameters
   
	  //6. Enable receive data interrupt
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//Open interrupt
		
#endif
		//7. Enable serial port
    USART_Cmd(USART1, ENABLE);                    //Enable serial port 

}

(1) Send data

1. Function 1 - serial port sends a byte of data

void Usart_SendByte(USART_TypeDef* pUSARTx,uint8_t data)
{
	//Call firmware library function
	USART_SendData(pUSARTx,data);//Write data to serial port
	
	//After sending data, check whether the TXE bit is set to 1. If the sending data register is empty, it indicates that the data has been transferred to the data shift register
	//A firmware library function is also required to detect the TXE bit
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);
	//If this bit is always 0, it will wait. Only when it is SET will it jump out of the loop (indicating that a byte has been sent)
}

Write a statement in the main function:

//Try sending a data in the main function
Usart_SendByte(USART1,100);//Write data 100 to serial port 1

The serial debugging assistant does not display 100, but a letter d
No matter what data the serial port debugging assistant receives, it will be converted into characters
It is not a character only when sending hexadecimal data and the serial port assistant receives data in hexadecimal form

Usart_SendByte(USART1,'A');//Write A character A to serial port 1
//Character A received by serial port

Whether sending or receiving, the serial port assistant is transmitted in the form of characters

If the serial port assistant sends a number 1, if the stm32 serial port can receive it, it needs to parse according to the character '1' in the process of data parsing (regard 1 as a character, not a decimal 1)

2. Function 2 - send two bytes of data

Sometimes the sensor data may be 16 bits. How to send it? Send two bytes?

Sending two bytes of data is sixteen bits.

//Send two byte data function
void Usart_SendHalfWord(USART_TypeDef* pUSARTx,uint16_t data)
{
	//Sixteen bit data shall be sent twice, and two variables shall be defined first
	uint8_t temp_h,temp_l;//Define 8-bit variables (store the upper 8 bits and the lower 8 bits respectively)

	//First, take out the high 8 bits
	temp_h=(data&0xff00)>>8;//The lower eight bits first phase with 0 &, the lower eight bits become 0, and then shift 8 bits to the right (0xff00 is 16 bits in total)
	//Then take out the lower 8 bits
	temp_l=data&0xff;//Take out the lower 8-bit data
	//The 16 bit data is put into two variables (16 bits in total)
	
	//Call firmware library function
	USART_SendData(pUSARTx,temp_h);//Send the upper 8 bits first
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);//Wait for the data to be sent

	USART_SendData(pUSARTx,temp_l);//Then send the lower 8 bits
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);//Wait for the data to be sent

}

Send hexadecimal data in the main function:

Usart_SendHalfWord(USART1,0XFF56);//Send 16 bit data

The serial port assistant displays characters. If you want to receive the same data as sending, you need to select the serial port assistant as hexadecimal receiving
Serial assistant received ff 56. Although it is 16 bit data, it is displayed byte by byte. Hex ff is a byte and 56 is a byte

3. Function 3 - send an array of data

//Send an array of data
void Usart_SendArray(USART_TypeDef* pUSARTx,uint8_t *array,uint8_t num)
{
	//How much data you want to send each time is passed in through the formal parameter num. num defines 8 bits, so the function can send 255 at most
	int i;
	for(i=0;i<num;i++)
	{
		//Call the send a byte function to send data (the following two methods can be used)
		//Usart_SendByte(USART1,*array++);
		Usart_SendByte(USART1,array[i]);//Only 8 bits of data can be sent at a time
	}
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);//Wait for sending to complete
}

Judge to send a byte of data flag bit: USART_FLAG_TXE
Determine the data flag bit to send a series of bytes: USART_FLAG_TC

Define an array in the main function

uint8_t a[12]={1,2,3,4,5,6,7,8,9,10,98,100};

Send data content

Usart_SendArray(USART1,a,12);

Serial assistant: receive data in hexadecimal form

Serial assistant: receive data in non hexadecimal data form: ASCII of 1-10 cannot be displayed

4. Function 4 - send string function

//Send string
	void Usart_SendStr(USART_TypeDef* pUSARTx,uint8_t *str)//Specify the serial port and the string content to be sent
	{
		uint8_t i=0;
		//Using the do while loop, the do has already started to be sent
		do{
			//The function to send a byte needs to be called
			Usart_SendByte(USART1,*(str+i));//After sending once, the pointer address moves back one
			i++;
		}while(*(str+i)!='\0');//The last end is not equal to '\ 0'. Continue sending
		//If = '\ 0' indicates that the sending is completed
		while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);//Wait for sending to complete
	}

Calling a function in the main function to send a string

Usart_SendStr(USART1,"Welcome stm32\n");//At this time, characters are sent, and the serial port assistant wants to cancel hexadecimal reception

5. Function 5 - print data using the printf function

Sometimes you can't send directly with printf function
There will be an fputc at the bottom of the printf function. If you want to use it, you need to redefine it

int fputc(int ch FILE *f)
{
	//Send a byte of data to the serial port
	USART_SendData(USART1,(uint8_t)ch);
	//Wait for sending to complete
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	//Check whether the send data register is empty USART_FLAG_TXE

	return (ch);
}

Write code in the main function

printf("Serial port test experiment\n");
//You can also use putchar('a ') directly to send a character

putchar('a');//The serial assistant will receive the letter a

6. Function 6 - use getchar function

getchar() is equivalent to the scanf() function
If you use the getchar function, you also need to redefine it
Redirect the c library function scanf to the serial port. After rewriting, you can use scanf and getchar functions

redirect c Library function scanf To the serial port, which can be used after rewriting scanf and getchar function
int fgetc(FILE *f)
{
	//Wait for serial port input data
	/* With this wait, there is no need to interrupt */
	while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE)==RESET);
	return (int)USART_ReceiveData(USART1);
}

If you use getchar() in the main function, you need to comment out the following interrupt setting code (as shown in the figure below), otherwise it will conflict
Because it doesn't need to be in an interrupt


Add the code of receiving data and sending data to the while(1) loop of the main function

ch=getchar();
printf("ch=%c\n",ch);//Print received characters

What characters are sent by the serial port assistant, the serial port assistant will receive what characters are returned by the MCU

(2) Receive data

An interrupt occurs when data is received

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//Open interrupt

If there is an interrupt, set the interrupt priority

NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//Preemption priority 3
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//Sub priority 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ channel enable
NVIC_Init(&NVIC_InitStructure);	//Initializes the VIC register according to the specified parameters

Write interrupt service function

1. Serial port assistant sends and returns data interrupt function

What data the serial port assistant sends to the single chip microcomputer, and the single chip microcomputer automatically returns the received data to the serial port assistant

//Interrupt service function
void USART1_IRQHandler() 
{
	u8 ucTemp;
	if(USART_GetFlagStatus(USART1,USART_IT_RXNE)!=RESET)
	{
		ucTemp=USART_ReceiveData(USART1);
		USART_SendData(USART1, ucTemp);
	}
}

When the external device or serial port debugging assistant sends data to the single chip microcomputer, the single chip microcomputer detects that the data receiving register is not empty, indicating that the data is coming. At this time, an interrupt is generated. Go in to the interrupt service function and call the firmware library function. Whether the flag bit is really set to 1 to avoid false interruption. If 1 is generated, call USART_ The receivedata (usart1) function receives data and puts the data in the ucTemp variable
Then call USART_SendData(USART1, ucTemp) sends the data back to the serial port assistant

2. The serial port sends data to control the led on and off

The led light is controlled by the data received through the serial port, so that there is no need to interrupt to receive. You can query the method. At this time, you should annotate the interrupt part and annotate the interrupt service function

Add the following code to the main function:

   u8 ch;//Store the data received by the computer
	while(1)
	{
		ch=getchar();//Read serial port data
		printf("ch=%c\n",ch);//Print received characters
		switch(ch)//Match
		{
			case '1':
				LED0=0;break;//Open LED0
			case '2': 
				LED1=0;break;//Open LED1
		}

	}

If an error occurs: refer to this link
Solution - link
Use the serial assistant to send 1 and 2 respectively to control the led light on and off

3. STM32 custom protocol receives hexadecimal data (calculate CO2 concentration using three in one gas sensor)

As mentioned earlier, if hexadecimal data receiving and sending are not checked, other cases are sent and received in the form of characters, so the hex option should be checked when sending and receiving data

The data stream format of three in one gas sensor serial port is as follows:

Since the first two bytes of data are fixed module addresses, they can be used as judgment criteria to define a protocol for receiving data: only when the first byte and the second byte meet the conditions, the data will be stored in the array

Module data flow: 2C E4 04 00 00 AD 03 38 FC

The data stream consists of 9 bytes, so first define an octet array that stores 9 bytes

u8 table_data[9];//This is to define an array in advance to store the received data
u8 table_cp[9];//This is to define an additional array to copy the received data into it
u16 count=0;//Received data count

Write interrupt service function

//Receive hexadecimal data using a custom protocol

void USART1_IRQHandler(void)                	//Serial port 1 interrupt service program
{
		u8 Res,i;
		if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)  //Receive interrupt
		{
			Res =USART_ReceiveData(USART1);//(USART1->DR); 	// Read received data
			
			if(count==0)//If it is the first data received
			{
				table_data[count]=Res;//Save the first data to the first element in the data
				if(table_data[0]==0x2c)//Judge whether the first data received is hexadecimal 0X2C
				  count++;//If the first data is 0X2C, it means correct count + 1
			}
			else if(count==1)//When the first data is received correctly, judge the second data
			{
				if(Res==0xe4)//If the data just received is 0xE4, it indicates that the data is correct
				{
					table_data[count]=Res;//Stores the data to the second element of the array
					count++;//Received data count + 1
				}
				else//If the second character is not 0XE4, the count is cleared and received again
					count=0;
			}
			else if(count==2&&Res==0)//If the first two data are correct and the third data received is 0, the count is cleared and the data is received again
			{
				count=0;
			}
			else if(count>1&&count<9)//This is the range of data that can be received. As long as the count is within the range of data that can be received, the data can be stored
			{
				table_data[count]=Res;
				count++;
			}
			else if(count>=9)//If the received data exceeds the array size, it will be cleared and received again
			{
				count=0;
			}		
   } 
	 
		memset(table_cp, 0, sizeof(table_data));//Using array table_ Empty at CP
		for(i=0;i<9;i++)//Copy the received data to table_cp array
		{
			 table_cp[i]= table_data[i];
	}
} 

The above implementation is to receive and store data through a custom protocol

After receiving hexadecimal data, if you want to extract two bytes and convert them into decimal data, you first need to write a hexadecimal conversion function: (input hexadecimal data and return decimal data)

int hextoDec(int hex)
{
int sum=0,mul=1;
int i,r;
int count=0;
do{
r=hex%16;
for(i=0;i<count;i++)
mul*=16;
mul*=r;
sum+=mul;
mul=1;
count++;
}while(hex/=16);
return sum;
}

The while content of the main function is as follows:

while(1)
	{
	  if(table_cp[0]==0x2c)//If the first hexadecimal data of the array is 0X2C
		{
			//Print the received data with decimal data
			//The raw data (hexadecimal data) is 2C E4 04 00 00 AD 01 23 FC
			//The first two bits are fixed. The 7th and 8th hexadecimal data are the high and low octets of CO2 respectively
			for(i=0;i<9;i++)
			{
					printf(" %d \n",table_cp[i]);
				
			}
			
			printf("\r\n");//Add a carriage return to wrap the line
			//Convert the corresponding hexadecimal data into decimal data
			num_H=hextoDec(table_cp[6]);//High 8 bits
			num_L=hextoDec(table_cp[7]);//Lower 8 bits
			printf("hh=%d LL=%d \n",num_H,num_L);
			num=num_H*256+num_L;//The value is calculated using the CO2 concentration calculation formula
			printf("CO2=%d\n",num);
				
			}
	}


The verification is as follows:
As shown in the following figure: send hexadecimal data using serial port assistant:
2C E4 04 00 00 AD 03 38 FC
The 7th byte is the upper 8 bits of CO2 and the 8th byte is the lower 8 bits of CO2 concentration. Then calculate according to the calculation formula to calculate the CO2 concentration

Full code download link

Topics: Single-Chip Microcomputer Alibaba Cloud stm32 ARM