STM32 Getting Started Tutorial (Serial Port)

Reference tutorial:[9-1] USART serial port protocol_bilibili_bilibili

1. Communication interface:

(1) Purpose of communication:Transmit data from one device to another device, expand the hardware system.

(2) Communication protocol: Establish communication rules, and both communicating parties will send and receive data in accordance with the protocol rules.

name

pin

duplex

level

equipment

USE

TX、RX

full duplex

single ended

peer to peer

2C

SCL、SDA

half duplex

single ended

Multiple devices

SPI

SCLK、MOSI、MISO、CS

full duplex

single ended

Multiple devices

CAN

CAN_H、CAN_L

half duplex

difference

Multiple devices

USB

DP、DM

half duplex

difference

peer to peer

Related terms:

①Full duplex: Both communicating parties can transmit data to each other at the same time.

    Half-duplex: The communicating parties can transmit data to each other, but they must time-share and reuse one data line.

    Simplex: Communication can only be sent from one party to the other, and cannot be transmitted in the reverse direction.

②Asynchronous: Both communicating parties agree on the communication rate.

    Synchronization: Both communicating parties rely on a clock line to agree on the communication rate.

③Bus: A data transmission line that connects various devices (similar to a road, connecting households on the roadside so that households can communicate with each other).

2. Serial communication:

(1) The serial port is a widely used communication interface. The serial port is low in cost, easy to use, and has simple communication lines. It can realize mutual communication between two devices.

(2) The serial port of the microcontroller allows the microcontroller to communicate with each other, the microcontroller and the computer, and the microcontroller and various modules. It greatly expands the application scope of the microcontroller and enhances the hardware strength of the microcontroller system.

3. Hardware circuit:

(1) Simple two-way serial communication has two communication lines (sending end TX and receiving end RX).

(2)TX and RX required crossover connection.

(3) When only one-way data transmission is required, only one communication line can be connected.

(4) When the level standards are inconsistent, a level conversion chip needs to be added. The level standard is the expression of data 1 and data 0. It is the correspondence between the artificially specified voltage and data in the transmission cable. There are three commonly used level standards for serial ports:

①TTL level: +3.3V or +5V means 1, 0V means 0 (commonly used in microcontrollers)

②RS232 level: -3~-15V means 1, +3~+15V means 0 (commonly used in large machines)

③RS485 level: The voltage difference between the two lines +2~+6V means 1, -2~-6V means 0 (differential signal, strong anti-interference ability, long transmission distance)

4. Serial port parameters andtiming:

(1) Baud rate:Serial port communication rate.

(2) Start bit: marks the start of a data frame, fixed to low level ( is high level when idle, low level/falling edge represents preparation Start transmitting data).

(3) Data bit: payload of the data frame (real data content), 1 is high level, 0 is low level, low bit first.

(4) Check digit: used for data verification, calculated based on the data bits. (The picture on the left does not have a check digit, the picture on the right has a check digit)

(5) Stop bit: used for data frame interval, fixed to high level ( represents the completion of a data transmission).

5. USART (Universal Synchronous/Asynchronous Receiver/Transmitter) Universal Synchronous/Asynchronous Receiver/Transmitter:

(1) USART is a hardware peripheral integrated inside STM32. can automatically generate data frame timing based on one byte of data in the data register and send it out from the TX pin. It can automatically receive the data frame timing of the RX pin, splice it into one byte of data, and store it in the data register (that is to say, there is no need to implement timing in the software, the bottom layer has already packaged the timing) .

(2) Comes with a baud rate generator, up to 4.5Mbits/s.

(3) Configurable data bit length (8/9) and stop bit length (0.5/1/1.5/2).

(4) Optional parity bit (no parity/odd parity/even parity).

(5) Supports synchronous mode, hardware flow control, DMA, smart card, IrDA, and LIN.

(6) USART resources of STM32F103C8T6: USART1 (mounted on the APB2 bus), USART2 (mounted on the APB1 bus), USART3 (mounted on the APB1 bus).

6. USART block diagram:

(1) Upper left cornerTX and RX are the generation and reception pins respectively, SW_RX and IRDA_OUT/IN are for smart card and IrDa communication pin.

(2)The transmit shift register writes the data to the TX pin bit by bit (driven by the transmitter control), After the transmit shift register completes sending one byte of data, TDRautomatically transmits the next byte of data once Write to the transmit shift register permanently and set the flag bit TXE (TX Empty, the transmit data register is empty) to 1. When the flag bit TXE is 1, you can write data to the TDR, and the hardware will automatically Set TXE to 0.

(3)The receive shift register reads data from the RX pin bit by bit (driven by the receiver control), When the receiving shift register receives a byte of data, it will automatically Write the RDR permanently and set the flag RXNE (RX Not Empty, the receiving data register is not empty) to 1. When the program detects that RXNE is 1, it can read the data in the RDR. HardwareAutomatically sets RXNE to 0.

(4) The sending data register (the software can only perform writing operations) and the receiving data register (the software can only perform reading operations) occupy the same address (the same address in the software, but actually two different hardware).

(5) If the sending device generates data too fast, the receiving device may not have time to process it, and the data will be discarded or overwritten. Hardware data flow control can solve this problem. The nRTS pin is used as a receiver to notify the sender whether it is ready to receive data. The nCTS pin is used as a sender to determine whether the receiver is ready to receive data. The two pins The pins are all active at low level.

(6) SCLK is a clock signal that generates synchronization. It cooperates with the output of the transmit shift register (only supports output, not input). Every time the transmit register shifts, the synchronous clock level jumps for one cycle. The synchronous clock signal can be compatible with other communication protocols, and can also help the receiver receive data and provide an adaptive baud rate (for example, if the receiving device is not sure about the baud rate of the sending device, it can measure the period of the clock and calculate the baud rate. Rate).

(7) USART also has interrupt control, and its status register has various flag bits, the more important ones are TXE and RXNE.

7. USART basic structure:

8. Data frame: (the data receiving end can sample on the rising edge of the clock to read data)

9. Start bit detection:

(1) The input circuit will subdivide the sampling clock, it willsample 16 times within the time of transmitting one bit of data (The sampling clock frequency is 16 times the baud rate) , if a falling edge is found between two samples, it means the start bit starts .

(2) will be sampled 16 times at the starting bit. If there is no noise, the sampling of the starting bit will be 0, standard It is required that there are at least 2 0s in every 3 bits, because in practice there will be more or less noise effects. If 3 consecutive bits are all 0, the start bit detection is successful; if 1 of the 3 consecutive bits is 1, although the start bit is detected successfully, the noise flag NE will be set to 1; if the number of 0s is not The requirements are met, the start bit detection fails, and the input circuit re-detects the next start bit.

(3)When the input circuit detects the starting bit of a data frame, it will continuously sample one frame of data. At the same time, starting from the starting bit, the sampling position will Align to the exact middle of the signal for each bit of data.

10. Data sampling: There are 16 sampling clock pulses in one data bit. Data sampling is directly in the middle of the signal of each bit of data, that is, the 8th , 9, 10 sampling points sampling data bits (sampling three times in succession is to ensure the reliability of the data. If more than 0 are collected, it will be 0, and if there are more than 1, it will be 1. If it is sampled 3 times If the sampling is different, the noise flag NE will be set to 1).

11. Baud rate generator:

(1) The baud rate of the transmitter and receiver is determined by the DIV in the baud rate register BRR.

(2) Calculation formula: Baud rate = fPCLK2/1 / (16 * DIV)

12. Serial port sending: (the microcontroller sends data to the computer through the serial port)

(1) Connect the circuit as shown in the figure below, and copy the OLED display project folder to use as a template. (USART1_TX is multiplexed on the PA9 pin, USART1_RX is multiplexed on the PA10 pin)

(2) Add the Serial.h file and Serial.c file to the Hardware group of the project to encapsulate the code of the serial port module.

①Serial.h file:

#ifndef __Serial_H
#define __Serial_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

#endif

②Serial.c file:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

void Serial_Init(void)
{
	//开启GPIO和USART的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//把USART1_TX(PA9)配置为复用输出模式,把USART1_RX(PA10)配置为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/*本例并不需要实现接收功能,可以暂时不用配置PA10
	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);
	*/
	
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;              //波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //不使用硬件流控
	USART_InitStructure.USART_Mode = USART_Mode_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);
	
	//如果只需要使用发送功能,现在就可以开启USART1(使用接收功能还需要配置中断)
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)  //发送一个字节
{
	USART_SendData(USART1, Byte);   //写一字节数据进TDR寄存器
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	//标志位TXE为0时,TDR中的数据还没写进发送移位寄存器,需要等待
	//TXE标志位不需要软件清除,写TDR寄存器时TXE自动置0
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)  //发送一个数组
{
	uint16_t i = 0;
	for(i = 0; i < Length; i++)     //有多少个元素就循环几次
	{
		Serial_SendByte(Array[i]);  //发送1个元素(1个元素正好1个字节)
	}
}

void Serial_SendString(char *String)  //发送一个字符串
{
	uint16_t i = 0;
	for(i = 0; String[i] != '\0'; i++) //直到遇到字符串结束标志,否则持续发送
	{
		Serial_SendByte(String[i]);  //发送1个字符(1个字符正好1个字节)
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)  //计算X的Y次方(用于分离个十百千万位)
{
	uint32_t Result = 1;
	while(Y--)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)  //发送一个数字
{
	uint8_t i = 0;
	for(i = 0; i < Length; i++) //数字有几位就发送几次,每次发送一位数,从高位开始
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');  //发送1个字符(1个字符正好1个字节)
	}
}

int fputc(int ch, FILE *f)   //重写fputc函数(它是printf函数的底层)
{
	Serial_SendByte(ch);  //将fputc重定向到串口,这样调用printf时就能在串口助手上输出字符串
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

(3) There are functions for configuring USART in the stm32f10x_usart.h file. A few are briefly introduced below.

[1]USART_DeInit function: restore USART default configuration.

[2]USART_Init function: Initialize USART using parameters in the structure.

[3]USART_StructInit function: Assign a default value to the parameters in the structure.

[4]USART_ClockInit function: Use the parameters in the structure to configure the synchronous clock output.

[5]USART_ClockStructInit function: Assign a default value to the parameters in the structure.

[6]USART_Cmd function: Enable USART.

[7]USART_ITConfig function: Interrupt output control, used to control whether an interrupt can pass to the NVIC.

[8]USART_DMACmd function: Open the trigger channel from USART to DMA, allowing USART to send requests to DMA.

[9]USART_SendData function: Write the DR (TDR) register for sending data.

[10]USART_ReceiveData function: Read the DR (RDR) register, used to receive data.

[11]USART_GetFlagStatus function: Get the status flag bit.

[12]USART_ClearFlag function: clear the flag bit.

[13]USART_GetITStatus function: Get the interrupt status.

[14]USART_ClearITPendingBit function: clear the interrupt pending bit.

(4) Paste the following code in the main.c file, compile it, and download the program to the development board.

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "Serial.h"

int main()
{
	OLED_Init();
	Serial_Init();
	
	uint8_t MyArray[] = {0x41, 0x42, 0x43, 0x44};
	Serial_SendArray(MyArray, sizeof(MyArray)/sizeof(MyArray[0]));
	
	Serial_SendString("\r\nNum1=");
	Serial_SendNumber(111,3);
	
	printf("\r\nNum2=%d", 222);
	
	char String[100];
	sprintf(String,"\r\nNum3=%d", 333);  //指定打印位置为String
	Serial_SendString(String);
	
	Serial_Printf("\r\nNum4=%d", 444);
	Serial_Printf("\r\n");
	
	while(1)
	{
		
	}
}

(5) Open the serial port assistant, configure the serial port of the receiver, open the serial port, and use the reset button to debug. (Select text mode for the receiving mode in the receiving area)

(6) The following operations need to be done before using the printf function.

13. Serial port sending + receiving:

(1) Connect the circuit as shown in the figure below, and copy the project folder of the above example to use as a template.

(2) Modify the Serial.h file and Serial.c file:

①Serial.h file:

#ifndef __Serial_H
#define __Serial_H

#include <stdio.h>

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
uint8_t Serial_GetRxData(void);

#endif

②Serial.c file:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_RxData;  //接收数据“暂存器”
uint8_t Serial_RxFlag;  //供软件判断是否有新数据到来的标志位

void Serial_Init(void)
{
	//开启GPIO和USART的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//把USART1_TX(PA9)配置为复用输出模式,把USART1_RX(PA10)配置为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	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);
	
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;              //波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //不使用硬件流控
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;         //本例需要发送和接收功能
	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);
	
	//开启中断用于接收数据,配置NVIC
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);       //RXNE位置为1,也就是RDR中有新数据,会触发一次中断(不使用中断会消耗很多软件资源)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //分组方式2(2位抢占优先级,2位响应优先级)
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;         //USART1到NVIC的通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           //开启中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        //响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	//开启USART1
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)  //发送一个字节
{
	USART_SendData(USART1, Byte);   //写一字节数据进TDR寄存器
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	//标志位TXE为0时,TDR中的数据还没写进发送移位寄存器,需要等待
	//TXE标志位不需要软件清除,写TDR寄存器时TXE自动置0
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)  //发送一个数组
{
	uint16_t i = 0;
	for(i = 0; i < Length; i++)     //有多少个元素就循环几次
	{
		Serial_SendByte(Array[i]);  //发送1个元素(1个元素正好1个字节)
	}
}

void Serial_SendString(char *String)  //发送一个字符串
{
	uint16_t i = 0;
	for(i = 0; String[i] != '\0'; i++) //直到遇到字符串结束标志,否则持续发送
	{
		Serial_SendByte(String[i]);  //发送1个字符(1个字符正好1个字节)
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)  //计算X的Y次方(用于分离个十百千万位)
{
	uint32_t Result = 1;
	while(Y--)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)  //发送一个数字
{
	uint8_t i = 0;
	for(i = 0; i < Length; i++) //数字有几位就发送几次,每次发送一位数,从高位开始
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');  //发送1个字符(1个字符正好1个字节)
	}
}

int fputc(int ch, FILE *f)   //重写fputc函数(它是printf函数的底层)
{
	Serial_SendByte(ch);  //将fputc重定向到串口,这样调用printf时就能在串口助手上输出字符串
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

uint8_t Serial_GetRxFlag(void)  //返回标志位
{
	if(Serial_RxFlag == 1)  //如果有接收到新数据,返回1并将标志位置0,等待下一个数据到来
	{
		Serial_RxFlag = 0;  //防止同一个数据多次返回
		return 1;
	}
	return 0;
}

uint8_t Serial_GetRxData(void)  //返回接收到的数据
{
	return Serial_RxData;
}

void USART1_IRQHandler(void)   //USART1的中断函数
{
	if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)  //程序检测到RXNE为1时,就可以将RDR中的数据读走
	{
		Serial_RxData = USART_ReceiveData(USART1);         //将接收到的数据存入Serial_RxData
		Serial_RxFlag = 1;                                 //已经接收到数据,置标志位为1
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);    //其实读取RDR时硬件会自动置RXNE为0,软件可以不考虑这一点
	}
}

(3) Paste the following code in the main.c file, then compile it, download the program to the development board, send data to the microcontroller in the serial port assistant, and debug according to the comments of the main function.

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "Serial.h"

uint8_t RxData ;

int main()
{
	OLED_Init();
	Serial_Init();
	
	OLED_ShowString(1, 1, "RxData:");
	
	while(1)
	{
		if(Serial_GetRxFlag() == 1)  //判断是否有新数据到来
		{
			RxData = Serial_GetRxData();  //把接收的新数据拷贝到RxData中
			Serial_SendByte(RxData);      //把接收到的数据传给电脑
			//(调试时注意选择HEX模式进行发送和接收)
			OLED_ShowHexNum(1, 8, RxData, 2);
		}
	}
}

14. Data mode:

(1) HEX mode/hexadecimal mode/binary mode: displayed in the form of original data.

(2) Text mode/character mode: displayed in the decoded form of the original data.

15. Serial port sends and receives HEX data packets:

(1) There are two types of HEX data packets:

①Fixed package length, including header and tail:

②Variable packet length, including header and tail:

(2) HEX data packet reception: (the figure below shows the fixed packet length)

(4) Connect the circuit as shown in the figure below, and copy the project folder of the above example to use as a template.

(5) Modify the Serial.h file and Serial.c file:

①Serial.h file:

#ifndef __Serial_H
#define __Serial_H

#include <stdio.h>

extern uint8_t Serial_TxPacket[];
extern uint8_t Serial_RxPacket[];

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

uint8_t Serial_GetRxFlag(void);
void Serial_SendPacket(void);

#endif

②Serial.c file:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

uint8_t Serial_TxPacket[4];  //存放单片机发送给电脑的数据包
uint8_t Serial_RxPacket[4];  //存放单片机收到的数据包
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	//开启GPIO和USART的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//把USART1_TX(PA9)配置为复用输出模式,把USART1_RX(PA10)配置为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	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);
	
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;              //波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //不使用硬件流控
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;         //本例需要发送和接收功能
	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);
	
	//开启中断用于接收数据,配置NVIC
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);       //中断检测RXNE位(不使用中断会消耗很多软件资源)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //分组方式2(2位抢占优先级,2位响应优先级)
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;         //USART1到NVIC的通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           //开启中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        //响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	//开启USART1
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)  //发送一个字节
{
	USART_SendData(USART1, Byte);   //写一字节数据进TDR寄存器
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	//标志位TXE为0时,TDR中的数据还没写进发送移位寄存器,需要等待
	//TXE标志位不需要软件清除,写TDR寄存器时TXE自动置0
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)  //发送一个数组
{
	uint16_t i = 0;
	for(i = 0; i < Length; i++)     //有多少个元素就循环几次
	{
		Serial_SendByte(Array[i]);  //发送1个元素(1个元素正好1个字节)
	}
}

void Serial_SendString(char *String)  //发送一个字符串
{
	uint16_t i = 0;
	for(i = 0; String[i] != '\0'; i++) //直到遇到字符串结束标志,否则持续发送
	{
		Serial_SendByte(String[i]);  //发送1个字符(1个字符正好1个字节)
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)  //计算X的Y次方(用于分离个十百千万位)
{
	uint32_t Result = 1;
	while(Y--)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)  //发送一个数字
{
	uint8_t i = 0;
	for(i = 0; i < Length; i++) //数字有几位就发送几次,每次发送一位数,从高位开始
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');  //发送1个字符(1个字符正好1个字节)
	}
}

int fputc(int ch, FILE *f)   //重写fputc函数(它是printf函数的底层)
{
	Serial_SendByte(ch);  //将fputc重定向到串口,这样调用printf时就能在串口助手上输出字符串
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

void Serial_SendPacket(void)
{
	Serial_SendByte(0xFF);    //发送包头
	Serial_SendArray(Serial_TxPacket, 4);  //发送4个数据
	Serial_SendByte(0xFE);    //发送包尾
}

uint8_t Serial_GetRxFlag(void)  //返回标志位
{
	if(Serial_RxFlag == 1)  //如果有接收到新数据包,返回1并将标志位置0,等待下一个数据包到来
	{
		Serial_RxFlag = 0;  //防止同一个数据包多次返回
		return 1;
	}
	return 0;
}

void USART1_IRQHandler(void)   //USART1的中断函数
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)  //程序检测到RXNE为1时,就可以将RDR中的数据读走
	{
		uint8_t RxData = USART_ReceiveData(USART1);  //获取1字节数据
		if(RxState == 0)   //状态0——等待包头
		{
			if(RxData == 0xFF)  //识别到包头,转入状态1
			{
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if(RxState == 1)   //状态1——接收数据
		{
			Serial_RxPacket[pRxPacket] = RxData;  //读取数据包的内容
			pRxPacket++;
			if(pRxPacket >= 4)    //读取完毕,转入状态2(固定包长以数据个数作为判断)
			{
				RxState = 2;
			}
		}
		else if(RxState == 2)   //状态2——等待包尾
		{
			if(RxData == 0xFE)  //识别到包尾,转入状态0
			{
				RxState = 0;
				Serial_RxFlag = 1;   //读取到新数据包,标志位置为1
			}
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);    //其实读取RDR时硬件会自动置RXNE为0,软件可以不考虑这一点
	}
}

(6) Paste the following code in the main.c file, compile it, download the program to the development board, and debug it according to the comments of the main function. (The code of the button module does not need to be changed)

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "Serial.h"
#include "Key.h"

uint8_t KeyNum;

int main()
{
	OLED_Init();
	Serial_Init();
	Key_Init();
	
	OLED_ShowString(1,1,"TxPacket:");
	OLED_ShowString(3,1,"RxPacket:");
	
	//单片机发送数据包的初值
	Serial_TxPacket[0] = 0x01;
	Serial_TxPacket[1] = 0x02;
	Serial_TxPacket[2] = 0x03;
	Serial_TxPacket[3] = 0x04;
	
	while(1)
	{
		KeyNum = Key_GetNum();
		if(KeyNum == 1)   //按下按键1,改变数据包内容并发送到电脑端
		{
			Serial_TxPacket[0]++;
			Serial_TxPacket[1]++;
			Serial_TxPacket[2]++;
			Serial_TxPacket[3]++;
			Serial_SendPacket();  //将新数据包Serial_TxPacket连同包头包尾发送到电脑端(电脑端不解析数据包,连同包头包尾全部显示)
			
			OLED_ShowHexNum(2, 1, Serial_TxPacket[0], 2);
			OLED_ShowHexNum(2, 4, Serial_TxPacket[1], 2);
			OLED_ShowHexNum(2, 7, Serial_TxPacket[2], 2);
			OLED_ShowHexNum(2, 10, Serial_TxPacket[3], 2);
		}
		//在串口助手中往单片机发送“FF 11 22 33 44 FE”数据包,OLED屏显示去包头去包尾的数据包(可能存在的问题:如果数据包发送频率过快,主程序可能来不及处理,这会引发数据包丢失或者错位的现象,下例给出解决错位的其中一种方法)
		if(Serial_GetRxFlag() == 1)   //如果单片机收到新数据包
		{
			OLED_ShowHexNum(4, 1, Serial_RxPacket[0], 2);
			OLED_ShowHexNum(4, 4, Serial_RxPacket[1], 2);
			OLED_ShowHexNum(4, 7, Serial_RxPacket[2], 2);
			OLED_ShowHexNum(4, 10, Serial_RxPacket[3], 2);
		}
	}
}

16. Serial port sends and receives text packets:

(1) There are two types of text data packets:

①Fixed package length, including header and tail:

②Variable packet length, including header and tail:

(2) Text data packet reception: (The figure below shows the variable packet length)

(4) Connect the circuit as shown in the figure below, and copy the project folder of the above example to use as a template.

(5) Modify the Serial.h file and Serial.c file:

①Serial.h file:

#ifndef __Serial_H
#define __Serial_H

#include <stdio.h>

extern char Serial_RxPacket[];
extern uint8_t Serial_RxFlag;

void Serial_Init(void);
void Serial_SendByte(uint8_t Byte);
void Serial_SendArray(uint8_t *Array, uint16_t Length);
void Serial_SendString(char *String);
void Serial_SendNumber(uint32_t Number, uint8_t Length);
void Serial_Printf(char *format, ...);

#endif

②Serial.c file:

#include "stm32f10x.h"                  // Device header
#include <stdio.h>
#include <stdarg.h>

char Serial_RxPacket[100];
uint8_t Serial_RxFlag;

void Serial_Init(void)
{
	//开启GPIO和USART的时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
	
	//把USART1_TX(PA9)配置为复用输出模式,把USART1_RX(PA10)配置为输入模式
	GPIO_InitTypeDef GPIO_InitStructure;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	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);
	
	//配置USART1
	USART_InitTypeDef USART_InitStructure;
	USART_InitStructure.USART_BaudRate = 9600;              //波特率
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;  //不使用硬件流控
	USART_InitStructure.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;         //本例需要发送和接收功能
	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);
	
	//开启中断用于接收数据,配置NVIC
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);       //中断检测RXNE位(不使用中断会消耗很多软件资源)
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //分组方式2(2位抢占优先级,2位响应优先级)
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;         //USART1到NVIC的通道
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;           //开启中断
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;        //响应优先级
	NVIC_Init(&NVIC_InitStructure);
	
	//开启USART1
	USART_Cmd(USART1, ENABLE);
}

void Serial_SendByte(uint8_t Byte)  //发送一个字节
{
	USART_SendData(USART1, Byte);   //写一字节数据进TDR寄存器
	while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
	//标志位TXE为0时,TDR中的数据还没写进发送移位寄存器,需要等待
	//TXE标志位不需要软件清除,写TDR寄存器时TXE自动置0
}

void Serial_SendArray(uint8_t *Array, uint16_t Length)  //发送一个数组
{
	uint16_t i = 0;
	for(i = 0; i < Length; i++)     //有多少个元素就循环几次
	{
		Serial_SendByte(Array[i]);  //发送1个元素(1个元素正好1个字节)
	}
}

void Serial_SendString(char *String)  //发送一个字符串
{
	uint16_t i = 0;
	for(i = 0; String[i] != '\0'; i++) //直到遇到字符串结束标志,否则持续发送
	{
		Serial_SendByte(String[i]);  //发送1个字符(1个字符正好1个字节)
	}
}

uint32_t Serial_Pow(uint32_t X, uint32_t Y)  //计算X的Y次方(用于分离个十百千万位)
{
	uint32_t Result = 1;
	while(Y--)
	{
		Result *= X;
	}
	return Result;
}

void Serial_SendNumber(uint32_t Number, uint8_t Length)  //发送一个数字
{
	uint8_t i = 0;
	for(i = 0; i < Length; i++) //数字有几位就发送几次,每次发送一位数,从高位开始
	{
		Serial_SendByte(Number / Serial_Pow(10, Length - i - 1) % 10 + '0');  //发送1个字符(1个字符正好1个字节)
	}
}

int fputc(int ch, FILE *f)   //重写fputc函数(它是printf函数的底层)
{
	Serial_SendByte(ch);  //将fputc重定向到串口,这样调用printf时就能在串口助手上输出字符串
	return ch;
}

void Serial_Printf(char *format, ...)
{
	char String[100];
	va_list arg;
	va_start(arg, format);
	vsprintf(String, format, arg);
	va_end(arg);
	Serial_SendString(String);
}

void USART1_IRQHandler(void)   //USART1的中断函数
{
	static uint8_t RxState = 0;
	static uint8_t pRxPacket = 0;
	if(USART_GetFlagStatus(USART1, USART_IT_RXNE) == SET)  //程序检测到RXNE为1时,就可以将RDR中的数据读走
	{
		uint8_t RxData = USART_ReceiveData(USART1);  //获取1字节数据
		if(RxState == 0)   //状态0——等待包头
		{
			if(RxData == '@' && Serial_RxFlag == 0)  //上一个数据包处理完毕且识别到包头,读走包头,转入状态1
			{
				RxState = 1;
				pRxPacket = 0;
			}
		}
		else if(RxState == 1)   //状态1——接收数据
		{
			if(RxData == '\r')  //识别到第一个包尾‘\r’,转入状态2
			{
				RxState = 2;
			}
			else
			{
				Serial_RxPacket[pRxPacket] = RxData;  //读取数据包的内容
				pRxPacket++;
			}
		}
		else if(RxState == 2)   //状态2——等待包尾
		{
			if(RxData == '\n')  //识别到第二个包尾,转入状态0
			{
				Serial_RxPacket[pRxPacket] = '\0';  //字符串结束标志
				RxState = 0;
				Serial_RxFlag = 1;   //读取到新数据包,标志位置为1
			}
		}
		USART_ClearITPendingBit(USART1, USART_IT_RXNE);    //其实读取RDR时硬件会自动置RXNE为0,软件可以不考虑这一点
	}
}

(6) Paste the following code in the main.c file, compile it, download the program to the development board, and debug it according to the comments of the main function. (The code of the LED module does not need to be changed)

#include "stm32f10x.h"                  // Device headerCmd
#include "OLED.h"
#include "Serial.h"
#include <string.h>
#include "LED.h"

int main()
{
	OLED_Init();
	Serial_Init();
	LED_Init();
	
	OLED_ShowString(1,1,"TxPacket:");
	OLED_ShowString(3,1,"RxPacket:");
	
	while(1)
	{
		//在串口助手中往单片机发送命令(注意带上包头包尾),OLED屏显示去包头去包尾的文本数据包
		if(Serial_RxFlag == 1)   //如果单片机收到新数据包
		{
			OLED_ShowString(4,1,"                ");  //擦除第四行原本的显示(旧文本比新文本长,显示会有瑕疵)
			OLED_ShowString(4,1,Serial_RxPacket);     //显示新文本
		
			if(strcmp(Serial_RxPacket, "LED_ON") == 0)  //发送命令"LED_ON"(要带包头包尾),LED灯打开
			{
				LED1_ON();
				Serial_SendString("LED_ON_OK\r\n");       //单片机向电脑发送结果
				OLED_ShowString(2,1,"                ");  //擦除第二行原本的显示(旧文本比新文本长,显示会有瑕疵)
				OLED_ShowString(2,1,"LED_ON_OK");         //显示新文本
			}
			else if(strcmp(Serial_RxPacket, "LED_OFF") == 0)  //发送命令"LED_OFF"(要带包头包尾),LED灯关闭
			{
				LED1_OFF();
				Serial_SendString("LED_ON_OFF\r\n");      //单片机向电脑发送结果
				OLED_ShowString(2,1,"                ");  //擦除第二行原本的显示(旧文本比新文本长,显示会有瑕疵)
				OLED_ShowString(2,1,"LED_ON_OFF");        //显示新文本
			}
			else    //发送错误命令
			{
				Serial_SendString("ERROR_COMMAND\r\n");   //单片机向电脑发送结果
				OLED_ShowString(2,1,"                ");  //擦除第二行原本的显示(旧文本比新文本长,显示会有瑕疵)
				OLED_ShowString(2,1,"ERROR_COMMAND");     //显示新文本
			}
			
			Serial_RxFlag = 0;   //新数据包处理完毕,允许接收下一个数据包
			//不过处理数据包需要耗费时间,如果数据包发送频率很高,还是会存在丢包现象
		}
	}
}

Guess you like

Origin blog.csdn.net/Zevalin/article/details/134753740