STM32平台的USART串口通信

本文目的是编写stm32串口通信程序,实现stm32上电自动循环发送hello LYJ!!!,并可通过上位机控制stm32串口发送与否。通过亲自做一下USART串口通信实验,学习如何使用stm32平台的USART串口通信功能。

(一)串口通信简介

1.串口通信

  • 概念
    串口通信是指外设和计算机间,通过数据信号线 、地线、控制线等,按位进行传输数据的一种通讯方式,它将需要传输的数据通过串口按位(bit)发送和接收字节。
  • 分类
    • 单工:数据传输只支持数据在一个方向上传输。
    • 半双工:允许数据在两个方向上传输。但同一时刻只允许数据在一个方向上传输,它实际上是一种能够切换方向的单工通信,不需要独立的接收端和发送端,两者可以合并使用一个端口。
    • 全双工:允许数据同时在两个方向上传输。因此全双工通信是两个单工通信方式的结合,需要独立的接收端和发送端。

2.USART(通用同步异步收发器)

  • USART简要介绍
    USART提供了一种灵活的方法与使用工业标准NRZ异步串行数据格式的外部设备之间进行全双工数据交换。
    USART利用分数波特率发生器提供宽范围的波特率选择,支持同步单向通信和半双工单线通信,也支持LIN(局部互连网),智能卡协议和IrDA(红外数据组织)SIR ENDEC规范以及调制解调器(CTS/RTS)操作。除此之外,它还允许多处理器通信,使用多缓冲器配置的DMA方式可以实现高速数据通信。
  • USART功能框图及说明
    USART
    • 任何USART双向通信至少需要两个引脚:接收数据输入(RX)和发送数据输出(TX)。
      • RX:接收数据串行输入。通过过采样技术来区别数据和噪音,从而恢复数据。
      • TX:发送数据串行输出。当发送器被禁止时,输出引脚恢复到它的I/O端口配置。当发送器被激活且不发送数据时,TX引脚处于高电平。在单线和智能卡模式里,此I/O口被同时用于数据的发送和接收。
    • SCLK:发送器时钟输出。此引脚输出用于同步传输的时钟, (在Start位和Stop位上没有时钟脉冲,软件可选地,可以在最后一个数据位送出一个时钟脉冲)。数据可以在RX上同步被接收。这可以用来控制带有移位寄存器的外部设备(例如LCD驱动器)。时钟相位和极性都是软件可编程的。在智能卡模式里,CK可以为智能卡提供时钟。
    • 在IrDA模式里需要下列引脚:
      • IrDA_RDI: IrDA模式下的数据输入。
      • IrDA_TDO: IrDA模式下的数据输出。
    • 下列引脚在硬件流控模式中需要:
      • nCTS: 清除发送,若是高电平,在当前数据传输结束时阻断下一次的数据发送。
      • nRTS: 发送请求,若是低电平,表明USART准备好接收数据
  • UART(通用异步收发器)
    相比于USART来说,UART少了USART的同步通信功能,而只有异步通信的功能。简单区分同步和异步的方法就是看通信时需不需要对外提供时钟输出,如果需要对外提供时钟输出就是同步通信方式,否则为异步通信方式。我们平时用的串口通信基本都是 UART。

以上为串口通信以及常用的串口通信USART的简要介绍,如需深入了解串口通信USART可参考《STM32中文参考手册》

(二)USART串口实验

1.实验要求及原理

  • 实验要求
    • 设置波特率为115200,1位停止位,无校验位。
    • STM32给上位机(windows10)连续发送hello LYJ!!!,上位机接收程序使用串口调试助手。
    • 上位机(windows10)给stm32发送Stop,stm32后,stm32停止发送。
    • 上位机(windows10)给stm32发送Begin,stm32后,stm32恢复发送。
  • 2.实验原理
    • 串口接收
      由于前面已经配置好了stm32的串口1中断,因此当上位机给stm32发送数据时就会触发该中断进入中断处理函数USART1_IRQHandler()接收数据并保存到接受数据缓冲区。
    • 串口发送
      当串口接收完数据并存储到接受数据缓冲区后,主程序就将该缓冲区中的数据按位放到发送数据缓冲区中并发送出去,由上位机的串口调试助手显示stm32发送出来的数据。

2.实验核心代码

  • 串口初始化
void uart_init(u32 bound)
{
  //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	//串口时钟使能,GPIO时钟使能,复用时钟使能 
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1|RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
  
	//USART1_TX   GPIOA.9
	//GPIO端口模式设置
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;	//复用推挽输出
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.9

    //USART1_RX	  GPIOA.10初始化
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;//PA10
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入
    GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.10  

    //Usart1 NVIC 配置
    //初始化NVIC
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3 ;//抢占优先级3
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;		//子优先级3
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;			//IRQ通道使能
	NVIC_Init(&NVIC_InitStructure);	//根据指定的参数初始化VIC寄存器
  
   //USART 初始化设置
	//串口参数初始化
	USART_InitStructure.USART_BaudRate = bound;//串口波特率
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式
	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(USART1, &USART_InitStructure); //初始化串口1
    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启串口接受中断
    USART_Cmd(USART1, ENABLE);                    //使能串口1 

}
  • 主函数
#include "led.h"
#include "delay.h"
//#include "key.h"
#include "sys.h"
#include "usart.h"

 int main(void)
 {	
    u8 Stopflag[]="Stop,stm32";     //停止发送信号
    u8 Beginflag[]="Begin,stm32";   //恢复发送信号
 	u16 t,len;  	
	u16 times=0;    //设置计数器
    u16 flag=1,flag_S,flag_B;   //设置标志位
	delay_init();	    	 //延时函数初始化	  
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //设置NVIC中断分组2:2位抢占优先级,2位响应优先级
	uart_init(115200);	 //串口初始化为115200
 	LED_Init();			     //LED端口初始化
	//KEY_Init();          //初始化与按键连接的硬件接口
 	while(1)
	{
        flag_S=flag_B=1;
		if(USART_RX_STA&0x8000)
		{					   
			len=USART_RX_STA&0x3fff;//得到此次接收到的数据长度
			printf("\r\n您发送的消息为:\r\n");
			for(t=0;t<len;t++)
			{
                if(Stopflag[t]!=USART_RX_BUF[t]) flag_S=0;  //若接受数据与停止发送信号不同将flag_S置为0
                if(Beginflag[t]!=USART_RX_BUF[t]) flag_B=0; //若接受数据与恢复发送信号不同将flag_B置为0
				USART_SendData(USART1, USART_RX_BUF[t]);//向串口1发送数据
				while(USART_GetFlagStatus(USART1,USART_FLAG_TC)!=SET);//等待发送结束
			}
			printf("\r\n\r\n");//插入换行
            if(flag_S==1)   //如果flag_S==1表示上位机发送的是停止发送信号
            {
                flag=0;
                printf("已停止发送\r\n\r\n");
            }
            if(flag_B==1)   //如果flag_B==1表示上位机发送的是恢复发送信号 
            {  
                flag=1;
                printf("已恢复发送\r\n\r\n");
            }
			USART_RX_STA=0;
		}
        else
		{
            //flag为1表示允许stm32发送数据
            if(flag==1) times++;
			if(times%100==0&&flag==1)    printf("hello LYJ!!!\r\n"); 
			if(times%30==0&&flag==1) LED=!LED;   //闪烁LED,提示系统正在运行.
			delay_ms(10);   
		}
	}	 
 }

3.实验说明

  • 实验环境

    • 主控芯片:STM32F103C8T6
    • 实验软件:Keil 5、串口调试助手、FlyMcu程序下载软件
    • 实验器材:USB转串口、stm32核心板、面包板、若干杜邦线
    • 引脚连线:
    stm32核心板USB转串口
    PA9 (USART1-TX)RXD
    PA10 (USART1-RX)TXD
    3V33V3
    GNDGND
  • 基于寄存器与库函数的区别

    • 寄存器
      寄存器是CPU内部用来存放数据的一些小型存储区域,用来暂时存放参与运算的数据和运算结果。基于寄存器编写stm32程序时,其实就是通过直接操作寄存器实现自己想要的功能。虽然通过寄存器能直接实现自己的目的,但是由于基于寄存器编程涉及到大量的位操作和各种各样的寄存器,因而上手难度较高。
    • 库函数
      STM32标准外设库函数是一个固件函数包,它由程序、数据结构和宏组成,包括了微控制器所有外设的性能特征,其本质上就是将寄存器的操作封装成API提供给我们直接调用。该函数库还包括每一个外设的驱动描述和应用实例,为我们访问底层硬件提供了一个中间API,通过使用固件函数库,无需深入掌握底层硬件细节,我们就可以轻松应用每一个外设而不需要记忆大量寄存器,但同时也让我们不能深入了解底层功能的实现。
  • printf()函数重定向
    想必大家应该注意到了程序中循环发送的hello LYJ!!!使用的是printf(“hello LYJ!!!”)语句进行发送,这是为什么呢?因为在程序中已经将 fputc()里面的输出指向串口 (重定向 ),然后就可以使用printf()函数将信息输出到串口即直接发送信息到上位机。
    注意:
    因为printf()之类的标准库函数会使用半主机模式,这会导致程序无法运行 ,以下是解决方法 :

    • 1.使用微库(microLIB) ,因为使用微库的话 ,不会使用半主机模式
      如果使用的是 MDK,请在工程属性的 “Target “- 》”Code Generation “中勾选 ”Use MicroLIB “这样以后就可以使用 printf ,sprintf 函数了
    • 2.仍然使用标准库 ,在主程序添加下面代码 :
      为确保没有从C库链接使用半主机的函数,标准C库stdio.h中有些使用半主机的函数需要自己重写(重定向),以下为示例:
    #pragma import(__use_no_semihosting)  // 确保没有从 C 库链接使用半主机的函数
    _sys_exit(int  x) //定义 _sys_exit() 以避免使用半主机模式
    {
    x = x;
    }
    struct __FILE  // 标准库需要的支持函数
    {
    int handle;
    };
    /* FILE is typedef ’ d in stdio.h. */
    FILE __stdout;
    //重定义fputc函数 
    int fputc(int ch, FILE *f)
    {      
    	while((USART1->SR&0X40)==0);//循环发送,直到发送完毕   
        USART1->DR = (u8) ch;      
    	return ch;
    }
    
  • 串口调试助手发送新行
    由于stm32串口接收数据时通过换行符来判断发送数据是否结束,因此需要在发送的字符串后加上换行。第一种方法是直接在发送的字符串后加入换行(回车换行),第二种方法是直接勾选发送新行选项,由软件在发送的字符串后自动添加换行。

(三)实验结果及总结

1.实验结果

  • 串口调试助手
    test
    这里分别测试了Stop,stm32Begin,stm32hello,world!!!HELLO LYJ!!!四条发送语句对于stm32发送数据的影响。其中Stop,stm32Begin,stm32分别控制stm32停止发送和恢复发送,其余两条语句对于串口发送没有影响。
  • LED灯状态(LED灯闪烁表示串口正常发送)
    串口状态

2.实验总结

通过对stm32USART串口发送接收数据实验的学习,我明白了stm32是如何通过串口中断接收数据的,并通过上位机(windows10)发送控制指令控制stm32串口是否发送数据,同时还通过LED灯的闪烁表示串口发送的状态。虽然在整个过程中不可避免地遇到了些问题,但都被自己所解决,这也提升了我解决故障和问题的能力。

感谢以下文章对我的帮助:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值