STM32串口通信指南:构建高效可靠的数据链路

目录

一. 引言

二. 串口通信基础知识

三. 实验硬件平台搭建

四. 实验软件设计

五. 串口通信实验

六. 总结


一. 引言

        STM32结合串口通信可以实现数据的传输和控制,是嵌入式系统中的重要技术之一。本实验旨在通过STM32的串口通信接口进行数据的传输和控制,实现串口通信的基本功能和使用方法。通过本实验,可以掌握STM32串口通信的基本原理和实现方法,理解串口通信协议和同步/异步通信方式的特点和应用场景,加深对STM32串口通信的理解和应用能力,为后续嵌入式系统开发中的串口通信应用打下基础。

二. 串口通信基础知识

2.1、串口基本原理简介

        串口通信是一种通过串行接口进行数据传输的通信方式,它基于串行通信原理,将数据以位的形式逐个发送和接收。在串口通信中,数据被组织成数据帧的形式进行传输,包括起始位、数据位、校验位和停止位等部分,波特率决定了数据传输的速度,发送方和接收方必须以相同的波特率进行通信,为了使通信顺利进行,需要定义一套串口协议,规定数据的传输格式、起始和结束方式以及校验方法等。

2.2、串口通信协议(如RS-232, RS-485)

原生串口协议:原生串口通信通常使用TTL电平(Transistor-Transistor Logic,晶体管—晶体管逻辑电平),其中逻辑1通常表示高电平(3.3V或5V),逻辑0通常表示低电平(0V),使用异步串行通信方式,定义了数据的格式、起始位、停止位、数据位数以及波特率等。

RS-232:RS-232是一种较为常见的串口通信协议,用于在数据通信设备之间进行数据传输,RS-232通信使用负逻辑电平表示逻辑1,正逻辑电平表示逻辑0。典型的电压范围是+12V至-12V,但实际上可以容忍更大的波动范围,RS-232定义了一个点对点的串行通信接口,并且通常使用异步通信方式,也规定了数据的格式、起始位、停止位、数据位数以及校验方式等。

RS-485:RS-485适用于多点和远距离通信,使用差分信号传输,即使用两根信号线进行数据传输,一条线传输正信号,另一条线传输负信号。典型的电压范围是-7V至+12V,支持多主机和多从机的半双工或全双工通信方式。在定义了数据的格式校验方式的基础上,还规定了总线上设备的驱动方式数据冲突检测和解决机制等。

三. 串口功能框图

这里将串口功能框图分成四个部分进行解释。

首先是引脚部分,我们最常用的就是RX和TX 引脚,分别是接收和发送引脚;SCLK是时钟引脚,通常在同步通信时使用;nCTS和nRTS引脚会在硬件流控模式中需要,nCTS是允许发送,nRTS是请求发送,均为低电平有效;IRDA_OUT和IRDA_IN分别是IrDA模式下的数据输出和输入。

其次是数据寄存器部分,发送和接收的数据都由这里进行解析。

发送过程:当发送数据寄存器从CPU或者DMA读取数据之后,将其转移到发送移位寄存器中,数据转移完之后,TXE标志位被置位,代表数据已经转移完毕,可以接收下一组数据了,当发送移位寄存器将数据一位一位的通过串口的TX引脚传输到外部设备后,TC标志位被置位,表示数据发送完成;

接收过程:当外部设备向串口发数据时,会先经过RX引脚进入,传到接收移位寄存器,然后传给RDR寄存器,当RDR寄存器将数据完全转到DR寄存器时RXNE被置位,表示收到数据,可以读出数据。

这整个过程中串口使能UE、发送使能TE以及接收使能RE都应该被置位。

紧接着我们来看控制部分,主要通过控制寄存器CR1、CR2、CR3来进行控制,通过CR1寄存器来配置串口的字长、校验位、唤醒方式、发送使能和接收使能等;通过CR2寄存器来控制串口的数据停止位、LIN模式、时钟极性和时钟相位等;CR3寄存器来控制硬件流控制模式、智能卡模式以及DMA使能发送和DMA使能接收,这部分内容需要通读数据手册,了解这些寄存器的每个位配置的功能就能够实现想要的控制效果。

最后时波特率部分,串口的波特率是由波特比率寄存器(USART_BRR)来进行计算和设置的,通过写入波特率参数,这个寄存器会自动计算整数部分和小数部分写入寄存器,并同时同步给接收器和发送器,波特率的计算公式如图

f_{ck}是时钟频率,波特率是我们设定的参数(如9600、115200等),USART_BRR寄存器会算出USARTDIV,然后分整数部分和小数部分写入寄存器。

四.源码分析

        首先依旧按照惯例,在本地文件夹内创建USART.c和USART.h文件,然后到keil5里面将文件添加到对应的组里,切记要将头文件路径设置,不然找不到头文件报错,以下程序部分代码来自野火的例程。

USART.c

#include "USART.h"

void USART_Config(void)
{

	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;

	// 打开串口GPIO的时钟
	DEBUG_USART_GPIO_APBxClkCmd(DEBUG_USART_GPIO_CLK, ENABLE);
	
	// 打开串口外设的时钟
	DEBUG_USART_APBxClkCmd(DEBUG_USART_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);
	
	// 配置串口的工作参数
	// 配置波特率
	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);	

}

/*****************  发送一个字符 **********************/
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch)
{
	/* 发送一个字节数据到USART */
	USART_SendData(pUSARTx,ch);
		
	/* 等待发送数据寄存器为空 */
	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)
  {
	}

}

/*****************  发送一个16位数 **********************/
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch)
{
	uint8_t temp_h, temp_l;
	
	/* 取出高八位 */
	temp_h = (ch&0XFF00)>>8;
	/* 取出低八位 */
	temp_l = ch&0XFF;
	
	/* 发送高八位 */
	USART_SendData(pUSARTx,temp_h);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);
	
	/* 发送低八位 */
	USART_SendData(pUSARTx,temp_l);	
	while (USART_GetFlagStatus(pUSARTx, USART_FLAG_TXE) == RESET);	
}

///重定向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);
}

///重定向c库函数scanf到串口,重写向后可使用scanf、getchar等函数
int fgetc(FILE *f)
{
		/* 等待串口输入数据 */
		while (USART_GetFlagStatus(DEBUG_USARTx, USART_FLAG_RXNE) == RESET);

		return (int)USART_ReceiveData(DEBUG_USARTx);
}

        首先分析函数USART_Config(),它的功能是配置并初始化一个USART,先定义了两个结构体,GPIO_InitTypeDef GPIO_InitStructure; 和 USART_InitTypeDef USART_InitStructure;分别用于GPIO的初始化和USART的初始化。紧接着打开串口外设和GPIO的时钟,分别向两个结构体中写参数,GPIO结构体中将USART Tx的GPIO配置为推挽复用模式,速度为50MHz,USART Rx的GPIO配置为浮空输入模式,速度为50MHz,然后调用GPIO初始化函数。串口结构体中将串口配置波特率、数据字长、停止位、校验位、硬件流控制和工作模式等内容,然后调用函数进行串口初始化,最后使能串口,为数据传输做准备。

接下来具体来分析串口是如何进行初始化的,选中串口初始化函数,按F12或右键点击跳转到定义处,USART_Init()函数首先进行参数合法性检查,传入参数都在有效范围内再进行下一步操作,否则会终止运行。

        参数合法性检查完毕之后进行串口配置,首先配置的是USART的停止位参数,从CR2寄存器中读取原有设置,清除特定的位(STOP[13:12]),然后根据初始化结构体的值重新设置这些位,最后将最终的设置写回到CR2寄存器中。

/*---------------------------- USART CR2 Configuration -----------------------*/
  tmpreg = USARTx->CR2;
  /* Clear STOP[13:12] bits */
  tmpreg &= CR2_STOP_CLEAR_Mask;
  /* Configure the USART Stop Bits, Clock, CPOL, CPHA and LastBit ------------*/
  /* Set STOP[13:12] bits according to USART_StopBits value */
  tmpreg |= (uint32_t)USART_InitStruct->USART_StopBits;
  
  /* Write to USART CR2 */
  USARTx->CR2 = (uint16_t)tmpreg;

        然后配置USART的字长度、校验位和模式,同上述操作一样,首先从CR1寄存器中读取原有设置,清除特定的位(M, PCE, PS, TE和RE),然后根据初始化结构体的值重新设置这些位,最后将最终的设置写回到CR1寄存器中。

M

定义了数据字的长度

0:一个起始位,8个数据位,n个停止位;
1:一个起始位,9个数据位,n个停止位。

PCE

检验控制使能

0:禁止校验控制;
1:使能校验控制。

PS

校验选择

0:偶校验;
1:奇校验。

TE

发送使能

0:禁止发送;
1:使能发送。

RE

接收使能

0:禁止接收;
1:使能接收,并开始搜寻RX引脚上的起始位

        之后来到控制寄存器CR3的配置,这主要用于配置USART的硬件流控制,STM32的硬件流控制(Hardware Flow Control)主要适用于USART1、USART2和USART3。那什么是硬件流控制呢?硬件流控制是用来解决数据拥塞和丢失问题的一种方法。当接收方不能接收更多的数据时,发送方就会停止发送数据,这就是所谓的流控制(Flow Control)。而硬件流控制是一种通过硬件设备进行流控制的方法。在硬件流控制中,通常使用专门的硬件设备来监测数据的传输状态。当数据的传输速度过快,以至于接收方无法处理时,硬件设备就会通知发送方暂停发送数据。反之,当接收方能够接收更多的数据时,硬件设备就会通知发送方继续发送数据。

/*---------------------------- USART CR3 Configuration -----------------------*/  
  tmpreg = USARTx->CR3;
  /* Clear CTSE and RTSE bits */
  tmpreg &= CR3_CLEAR_Mask;
  /* Configure the USART HFC -------------------------------------------------*/
  /* Set CTSE and RTSE bits according to USART_HardwareFlowControl value */
  tmpreg |= USART_InitStruct->USART_HardwareFlowControl;
  /* Write to USART CR3 */
  USARTx->CR3 = (uint16_t)tmpreg;

最后进行波特率配置,通过配置USART_BRR寄存器来实现。

/*---------------------------- USART BRR Configuration -----------------------*/
  /* Configure the USART Baud Rate -------------------------------------------*/
  RCC_GetClocksFreq(&RCC_ClocksStatus);
  if (usartxbase == USART1_BASE)
  {
    apbclock = RCC_ClocksStatus.PCLK2_Frequency;
  }
  else
  {
    apbclock = RCC_ClocksStatus.PCLK1_Frequency;
  }
  
  /* Determine the integer part */
  if ((USARTx->CR1 & CR1_OVER8_Set) != 0)
  {
    /* Integer part computing in case Oversampling mode is 8 Samples */
    integerdivider = ((25 * apbclock) / (2 * (USART_InitStruct->USART_BaudRate)));    
  }
  else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
  {
    /* Integer part computing in case Oversampling mode is 16 Samples */
    integerdivider = ((25 * apbclock) / (4 * (USART_InitStruct->USART_BaudRate)));    
  }
  tmpreg = (integerdivider / 100) << 4;

  /* Determine the fractional part */
  fractionaldivider = integerdivider - (100 * (tmpreg >> 4));

  /* Implement the fractional part in the register */
  if ((USARTx->CR1 & CR1_OVER8_Set) != 0)
  {
    tmpreg |= ((((fractionaldivider * 8) + 50) / 100)) & ((uint8_t)0x07);
  }
  else /* if ((USARTx->CR1 & CR1_OVER8_Set) == 0) */
  {
    tmpreg |= ((((fractionaldivider * 16) + 50) / 100)) & ((uint8_t)0x0F);
  }
  
  /* Write to USART BRR */
  USARTx->BRR = (uint16_t)tmpreg;

        可以看到该代码首先获取时钟频率,然后判断使用的USART类型,如果是串口1,就使用PCLK2的时钟频率,最大为72MHz,否则使用PCLK1的时钟频率,最大为36MHz。根据USART的过采样模式计算整数部分。过采样模式有两种,8样本和16样本。在8样本模式下,整数部分是APB时钟频率除以2乘以波特率;在16样本模式下,整数部分是APB时钟频率除以4乘以波特率。整数部分除以100,然后右移4位,得到的是整数部分的100倍。然后从整数部分100倍中减去这个值,就得到了小数部分。根据整数和小数部分,设置USART的BRR(Baud Rate Registers)寄存器,这个寄存器的值决定了USART的波特率。在8样本模式下,(小数部分*8+50)/100,最后取低5位;在16样本模式下,(小数部分*16+50)/100,最后取低12位,到此就完成串口的初始化配置,可以看到整个配置过程中对控制寄存器CR1、CR2、CR3以及波特率寄存器USART_BRR进行了配置,结合上面的串口功能框图更容易理解。

        关于数据的传输函数,只需要关注数据寄存器中几个标志位的值的变化,对其进行读取判断数据是否已经发送完成或者接收完成。这里面还用到将C库函数重定向到串口进行数据传输,具体来说,通过重定向,可以将C标准库中的输入输出函数(如printfscanf等)的数据流重定向到串口,使得我们可以在串口中观察和发送数据,这在调试程序、观察系统运行状态、以及在无文件系统环境中进行数据传输等方面非常有用。

TXE发送数据寄存器为空被置位
TC发送完成后置位
RXNE接收数据寄存器不为空时置位

USART.h

#ifndef __USART_H
#define	__USART_H


#include "stm32f10x.h"
#include <stdio.h>

#define  DEBUG_USARTx                   USART2
#define  DEBUG_USART_CLK                RCC_APB1Periph_USART2 
#define  DEBUG_USART_APBxClkCmd         RCC_APB1PeriphClockCmd
#define  DEBUG_USART_BAUDRATE           115200

#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_2
#define  DEBUG_USART_RX_GPIO_PORT       GPIOA
#define  DEBUG_USART_RX_GPIO_PIN        GPIO_Pin_3

#define  DEBUG_USART_IRQ                USART2_IRQn
#define  DEBUG_USART_IRQHandler         USART2_IRQHandler

void USART_Config(void);
void Usart_SendByte( USART_TypeDef * pUSARTx, uint8_t ch);
void Usart_SendString( USART_TypeDef * pUSARTx, char *str);
void Usart_SendHalfWord( USART_TypeDef * pUSARTx, uint16_t ch);

#endif 

五. 串口通信实验

5.1 串口通信测试

main.c

#include "stm32f10x.h"  
#include "led.h"      
#include "USART.h"
  
int main(void)
{	
    USART_Config();

    Usart_SendByte(DEBUG_USARTx,'A');
	printf("成功接收到字符\n\n\n\");

	Usart_SendString(DEBUG_USARTx,"这是串口1接收实验\n");
	printf("成功接收到字符串\n\n\n\");
	
}


5.2 串口控制LED

main.c

#include "stm32f10x.h"  
#include "led.h"      
#include "USART.h"

int main(void)
{	
	char ch;
 
    LED_Init();
  
    USART_Config();

 while(1)
	{	
    
    ch=getchar();
    printf("接收到字符:%c\n",ch);
   
    switch(ch)
    {
      case '1':
        LED_ON;
      break;
	    case '0':
			LED_OFF;
      break;
   
    }   
	}	
}


led.c

#include "led.h"   

// LED初始化函数
void LED_Init()
{
		// 使能GPIOB时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
        
		GPIO_InitTypeDef GPIO_InitStructure;
		// 设置GPIOB的第0位
		GPIO_InitStructure.GPIO_Pin=GPIO_Pin_0;
		// 设置为推挽输出模式
		GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
		// 输出速度为50MHz
		GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz; 
		// 初始化GPIOB
		GPIO_Init(GPIOB, &GPIO_InitStructure);
		
		// 设置GPIOB的第0位为高电平,LED为熄灭状态
		GPIO_SetBits(GPIOB, GPIO_Pin_0);
}

// 延迟函数
void Delay(uint32_t count)
{
    volatile uint32_t i;
    for(i=0; i<count; i++);
}

led.h

#ifndef __LED_H
#define	__LED_H

#include "stm32f10x.h"

void LED_Init(void);
void Delay(uint32_t count);
#define   LED_ON   	GPIO_ResetBits(GPIOB, GPIO_Pin_0);
#define   LED_OFF   	GPIO_SetBits(GPIOB, GPIO_Pin_0);

#endif /* __LED_H */


六. 总结

        本文讲述了串口通信的基本原理和操作方法,对串口功能框图进行了阐述,并成功进行了串口通信测试和LED灯控制实验,为后续的嵌入式系统开发提供了有益的参考。还可以进一步探索STM32D的串口通信应用,如实现多个设备之间的通信和控制,或者通过串口进行数据采集和传输等。

  • 20
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值