STM32串口通信(串口中断、FIFO机制)之安富莱代码学习笔记

2023-05-16

创建串口设备结构体

typedef struct
{
  USART_TypeDef *uart;/*STM32内部串口设备指针*/
	uint8_t *pTxBuf;/*发送缓冲区*/
	uint8_t *pRxBuf;/*接收缓冲区*/
	uint16_t usTxBufsize;/*发送缓冲区大小*/
	uint16_t usRxBufsize;/*接收缓冲区大小*/
	uint16_t usTxWrite;/*发送缓冲区写指针(FIFO机制)*/
	uint16_t usTxRead;/*发送缓冲区读指针*/
	uint16_t usTxCount;/*等待发送的数据个数*/
	
	uint16_t usRxWrite;/*接收缓冲区写指针*/
	uint16_t usRxRead;/*接收缓冲区读指针*/
	uint16_t usRxCount;/*还未读取的新数据的个数*/
	
	void (*SendBefor)(void);/*开始发送之前的回调函数*/
	void (*SendOver)(void);/*发送完毕的回调函数指针*/
	void (*ReciveNew)(void);/*串口收到数据的回调函数指针*/
}UART_T;

上述的接收缓冲区读写指针和发送缓冲区读写指针虽然是uint16_t的数据类型,但是在这里称之为指针,是因为其在队列中反应了数据读写时位置的变化。

static void UartVarInit(void)
{
	
	g_tUart1.uart = USART1;/*串口设备*/
	g_tUart1.pTxBuf = g_TxBuf1;/*发送缓冲区指针*/
	g_tUart1.pRxBuf = g_RxBuf1;/*接收缓冲区指针*/
	g_tUart1.usTxBufsize = UART1_TX_BUF_SIZE;/*发送缓冲区大小*/
    g_tUart1.usRxBufsize = UART1_RX_BUF_SIZE;/*发送缓冲区大小*/
    g_tUart1.usTxWrite = 0;/*发送缓冲区写指针初始化为0*/
	g_tUart1.usTxRead = 0;/*发送缓冲区读指针初始化为0*/
	g_tUart1.usRxWrite = 0;/*接收缓冲区写指针初始化为0*/
	g_tUart1.usRxRead = 0;/*接收缓冲区读指针初始化为0*/
	g_tUart1.usRxCount = 0;/*接收到的新数据个数*/
	g_tUart1.usTxCount = 0;/*待发送的数据个数*/
	g_tUart1.SendBefor = 0;/*发送数据前的回调函数*/
	g_tUart1.SendOver = 0;/*发送完成的回调函数*/
	g_tUart1.ReciveNew = 0;/*接收到新数据的回调函数*/
}

上述的初始化过程将读写指针初始化为0,表明最开始缓冲区中没有数据,也就是处于队列的最底部。
下面是初始化串口的硬件部分

static void InitHardUart(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	USART_ClockInitTypeDef USART_ClockInitStructure;
	
	/*第一步:配置GPIO*/
	/*使能串口1,PA,AFIO总线*/
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO | RCC_APB2Periph_USART1, ENABLE);
	
	/*A9 USART1-Tx*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/*A10 USART1-Rx*/
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA,&GPIO_InitStructure);
	
	/*第2步:配置串口硬件参数*/
	USART_InitStructure.USART_BaudRate = UART1_BAUD;
	USART_InitStructure.USART_WordLength = UART1_WORD_LENGTH;
	USART_InitStructure.USART_StopBits = UART1_STOP_BIT;
	USART_InitStructure.USART_Parity = UART1_PARITY;
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
	
	USART_ClockInitStructure.USART_Clock = USART_Clock_Disable;
	USART_ClockInitStructure.USART_CPOL = USART_CPOL_Low;
	USART_ClockInitStructure.USART_CPHA = USART_CPHA_2Edge;
	USART_ClockInitStructure.USART_LastBit = USART_LastBit_Disable;
	
	USART_ClockInit(USART1, &USART_ClockInitStructure);
	USART_Init(USART1, &USART_InitStructure);
	
	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);  /*使能接收中断*/
	/*不能在此处打开发送中断*/
	
	USART_Cmd(USART1, ENABLE);
	
	/*小缺陷:串口配置好,如果直接send,则第一个字节发送不出去*/
	USART_ClearFlag(USART1, USART_FLAG_TC);/*清发送完成标志,可以解决第一个字节无法正确发送的问题*/

}

上述过程是程序化步骤,没有思维难点,接下来配置串口中断

static void ConfigUartNVIC(void)
{
  NVIC_InitTypeDef NVIC_InitStructure;
	
  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
	
  NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

在发送数据的时候,采用UartSend();这个函数进行发送
具体的代码如下所示:

static void UartSend(UART_T *_pUart, uint8_t *_ucaBuf, uint16_t _usLen)
{
  uint16_t i;

  for(i = 0;i < _usLen;i++)
  {
    while(1)
    {
      __IO uint16_t usCount;
      DISABLE_INT();
      usCount = _pUart->usTxCount;
      ENABLE_INT();
      if(usCount < _pUart->usTxBufSize)
      {
         break;
      }
    }
   _pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i]; 
   DISABLE_INT();
   if (++_pUart->usTxWrite >= _pUart->usTxBufSize)
	{
	  _pUart->usTxWrite = 0;
	}
	_pUart->usTxCount++;/*等待发送的数据个数*/
   ENABLE_INT();
  }
  USART_ITConfig(_pUart->uart, USART_IT_TXE, ENABLE);/*使能发送中断*/
}
  if(usCount < _pUart->usTxBufSize)
  {
     break;
  }

通过上述的这句话保证每次写入缓冲区时都能保证等待发送的个数都小于缓冲区本身的大小,当等待发送的数据个数超过缓冲区本身的大小时,程序将陷入死循环。
_pUart->pTxBuf[_pUart->usTxWrite] = _ucaBuf[i]; 通过此条语句将要发送的数据送入缓冲区。
if (++_pUart->usTxWrite >= _pUart->usTxBufSize)
{
_pUart->usTxWrite = 0;
}
上述语句即表示如果当前的写指针已经移动到FIFO的顶端时,对指针进行复位。

同时在将要发送的数据存储到缓冲区之后,就可以通过触发中断发送数据
具体的发送中断的函数如下所示:

if (USART_GetITStatus(_pUart->uart, USART_IT_TXE) != RESET)
{
  if(_pUart->usTxCount == 0)
  {
    USART_ITConfig(_pUart->uart, USART_IT_TXE, DISABLE);/*发送缓冲区的数据已经取完        时,禁止发送缓冲区空中断*/
    USART_ITConfig(_pUart->uart, USART_IT_TC, ENABLE);/*使能数据发送完毕中断*/
  }
  else
  {
   /*从发送FIFO取一个字节写入发送数据寄存器*/
   USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);/*此条语句通过调用库函数实现*/
   if (++_pUart->usTxRead >= _pUart->usTxBufSize)
   {
	  _pUart->usTxRead = 0;
	}
	  _pUart->usTxCount--;/*每发送一个字节之后待发送数据个数减一*/
    }
  }
  else if (USART_GetITStatus(_pUart->uart, USART_IT_TC) != RESET)
  {
    if (_pUart->usTxCount == 0)
    {
      /*如果发送的数据全部发送完毕时,禁止数据发送中断*/
      USART_ITConfig(_pUart->uart, USART_IT_TC, DISABLE)}
    else
    {
       /*正常情况下,不会进入这个分支*/
       USART_SendData(_pUart->uart, _pUart->pTxBuf[_pUart->usTxRead]);
       /*进入此分支后,再次发送缓冲区的值*/
       if (++_pUart->usTxRead >= _pUart->usTxBufSize)
	   {
		  _pUart->usTxRead = 0;
	  }
	  _pUart->usTxCount--;
     }
  }

上述代码完成了一个完整的数据发送的过程
下面阐述关于接收数据的过程,接收数据通过触发中断接收数据。
中断处理函数如下:

if (USART_GetITStatus(_pUart->uart, USART_IT_RXNE) != RESET)
{
  uint8_t ch;

  ch = USART_ReceiveData(_pUart->uart);/*通过库函数从接收移位寄存器中读取收到的数据*/
  _pUart->pRxBuf[_pUart->usRxWrite] = ch;/*将数据存入接收缓冲区,每进入中断一次存取一次*/		
  if (++_pUart->usRxWrite >= _pUart->usRxBufSize)
  {
	_pUart->usRxWrite = 0;
  }
  if (_pUart->usRxCount < _pUart->usRxBufSize)
  {
	_pUart->usRxCount++;
  }
}

在接收中断里面读取到接收移位寄存器的值并将值存储到FIFO中,然后通过UartGetChar()函数读取缓冲区的值。

static uint8_t UartGetChar(UART_T *_pUart, uint8_t *_pByte)
{
  uint16_t usCount;

 DISABLE_INT();
 usCount = _pUart->usRxCount;
 ENABLE_INT();

 if(usCount == 0)/*沒有数据则返回*/
 {
    return 0;
 }
 else
 {
   *_pByte = _pUart->pRxBuf[_pUart->usRxRead];	
  }  
}

通过上述函数*_pByte便是从缓冲区读出的值
最后,再指出一点,再发送数据的时候,将要发送的数据送入缓冲区时,用的是发送写指针,在中断中将发送缓冲区中的数据填入发送数据寄存器中用的是发送读指针。
在中断接收时用的是接收写指针,读取程序中用的是接收读指针。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

STM32串口通信(串口中断、FIFO机制)之安富莱代码学习笔记 的相关文章

  • rt-thread studio中新建5.02版本报错

    先吐槽一下 rt thread studio出现BUG真多 好多时间都是在找BUG 但里面用好多控件还是挺好用的 真是又爱又恨 所以一般使用功能不多的话还是用keil多一点 创建5 02版本工程之后直接进行编译 直接会报下面这个错误 资源
  • Push_back() 导致程序在进入 main() 之前停止

    我正在为我的 STM32F3 Discovery 板使用 C 进行开发 并使用 std deque 作为队列 在尝试调试我的代码 直接在带有 ST link 的设备上或在模拟器中 后 代码最终在 main 中输入我的代码之前在断点处停止 然
  • 物联网网关

    物联网网关是 连接物联网设备和互联网的重要桥梁 它负责将物联网设备采集到的数据进行处理 存储和转发 使其能够与云端或其它设备进行通信 物联网网关的作用是实现物联网设备与云端的无缝连接和数据交互 物联网网关功能 数据采集 物联网网关可以从物联
  • STM32F103概要

    The STM32F103x4 STM32F103x6 STM32F103xC STM32F103xD and STM32F103xE are a drop in replacement for STM32F103x8 B medium d
  • HAL 锁定和解锁函数如何使用以及为什么?

    我试图理解另一位程序员编写的代码 它使用了I C http en wikipedia org wiki I C2 B2C通信以将数据写入 STM32 微控制器的 EEPROM 一般来说 我理解他的代码是如何工作的 但我不明白他为什么使用HA
  • 解决KEIL编译慢问题

    两种方案 使用v6版本的ARM Compiler 如果v6版本编译不过 必须使用v5版本的 则可以勾选掉Browse Information选项 提升很明显 1分多钟能优化到几秒 看代码量 但是这个有个弊端 在KEIL中会影响函数跳转 建议
  • 擦除后无法写入闪存

    所以我不能在擦除后直接写入内部闪存 如果写操作之前没有擦除操作 那么我可以 有什么想法吗 编程函数返回 成功写入 值 但查看内存时 没有写入任何数据 这是代码 uint32 t pageAddress 0x08008000 uint16 t
  • 为什么将队列实现为循环数组?

    当实现像队列这样的 FIFO 时 我的导师总是建议我们将其表示为循环数组而不是常规数组 为什么 是因为在后者中 我们最终会在数组中出现垃圾数据吗 如果您使用固定数量的阵列插槽 元素 则以圆形排列回收插槽会更容易 因为您不需要重新排序元素 每
  • 串口通讯第一次发送数据多了一字节

    先初始化IO再初始化串口 导致第一次发送时 多出一个字节数据 优化方案 先初始化串口再初始化IO 即可正常通讯
  • STM32F4XX的12位ADC采集数值超过4096&右对齐模式设置失败

    文章目录 一 前言 二 问题1 数值超过4096 三 问题1的排错过程 四 问题2 右对齐模式设置失败 五 问题2的解决方法 5 1 将ADC ExternalTrigConv设置为0 5 2 使用ADC StructInit 函数 一 前
  • 嵌入式开发--STM32G4系列片上FLASH的读写

    这个玩意吧 说起来很简单 就是几行代码的事 但楞是折腾了我大半天时间才搞定 原因后面说 先看代码吧 读操作 读操作很简单 以32位方式读取的时候是这样的 data IO uint32 t 0x0800F000 需要注意的是 当以32位方式读
  • systick定时器

    systick定时器 文章目录 前言 一 前期疑惑 二 解答 1 关于systick是阻塞的吗 2 非阻塞 三 软件编写 总结 前言 这边记录systick相关知识点 一 前期疑惑 在学习systick志气啊 其实对于systick还是一脸
  • Cortex-M3与M4权威指南

    处理器类型 所有的ARM Cortex M 处理器是32位的精简指令集处理器 它们有 32位寄存器 32位内部数据路径 32位总线接口 除了32位数据 Cortex M处理器也可以有效地处理器8位和16位数据以及支持许多涉及64位数据的操作
  • 从没有中断引脚并且在测量准备好之前需要一些时间的传感器读取数据的最佳方法

    我正在尝试将压力传感器 MS5803 14BA 与我的板 NUCLEO STM32L073RZ 连接 根据 第 3 页 压力传感器需要几毫秒才能准备好读取测量值 对于我的项目 我对需要大约 10 毫秒来转换原始数据的最高分辨率感兴趣 不幸的
  • STM32 上的 ADC 单次转换

    我正在研究 STM32 F103x 上的 ADC 编程 并从最简单的情况 单次转换开始 测量内部温度传感器 连接到 ADC1 的值 并使用 USART 将其发送到 COM 端口 目标似乎很明确 但是当我尝试将源代码下载到闪存时 它不会向 C
  • STM32F0、ST-link v2、OpenOCD 0.9.0:打开失败

    我在用着发射台 http www ti com ww en launchpad about htmlgcc arm none eabi 4 9 2015q2 为 STM32F0 进行编译 现在我想使用该集合中的 arm none eabi
  • 哪些变量类型/大小在 STM32 微控制器上是原子的?

    以下是 STM32 微控制器上的数据类型 http www keil com support man docs armcc armcc chr1359125009502 htm http www keil com support man d
  • 如何在命名管道 (mkfifo) 上执行非阻塞 fopen?

    如果我有一个程序使用 mkfifo 创建并尝试打开命名管道 如何在不阻塞的情况下打开管道进行读取或写入 具体来说 我正在编写一个 C 程序 它可以在有或没有 GUI 的情况下运行 用 Java 编写 在 C 程序中 我使用 mkfifo 成
  • 移动数组中的元素

    我需要一点帮助 我想将数组中的元素向上移动一个元素 以便新位置 1 包含位置 1 中的旧值 new 2 包含 old 1 依此类推 旧的最后一个值被丢弃 第一个位置的新值是我每秒给出的新值 我使用大小为 10 的数组 uint32 t TE
  • 在内核 OpenCL 中实现 FIFO 的最佳方法

    目标 在 OpenCL 中实现下图所示 OpenCl 内核所需的主要内容是将系数数组和临时数组相乘 然后最后将所有这些值累加为 1 这可能是最耗时的操作 并行性在这里非常有帮助 我正在为内核使用一个辅助函数来执行乘法和加法 我希望这个函数也

随机推荐