stm32 serial communication–use ring queue buffer to receive strings

stm32 serial communication—use receiving string

  • The concept of circular queue
  • Application of ring queue in serial communication
  • Program implementation
  • Application case: Smart car Bluetooth remote control

The concept of circular queue

A queue is a linear table with restricted operations, with fixed inputs and outputs, and a first-in-first-out (FIFO) feature. A circular queue is a commonly used data structure that can implement a queue on a fixed-size array. function. Compared with the chain queue, because the ring queue can recycle the space of the array, it can efficiently implement enqueue and dequeue operations.
Ring queue diagram
In the embedded development environment, the advantages of ring queues compared to chain queues are as follows:
1. Small space occupation
2. High reliability
3. Easy to implement

Application of ring queue in serial communication

In serial port communication, we usually use an array as a buffer (as shown in the following code) and save each character in the serial port receiving interrupt. However, this solution is often somewhat insufficient, and then determine which string to match in the serial port receiving interrupt. Equality results in different responses, but this solution often has some shortcomings:
We all know that when writing an interrupt service function, the interrupt service function should be as short as possible and need to follow the principle of fast in and fast out.
In some extreme cases, it is easy to cause problems. For example, if I need to judge 100 or even 1,000 different strings in the interrupt service function, there will be no time to receive the next frame of data, resulting in data loss. Moreover, this idea also violates It adopts the single responsibility principle of program design and reduces the maintainability of the code. And it may affect the normal operation of other codes. Even if we do not write the string judgment in the interrupt service function, similar problems will occur. For example, the amount of serial port messages is too large, resulting in frequent serial port reception interruptions. The function used to judge the string is too late to process, causing the program to be unable to reach the target. expected result. Even adding extra code to deal with this problem is very time-consuming and laborious.

void USART1_IRQHandler(void)
{<!-- -->
if(USART_GetITStatus(USART1,USART_IT_RXNE) == SET)
{<!-- -->
//char ch;
if(index >=10)
{<!-- -->
index =0;
memset(str,0,10);
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
str[index] = (char)(USART_ReceiveData(USART1) & amp;0xff);
USART_SendData(USART1,USART_ReceiveData(USART1));
index + + ;
if(!strcmp(str,"openled1"))
{<!-- -->
led_open1();
memset(str,0,10);
index = 0;
}
if(!strcmp(str,"closed1"))
{<!-- -->
led_close1();
memset(str,0,10);
index = 0;
}
}

}

Under this background, the programming idea of ring queue to receive serial port messages came into being. The use of ring queue can solve the above problems well. We can use the serial port reception interrupt to enqueue the received characters, dequeue them in other functions and then assemble them into strings to achieve host computer control. This will not cause data loss, and the ring queue is simpler to implement, while also improving the maintainability and readability of the program.

Program implementation

Here, serial port 3 of stm32f103 is used as an example to implement a ring queue. The function of each function here has been explained in detail in the comments.
ds_queue.h

#ifndef __DS_QUEUE_H__
#define __DS_QUEUE_H__
#include "stm32f10x.h"

//Queue capacity, the size of each element in this program is 1 byte
//ds= data structure, Chinese: data structure
//This program is based on STM32 ring queue


typedef struct Queue_t
{<!-- -->
uint32_t front; //queue head
uint32_t rear; //queue tail
uint32_t capacity; //Capacity: the capacity of the array
uint8_t *data; //Array pointer, used to save the data in the queue
}Queue;

//Chinese and English comparison table
//enqueue into the queue
//dequeue dequeue

/************************************************
*Function name: uint8_t ds_Queue_init(Queue *q, uint8_t *data_p, uint32_t capacity)
*Function: Queue structure initialization
*parameter:
* q: Queue to be initialized
* data_p: first address of data domain
* capacity: data field size
*Return value: None
*other instructions:
*\t\t\t\t\tPrecautions:
* This function will not apply for memory from the heap area, and will use structures and arrays.
* It must be defined in static memory first, or apply for memory in the heap area in advance
*************************************************/
uint8_t ds_Queue_init(Queue *q,uint8_t *data_p,uint32_t capacity);

/************************************************
*Function name: uint8_t ds_Queue_isEmpty(Queue *q)
*Function: Queue empty
*parameter:
q: The queue to be judged
*return value:
0: non-empty queue
1: Empty queue
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_isEmpty(Queue *q);

/************************************************
*Function name: uint8_t ds_Queue_isFull(Queue *q)
*Function: Queue is full
*parameter:
* q: The queue to be judged
*return value:
* 0: The queue is not full
*1: The queue is full
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_isFull(Queue *q);


/************************************************
*Function name: uint8_t ds_Queue_EnQueue(Queue *q, uint8_t element)
*Function: join the team
*parameter:
* q: The queue to be enqueued
* element: element to be added to the queue
*return value:
* 0: Failure
*1: Success
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_EnQueue(Queue *q,uint8_t element);


/**********************************************
*Function name: uint8_t ds_Queue_Dequeue(Queue *q,uint8_t* result)
*Function: Dequeue
*parameter:
* q: The queue to be enqueued
* result: used to store elements that have been dequeued
*return value:
* 0: Failure
*1: Success
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_Dequeue(Queue *q,uint8_t *result);


/**********************************************
*Function name: uint32_t ds_Queue_length(Queue *q)
*Function: Get the length of the current queue
*parameter:
* q: Queue whose length is to be obtained
*Return value: the length of the queue
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint32_t ds_Queue_length(Queue *q);

#endif /*__DS_QUEUE_H__*/

ds_quque.c

#include "ds_queue.h"


#define NULL_POINT ((void*)0)

/************************************************
*Function name: uint8_t ds_Queue_init(Queue *q, uint8_t *data_p, uint32_t capacity)
*Function: Queue structure initialization
*parameter:
* q: Queue to be initialized
* data_p: first address of data domain
* capacity: data field size
*Return value: None
*other instructions:
*\t\t\t\t\tPrecautions:
* This function will not apply for memory from the heap area, and will use structures and arrays.
* It must be defined in the static storage area first, or apply for memory in the heap area in advance
*************************************************/
uint8_t ds_Queue_init(Queue *q,uint8_t *data_p,uint32_t capacity)
{<!-- -->
if(q == NULL_POINT || data_p == NULL_POINT) return 0;
q->capacity = capacity;
q->data = data_p;
q->front = q->rear = 0;
return 1;
}

/**********************************************
*Function name: uint8_t ds_Queue_isEmpty(Queue *q)
*Function: Queue empty
*parameter:
q: The queue to be judged
*return value:
0: non-empty queue
1: Empty queue
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_isEmpty(Queue *q)
{<!-- -->
if(q == NULL_POINT) return 1;
return (q->front == q->rear);
}

/**********************************************
*Function name: uint8_t ds_Queue_isFull(Queue *q)
*Function: Queue is full
*parameter:
* q: The queue to be judged
*return value:
* 0: The queue is not full
*1: The queue is full
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_isFull(Queue *q)
{<!-- -->
if(q == NULL_POINT) return 1;
return ( ((q->rear) + 1) % (q->capacity) ) == q->front;
}

/************************************************
*Function name: uint8_t ds_Queue_EnQueue(Queue *q, uint8_t element)
*Function: join the team
*parameter:
* q: The queue to be enqueued
* element: element to be added to the queue
*return value:
* 0: Failure
*1: Success
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_EnQueue(Queue *q,uint8_t element)
{<!-- -->
if(ds_Queue_isFull(q)) return 0;//The queue is full
q->data[q->rear] = element;
q->rear = (q->rear + 1) % (q->capacity);
return 1;
}


/**********************************************
*Function name: uint8_t ds_Queue_Dequeue(Queue *q,uint8_t* result)
*Function: Dequeue
*parameter:
* q: The queue to be enqueued
* result: used to store elements that have been dequeued
*return value:
* 0: Failure
*1: Success
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint8_t ds_Queue_Dequeue(Queue *q,uint8_t *result)
{<!-- -->
if(ds_Queue_isEmpty(q)) return 0;//Empty queue
\t
*result = q->data[q->front];
q->front = (q->front + 1)%(q->capacity);
\t
return 1;
}

/************************************************
*Function name: uint32_t ds_Queue_length(Queue *q)
*Function: Get the length of the current queue
*parameter:
* q: Queue whose length is to be obtained
*Return value: the length of the queue
*other instructions:
* Note: Please do not pass in a null pointer or wild pointer
*************************************************/
uint32_t ds_Queue_length(Queue *q)
{<!-- -->
uint32_t len = 0;
if(q == NULL_POINT) return 0;
if(q->rear >= q->front)
{<!-- -->
len = q->rear - q->front;
}else{<!-- -->
len = (q->capacity - q->front) + q->rear;
}
return len;
}

dri_usart3.h

#ifndef __DRI_USART3_H__
#define __DRI_USART3_H__

#include "stm32f10x.h"

extern uint8_t ur_data;
/************************************************
*Function name: void dri_usart3_init(void)
*Function: Serial port 3 initialization
*Parameters: None
*Return value: None
*Other instructions: None
*************************************************/
void dri_usart3_init(void);

/**********************************************
*Function name: void dri_usart3_assemble_data(const uint8_t *dat)
*Function: Load characters into the queue in the serial port receiving interrupt
*Parameter: dat: character received by serial port reception interrupt
*Return value: actual received size
*Other instructions: None
*************************************************/
void dri_usart3_assemble_data(const uint8_t *dat);

/**********************************************
*Function name: uint32_t dri_usart3_receive_data(uint8_t *buf,uint32_t size)
*Function: Receive size bytes of data from the queue buffer
*parameter:
*buf: buffer
* size: size of received data
*Return value: actual received size
*Other instructions: None
*************************************************/
uint32_t dri_usart3_receive_data(uint8_t *buf,uint32_t size);
#endif /*__DRI_USART3_H__*/

dri_usart3.c

#include "dri_usart3.h"
#include "ds_queue.h"
#include <stdio.h>
#include "dri_tim7.h"
//Serial port 3 receiving buffer size
#define R_BUFFER_SIZE 128

//Serial port receiving queue buffer, mainly used to save previously received messages
//Adding 1 is because the circular queue has space for empty judgment. For example, the array length is 8, but the actual available data is only 7.
static uint8_t rd_queue_buffer[R_BUFFER_SIZE + 1] = {<!-- -->0};

//r=receive, queue for receiving messages
static Queue rqueue;
/******************************
*Read related information to know:
*usart3 pins:
* TX-----PB10
*RX-----PB11
*clock:
*The clock of serial port 3 is APB1
****************************/

//Currently received data
uint8_t ur_data = 0x00;


/************************************************
*Function name: void dri_usart3_init(void)
*Function: Serial port 3 initialization
*Parameters: none
*Return value: None
*Other instructions: None
***********************************************/
void dri_usart3_init(void)
{<!-- -->
//Enable the peripheral clock bus of the peripheral GPIOB. The first thing before configuring any peripheral is to enable the peripheral clock bus.
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
//Enable the clock bus of the usart3 serial port peripheral. The first thing before configuring any peripheral is to enable the clock bus of the peripheral.
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3,ENABLE);
\t
/*Configure GPIO*/
\t
//Send TX
GPIO_InitTypeDef GPIO_InitStrcut;
GPIO_InitStrcut.GPIO_Mode = GPIO_Mode_AF_PP;//Multiplex push-pull output
GPIO_InitStrcut.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStrcut.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, & amp;GPIO_InitStrcut);
\t
//Receive RX
GPIO_InitStrcut.GPIO_Pin = GPIO_Pin_11;
GPIO_InitStrcut.GPIO_Mode = GPIO_Mode_IN_FLOATING;//Configured as floating input
GPIO_Init(GPIOB, & amp;GPIO_InitStrcut);
\t
USART_InitTypeDef USART_InitStruct;
\t
/*Configure serial port*/
\t
//Baud rate 9600
USART_InitStruct.USART_BaudRate = 9600;
//Hardware flow control, not used
USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
//Mode: send + receive
USART_InitStruct.USART_Mode =USART_Mode_Rx|USART_Mode_Tx;
//Verification: No verification
USART_InitStruct.USART_Parity = USART_Parity_No;
//Stop bit: 1 bit
USART_InitStruct.USART_StopBits = USART_StopBits_1;
//Data bits: 8 bits
USART_InitStruct.USART_WordLength = USART_WordLength_8b;
\t
USART_Init(USART3, & amp;USART_InitStruct);
USART_Cmd(USART3,ENABLE);
USART_ClearFlag(USART3,USART_FLAG_TC);//Solve the problem of failure to send the first byte
\t
//Enable the receive interrupt of serial port 3
USART_ITConfig(USART3,USART_IT_RXNE, ENABLE);
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
\t
//Configure the interrupt vector manager
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn; //Interrupt channel number
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;//Enable
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;//Preemption priority
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;//Sub-priority
NVIC_Init( & amp;NVIC_InitStruct);

ds_Queue_init( & amp;rqueue,rd_queue_buffer,sizeof(rd_queue_buffer));
}


int fputc(int ch,FILE *f)
{<!-- -->
while(USART_GetFlagStatus(USART3,USART_FLAG_TXE) == RESET);
USART_SendData(USART3,ch);
return ch;
}


/************************************************
*Function name: void dri_usart3_assemble_data(const uint8_t *dat)
*Function: Load characters into the queue in the serial port receiving interrupt
*Parameter: dat: character received by serial port reception interrupt
*Return value: actual received size
*Other instructions: None
*************************************************/
void dri_usart3_assemble_data(const uint8_t *dat)
{<!-- -->
if(ds_Queue_isFull( & amp;rqueue) == 1) return;//If it is full, do not assemble
ds_Queue_EnQueue( & amp;rqueue,(uint8_t)*dat);
}

/**********************************************
*Function name: uint32_t dri_usart3_receive_data(uint8_t *buf,uint32_t size)
*Function: Receive size bytes of data from the queue buffer
*parameter:
*buf: buffer
* size: size of received data
*Return value: actual received size
*Other instructions: None
*************************************************/
uint32_t dri_usart3_receive_data(uint8_t *buf,uint32_t size)
{<!-- -->
uint32_t j = 0,p=0;
//dri_tim7_delay_ms(5);
if(ds_Queue_isEmpty( & amp;rqueue) == 1) return 0;//If the queue is empty, no elements will be returned
j = ds_Queue_length( & amp;rqueue);
if(size > j)
{<!-- -->
//Indicates that the receiving buffer is too large and the assembled string is too short.
while(j--)
{<!-- -->
ds_Queue_Dequeue( & amp;rqueue,buf + p);
p + + ;
}
}else{<!-- -->
//Indicates that the assembled string is long enough and the buffer is large enough to hold it.
while(size--)
{<!-- -->
ds_Queue_Dequeue( & amp;rqueue,buf + p);
p + + ;
}
}
\t
//printf("__de:%c,0x%x,rr=%d___\
",*buf,*buf,p);
return p;
}

Serial port receive interrupt

uint8_t ur_data = 0x00;
void USART3_IRQHandler(void)
{<!-- -->
\t
if(USART_GetITStatus(USART3,USART_IT_RXNE))
{<!-- -->
\t\t
ur_data = USART_ReceiveData(USART3);
dri_usart3_assemble_data( & amp;ur_data);//Load the string into the queue
USART_ClearITPendingBit(USART3,USART_IT_RXNE);
}
}

Application case: Smart car Bluetooth remote control

1. Define the Bluetooth module button and end it with @ for easy segmentation.
Please add image description
2. Write relevant code
bluetooth_upper_computer.h file

#ifndef __BLUETOOTH_UPPER_COMPUTER_H__

#define __BLUETOOTH_UPPER_COMPUTER_H__

#include "stm32f10x.h"

/*Smart car Bluetooth remote control macro definition*/
//These written macros are used as the return value of the Bluetooth module and are called in the main function while loop
//go ahead
#define CAR_BLUE_ADVANCE 0x00
//Back
#define CAR_BLUE_REVERSE 0x01
//Turn left
#define CAR_BLUE_LEFT 0x02
//Turn right
#define CAR_BLUE_RIGHT 0x03
//stop
#define CAR_BLUE_STOP 0x04
//Tracking mode
#define CAR_BLUE_TRA 0x05
//Bluetooth remote control mode
#define CAR_BLUE_REM 0X06
//obstacle avoidance mode
#define CAR_BLUE_AVOI 0x07

//The car starts automatically and is mainly used for functions such as automatic obstacle avoidance and tracking.
#define CAR_BLUE_START 0x08
extern uint8_t blue_command;

/**********************************************
*Function name: void buc_get_command(void)
*Function: Receive instructions from Bluetooth host computer
*Parameters: None
*Return value: None
*other instructions:
* If received, the global variable blue_command will be automatically updated.
* The function of blue_command is to save the recently received Bluetooth remote control command
* Bluetooth host computer related instructions and macro definition encapsulation
* Please check the bluetooth_upper_computer.h file for details
* Lines 7-24 of code
*************************************************/
void buc_get_command(void);
#endif


The following is the highlight. void buc_get_command(void) is used to split the string, using @ as the splitting symbol. Each time, one character is taken out from the queue and judged one by one. When the character taken out is @, replace it with \0, and then enter the if In the judgment statement, it is judged what the instruction sent by the user is, and the corresponding macro is used to assign a value to the global variable blue_comman, and then the judgment is made in the main function.
bluetooth_upper_computer.c

#include "bluetooth_upper_computer.h"
#include "dri_usart3.h"
#include <string.h>


//Bluetooth host computer
//buc= bluetooth upper computer

#define COM_LENGTH 36

//The default is to stop when receiving the Bluetooth command
uint8_t blue_command = CAR_BLUE_STOP;

static char command_str[COM_LENGTH] = {<!-- -->0};
static uint8_t command_pos = 0;


/************************************************
*Function name: void buc_get_command(void)
*Function: Receive instructions from Bluetooth host computer
*Parameters: none
*Return value: None
*other instructions:
* If received, the global variable blue_command will be automatically updated.
* The function of blue_command is to save the recently received Bluetooth remote control command
* Bluetooth host computer related instructions and macro definition encapsulation
* Please check the bluetooth_upper_computer.h file for details
* Lines 7-24 of code
*************************************************/
void buc_get_command(void)
{<!-- -->
volatile uint8_t receive_res = 0;
\t
//Receive one byte of data each time
receive_res = dri_usart3_receive_data((uint8_t*)(command_str + command_pos),1);
\t
if(command_str[command_pos] == '@')
{<!-- -->
command_str[command_pos] = '\0';
if(!strcmp(command_str,"front")){<!-- -->
blue_command = CAR_BLUE_ADVANCE;//Forward
}else if(!strcmp(command_str,"reverse")){<!-- -->
blue_command = CAR_BLUE_REVERSE;//Back
}else if(!strcmp(command_str,"left")){<!-- -->
blue_command = CAR_BLUE_LEFT;//Turn left
}else if(!strcmp(command_str,"right")){<!-- -->
blue_command = CAR_BLUE_RIGHT;//Turn right
}else if(!strcmp(command_str,"stop")){<!-- -->
blue_command = CAR_BLUE_STOP;//Stop
}else if(!strcmp(command_str,"avoidance")){<!-- -->
blue_command = CAR_BLUE_AVOI;//Obstacle avoidance
}else if(!strcmp(command_str,"remote_control")){<!-- -->
blue_command = CAR_BLUE_REM;//Bluetooth remote control
}else if(!strcmp(command_str,"tracking")){<!-- -->
blue_command = CAR_BLUE_TRA;//Car automatic tracking mode
}else if(!strcmp(command_str,"start")){<!-- -->
blue_command = CAR_BLUE_START;//The car starts automatically
}
command_pos = 0;
memset(command_str,0x00,sizeof(command_str));
}else{<!-- -->
//If a byte is received, move backward one byte
command_pos + = receive_res;
}
\t
\t
if(command_pos >= COM_LENGTH)
{<!-- -->
command_pos = 0;
memset(command_str,0x00,sizeof(command_str));
}
}

Main function main.c
The obtained instructions are further judged to perform different actions, thus realizing the Bluetooth remote control of the smart car.

#include "stm32f10x.h"
#include "interface.h"
//tick timer
#include "dri_systick.h"
//Servo control program
#include "dri_servo_motor.h"
//ultrasound
#include "dri_ultrasonic.h"
//Basic timer 7
#include "dri_tim7.h"
//DC
#include "dri_motor.h"
//Car movement control
#include "car_move.h"
//Infrared obstacle avoidance sensor
#include "dri_ir_avoidance.h"
//Serial port 3, connected to an external Bluetooth module
#include "dri_usart3.h"
//Trace
#include "dri_tracking.h"
//Bluetooth host computer
#include "bluetooth_upper_computer.h"
//Standard input and output
#include <stdio.h>
//Car mode macro definition
#define REMOTE 0
#define AVOIDANCE 1
#defineTRACKING 2
//Car operating mode
uint8_t run_mode = REMOTE;
int main()
{<!-- -->
//Configure interrupt priority grouping
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
dri_tim7_init();
dri_ultrasonic_init();
dri_servo_motor_init();
dri_servo_motor_postion(SERVO_MOTOR_MIDDLE);//Return the servo to the center
dri_motor_init();
dri_ir_avoidance_init();
dri_tracking_init();
dri_usart3_init();
car_move_init();
while(1)
{<!-- -->
buc_get_command();
if((blue_command == CAR_BLUE_REM) & amp; & amp; run_mode != REMOTE)
{<!-- -->
run_mode = REMOTE;
car_move(CAR_MOVE_STOP);
printf("Remote control mode\
");
}else if((blue_command == CAR_BLUE_AVOI) & amp; & amp; run_mode != AVOIDANCE){<!-- -->
run_mode = AVOIDANCE;
car_move(CAR_MOVE_STOP);
printf("Auto obstacle avoidance\
");
}else if((blue_command == CAR_BLUE_TRA) & amp; & amp; run_mode != TRACKING){<!-- -->
run_mode = TRACKING;
car_move(CAR_MOVE_STOP);
printf("Tracking mode\
");
}
\t\t
switch(run_mode)
{<!-- -->
case(REMOTE):
//car remote control
car_bluetooth_control(blue_command);
break;
\t\t\t
case(AVOIDANCE):
if(blue_command == CAR_BLUE_START){<!-- -->
//Automatically avoid obstacles, please note that the wall or the root of the wall cannot be black
car_ultrasonic_obstacle_avoidance_driver();
}else{<!-- -->
car_move(CAR_MOVE_STOP);
}
break;
\t\t\t\t
case(TRACKING):
if(blue_command == CAR_BLUE_START){<!-- -->
//Automatic tracking
car_tracking_driver();
}else{<!-- -->
car_move(CAR_MOVE_STOP);
}
break;
\t\t
}
}
}