STM32学习笔记(7)——串口通信

一、串口通信协议

1、物理层

        串口通信的物理层有很多标准及变种

1、RS-232标准        

2、USB转串口

3、原生串口到串口

        使用RS-232标准的串口设备间常见的通讯结构如下:

         两通讯设备的“DB9接口”之间通过串口信号线建立连接,串口信号线使用“RS-232标准”传输数据信号。由于RS-232电平标准的信号不能直接被控制器直接识别,所以该信号会经过“电平转换芯片”转换成控制器能够识别的“TTL标准”电平信号,才能实现通讯。

        (1)电平标准

        根据通讯使用的电平标准不同,串口通讯可分为TTL标准及RS-232标准。

        常见的电子电路中常使用TTL电平标准,理想状态下,使用5V表示二进制逻辑1,0V表示二进制逻辑0;而RS-232电平标准为了增加串口通讯的远距离传输以及抗干扰能力(对抗衰减),使用-15V表示逻辑1,+15V表示逻辑0。下图为分别用两种不同的电平标准表示同一信号。

        因为控制器一般使用TTL电平标准,所以常常会使用MAX3232芯片对TTL及RS-232电平的信 号进行互相转换。

        (2)RS-232信号线

        接线口以针式引出信号线的称为公头,以孔式引出信号线的称为母头。

        DB9接口中的公头及母头的各个引脚的标准信号线接法如下:

        DB9信号线说明

        在目前的其它工业控制使用的串口通讯中,一般只使用RXD、TXD以及GND三条信号线,直 接传输数据信号,而RTS、CTS、DSR、DTR及DCD信号都被裁剪掉了。

2、协议层

        串口通讯的数据包由发送端的TXD接口传输到接收端的RXD接口。在串口通信的协议层中,规定了数据包的内容,由起始位,主体数据、校验位以及停止位组成,通信双方的数据包格式要约定一致方能正常通信。下图为串口数据包基本组成。

(1)波特率

        在串口异步通信中由于没有时钟信号,所以两个设备之间需要约定好波特率,即每个码元的长度,常见的波特率为4800、9600、115200等。

(2)起始和停止信号

        串口通信的一个数据包从起始信号开始,直到停止信号结束。数据包的起始信号由一个逻辑0的数据位表示,而数据包的停止信号可由0.5、1、1.5或2个逻辑1的数据位表示,只要双方约定一致即可。

起始位固定为低电平

停止位固定为高电平

(3)有效数据

        在数据包的起始位之后紧接着的就是要传输的主体数据内容,也称为有效数据,有效数据的长度 常被约定为5、6、7或8位长。

(4)数据校验

        在有效数据之后,有一个可选的数据校验位。由于数据通信相对更容易受到外部干扰导致传输数据出现偏差,可在传输过程中加上校验位解决这个问题。校验方法有奇校验、偶校验、0校验、1校验以及无校验。

奇校验要求有效数据和校验位中“1”的个数为奇数;

偶校验要求帧数据和校验位中“1”的个数为偶数;

0 校验是不管有效数据中的内容是什么,校验位总为“0”,1校验是校验位总为“1”。

 二、USART

        USART是通用同步异步收发器的简称,它是一个串行通信设备,UART是在USART的基础上裁剪了同步通信功能,只有异步通信。USART支持同步单向通信和半双工单线通信;USART支持使用DMA,可实现高速数据通信。

        USART 在STM32 应用最多莫过于“打印”程序信息,一般在硬件设计时都会预留一个USART 通信接口连接电脑,用于在调试程序是可以把一些调试信息“打印”在电脑端的串口调试助手工具上,从而了解程序运行是否正确、如果出错哪具体哪里出错等等。

1、USART功能

(1)引脚功能

        TX:发送数据输出引脚;

        RX:接收数据输入引脚;

        SW_RX:数据接收引脚,只用于单线和智能卡模式,属于内部引脚,没有具体外部引脚;

        nRTS:请求以发送(Request To Send),n表示低电平有效;该引脚只适用于硬件流控制。

如果使能RTS流控制

低电平:USART接收器准备好接收新数据

高电平:接收寄存器已满

        nCTS:清除以发送(Clear To Send),n表示低电平有效;该引脚只适用于硬件流控制。

如果使能CTS流控制

低电平:USART发送器可以发送数据

高电平:发送完当前数据后停止发送

        SCLK:发送器时钟输出引脚。这个引脚仅适用于同步模式。

        STM32F103C8T6系统控制器有三个USART和两个UART,其中USART1和时钟来源于APB2总线时钟,其最大频率为72MHz,其他四个的时钟来源于APB1总线时钟,其最大频率为36MHz。 UART只是异步传输功能,所以没有SCLK、nCTS和nRTS功能引脚。

(2)数据寄存器

        USART数据寄存器(USART_DR)只有低9位有效,并且第9位数据是否有效取决于USART控制寄存器1(USART_CR1)的M位设置,当M位为0时表示8位数据字长,当M位为1时表示9位数据字长。

        USART_DR 包含了已发送的数据或者接收到的数据。USART_DR实际是包含了两个寄存器,一 个专门用于发送的可写TDR,一个专门用于接收的可读RDR。

        TDR和RDR都是介于系统总线和移位寄存器之间。串行通信是一个位一个位传输的,发送时把 TDR 内容转移到发送移位寄存器,然后把移位寄存器数据每一位发送出去,接收时把接收到的 每一位顺序保存在接收移位寄存器内然后才转移到RDR。

(3)控制器

        USART 有专门控制发送的发送器、控制接收的接收器,还有唤醒单元、中断控制等等。使用 USART 之前需要向USART_CR1寄存器的UE位置1使能USART,UE位用来开启供给给串口的时钟。

        发送或者接收数据字长可选8位或9位,由USART_CR1的M位控制。

(4)发送器

        当USART_CR1寄存器的发送使能位TE置1时,启动数据发送,低位在前,高位在后;

        一个数据帧包括:起始位(一个周期的低电平)+数据帧(8位或9位数据)+停止位(一定时间周期的高电平);

        停止位时间长短由USART_CR2的STOP位控制,可选0.5、1、1.5和2个停止位。

(5)接收器

        如果将USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在RX线开始搜索 起始位。在确定到起始位后就根据RX线电平状态把数据存放在接收移位寄存器内。接收完成后就把接收移位寄存器数据移到RDR内,并把USART_SR寄存器的RXNE位置1,同时如果 USART_CR2 寄存器的RXNEIE置1的话可以产生中断。

(6)波特率

        波特率指数据信号对载波的调制速度,它用单位时间内载波调制状态改变次数表示,单位为波特,波特率越大,传输速率越快。

        USART的发送器和接收器使用相同的波特率。计算公式如下:

fCK为USART时钟

USARTDIV为一个存放在波特率寄存器(USART_BRR)的一个无符号定点数,其中DIV_Mantissa[11:0]位定义USARTDIV的整数部分,DIV_Fraction[3:0]位定义 USARTDIV 的小数部分。

我们知道USART1使用APB2总线时钟,最高可达72MHz,其他USART的最高频率为36MHz。 我们选取USART1作为实例讲解,即fPLCK=72MHz。为得到115200bps的波特率,此时:

115200 = 72000000/16∗USARTDIV

解得USARTDIV=39.0625,可得DIV_Fraction=0.0625*16=1=0x01,DIV_Mantissa=39=0x27,即 应该设置USART_BRR的值为0x271。

2、校验控制

        STM32F103 系列控制器USART支持奇偶校验。当使用校验位时,串口传输的长度将是8位的数 据帧加上1位的校验位总共9位,此时USART_CR1寄存器的M位需要设置为1,即9数据位。 将USART_CR1 寄存器的PCE位置1就可以启动奇偶校验控制,奇偶校验由硬件自动完成。启动了奇偶校验控制之后,在发送数据帧时会自动添加校验位,接收数据时自动验证校验位。接收 数据时如果出现奇偶校验位验证失败,会见USART_SR寄存器的PE位置1,并可以产生奇偶校验中断。 使能了奇偶校验控制后,每个字符帧的格式将变成:起始位+数据帧+校验位+停止位。

3、中断控制

USART有多个中断请求事件

三、USART初始化结构体详解

1、USART初始化结构体

        标准库函数对每个外设都建立了一个初始化结构体,比如USART_InitTypeDef,结构体成员用于 设置外设工作参数,并由外设初始化配置函数,比如USART_Init()调用,这些设定参数将会设置 外设相应的寄存器,达到配置外设工作环境的目的。

typedef struct
{
  uint32_t USART_BaudRate;            /*波特率设置,一般设置为2400、9600、19200、115200,标                        
                                      准库函数会根据设定值计算得到USAETDIV值,从而设置    
                                      USART_BRR寄存器值*/

  uint16_t USART_WordLength;          /*设置数据帧字长,可选8位或9位,若无校验位使用8位,如果使                 
                                      用校验则一般设置为9位*/

  uint16_t USART_StopBits;            /*停止位设置,可选0.5、1、1.5和2个停止位*/

  uint16_t USART_Parity;              /*奇偶校验控制选择*/
 
  uint16_t USART_Mode;                /*USART模式选择*/

  uint16_t USART_HardwareFlowControl; /*硬件流控制选择,只有在硬件流控制模式才有效,可选有使能RTS、使能CTS、同时使能RTS和CTS、不使能硬件流。*/
} USART_InitTypeDef;

2、USART时钟初始化结构体

        当使用同步模式时需要配置SCLK引脚输出脉冲的属性,标准库使用一个时钟初始化结构体 USART_ClockInitTypeDef 来设置,该结构体内容也只有在同步模式才需要设置。

typedef struct
{

  uint16_t USART_Clock;   /*时钟使能控制*/

  uint16_t USART_CPOL;    /*时钟极性*/

  uint16_t USART_CPHA;    /*时钟相位*/

  uint16_t USART_LastBit; /*最尾位时钟脉冲*/
} USART_ClockInitTypeDef;

四、USART1收发通信实验

1、硬件

        在利用USART实现开发板与电脑通信,需要用到一个USB转串口的IC,选择CH340G芯片实现这个功能。

2、软件

(1)编程要点

1)使能GPIO时钟和USART时钟;

2)初始化GPIO,并将GPIO复用到USART上;

3)配置USART参数;

4)配置中断控制器并使能USART中断;

5)使能USART;

6)在USART接收中断服务函数中实现数据接收和发送。

(2)代码分析

1)串口发送

bsp_usart.h

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

void USART_Config(void);

void Usart_SendByte(uint8_t tx_value);
void Usart_SendArray(uint8_t *Array,uint16_t Length);
void Usart_SendString(char *String);
uint32_t Tx_Pow(uint32_t X,uint32_t Y);
void Usart_SendNumber(uint32_t Number,uint32_t Length);

#endif

bsp_usart.c

void USART_Config(void)
{
	//打开串口GPIO外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//打开串口外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*1、初始化串口GPIO*/
	
	GPIO_InitTypeDef GPIO_InitStructure;
	//将USART_Tx的GPIO配置为推挽复用输出模式
	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);

/*2、配置串口的各种参数*/
	
	USART_InitTypeDef USART_InitStructure;        
	USART_InitStructure.USART_BaudRate = 9600;                        //波特率
	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_Tx | USART_Mode_Rx;;  //串口模式
	USART_Init(USART1, &USART_InitStructure);
    
    //使能串口
    USART_Cmd(USART1, ENABLE);
}

//发送一个字符
void USART_SendByte(uint8_t tx_value)
{
    //注意串口数据寄存器只有低八位有效
    USART_SendData(USART1, tx_value);
    //等待串口状态寄存器的TXE(发送数据寄存器空)置‘1’
    //0:数据还未被转移到移位寄存器
    //1:数据已经被转移到移位寄存器
    while(USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET);   
}

/*发送一个数组*/
void Usart_SendArray(uint8_t *Array,uint16_t Length)
{
	uint16_t i;
	for(i=0;i<Length;i++)
	{
		Usart_SendByte(Array[i]);
	}
	
}

/*发送一个字符串*/
void Usart_SendString(char *String)
{
	uint16_t i;
	for(i=0;String[i] != '\0';i++)
	{
		Usart_SendByte(String[i]);
	}
	
}

uint32_t Tx_Pow(uint32_t X,uint32_t Y)
{
	uint32_t value = 1;
	while(Y--)
	{
		value *= X;
	}
	return value;
}

/*发送一个十进制数*/
void Usart_SendNumber(uint32_t Number,uint32_t Length)
{
	uint16_t i;
	for(i=0;i<Length;i++)
	{
		Usart_SendByte(Number/Tx_Pow(10,Length - i - 1)%10+'0');           //需要以字符的形式显示,所以需要加上偏移“0x30”。
	}	
}

2)串口接收

bsp_usart.h

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

void USART_Config(void);

void Usart_SendByte(uint8_t tx_value);
void Usart_SendArray(uint8_t *Array,uint16_t Length);
void Usart_SendString(char *String);
uint32_t Tx_Pow(uint32_t X,uint32_t Y);
uint8_t USART_GetRxData(void);
void Usart_SendNumber(uint32_t Number,uint32_t Length);
uint8_t USART_GetRxFlag(void);
void USART1_IRQHandler(void);

#endif

bsp_usart.c

#include "bsp_usart.h"

uint8_t Rx_Data;
uint8_t Rx_Flag;

void USART_Config(void)
{
	//打开串口GPIO外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
	
	//打开串口外设时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);
/*1、初始化串口GPIO*/
	
	GPIO_InitTypeDef GPIO_InitStructure;
	//将USART_Tx的GPIO配置为推挽复用输出模式
	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);
	
	//将USART_Rx的GPIO配置为浮空输入模式
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
/*2、配置串口的各种参数*/
	
	USART_InitTypeDef USART_InitStructure;
	
	USART_InitStructure.USART_BaudRate = 9600;
	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_Tx | USART_Mode_Rx;;
	USART_Init(USART1, &USART_InitStructure);
	
/*3、串口中断向量控制器参数配置*/
	
	NVIC_InitTypeDef NVIC_InitStructure;
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
	
	NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
/*4、使能串口接收中断*/
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
	
/*5、使能串口*/
	USART_Cmd(USART1, ENABLE);
}

uint8_t USART_GetRxFlag(void)
{
	if(Rx_Flag == 1){
		Rx_Flag = 0;
		return 1;
	}
	else{
		return 0;
	}
}

uint8_t USART_GetRxData(void)
{
	return Rx_Data;
}

void USART1_IRQHandler(void)
{
	if(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) != RESET)
	{
		Rx_Data =  USART_ReceiveData( USART1 );
		Rx_Flag = 1;
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);            //也可以不用,可以硬件清    
                                                                    除标志位
	}
}


main.c

#include "stm32f10x.h"                  // Device header
#include "bsp_led.h"
#include "bsp_key.h"
#include "bsp_systick.h"
#include "bsp_usart.h"
extern void delay_m(uint32_t num);
uint8_t KEYNum;
uint8_t USART_Data;
int main(void)
{
	LED_Init();
	KEY_Init();
	USART_Config();

while(1)
	{
		if(USART_GetRxFlag() == 1)
		{
			Usart_SendByte(USART_GetRxData());
		}	
	}
}

五、重定向prinft和scanf函数

int fputc(int ch,FILE *f)
{
	//发送一个字节数据到串口
	USART_SendData(DEBUG_USARTx,(uint8_t)ch);
	
	//等待发送完毕
	while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET);
	
	return (ch);
}


int fgetc(FILE *f)
{
	 while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET);
	
	
	 return (int)USART_ReceiveData(USART1);
}

        在C语言标准库中,fputc函数是printf函数内部的一个函数,功能是将字符ch写入到文件指针f所指向文件的当前写指针位置,简单理解就是将字符写入到特定文件中。

注意

需要包含#include <stdio.h>文件

需要勾选“UseMicroLIB”,MicoroLIB是缺省C库的备选库,它对标准C库进行了 高度优化使代码更少,占用更少资源。

### 关于STM32学习资源推荐 对于初学者而言,选择合适的入门材料至关重要。针对STM32学习路径可以从最广泛使用的型号——STM32F103开始[^2]。这款微控制器在市场上拥有丰富的教程和支持文档,有助于新手快速上手。 #### 工具与环境搭建 使用STM32CubeIDE作为开发工具能极大提高编程效率,并简化外设初始化过程[^1]。通过这个集成开发环境(IDE),开发者不仅可以轻松创建工程项目,还能利用其内置的功能向导来配置硬件参数,减少手动编码的工作量。 #### 基础概念理解 了解ARM Cortex-M架构以及如何操作GPIO端口、定时器和其他基本外围设备是必要的基础知识。这些内容通常会在官方手册和技术参考中详细介绍。此外,网络上有许多免费视频课程和博客文章专门讲解这些问题。 #### 实践练习 理论学习之后应立即付诸实践。尝试构建简单的应用程序如LED闪烁或按键读取可以帮助巩固所学的知识点。随着技能的增长,可以逐步挑战更复杂的项目,例如串行通信协议实现或是传感器数据采集处理等任务。 #### 高级特性探索 当掌握了初步技巧后,可考虑转向更高阶的产品线如STM32U5系列来进行深入研究[^3]。这类芯片提供了更好的性能表现及更多样化的接口选项,同时也引入了一些新颖的技术特征(如图形加速),使得它们成为理想的选择用于高级课题的研究和发展工作。 ```python # 示例代码:点亮一个连接至PA5引脚的LED灯 import pyb led = pyb.LED(1) # 定义LED对象关联到蓝色LED (即PA5) while True: led.toggle() # 切换LED状态 pyb.delay(500) # 等待半秒 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值