IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 嵌入式 -> STM32 深入串口通信UART -> 正文阅读

[嵌入式]STM32 深入串口通信UART

STM32串口通信(STM32F103/STM32F407)

1.GPIO引脚复用AF机制
2.模块Clock时钟树,使能机制。(低功耗)
3.UART串口通信机制
4.NVIC中断配置机制
5.DMA搬移机制
6.Freertos串口

选择USART RX TX 引脚

  1. GPIO 口复用 机制
    微控制器 I/O 引脚通过一个复用器连接到板载外设/模块,该复用器一次仅允许一个外设的复
    用功能 (AF) 连接到 I/O 引脚。这可以确保共用同一个 I/O 引脚的外设之间不会发生冲突。
    在这里插入图片描述
    进行配置:
    ● 完成复位后,所有 I/O 都会连接到系统的复用功能 0 (AF0)。
    ● 外设的复用功能映射到 AF1 至 AF13。
    ● Cortex?-M4F EVENTOUT 映射到 AF15

在这里插入图片描述
PA9
在这里插入图片描述
PA10
在这里插入图片描述
复用端口的意义是SOC外设或者系统的模块的i/o功能有多路,需要配置到具体的引脚输出
引脚只是模块的输出通路,是工具人。
端口复用在datesheet中有描述,什么模块的端口事实上是指定的
在这里插入图片描述
PA9的复用AF7是串口TX。AF7系列是USART和I2S系列 引脚。
USART1 TX,RX的其中一条通路是PA9,PA10
另外一条通路是PB5 ,PB7
在这里插入图片描述

在这里插入图片描述
对于模块而言其功能的实现有多个PIN通路

在这里插入图片描述
对于PIN而言,他可能被多个模块链接

PIN越多可以同时实现模块功能就越多,复用通路只有一条,一但被选择那么其他的通路就无法联通。所以PIN越多,提供的外设能力就越大。

本节关键 Alternate function mapping
复用端口的配置
void GPIO_PinAFConfig(GPIO_TypeDef* GPIOx, uint16_t GPIO_PinSource, uint8_t GPIO_AF)

在这里插入图片描述
实现USART功能的复用需要选择复用AF7

	GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1);
	GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1);

配置PA9,PA10的串口功能

2.模块时钟使能
在这里插入图片描述
三条总线AHB,APB1,APB2,模块挂在相应的总线上。没有用到的模块就不需要开启,所以每个模块都有单独的时钟使能。
挂载哪条总线上就有哪个控制/管理
AHB1所管理的模块
在这里插入图片描述
AHB2 所管理的模块
在这里插入图片描述
APB管理的设备
在这里插入图片描述
APB2 管理的设备时钟
在这里插入图片描述
数据手册系统架构章节,对每条总线上的设备进行了列表,可以直接查询。
多数情况下,在库函数里面查看会更加方便
在这里插入图片描述
3.串口通信机制

在这里插入图片描述

UART 模块的输入和输出

TX发送口,RX接收口

发送的数据从TX发出从RX口接收。所以数据要发送通过本模块的TX。数据要接收通过本模块的RX。
所以两个串口设备的连线入上图所示。
人说话,从口出,听声音从耳入。
我说话,从口出,声音进别人耳朵,别人听到。
逻辑非常清晰。线不能链接错

MCU串口模块属于CPU外设。在MCU片内,电平TTL机制
TTL电平范围小,不利于长距离传输,可以通过提高传输电压提高抗干扰能力常用RS232电平。
在这里插入图片描述
电平机制的转换一般MCU无法实现,所以需要中间的转换IC
线路的联通需要物理接口,所以又会有接口标准
在这里插入图片描述
接口和电平会随着时代的发展而变化,和使用的场景关系非常大。
很显然他们都是串口通信的一部分。所以通信具有分层机制
接口和电平属于物理层。

传输
在这里插入图片描述
起始位,告知接收方数据开始发送,原则是和无信号的时候要有区别。约定无信息传递时高电平。开始传递时给低电平,表示开始
开始位S本身不不带有传输的信息,是告知对方“后面数据来了,请开始接收”
可以互相约定数据一次传输多少位 可以为5,6,7,8一般而言选择8位,代表一个字节。
发送完成数据后,需要表示传输完成,那么前面约定无信息传递时高电平。所以配备一个停止位T。
就这样重复的一个字节一个字节的传输。其实是一个Bit一个Bit传输。
为了适应跟多的情况,传输数据位可以配置,停止位也可以配置长度,需要双方约定好即可。

USART模块
可配置为 16 倍过采样或 8 倍过采样,因而为速度容差与时钟容差的灵活配置提供了可能
传输检测标志:
— 接收缓冲区已满
— 发送缓冲区为空
— 传输结束标志

十个具有标志位的中断源:
— CTS 变化
— LIN 停止符号检测
— 发送数据寄存器为空
— 发送完成
— 接收数据寄存器已满
— 接收到线路空闲
— 溢出错误
— 帧错误
— 噪声错误
— 奇偶校验错误

在这里插入图片描述
TX ,RX外部引脚

发送数据,由外部(CPU、DMA)写入数据到发送数据寄存器(TDR),按位发送到TX端口
接收数据,RX数据按位移入接收移位寄存器,然后写入RDR,可以由CPU或者DMA读走。
TDR,RDR都是8位的,所以读满就要取走
在这里插入图片描述
发送控制器,接收控制器分别控制模块的 发送和接收功能。
中断控制识别传输的各种事件,上报中断待处理。

当发送使能位 TE 置 1 之后,发送器开始会先发送一个空闲帧 (一个数据帧长度的高电平),接下
来就可以往 USART_DR 寄存器写入要发送的数据。在写入最后一个数据后,需要等待 USART 状
态寄存器 (USART_SR) 的 TC 位为 1,表示数据传输完成,如果 USART_CR1 寄存器的 TCIE 位置
1,将产生中断。
空闲帧
在这里插入图片描述

在这里插入图片描述
从起始位开始到结束位结束,测量时间大概87us 。波特率115200. 理论时间((1/115200 ) * 10 bit =86.8us)
这个时间87us是一帧的时间
在这里插入图片描述
空闲帧的开始是TX拉低的时候,也是大概87us
在发送数据时,编程的时候有几个比较重要的标志位我们来总结下。
在这里插入图片描述
如果将 USART_CR1 寄存器的 RE 位置 1,使能 USART 接收,使得接收器在 RX 线开始搜索
起始位。在确定到起始位后就根据 RX 线电平状态把数据存放在接收移位寄存器内。接收完成
后就把接收移位寄存器数据移到 RDR 内,并把 USART_SR 寄存器的 RXNE 位置 1,同时如果
USART_CR2 寄存器的 RXNEIE 置 1 的话可以产生中断。
在这里插入图片描述
为得到一个信号真实情况,需要用一个比这个信号频率高的采样信号去检测,称为过采样,这个
采样信号的频率大小决定最后得到源信号准确度,一般频率越高得到的准确度越高,但为了得到
越高频率采样信号越也困难,运算和功耗等等也会增加,所以一般选择合适就好。
在这里插入图片描述
在测量一个bit周期的时候,测量值8.91us 和理论时间8.68存在误差,这是由于采样频率造成的。频率越高就越能还原原始信号。
STM32F407 UART 可以进行16或者8倍的过采样频率。
传输完成或者接收完成需要通知外界,一般使用中断
在这里插入图片描述
也就是说,USART内部产生事件,要不要传递出去到NVIC中断控制器,由各自的使能位决定。

ARM cortex M4 NVIC中断控制器
在这里插入图片描述
USART 模块属于peripherals ,内部的事件使能后,以IRQs的形式传达给NVIC。

typedef struct
{
  uint8_t NVIC_IRQChannel;                    /*!< Specifies the IRQ channel to be enabled or disabled.
                                                   This parameter can be an enumerator of @ref IRQn_Type 
                                                   enumeration (For the complete STM32 Devices IRQ Channels
                                                   list, please refer to stm32f4xx.h file) */

  uint8_t NVIC_IRQChannelPreemptionPriority;  /*!< Specifies the pre-emption priority for the IRQ channel
                                                   specified in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table
                                                   A lower priority value indicates a higher priority */

  uint8_t NVIC_IRQChannelSubPriority;         /*!< Specifies the subpriority level for the IRQ channel specified
                                                   in NVIC_IRQChannel. This parameter can be a value
                                                   between 0 and 15 as described in the table @ref MISC_NVIC_Priority_Table
                                                   A lower priority value indicates a higher priority */

  FunctionalState NVIC_IRQChannelCmd;         /*!< Specifies whether the IRQ channel defined in NVIC_IRQChannel
                                                   will be enabled or disabled. 
                                                   This parameter can be set either to ENABLE or DISABLE */   
} NVIC_InitTypeDef;

NVIC_IRQChannel

USART1_IRQn
在这里插入图片描述
上图称之为中断索引,当中断可以处理的时候,会通过索引去中断向量表上找到处理函数入口。

USART1 所有的中断,都是以USART1_IRQn的方式上报给NVIC。
USART内部的中断非常多,如何识别,通过
ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)

ITStatus USART_GetITStatus(USART_TypeDef* USARTx, uint16_t USART_IT)
{
  uint32_t bitpos = 0x00, itmask = 0x00, usartreg = 0x00;
  ITStatus bitstatus = RESET;
  /* Check the parameters */
  assert_param(IS_USART_ALL_PERIPH(USARTx));
  assert_param(IS_USART_GET_IT(USART_IT)); 

  /* The CTS interrupt is not available for UART4 and UART5 */ 
  if (USART_IT == USART_IT_CTS)
  {
    assert_param(IS_USART_1236_PERIPH(USARTx));
  } 
    
  /* Get the USART register index */
  usartreg = (((uint8_t)USART_IT) >> 0x05);
  /* Get the interrupt position */
  itmask = USART_IT & IT_MASK;
  itmask = (uint32_t)0x01 << itmask;
  
  if (usartreg == 0x01) /* The IT  is in CR1 register */
  {
    itmask &= USARTx->CR1;
  }
  else if (usartreg == 0x02) /* The IT  is in CR2 register */
  {
    itmask &= USARTx->CR2;
  }
  else /* The IT  is in CR3 register */
  {
    itmask &= USARTx->CR3;
  }
  
  bitpos = USART_IT >> 0x08;
  bitpos = (uint32_t)0x01 << bitpos;
  bitpos &= USARTx->SR;
  if ((itmask != (uint16_t)RESET)&&(bitpos != (uint16_t)RESET))
  {
    bitstatus = SET;
  }
  else
  {
    bitstatus = RESET;
  }
  
  return bitstatus;  
}

这样可以获知USART内部具体是发生了什么事件而产生中断,在去做对应的处理。
NVIC所管理的称为全局中断。
NVIC的使能比较简单,因变对NVIC来说只有USART1_IRQn.不需要关注USART内部的各种事件。

	NVIC_InitStructure_uart.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStructure_uart.NVIC_IRQChannelPreemptionPriority = 1;
	NVIC_InitStructure_uart.NVIC_IRQChannelSubPriority = 1;
	NVIC_InitStructure_uart.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure_uart);

定义中断处理函数
在启动文件中 ; External Interrupts 写明了USART产生的中断服务函数 叫 USART1_IRQHandler
我们可以去修改这个函数名称,不过不建议这么做,保持S文件的原始性,也方便代码移植。
可以通过define的方式来定义中断服务函数。
在这里插入图片描述
一般来说,接收数据完成后会产生中断,这个时候把数据从寄存器中拿出来,放入我们准备好的缓存当中,这个时候数据是一个字节。
一个字节对于我们来说可能是一段话的而一部分,需要等待多个字节来组合一段有意义的内容。
所以把它占时放在缓存中
我们可以在缓存中对数据进行判断,识别数据包,或者一句话等等

unsigned char buff_index=0;
void USART1_IRQHandler(void)
{
	
	uint8_t ucTemp;
	
	 if (USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) {
		ucTemp = USART_ReceiveData( USART1 );
		UART1_BUFFER[buff_index]  = ucTemp;
	 	buff_index++;
		if(buff_index > UART1_BUFFER_SIZE)
		{
			buff_index = 0;
			memset(UART1_BUFFER,0,sizeof(UART1_BUFFER));
		}
		
	 }

}

在这里插入图片描述

启用在线调试,观察UART1_BUFFER的内容。
0x0D(ascii码是13) 指的是“回车” \r是把光标置于本行行首
0x0A(ascii码是10) 指的是“换行” \n是把光标置于下一行的同一列

DMA数据搬移

在这里插入图片描述
串口的USART框图如图所示。数据寄存器(发送数据,接收数据寄存器)只能通过CPU或者DMA访问。
CPU访问数据寄存器的流程:数据寄存器接收满或者发送完毕会产生事件,事件传入NVIC中断模块后上报给CPU,CPU通过中断服务读取。这条链路在执行就会使用CPU,增加系统的负担。如果通过DMA可以在CPU不参与的情况下自己搬运数据,就好像不执行指令就能
做事一样。DMA模块本身的内容比较复杂,这里注重实现。
在这里插入图片描述
DMA的初始化结构体比较长,最后会附上dma的初始化代码。

DMA_InitTypeDef dma_init_struct;
dma_init_struct.DMA_Channel = DMA_Channel_4;

在这里插入图片描述
DMA的信号通道选择和GPIO的复用结构有点相识。
DMA本身只管理STREAM0~STREAM7,只有这8条通路,然后STREAMx(x = 1,2,3,…8)通过多路复用器,和REQ_STRx_CH链接。
REQ_STRx_CH负责链接到具体的外设。
在这里插入图片描述

串口USART1 RX在数据流5(STREAM5)的通道4上。所以dma_init_struct.DMA_Channel = DMA_Channel_4;
初始化USART1 的代码如下所示

static void DMA_config(void)
{
	DMA_InitTypeDef dma_init_struct;
	extern unsigned char UART1_BUFFER[120];
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2, ENABLE);
	DMA_DeInit(DMA2_Stream5);
	while (DMA_GetCmdStatus(DMA2_Stream5) != DISABLE) {
	 }

	dma_init_struct.DMA_Channel = DMA_Channel_4;
	dma_init_struct.DMA_BufferSize = 120;
	dma_init_struct.DMA_DIR = DMA_DIR_PeripheralToMemory;
	
	dma_init_struct.DMA_FIFOMode = DMA_FIFOMode_Disable;
	dma_init_struct.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
	
	dma_init_struct.DMA_Memory0BaseAddr = (unsigned int)UART1_BUFFER;
	dma_init_struct.DMA_MemoryBurst = DMA_MemoryBurst_Single;
	
	dma_init_struct.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
	dma_init_struct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte;
	dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable ;
	dma_init_struct.DMA_PeripheralInc =DMA_PeripheralInc_Disable ;
	dma_init_struct.DMA_Mode =DMA_Mode_Circular;
	dma_init_struct.DMA_PeripheralBaseAddr =(USART1_BASE+0x04);
	dma_init_struct.DMA_PeripheralBurst =DMA_PeripheralBurst_Single;

	
	dma_init_struct.DMA_Priority = DMA_Priority_Medium;


	DMA_Init(DMA2_Stream5, &dma_init_struct);

	DMA_Cmd(DMA2_Stream5, ENABLE);
	while (DMA_GetCmdStatus(DMA2_Stream5) != ENABLE) {
	 }

}

在这里插入图片描述
把外围 设备的数据放到内存的BUFFER

dma_init_struct.DMA_DIR = DMA_DIR_PeripheralToMemory;

第一步把DR数据寄存器的数据取出来
1.DR寄存器地址

dma_init_struct.DMA_PeripheralBaseAddr =(USART1_BASE+0x04);

2.一次取多少数据,总共取多少次,取满次数怎么办

dma_init_struct.DMA_PeripheralDataSize =DMA_PeripheralDataSize_Byte;
dma_init_struct.DMA_BufferSize = 120;
dma_init_struct.DMA_Mode =DMA_Mode_Circular;

3。下一次取要不要取另外地址的数据

dma_init_struct.DMA_PeripheralInc =DMA_PeripheralInc_Disable ;

第二部 DMA获得数据
第三部 DMA写入数据到目标内存

1.内存地址

dma_init_struct.DMA_Memory0BaseAddr = (unsigned int)UART1_BUFFER;

2.写入地址会不会变化

dma_init_struct.DMA_MemoryInc = DMA_MemoryInc_Enable ;

根据以上的逻辑可以理清楚DMA结构体初始化的过程。
最后在主函数中使能 UART DMA

USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);

在这里插入图片描述
在这里插入图片描述
1.对面发送端的TX完成了一帧的数据传输,USART 接收完成标志RXNE置位,
2.DMA收到请求源请求
3,读取USART DR寄存器数据值
在这里插入图片描述
关掉CPU搬移数据。
在这里插入图片描述
最后开启调试,全速运行。

操作系统(Freertos)下的UART
OS下的USART

UART的数据接收是通过硬件进行,硬件没有缓存,所以一但接收数据寄存器满,就要及时的取出来
这个时候有两种方法
1.NVIC 中断服务函数处理

void USART1_IRQHandler(void)
{
	#if 1	
	uint8_t ucTemp;
	
	 if (USART_GetITStatus(USART1,USART_IT_RXNE)!=RESET) {
		ucTemp = USART_ReceiveData( USART1 );
		UART1_BUFFER[buff_index]  = ucTemp;
	 	buff_index++;
		if(buff_index > UART1_BUFFER_SIZE)
		{
			buff_index = 0;
			memset(UART1_BUFFER,0,sizeof(UART1_BUFFER));
		}
		
	 }
	#endif
}

2.使用DMA处理


数据从DR寄存器拿出来后放入指定的缓存,这个时候是否要通知CPU来对数据进行判别
数据是否可用。串口UART是基本的通信,它只有帧的概念 也就是起始位和结束位之间的数据。
没有数据包的概念。起始位和结束位之间的数据一般是一个字节
在这里插入图片描述
这是UART协议简单的缘故,应为简单所以普及。视乎有点矛盾。
其他的通信协议只是在这套基础上加入一些逻辑信息。
那么我们可以给串口加入 帧的概念
通信是 接收方和发送方的数据交互,所以都需要约定好。意思就是说,发送方接收方都要加入帧的概念。

我们约定一个字符 # 作为帧的开始 字符 &作帧结束。或者其他的约定好的字符都可以。
这样我们在OS中可以在适当的时候做帧识别。关键在于串口信息事件处理的实时性要求高不高。
嵌入式实时性一般摆在第一位,也就是说,如果优先级高就放在中断做,这样响应快
如果不是那么非常重要就可以用DMA搬运到BUFFER
用信号量或者互斥量去唤醒帧识别 函数处理任务。

void uart_rx_isr(uart_device_t *dev)
{
	int32_t ch = -1;

	if (!dev)
	{
		return;
	}

	hw_interrupt_disable();
	while (1)
	{
		ch = dev->ops->getc(dev);
		if (ch == -1)
		{
			break;
		}
		ch &= 0xFF;
		sw_fifo_put(&dev->rx_fifo, (const uint8_t *)&ch, 1, SW_FIFO_TYPE_COVER);
		if (dev->rx_indicate.cb)
		{
			dev->rx_indicate.cb(dev->rx_indicate.para);
		}
	}
	hw_interrupt_enable();
}

dev->rx_indicate.cb是一个回调函数,当接收数据后执行一个函数,这个时候可以做数据的处理或者发送信号量。

  嵌入式 最新文章
基于高精度单片机开发红外测温仪方案
89C51单片机与DAC0832
基于51单片机宠物自动投料喂食器控制系统仿
《痞子衡嵌入式半月刊》 第 68 期
多思计组实验实验七 简单模型机实验
CSC7720
启明智显分享| ESP32学习笔记参考--PWM(脉冲
STM32初探
STM32 总结
【STM32】CubeMX例程四---定时器中断(附工
上一篇文章      下一篇文章      查看所有文章
加:2021-12-13 12:59:30  更:2021-12-13 13:01:19 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/29 22:33:49-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码