STM32的USART窗口通讯程序——串口通讯

一、了解通讯

  1. 串行通讯与并行通讯
    串行通讯:设备之间通过少量数据信号线,地线以及控制信号线,按数据形式一位一位地传输数据。
    并行通讯:设备之间通过信号线,同时传输多个数据位的数据。

    串行通讯的通讯距离和抗干扰能力要优于并行通讯,并且成本更低,而并行通讯的传输速率要优于串行通讯。

  2. 全双工,半双工和单工通讯
    全双工通讯:设备之间可以同时收发数据。
    半双工通讯:设备之间可以收发数据,但是不能够同时进行。
    单工通讯:单方向的进行数据的发送和接收,即一个设备要么作为发送设备,要么作为接收设备。

  3. 同步通讯和异步通讯
    同步通讯:收发双方使用同一个信号线作为时钟信号,在时钟信号的驱动下双方进行协调,同步数据。
    异步通讯:不采用时钟信号进行数据同步,而是在数据信号中穿插一些同步用的信号位来实现同步。

本文章介绍的是一种常用的串行通讯方式——串口通讯

二、串口通讯的相关介绍

  1. 两种电平标准
    TTL标准:当电平处于2.4~5V之间时,表示逻辑1;当电平处于 0 ~0.5V时,表示逻辑0。
    RS-232标准:当电平处于-15~-3V之间时,表示逻辑1;当电平处于3 ~15V时,表示逻辑0。

    RS-232标准的传输距离及抗干扰能力更好。重点是两种标准的转换。

  2. USB转串口通讯
    USB转串口主要是设备跟电脑通信,该过程需要电平转换芯片来实现,常用的芯片有CH340,PL2303,CP2102,FT232。使用的时候需要安装电平转换芯片的驱动。

  3. 原生的串口到串口
    主要是控制器跟串口设备或者传感器通信,不需要电平转换芯片来转换电平,直接使用TTL电平通信。例如GPS模块。

  4. 波特率与比特率
    波特率即每秒钟传输的码元个数,便于对信号进行解码。常用的波特率4800,9600,115200。比特率即每秒钟传输的二进制位数。

  5. 通讯的起始和停止信号
    起始信号由逻辑0的数据位表示,停止信号可由0.5,1.5,1或2个1的数据位来表示。双方自行约定。

  6. 校验
    通过校验码来避免数据在传输过程中,受到外部干扰而发生偏差。常采用奇偶校验,只能检测出发生偏差位的1位。

三、USART接发通信

  1. 数据发送和接收的流程

    数据格式
    M:字长
    表示数据的长度,0表示长度为8bit,1表示长度为9bit,通常设置为0。
    STOP:停止位
    采用2位来设置停止位的位数,00表示1个停止位,01表示0.5个停止位,10表示两个停止位,11表示1.5个停止位。
    PCE:校验控制使能
    用于选择是否进行硬件校验控制,0表示禁止校验控制,1表示使能校验控制。
    PS:校验选择
    用于选择采用奇校验还是偶校验,0表示偶校验,1表示奇校验。
    PEIP:PE中断使能
    由软件设置或清除,0表示禁止产生中断,1表示当USART_SR中的PE为1时,产生USART中断。
    PE:校验错误
    在接收模式下,若出现奇偶校验错误,硬件对该位置位,0表示没有奇偶校验错误,1表示奇偶错误。

    发送数据过程
    数据先从PWDATA总线写入到发送数据寄存器(TDR),然后将数据一位一位的移到发送移位寄存器中,接着通过TX引脚发送出去。
    接收数据过程
    数据从RX引脚发到接收移位寄存器,接着将数据放到接收数据寄存器(ADR)中,最后CPU或者DMA进行读操作。

  2. 编写代码前的准备
    ①将使用已经建好的一个文件(使用固件库),将其备份成一个新文件
    ②在新文件中的User文件下,新建一个bsp_uart.h,bsp_uart.c,main.c文件
    ③使用Keil打开文件,将新建文件添加进来
    添加方式
    在这里插入图片描述
    ④查看相关文件
    对于本文章需要查看usart的文件
    查看方法
    在这里插入图片描述

  3. 相关数据的介绍
    ①USART初始化结构体

    typedef struct
    {
          
          
      uint32_t USART_BaudRate;//波特率BRR            
      uint16_t USART_WordLength;//字长CR1_M          	
      uint16_t USART_StopBits;//停止位CR2_STOP           
      uint16_t USART_Parity;//校验控制CR1_PCE,CR1_PS             
      uint16_t USART_Mode;//模式选择CR1_TE,CR1_RE(发送和接收)               
      uint16_t USART_HardwareFlowControl;//硬件流选择CR3_CTSE,CR3_RTSE 
    } USART_InitTypeDef;
    

    在固件库中,对每个属性都进行了一些相关设置,请自行进行查看。
    ②同步时钟初始化结构体

    typedef struct
    {
          
          
    
      uint16_t USART_Clock;//同步时钟CR2_CLKEN(时钟使能)
      uint16_t USART_CPOL;//极性CR2_CPOL
      uint16_t USART_CPHA;//相位CR2_CPRA
      uint16_t USART_LastBit;//最后一个位的时钟脉冲CR2_LBC
    } USART_ClockInitTypeDef;
    
  4. 相关固件函数介绍
    ①串口初始化函数

    void USART_Init(USART_TypeDef* USARTx, USART_InitTypeDef* USART_InitStruct)
    

    ②中断配置函数

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

    ③串口使能函数

    void USART_Cmd(USART_TypeDef* USARTx, FunctionalState NewState)
    

    ④数据发送函数

    void USART_SendData(USART_TypeDef* USARTx, uint16_t Data)
    

    ⑤数据接收函数

    uint16_t USART_ReceiveData(USART_TypeDef* USARTx)
    

    ⑥中断状态位获取函数

    ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
    

    在固件库中,还定义了其他函数,可以自行查看。

  5. 代码编写
    ①一个简单的串口发送数据
    bsp_uart.h

#ifndef _BSP_UART_H
#define _BSP_UART_H

#include "stm32f10x.h"
//串口1-USART1
#define DEBUG_UARTx										USART1;
#define DEBUG_UART_CLK      					RCC_APB2Periph_USART1
#define DEBUG_UART_APBxClkCmd					RCC_APB2PeriphClockCmd
#define DEBUG_UART_BAUDRATE						115200

//USART GPIO 引脚宏定义
#define DEBUG_USART_GPIO_CLK				  (RCC_APB2Periph_GPIOA)
#define DEBUG_USART_GPIO_APBxClkCmd		RCC_APB2PeriphClockCmd
#define DEBUG_USART_TX_GPIO_PORT			GPIOA
#define DEBUG_USART_TX_GPIO_PIN       GPIO_Pin_9
#define DEBUG_USART_RX_GPIO_PORT			GPIOA
#define DEBUG_USART_RX_GPIO_PIN				GPIO_Pin_10

#define DEBUG_USART_IRQ								USART1_IRQn
#define DEBUG_USART_IRQHandler				USART1_IRQHandler
void DEBUG_UART_Config(void);

#endif /*_BSP_UART_H*/

bsp_uart.c

#include "./uart/bsp_uart.h"

void DEBUG_UART_Config(void)
{
    
    
	/*第一步:初始化GPIO*/
	//定义GPIO对象
	GPIO_InitTypeDef GPIO_InitStructure;
	//定义串口对象
	USART_InitTypeDef USART_InitStructure;
	//打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK,ENABLE);
	//将USART Tx(发送数据)的GPIO的配置为推挽复用模式
	GPIO_InitStructure.GPIO_Pin=DEBUG_USART_TX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
	GPIO_Init(DEBUG_USART_TX_GPIO_PORT,&GPIO_InitStructure);
	//将USART Rx(接收数据)的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Pin=DEBUG_USART_RX_GPIO_PIN;
	GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IN_FLOATING;
	GPIO_Init(DEBUG_USART_RX_GPIO_PORT,&GPIO_InitStructure);
	
	/*第二步:配置串口的初始化结构体*/
	//打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_CLK,ENABLE);
	//配置波特率
	USART_InitStructure.USART_BaudRate=DEBUG_USART_BAUDRATE;
	//配置数据字长
	USART_InitStructure.USART_WordLength=USART_WordLength_8b;
	//配置停止位
	USART_InitStructure.USART_StopBits=USART_StopBits_1;
	//配置校验位
	USART_InitStructure.USART_Parity=USART_Parity_No;
	//配置硬件流控制
	USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None;
	//配置工作模式,采用收发一起,即可接收数据,也可发送数据
	USART_InitStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx;
	//串口的初始化配置
	USART_Init(DEBUG_USARTx,&USART_InitStructure);
	/*第三步:使能串口*/
	USART_Cmd(DEBUG_USARTx,ENABLE);
}

main.c

#include "stm32f10x.h"
#include "./uart/bsp_uart.h"
int main(void)
{
    
    	
	DEBUG_UART_Config();
	USART_SendData(DEBUG_USARTx,0xaa);
	while(1);
}

通过点击图中图标,进行编译
在这里插入图片描述
烧录程序的设置(本过程采用ST_Link)
Output的设置
在这里插入图片描述
选择对应的下载器
在这里插入图片描述
此处端口选择SW
在这里插入图片描述
将图中内容打上勾
在这里插入图片描述
设置完成后,再次编译程序,就可以进行烧录程序。
烧录成功
在这里插入图片描述
连接USB,打开串口多功能助手,如下图
在这里插入图片描述
需要安装CH34驱动程序

安装过程极其简单,下载地址可以参考下面链接:
http://www.wch.cn/search?t=all&q=CH341A

安装完成后,将调试助手的内容设置与程序中的设置一致。
发送数据效果(通过点击开发板的复位键,就会显示一次数据)
在这里插入图片描述
②实现串口的复杂通信
要求

STM32不断的给电脑发送 “hello windows!”;只有当电脑向STM32发送 “Stop,stm32!” 后,STM32才终止向电脑发送消息。

bsp_uart.h增添如下代码

void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch);
void USART_SendString(USART_TypeDef * pUSARTx,char *str);
void delay_ms(uint16_t delay_ms);

bsp_uart.c增添如下代码

//发送一个字节
void Usart_SendByte(USART_TypeDef * pUSARTx,uint8_t ch)
{
    
    
	USART_SendData(pUSARTx,ch);// 发送一个字节数据到USART
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TXE)==RESET);// 等待发送数据寄存器为空
}
//发送字符串
void USART_SendString(USART_TypeDef * pUSARTx,char *str)
{
    
    
	unsigned int k=0;
	do
	{
    
    
		Usart_SendByte(pUSARTx,*(str+k));
		k++;
	}while(*(str+k)!='\0');
	// 等待发送完成
	while(USART_GetFlagStatus(pUSARTx,USART_FLAG_TC)==RESET);
}
//微秒级的延时
void delay_us(uint32_t delay_s)
{
    
        
  volatile unsigned int i;
  volatile unsigned int t;
  for (i = 0; i < delay_s; i++)
  {
    
    
    t = 11;
    while (t != 0)
    {
    
    
      t--;
    }
  }
}
//毫秒级的延时函数
void delay_ms(uint16_t delay_ms)
{
    
        
  volatile unsigned int num;
  for (num = 0; num < delay_ms; num++)
  {
    
    
    delay_us(1000);
  }
}
//配置嵌套向量中断控制器NVIC
static void NVIC_Configuration(void)
{
    
    
  NVIC_InitTypeDef NVIC_InitStructure;
  
  // 嵌套向量中断控制器组选择
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
  
  // 配置USART为中断源
  NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;
  // 抢断优先级
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
  // 子优先级
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  // 使能中断
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  // 初始化配置NVIC
  NVIC_Init(&NVIC_InitStructure);
}

//并在DEBUG_USART_Config函数第三步前面添加
//串口中断优先级配置
NVIC_Configuration();
//使能串口接收中断
USART_ITConfig(DEBUG_USARTx,USART_IT_RXNE,ENABLE);

注意:static void NVIC_Configuration(void)函数要位于void DEBUG_UART_Config(void)函数之前,否则编译会出现错误。

main.c修改成如下

#include "stm32f10x.h"
#include "./uart/bsp_usart.h"


// 接收缓冲,最大100个字节
uint8_t USART_RX_BUF[100];
// 接收状态标记位
uint16_t USART_RX_FLAG=0;

//串口中断服务函数
void DEBUG_USART_IRQHandler(void)
{
    
    
	uint8_t temp;
	//接收中断
	if(USART_GetFlagStatus(DEBUG_USARTx, USART_IT_RXNE) != RESET)
	{
    
    
		// 读取接收的数据
		temp = USART_ReceiveData(DEBUG_USARTx);
		//接收未完成
		if((USART_RX_FLAG & 0x8000)==0)
		{
    
    
			//接收到了0x0d
			if(USART_RX_FLAG & 0x4000)
			{
    
    
				// 接收错误,重新开始
				if(temp != 0x0a) USART_RX_FLAG=0;
				// 接收完成
				else USART_RX_FLAG |= 0x8000;
			}
			// 还未接收到0x0d
			else
			{
    
    
				if(temp == 0x0d)
				{
    
    
					USART_RX_FLAG |= 0x4000;
				}
				else
				{
    
    
					USART_RX_BUF[USART_RX_FLAG & 0x3FFF]=temp;
					USART_RX_FLAG++;
					//接收数据错误,重新开始接收
					if(USART_RX_FLAG > 99) USART_RX_FLAG=0;
				}
			}
		}
	}
}


int main(void)
{
    
    
	uint8_t len=0;
	uint8_t i=0;
	// USART初始化
	USART_Config();
	while(1)
	{
    
    
		if(USART_RX_FLAG & 0x8000)
		{
    
    
			// 获取接收到的数据长度
			len = USART_RX_FLAG & 0x3FFF;
			USART_SendString(DEBUG_USARTx, "发送消息:\n");
			for(i=0; i<len;i++)
			{
    
    
				// 向串口发送数据
				USART_SendData(DEBUG_USARTx, USART_RX_BUF[i]);
				//等待发送结束
				while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TC)!=SET);
			}
			USART_SendString(DEBUG_USARTx, "\n\n");
			if(strcmp((char *)USART_RX_BUF,"Stop,stm32!")==0)
			{
    
    
				USART_SendString(DEBUG_USARTx, "stm32已停止发送!");
				break;
			}
			USART_RX_FLAG=0;
			memset(USART_RX_BUF,0,sizeof(USART_RX_BUF));
		}
		else
		{
    
    
			USART_SendString(DEBUG_USARTx, "hello windows!\n");
			delay_ms(800);
		}
	}
}

编译烧录程序
效果如下
在这里插入图片描述
分析代码
while循环

while循环的功能是让开发板不断向电脑发送 “hello windows!”的消息;当程序接收到中断(USART_IT_RXNE)时,主程序就会停下,然后执行中断服务函数,当中断服务函数执行完成后,将继续返回到主程序接收到中断的地方继续执行;此时,由于接收到上位机发送的数据,在中断服务函数对一些状态位进行改变并将接收的数据保存下来,所以程序将进入判断语句进行判断接收的数据是否为 “Stop,stm32!”,如果是,则跳出循环,终止程序;如果否,则继续循环。

中断服务函数
在这里插入图片描述

USART_RX_FLAG为一个16位的状态标记变量,其中,0~bit13是用于存储接收数据,根据需求选择,只要不超过这个范围就可以了;bit14用于存储是否接收到"回车\r(ASCLL:0x0d)",如果接收到则执行:将bit14置1(代码用USART_RX_FLAG |= 0x4000表示),否则不变化保持为0;如果接收了"回车\r",然后在判断下一个接收数据是否为"换行\n(ASCLL:0x0a)",如果是则执行:将bit15置1(USART_RX_FLAG |= 0x8000),表明数据接收已完成,如果有剩余的接收数据将不再存入缓存USART_RX_BUF中;如果否,则执行USART_RX_FLAG=0,表明接收错误,重新开始接收。

中断的流程

当产生一个中断的时候,主程序就会停止下来,接收到中断后,开始执行中断程序,直到中断程序执行完成后,返回到主程序接收到中断的地方,继续执行。

③使用printf来实现数据的显示
bsp_uart.c添加重定向c库函数printf

//重定向c库函数printf到串口,重定向后可使用printf函数
int fputc(int ch, FILE *f)
{
    
    
	// 发送一个字节数据到串口
	USART_SendData(DEBUG_USARTx, (uint8_t) ch);
	// 等待发送完毕
	while(USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_TXE) == RESET);
	return (ch);
}

将main.c中使用的USART_SendString函数修改成printf输出就可以,完成数据的收发效果一致。

四、总结

整个过程并不是很复杂,只不过需要留意一些小问题。比如对于发送数据的时候,需要回车,数据才能够被正常接收,否则点击发送,不会有相应的反应。对于初学者来说,可能不是很清楚一些线的接法,可以在网上查找资料,还是很容易看懂的。

五、参考资料

  1. 野火F103-MINI视频学习-【中级篇】
  2. 【正点原子】STM32开发板实验教程(F103)
  3. ST-LINK连接方式

猜你喜欢

转载自blog.csdn.net/qq_43279579/article/details/110138564