首页 > 其他分享 >串口通信操作方法及三种实现方式(基于百问网DshanMCU-F103)

串口通信操作方法及三种实现方式(基于百问网DshanMCU-F103)

时间:2024-07-21 16:55:08浏览次数:15  
标签:HAL UART void huart1 DshanMCU 百问 串口 circle buf

UART全称为通用异步收发器,英文全称(Universal Asynchronous Receiver/Transmitter)。是一种串行、异步、全双工的通信协议。

一、使用方法

UART首先将接收到的并行数据转换成串行数据来传输,消息帧从一个低位起始位开始,后面是5-8个数据位,一个可用的奇偶位和一个或几个高低停止位。

数据传输模拟图

发送过程:接收器发现开始位时它就知道数据准备发送,并尝试与发送器时钟频率同步。如果选择了奇偶,UART就在数据位后面加上奇偶位。奇偶位可用来帮助校验错误。

接收过程:UART从消息帧中去掉起始位和结束位,对进来的字节进行奇偶校验,并将数据字节从串行转换成并行。UART也产生额外的信号来指示发送和接收的状态。例如,如果产生一个奇偶错误,UART就置位奇偶标志。

在UART通讯协议中信号线上的状态位高电平表示‘1’低电平代表‘0’,特点是:通信线路简单,只要一对传输线就可以实现双向通信,大大降低了成本,但传输速度慢,传输距离短。

二、UART数据协议

在串口通信中,最主要的是数据流以及波特率。在传输过程中,需要关注的是起始位、数据位、校验位、停止位、波特率

起始位:通信线路空闲时为“1”,当检测到下降沿时,认为数据开始传输;

有效数据位:传输开始后传递需要发送和接收的数据值,可以是指令或者数据;

校验位:一般为奇偶校验,即通过来校验传输数据中‘1’的个数为奇数个奇校验)或者是偶数个偶校验)来表示传输的数据正确与否;

停止位:数据传输结束后,传输线恢复成‘1’状态;

波特率:1秒内传输信号的状态数(波形数)。比特率:1秒内传输数据的bit。如果是一个波形,能表示N个bit,那么,波特率 * N = 比特率。

三、STM32CubeMX配置USART

在CubeMX新建项目选择STM32F103C6开发板,配置USART1,配置使用Asynchronous异步模式,如下图:

实现中断方式需要选择NVIC配置,将Enabled选项勾选,如下图:

实现DMA方式,需要在usart1中作如下设置:

四、代码实现

/*定义一个huart1变量*/
UART_HandleTypeDef huart1;
​
/* USART1 init function */
​
void MX_USART1_UART_Init(void)
{
​
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart1) != HAL_OK)
  {
    Error_Handler();
  }
​
}
​
/*PA9为复用    推挽输出模式、PA10为浮空输入模式*/
void HAL_UART_MspInit(UART_HandleTypeDef* uartHandle)
{
​
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(uartHandle->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */
​
  /* USER CODE END USART1_MspInit 0 */
    /* USART1 clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();
​
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
​
    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
​
  }
}
​
​

1、轮询收发方式

在主函数中调用HAL_UART_Transmit即可在串口调试助手显示出我们定义的字符串变量。

/*函数原型HAL_UART_Transmit(UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size, uint32_t Timeout)*/
​
  /* USER CODE BEGIN 1 */
    extern UART_HandleTypeDef huart1;
    char *str1 = "I from www.100ask.net\r\n";
    char *message = "I am DshanMCU-F103\r\n";
    char c;
  /* USER CODE END 1 */
​
HAL_UART_Transmit(&huart1, (uint8_t *)message, strlen(message), 100);
HAL_UART_Transmit(&huart1, (uint8_t *)str1, strlen(str1), 100);
​
​
实验现象

2、中断收发方式

中断方式实现串口通信需要对NVIC进行设置,对内置函数不能之间修改,我们需要自己作函数定义,通过使用环形缓冲区来实现多数据的传输,避免数据丢失,将数据存储入环形缓冲区,从而实现串口通信。

uart.c

/* USER CODE BEGIN 0 */
#include "circle_buffer.h"
/* USER CODE END 0 */
​
/* USER CODE BEGIN 1 */
static volatile int g_tx_cplt = 0;
static volatile int g_rx_cplt = 0;
static uint8_t g_RecvBuf[100];
static uint8_t g_RecvChar;
static circle_buf g_uart1_rx_bufs;
​
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    g_tx_cplt=1;
}
​
void Wait_Tx_Complete(void)
{
    while(g_tx_cplt == 0);
    g_tx_cplt = 0;
}
​
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    g_rx_cplt=1;
    
    circle_buf_write(&g_uart1_rx_bufs, g_RecvChar);
    
    /*re-enable rxne interrupt*/
    HAL_UART_Receive_IT(&huart1, &g_RecvChar, 1);
}
​
void Wait_Rx_Complete(void)
{
    while(g_rx_cplt == 0);
    g_rx_cplt = 0;
}
​
void StartUARTRecv(void)
{
    /*init circle_buffer 用于存储数据 */
    circle_buf_init(&g_uart1_rx_bufs, 100, g_RecvBuf);
    /*enable rxne interrupt*/
    HAL_UART_Receive_IT(&huart1, &g_RecvChar, 1);
}
​
int UARTGetChar(uint8_t *pVal)
{
    return circle_buf_read(&g_uart1_rx_bufs, pVal);
}
​
/* USER CODE END 1 */
​

circle_buffer.h

#ifndef _CIRCLE_BUF_H
#define _CIRCLE_BUF_H
​
#include <stdint.h>
​
typedef struct circle_huf{
        uint32_t r;
        uint32_t w;
        uint32_t len;
        uint8_t *buf;
}circle_buf,*p_circle_buf;
​
void circle_buf_init(p_circle_buf pCircleBuf,uint32_t len,uint8_t *buf);
​
int circle_buf_read(p_circle_buf pCircleBuf,uint8_t *pVal);
​
int circle_buf_write(p_circle_buf pCircleBuf,uint8_t val);
#endif 
​

circle_buffer.c

#include "stdint.h"
#include "circle_buffer.h"
/*
环形缓冲区初始化,起始为空
三个参数分别是指向环形缓冲区结构体的指针 缓冲区长度 指向缓冲区内存的的指针
配置传入的长度和缓冲区地址赋值给结构体成员变量
*/
void circle_buf_init(p_circle_buf pCircleBuf,uint32_t len,uint8_t *buf)
{
    pCircleBuf->r = pCircleBuf->w = 0;
    pCircleBuf->len = len;
    pCircleBuf->buf = buf;
}
/*
函数功能:从环形缓冲区中读取数据的函数
参数:指向环形缓冲区结构体的指针,存储读取数据的指针pVal
功能解释:r!=w时 有数据可读,读取当前位置数据,读指针向后移动一位
成功读取返回为0,返回-1缓冲区为空
**/
int circle_buf_read(p_circle_buf pCircleBuf,uint8_t *pVal)
{
    if(pCircleBuf->r != pCircleBuf->w){
        *pVal = pCircleBuf->buf[pCircleBuf->r];
        
        pCircleBuf->r++;
        
        if(pCircleBuf->r == pCircleBuf ->len)
            pCircleBuf->r=0;
        return 0;
    }
    else
    {
        return -1;
    }
    
}
/**
函数功能:向缓冲区写入数据函数
参数:指向缓冲区的指针  写入的数据val
功能解释:如果next_w==w,表示缓冲区已满,无法写入返回-1
若返回0表示写入成功
**/
int circle_buf_write(p_circle_buf pCircleBuf,uint8_t val)
{
    uint32_t next_w;
    
    next_w = pCircleBuf->w + 1;
    if(next_w == pCircleBuf->len)
        next_w = 0;
    if(next_w != pCircleBuf->r)
    {
        pCircleBuf->buf[pCircleBuf->w] = val;
        pCircleBuf->w = next_w;
        return 0;
    }
    else
    {
        return -1;
    }
}

main.c

/* USER CODE BEGIN 1 */
extern UART_HandleTypeDef huart1;
char *str2 = "please enter a char:\r\n";
char c;
/* USER CODE END 1 */
​
/* USER CODE BEGIN PV */
/*函数声明*/
void Wait_Tx_Complete(void);
void Wait_Rx_Complete(void);
void StartUARTRecv(void);
int UARTGetChar(uint8_t *pVal);
/* USER CODE END PV */
​
/*enable rxne interrupt*/
StartUARTRecv();
​
while (1)
  {
      HAL_UART_Transmit_IT(&huart1, (uint8_t *)str2, strlen(str2));
      /*wait for tc*/
      Wait_Tx_Complete();
      
      while(UARTGetChar(&c) != 0);
    
      c+=1;
      
      HAL_UART_Transmit(&huart1, &c, 1, 100);
      HAL_UART_Transmit(&huart1, "\r\n", 2, 100);
    /* USER CODE END WHILE */
  }
实验现象

3、DMA方式

只是简单使用DMA方式的话,在配置好USART的DMA设置之后,更改函数HAL_UART_Transmit_IT为HAL_UART_Transmit_DMA就可以实现函数功能了。使用DMA方式配合IDLE空闲函数的话,能够实现它的功能。

IDLE,空闲的定义是:总线上在一个字节的时间内没有再接收到数据。 UART 的 IDLE 中断何时发生?RxD 引脚一开始就是空闲的啊,难道 IDLE 中断一直产生? 不是的。当我们使能 IDLE 中断后,它并不会立刻产生,而是:至少收到 1 个数据后,发现 在一个字节的时间里,都没有接收到新数据,才会产生 IDLE 中断。

uart.c

/* USER CODE BEGIN 1 */
static volatile int g_tx_cplt = 0;
static volatile int g_rx_cplt = 0;
//static uint8_t g_RecvChar;
static uint8_t g_RecvTmpBuf[10];
static uint8_t g_RecvBuf[100];
static circle_buf g_uart1_rx_bufs;
​
void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
{
    g_tx_cplt = 1;
}
​
void Wait_Tx_Complete(void)
{
    while (g_tx_cplt == 0);
    g_tx_cplt = 0;
}
​
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
    g_rx_cplt = 1;
    
    /* put data to circle buffer */
    for (int i = 0; i < 10; i++)
    {
        circle_buf_write(&g_uart1_rx_bufs, g_RecvTmpBuf[i]);
    }
    
    /* re-use dma+idle to recv */
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_RecvTmpBuf, 10);
}
void Wait_Rx_Complete(void)
{
    while (g_rx_cplt == 0);
    g_rx_cplt = 0;
}
​
void StartUART1Recv(void)
{
    /* init circle buffer */
    circle_buf_init(&g_uart1_rx_bufs, 100, g_RecvBuf);
    
    /* use dma+idle to recv */
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_RecvTmpBuf, 10);
}
​
int UART1GetChar(uint8_t *pVal)
{
    return circle_buf_read(&g_uart1_rx_bufs, pVal);
}
​
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
    for (int i = 0; i < Size; i++)
    {
        circle_buf_write(&g_uart1_rx_bufs, g_RecvTmpBuf[i]);
    }
​
    /* re-use dma+idle to recv */
    HAL_UARTEx_ReceiveToIdle_DMA(&huart1, g_RecvTmpBuf, 10);
}
​
/* USER CODE END 1 */
​

main.c

int main(void)
{
  /* USER CODE BEGIN 1 */
    extern UART_HandleTypeDef huart1;
    char *str1 = "I from www.100ask.net\r\n";
    char *message = "I am DshanMCU-F103\r\n";
    char *str2 = "please enter a char:\r\n";
    char c;
    
  /* USER CODE END 1 */
​
  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();
​
  /* USER CODE BEGIN Init */
​
  /* USER CODE END Init */
​
  /* Configure the system clock */
  SystemClock_Config();
​
  /* USER CODE BEGIN SysInit */
​
  /* USER CODE END SysInit */
​
  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_DMA_Init();
  MX_I2C1_Init();
  MX_USART1_UART_Init();
​
​
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  
    /*enable rxne interrupt*/
    StartUART1Recv();
  
    HAL_UART_Transmit(&huart1, (uint8_t *)message, strlen(message), 100);
    HAL_UART_Transmit(&huart1, (uint8_t *)str1, strlen(str1), 100);
    
  while (1)
  {
      HAL_UART_Transmit_DMA(&huart1, (uint8_t *)str2, strlen(str2));
      
      while(0 != UART1GetChar(&c));
      
      c+=1;
      
      HAL_UART_Transmit(&huart1, &c, 1, 100);
      HAL_UART_Transmit(&huart1, "\r\n", 2, 100);
}
实验现象

五、总结

①.UART协议的优点

  • 简单的硬件需求:只需要两根数据线,一个用来发送(TX),一个用来接收(RX),由于是异步通信,不需要额外的时钟信号线,进一步减少了硬件需求。

  • 全双工通信能力:UART允许设备在一根线上发送数据,同时在另一根线上接收数据,实现全双工通信,提高了数据传输效率。

  • 配置灵活性:用户可以根据需求配置不同的波特率和数据格式,提高了灵活的通信速率和适应不同通信场景的能力。

  • 错误检测和处理机制:UART通过奇偶校验机制能够检测单个比特的错误,确保数据传输的正确性,并能够指示帧错误、奇偶校验错误等多种错误状态,有助于及时发现并处理通信中的问题。

  • 中断驱动的操作方式:支持中断驱动的操作模式,可以在不占用CPU的情况下进行数据传输,从而释放CPU资源执行其他任务。

②.UART协议的缺点

  • 数据传输速率较低:与诸如SPI或I2C等其他串行通信协议相比,UART的数据传输速率通常较低,这限制了其在高速数据传输需求场合的应用。

  • 数据丢失问题:当接收端的数据缓冲区已满而无法及时处理新数据时,可能会发生数据丢失现象,这在一些高速或高容量数据传输应用中可能成为瓶颈。

标签:HAL,UART,void,huart1,DshanMCU,百问,串口,circle,buf
From: https://blog.csdn.net/weixin_62897522/article/details/140504320

相关文章

  • STM32+USART串口(1)
    GPIO口的复用功能是有对应的,作USART使用的话要选择对应的GPIO;可以参考引脚定义;(1)串口通信分为:串行通信和并行通信;(2)通信波特率:通常用波特率(BaudRate)来衡量数据通信的速度。波特率是指每秒钟传送数据的位数,单位为bps(BitPerSecond),用户可根据需要进行设定(3)异步通信:在异步通......
  • TI-MSPM0G3507外设使用,SPI串口连接ICM20602陀螺仪
    写在前面备战2024电赛,使用到了TI开发板,型号MSPM0G3507,该开发板除文档外,网上资料稀少。现在为大家提供spi连接icm20602陀螺仪的代码,以促共同进步。该代码由逐飞seekfree仓库移植而来,如有侵权请私信联系我删除,谢谢。代码亲测成功,如有bug欢迎评论区指正。头文件ICM20602......
  • 蓝桥杯单片学习总结(Day12 串口通讯实验)
    实验现象:        通过串口调试助手发送数字1~8,板子上面的对应指示灯亮。注意此处发送应选择文本模式发送。 实例代码:#include<STC15F2K60S2.H>#defineBUAD 9600//所需波特率、#defineSYSTEMCLOCK 11059200L//系统时钟频率,L表示该数据为长整型voiduart_......
  • AMD R2000 Bilby 单板 串口在Grub和Linux下的使用
    Ubuntu20启动时,自动加载了UART驱动,系统启动信息含有UART的相关信息。[2.418748]printk:console[ttyS4]disabled[2.418757]AMDI0020:00:ttyS4atMMIO0xfedc9000(irq=3,base_baud=3000000)isa16550A[2.418820]printk:console[ttyS4]enabled[......
  • C#调用C++库,进行串口通信
    1、添加C++接口实现,将dll放置在运行路径下。dll文件下载:https://files.cnblogs.com/files/ZM191018/SerialPortLib.7z?t=1721271982&download=true[DllImport("SerialPortLib.dll",CharSet=CharSet.Unicode,CallingConvention=CallingConvention.StdCall)]......
  • 【QT开发】串口通信管理QSerialPort类详解及实战应用
    QSerialPort是Qt提供的一个功能强大、简单易用的串口通信类。通过本文的学习,您应该对QSerialPort的基本使用、高级应用技巧及相关注意事项有了全面的理解。在实际项目中,QSerialPort可以帮助实现与外部设备的串口通信,确保数据的可靠传输和接收。希望本文能帮助您更好地......
  • 07:串口通信二
    串口编程1、与波特率之相关的寄存器2、PCON寄存器3、SCON寄存器4、配置的代码分析5、向PC发送一段字符串6、PC机向单片机发送字符控制LED1灯的亮灭1、与波特率之相关的寄存器如图,与串口通信相关的寄存器主要是SCON和PCON寄存器。2、PCON寄存器SMOD:为1时,通信方式1......
  • 串口、IIC、SPI的优缺点
    串口、IIC、SPI的优缺点串口(SerialPort)串口通信是一种基本的串行通信方式,它通过串行数据线(TX和RX)进行数据的发送和接收。串口通信通常用于微控制器与PC或其他设备之间的通信。特点:简单易用,硬件实现成本低。通信速率较低,适合长距离通信。可以实现全双工通信(同时发送和接收......
  • STMF4串口通信使用
    目录STMF4串口通信使用USART的使用流程注意STMF4串口通信使用前提回顾:串口通信概念介绍1STM32串口通信概念介绍2针对特定stm32F4板子的串口接线折叠文本USART指的是通用同步异步收发器,是STM32中的串行通信设备,STM32F407ZET6一共提供了6个串行接口供用户使用,其中4个为US......
  • USART串口协议 和va_list,va_start,va_end,vsprintf
    串口接口通信的目的:将一个设备的数据传送到另一个设备扩展硬件系统通讯协议:制定通信的规则,通信双方按照协议规则进行数据收发。注意:差分信号一般传输速度和距离都会非常高。多设备相当于老师在教师给所有同学讲课,点对点相当于老师找你到办公室谈话。串口是一种应用......