前言:

        本篇将使用串口与电脑进行通信,同时也会捎带着提一下串口的部分知识,后续会将树莓派与STM32建立通信

浅谈STM32串口:

        STM32F103RCT6芯片上拥有3个USART外设,(分别是UASRT1、UASRT2、UASRT3)以及两个UART外设(UART4、UART5)。

        USART即通用同步异步收发器,它能够灵活地与外部设备进行全双工数据交换,它支持同步单向通信和半双工单线通信;还支持 LIN(域互连网络)、智能卡协议与 IrDA(红外线数据协会) SIRENDEC 规范,以及调制解调器操作 (CTS/RTS)。并且可以使用 DMA 可实现高速数据通信。USART在STM32中应用最多的是printf输出调试信息,这里我会给大家讲解一下如何使用printf向串口助手打印信息

下面这段代码是对fputc的重定向,以便我们使用printf函数,我这里是使用UASRT2作为输出的,大家如果用其他串口可以更改一下串口号

#include "stdio.h"
int fputc(int ch, FILE *f)
{
	USART_SendData(USART2, (uint8_t) ch);
	
	while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET){}
	
  return (ch);
}
 
int fgetc (FILE *f)  
{
	while(USART_GetFlagStatus(USART2,USART_FLAG_RXNE) == RESET);
	return (int)USART_ReceiveData(USART2);
}

        此外,我们还需要调用一下专用的库,点击魔法棒,打开Target界面,选中箭头所指内容,最后点击OK就可以了

 之后,我们就可以在程序中使用printf了

        而UART即通用异步收发器,它是在USART基础上裁剪掉了同步通信功能,同步和异步主要看其时钟是否需要对外提供。

正片开始:

        配置串口的一般步骤如下所示

1、使能对应管脚时钟和串口时钟,这里我使用的PA2 PA3 USART2

        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);

        RCC_APB1PeriphClockCmd(RCC_APB2Periph_USART2,ENABLE);

2、设置GPIO工作模式,这里要注意,Tx要设置为GPIO_Mode_AF_PP,Rx要设置为GPIO_Mode_IN_FLOATING,不过Rx不需要配置管脚速度

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//TX

        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//RX

3、初始化串口参数

        USART_InitStructure.USART_BaudRate = Baudrate;
        USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
        USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
        USART_InitStructure.USART_Parity = USART_Parity_No;                 
        USART_InitStructure.USART_StopBits = USART_StopBits_1;        
        USART_InitStructure.USART_WordLength = USART_WordLength_8b;

        USART_Init(USART2, &USART_InitStructure);

4、使能串口

        USART_Cmd(USART2, ENABLE);

5、设置串口中断类型并使能

        USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);

6、设置串口中断优先级,使能串口中断通道

        这一点一开始我给忘了,结果就没能让串口进入中断,我这里是随便配置的,大家可以根据自己的需要进行配置

        NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; 
        NVIC_Init(&NVIC_InitStructure);

7、编写串口中断服务函数

串口中断中常见的几种函数:
        这里我给大家罗列一下几种常见的标志位,简单说一下这些标志位的功能,防止大家混淆

1、USART_FLAG_TXE,该标志位是发送数据寄存器空标志位。当发送数据寄存器里的数据被全部取完时,该寄存器是空的,那么该标志位就会被置1。通过这个标志位的值可以判断发送数据寄存器中的数据有没有完全被取走,当该寄存器是空的时候,可以提醒CPU继续往该寄存器里存入新的数据;

2、USART_FLAG_TC,是当发送移位寄存器里的每个字节通过TX脚一位一位发送出去之后,该标志位值就会被置1。通过这个标志位的值可以判断发送移位寄存器里的数据有没有被全部发送出去;若使用 USART_FLAG_TC来进行判断数据是否发送完成,注意需使能TC中断 USART_ITConfig(USART1, USART_IT_TC, ENABLE);

3、USART_IT_IDLE,是串口收到一帧数据后,也可以叫做一包数据,才会产生置位。这里要想清除这个中断的话,必须要先读取SR寄存器,再读取DR寄存器,

USART2->SR; //先读SR寄存器
USART2->DR; //再读DR寄存器

4、USART_IT_RXNE当接收到1个字节,会产生USART_IT_RXNE中断,这里要想清除这个中断的话,要使用这个函数USART_ClearITPendingBit(USART2, USART_IT_RXNE);

串口中断中常见的几种问题:

        1、数据传输的过程中出现乱码,如果是一开始出现乱码,那可能是波特率没有选对的问题,如果是过程中出现乱码,我们这时候就要考虑到我们定义的数组长度不够的情况,也有可能是中断被打断,所以这里对于中断优先级的要求是比较高的。

        2、程序卡在中断服务函数中无法跳出,这时候就要考虑到是不是USART_FLAG_ORE被置位了,如果是的话,那么就要先对SR寄存器进行操作,再对DR寄存器进行操作,这时候也可以用

USART_ClearFlag(USART1,USART_FLAG_ORE);//清除ORE标志位
USART_ReceiveData(USART1);               //抛弃接收到的数据

       3、如果出现数据漏发现象,这时候可能是USART_DR寄存器里的TDR寄存器还没有将内容发给移位寄存器,还没发送出去,新的数据就被重新传入移位寄存器了。

数据处理所用到的函数->sscanf:

        为了方便处理串口助手传回来的数据,我这里调用了stdio.h下的sscanf函数,它的返回值是匹配成功的次数,根据返回值我们可以检验一下是否提取成功,只不过我这里是用其来提取字符串中所包含的数字。其用法大致跟printf差不太多,我这里也不再赘述

if(sscanf((const char *)rxData,"r:%dl:%d",&right_param,&lift_param) == 2)

代码部分:

#include "stm32f10x.h"
#include "UART.h"
#include "stdio.h"
 
// PA2    ->  UART2_Tx
// PA3    ->  UART2_Rx
 
uint8_t flag_full=0;
uint8_t rxData[RXDATA_SIZE];
 
unsigned int lift_param = 0;		//设置为全局变量
unsigned int right_param = 0;		
 
 
void UART2_Init(unsigned int Baudrate)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;//TX
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//RX
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	USART_InitStructure.USART_BaudRate = Baudrate;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;//关闭            
    //硬件流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | 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(USART2, &USART_InitStructure);
  USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);	//允许接收中断
  USART_Cmd(USART2, ENABLE);
	
	NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;		
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			
	NVIC_Init(&NVIC_InitStructure);	
	
}
 
void USART_SendByte(USART_TypeDef * pUSARTx,uint8_t date)
{
	USART_SendData(pUSARTx,date);
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE) == RESET);
}
 
void USART_SendString(USART_TypeDef * pUSARTx,char *str)
{
	while( *str!='\0')
	{
		USART_SendByte(pUSARTx,*str++);
	}
	
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}
 
void USART2_IRQHandler()
{
	static unsigned int i=0;
	uint8_t Res;
	if(USART_GetITStatus(USART2, USART_IT_RXNE) != RESET)  
	{ 
		USART_ClearITPendingBit(USART2,USART_IT_RXNE);
		Res =USART_ReceiveData(USART2);	
 
		if(!flag_full)
		{
			rxData[i]=Res;
			i++;
			if(rxData[i-1]=='\n'||i>RXDATA_SIZE)
			{
			  rxData[i] = 0;
				flag_full=1;	//这个标志位保证每次只能进一次中断
				i=0;
			}
		}else
		{
    }		
	}
}
 
void data_processing()
{
	if(flag_full == 1)
	{
		if(sscanf((const char *)rxData,"r:%dl:%d",&right_param,&lift_param) == 2)
		{
			flag_full = 0;//开始下一次读取
//			USART_SendString(USART2,"r:ok");
//			USART_SendString(USART2,"l:ok\r\n");
//			while(USART_GetFlagStatus(USART2,USART_FLAG_TC) != SET);
			printf("r:%04d\r\n",right_param);//%04d,左侧自动补零,这样在串口助手上好看些
			printf("l:%04d\r\n",lift_param);
		}
	}
}
 
//重定向fputc方便使用printf向串口助手打印数据
int fputc(int ch, FILE *f)
{
	USART_SendData(USART2, (uint8_t) ch);
	
	while (USART_GetFlagStatus(USART2, USART_FLAG_TXE) == RESET){}
	
  return (ch);
}
 
int fgetc (FILE *f)  
{
	while(USART_GetFlagStatus(USART2,USART_FLAG_RXNE) == RESET);
	return (int)USART_ReceiveData(USART2);
}

uart.h

#ifndef  __UART_H
#define  __UART_H
 
#include "stm32f10x.h"
#include "stdio.h"
#define RXDATA_SIZE 500
 
void UART2_Init(unsigned int Baudrate);
void USART_SendByte(USART_TypeDef * pUSARTx,uint8_t date);
void USART_SendString(USART_TypeDef * pUSARTx,char *str);
void data_processing(void);
int fputc(int ch, FILE *f);
int fgetc (FILE *f);
 
#endif

main.c

#include "stm32f10x.h"
#include "led.h"
#include "sysclock.h"
#include "motor.h"
#include "UART.h"
 
 
int main()
{
	LED_init();
	Motor1_Init();
	Motor2_Init();
	Motor3_Init();
	Motor4_Init();
	USART2_Init(115200);
	GPIO_WriteBit(GPIOC, GPIO_Pin_12, Bit_RESET);
	USART_SendString(USART2,"hello\r\n");
	while(1)
	{
//		Motor1_forward(1999);
//		Motor2_forward(7999);
//		Motor3_forward(1999);
//		Motor4_forward(1999);
		data_processing();
	}
}

成果展示:

        这里呢,我用串口助手向STM32F103RCT6发送数据,数据格式位r:xxxxl:xxxx,回馈的数据格式为r:xxxx\r\n l:xxxx\r\n

 后续将会以树莓派4B作为上位机向STM32下位机发送数据,数据格式仍为上述格式。