STM32 UART串口通讯编程方法

发布者:Xiaoxue666最新更新时间:2021-08-21 来源: eefocus关键字:STM32  UART  串口通讯  编程方法 手机看文章 扫描二维码
随时随地手机看文章

在对通讯时间要求比较高的时候,就需要自己对UART的通讯底层直接进行操作。我以STM32单片机为例,讲一下比较快速的UART编程方法。——其实不止是STM32这么处理,我以前使用过51的单片机,TI的MSP单片机,三菱的16位单片机,都可以采用这种方法。


基本的处理思路如下:


1. UART接收的处理方法


打开UART的接收中断,每收到一个字节就放到接收缓冲区,同时更新接收指针。当连续100ms没有收到接收字符,则认为本次帧接收完毕,置位帧接收完成标志,由主程序进行处理。


2. UART发送的处理方法


将需要发送的数据放到发送缓冲区,设置发送长度。然后发送第一个字节,并打开发送中断。在发送中断中判断是否已经发送了指定长度的数据。如果没有发送完成,则继续发送;发送完成,则关闭发送中断。


以上方法,说起来比较简单,主要是容错的处理,以及细节的考虑。以下我以STM32单片机为例进行说明。


1. 定义需要的变量


uint8_t gcRXDBuffer[50], gcRXDPointer, gcRXDLength;    //接收的缓冲区、接收指针、接收的帧长度

uint8_t gcTXDBuffer[50], gcTXDPointer, gcTXDLength;    //发送的缓冲区,发送指针,发送的长度

uint8_t gcInRXDMode, gcInTXDMode;                //是否处于接收或发送的状态标志,在需要切入低功耗模式,或关闭其他功能时需要


2. 初始化UART寄存器,以LL库为例,HAL库也可以。其实这部分功能使用STM32CUBEMX自己生成就行,不用给自己编写。


/* USART2 init function */

static void MX_USART2_UART_Init(void)

{

  LL_USART_InitTypeDef USART_InitStruct;

  LL_GPIO_InitTypeDef GPIO_InitStruct;


  /* Peripheral clock enable */

  LL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_USART2);

  

  /**USART2 GPIO Configuration  

  PA9   ------> USART2_TX

  PA10   ------> USART2_RX 

  */

  GPIO_InitStruct.Pin = LL_GPIO_PIN_9;

  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;

  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;

  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;

  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;

  GPIO_InitStruct.Alternate = LL_GPIO_AF_4;

  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);


  GPIO_InitStruct.Pin = LL_GPIO_PIN_10;

  GPIO_InitStruct.Mode = LL_GPIO_MODE_ALTERNATE;

  GPIO_InitStruct.Speed = LL_GPIO_SPEED_FREQ_LOW;

  GPIO_InitStruct.OutputType = LL_GPIO_OUTPUT_PUSHPULL;

  GPIO_InitStruct.Pull = LL_GPIO_PULL_NO;

  GPIO_InitStruct.Alternate = LL_GPIO_AF_4;

  LL_GPIO_Init(GPIOA, &GPIO_InitStruct);


  /* USART2 interrupt Init */

  NVIC_SetPriority(USART2_IRQn, 0);

  NVIC_EnableIRQ(USART2_IRQn);


  USART_InitStruct.BaudRate = 9600;

  USART_InitStruct.DataWidth = LL_USART_DATAWIDTH_8B;

  USART_InitStruct.StopBits = LL_USART_STOPBITS_1;

  USART_InitStruct.Parity = LL_USART_PARITY_NONE;

  USART_InitStruct.TransferDirection = LL_USART_DIRECTION_TX_RX;

  USART_InitStruct.HardwareFlowControl = LL_USART_HWCONTROL_NONE;

  USART_InitStruct.OverSampling = LL_USART_OVERSAMPLING_16;

  LL_USART_Init(USART2, &USART_InitStruct);

  LL_USART_DisableOverrunDetect(USART2);

  LL_USART_ConfigAsyncMode(USART2);

  LL_USART_Enable(USART2);


}


3. 自己增加的初始化,初始化变量,并打开接收中断。 

/************************************

*不带流控的USART2函数****************

*/

void uart2Init(void)

{

gcRXDPointer=0;

gcRXDLength=0;

gcTXDPointer=0;

gcTXDLength=0;

gcInRXDMode=0;

gcInTXDMode=0;

LL_USART_EnableIT_RXNE(USART2);

//LL_USART_EnableIT_TXE(USART2);

}


4. 以上的初始化就完成了,下面看中断处理函数的编程方法。


/**

* @brief This function handles USART2 global interrupt / USART2 wake-up interrupt through EXTI line 26.

*/

void USART2_IRQHandler(void)

{

  /* USER CODE BEGIN USART2_IRQn 0 */

uint8_t ucTemp;

  /* USER CODE END USART2_IRQn 0 */

  /* USER CODE BEGIN USART2_IRQn 1 */

if(LL_USART_IsActiveFlag_RXNE(USART2))

{    //如果是接收中断

gcUartCounter=0;    //全局变量,每次收到一个自己就清零,到100ms没有更新认为接收完成。       

ucTemp=LL_USART_ReceiveData8(USART2);

gcRXDBuffer[gcRXDPointer++]=ucTemp;    //接收到的数据放入缓冲区

gcInRXDMode=1;                         //当前在接收模式

}

else if(LL_USART_IsActiveFlag_TXE(USART2))

{     //如果是发送中断

gcTXDPointer++;

if(gcTXDPointer {   //还没有发送完成,继续发送

LL_USART_TransmitData8(USART2, gcTXDBuffer[gcTXDPointer]);

}

else

{    //发送完成了,复位发送的变量

gcTXDLength=0;

gcTXDPointer=0;

LL_USART_DisableIT_TXE(USART2);  //关闭发送中断

gcInTXDMode=0;  //退出发送模式

delayms(1);     //有流控时需要延时关闭流控,比如RTS,或者485中断的发送引脚。

RTS_HIGH();

}

}

  /* USER CODE END USART2_IRQn 1 */

}


5. 在系统的1ms SysTick中断中,判断是否100ms没有收到数据了。


gcUartCounter++;   //这个变量在接收中断中不断清零

if(gcUartCounter>=100)

{   //100ms没有收到数据了,如果有数据,则打包帧

gcUartCounter=100;

gcInRXDMode=0;  //退出接收模式

if(gcRXDPointer>=3)

{  //根据协议长度,3是可以改动的。

gcRXDLength=gcRXDPointer;  //将长度放入gcRXDLength,由主程序处理 gcRXDPointer=0; }

}

6. 以上程序中,接收数据部分就完成了。在主程序,或主业务中,判断gcRXDLength就知道是否有数据需要处理。


7. 在需要发送数据的时候:


/************************************

清空发送缓冲区的函数,需要重新组织发送时调用。*/

void TxdClearBuff(void)

{

gcTXDPointer=0;

gcTXDLength=0;

}


/************************************

如果需要多次组织数据,就一次次调用Push函数,将发送数据送入发送缓冲区。*/

void TxdPushToBuff(uint8_t *buffer, unsigned int length)

{

memcpy(gcTXDBuffer+gcTXDLength, buffer, length);

gcTXDLength+=length;

}


/************************************

组织完数据后,调用TxdSend,进行发送。*/

void TxdSend(void)

{

LL_USART_TransmitData8(USART2, gcTXDBuffer[0]);

gcTXDPointer=0;    

LL_USART_EnableIT_TXE(USART2);   //打开发送中断。

gcInTXDMode=1;                   //进入发送模式。

}


 

以上发送需要使用3个函数,有些复杂。如果你一次就能将数据组织完成,就可以写简单点。


在对实时处理要求更严格的时候,会在接收中断中直接处理帧头的判断(是否是正确的帧头,不是则接收指针直接清零),并根据帧长度字节,判断接收是否完成,然后直接调用通讯处理函数。这样的处理方法最快速,但封装不好,不易维护。不是必须的时候,不建议这么使用。


关键字:STM32  UART  串口通讯  编程方法 引用地址:STM32 UART串口通讯编程方法

上一篇:STM32的map文件学习笔记
下一篇:STM32 PWM学习

推荐阅读最新更新时间:2024-11-06 18:35

意法半导体发布远距离无线微控制器,提高智能计量、智能建筑和工业监控的连接能效
新的STM32系统芯片低功耗,支持多种无线通信协议,简化各种用途的无线系统设计 中国,2023年11月24日 - 服务多重电子应用领域、全球排名前列的半导体公司意法半导体(STMicroelectronics,简称ST;) 发布了一款新的融合无线芯片设计专长与高性能、高能效STM32系统架构的微控制器(MCU)。全新的节能功能将这款无线MCU的电池续航时间延长到15年以上。 在远距离部署的应用领域,包括能源计量、监控设备、报警系统、执行器,以及智能建筑、智能工厂和智能城市的传感器,STM32WL3无线MCU的特别有用,有助于控制功耗,并给工作划分优先级。这些高能效MCU可以改善用户体验,提供服务,减少环境足迹。通过
[嵌入式]
意法半导体发布远距离无线微控制器,提高智能计量、智能建筑和工业监控的连接能效
STM32 HSI RCC时钟配置
因为不用精确的定时,因此想不用外部晶振,直接使用内部的8MHz RC振荡器作为主振,倍频后系统时钟为36MHz 现将源代码贴在此处。 /********************************************************************** * 名 称:void RCC_HSEConfiguration(void) * 功 能:使用HSI作为主时钟,然后经过倍频PLL=36MHz * 入口参数:无 * 出口参数: ----------------------------------------------------------------------- * 说 明:
[单片机]
如何设置STM32单片机非初始化数据变量不被零初始化
一些产品,当系统复位后(非上电复位),可能要求保持住复位前RAM中的数据,用来快速恢复现场,或者不至于因瞬间复位而重启现场设备。而keil mdk在默认情况下,任何形式的复位都会将RAM区的非初始化变量数据清零。 在给出方法之前,先来了解一下代码和数据的存放规则、属性,以及复位后为何默认非初始化变量所在RAM都被初始化为零了呢。 什么是初始化数据变量,什么又是非初始化数据变量?(因为我的文字描述不一定准确,所以喜欢举一些例子来辅助理解文字。) 定义一个变量:int nTimerCount=20;变量nTimerCount就是初始化变量,也就是已经有初值; 如果定义变量:int nTimerCount;变量nTimerCou
[单片机]
如何设置<font color='red'>STM32</font>单片机非初始化数据变量不被零初始化
STM32复习笔记(七)定时器&定时器中断
一、STM32定时器: STM32F10x系列总共最多有8个定时器: 二、三种STM32定时器区别: 三、通用定时器功能特点描述: STM32 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能特点包括: 位于低速的APB1总线上(APB1) 16 位向上、向下、向上/向下(中心对齐)计数模式,自动装载计数器(TIMx_CNT)。 16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值。 4 个独立通道(TIMx_CH1~4),这些通道可以用来作为: ① 输入捕获 ② 输出比较 ③ PWM 生成(边缘或中间对
[单片机]
<font color='red'>STM32</font>复习笔记(七)定时器&定时器中断
stm32驱动超声波模块
#define HCSR04_PORT GPIOB #define HCSR04_CLK RCC_APB2Periph_GPIOB #define HCSR04_TRIG GPIO_Pin_8 #define HCSR04_ECHO GPIO_Pin_9 #define TRIG_Send(n) do{ if(n == 0) GPIO_ResetBits(HCSR04_PORT,HCSR04_TRIG); else if(n == 1) GPIO_SetBits(HCSR04_PORT,HCSR04_TRIG); }while(0) #define ECHO_Reci GPIO_Re
[单片机]
stm32 can通信发送解释
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage) { uint8_t transmit_mailbox = 0; /* Check the parameters */ assert_param(IS_CAN_ALL_PERIPH(CANx)); assert_param(IS_CAN_IDTYPE(TxMessage- IDE)); assert_param(IS_CAN_RTR(TxMessage- RTR)); assert_param(IS_CAN_DLC(TxMessage- DLC)); /* Select one empty tr
[单片机]
STM32学习笔记:【001】时钟树与RCC
导言 如果学过单片机的同学应该不会陌生,学习51单片机时最经常听到的就是“最小系统”。 最小系统里面少不了晶振,否则单片机无法工作。 单片机需要晶振(时钟源)来工作,那么对于STM32芯片同样如此。 此外,同一个电路,时钟越快功耗越大,抗电磁干扰能力也随之变弱。 所以对于较为复杂的MCU一般都是采取多时钟源的方法来解决这些问题。 STM32的时钟树与时钟源 时钟源 在STM32F4中,有5个最重要的时钟源,按来源分为内外部两种;按速率也可以分为高低速两种。 时钟源 含义 时钟速率 用途 HSI 高速内部时钟 16MHz 可以直接作为系统时钟或者PLL输入 HSE 高速外部时钟 4~26MHz 可以直接作为系统时钟 L
[单片机]
<font color='red'>STM32</font>学习笔记:【001】时钟树与RCC
stm32 CAN总线例子
利用stm32实现了1个简单的CAN功能,使用了队列缓存 can.c 文件 #include includes.h #define GPIO_CAN GPIOB #define RCC_APB2Periph_GPIO_CAN RCC_APB2Periph_GPIOB #define GPIO_Pin_RX GPIO_Pin_8 #define GPIO_Pin_TX GPIO_Pin_9 #define GPIO_Remap_CAN GPIO_Remap1_CAN1 #define MAX_MAIL_NUM 3 static u8 CAN_msg_num ; //
[单片机]
小广播
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
随便看看

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved