Wiki:知识&笔记 Wiki:知识&笔记
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
  • 基础

  • 底层&寄存器

  • HAL库外设

    • HAL库函数
    • HAL结构
    • GPIO各种点亮LED
    • 定时器
    • PWM控制LED颜色
    • 按键外部中断
    • ADC使用
    • 串口与DMA
      • 写在前面
      • 非 DMA 方式传输
        • 阻塞式发送函数
        • 非阻塞式发送函数
        • 非阻塞式接收函数
        • 阻塞式接收函数
      • 非 DMA 实例
        • 阻塞式发送与非阻塞式接收
      • DMA 方式
        • DMA 是什么
        • CubeMX设置
        • 函数部分
        • 接收函数
      • 重定向printf
        • 通过 MicroLib 重定向
    • FLASH 读写
    • IIC通信
    • SPI 通信
    • CAN
  • STM32
  • HAL库外设
2020-10-01
写在前面
非 DMA 方式传输
阻塞式发送函数
非阻塞式发送函数
非阻塞式接收函数
阻塞式接收函数
非 DMA 实例
阻塞式发送与非阻塞式接收
DMA 方式
DMA 是什么
CubeMX设置
函数部分
接收函数
重定向printf
通过 MicroLib 重定向

串口与DMA

# 写在前面

如果要了解串口的原理与一些配置相关的东西,可以去找之前写过的一篇这篇文章。但是它到之后的程序阶段就最好先别看了,因为我自己也无法验证到底对不对。所以程序的部分改为以下内容

# 非 DMA 方式传输

# 阻塞式发送函数

特点:发送时没有发完单片机不能执行其他工作

HAL_StatusTypeDef  HAL_UART_Transmit(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size, uint32_t Timeout);

//参数1:huart,串口实例的指针
//参数2:*pData,待发送数据缓冲区的指针
//参数3:Size,待发送数据的字节数
//参数4:Timeout,超时时间值
//返回值:HAL_StatusTypeDef,函数执行状态。
1
2
3
4
5
6
7

如果超出超时时间值,会自己结束以免打扰其他进程

# 非阻塞式发送函数

HAL_StatusTypeDef  HAL_UART_Transmit_IT(UART_HandleTypeDef *huart,uint8_t *pData, uint16_t Size);

//参数1:huart,串口实例的指针
//参数2:*pData,待发送数据缓冲区的指针
//参数3:Size,待发送数据的字节数
//返回值:HAL_StatusTypeDef,函数执行状态

////串口发送完毕中断回调函数  
void HAL_UART_TxCpltCallback(UART_HandleTypeDef  *huart);
void HAL_UART_TxHalfCpltCallback(UART_HandleTypeDef  *huart)  //发完一半进入
1
2
3
4
5
6
7
8
9
10

发送完之后,会进入发送完中断

运行示例:

HAL_UART_Transmit_IT(&huart1,  dat_Txd, 5);

void HAL_UART_TxCpltCallback(UART_HandleTypeDef  *huart) 
{         
    if(huart->Instance == USART1);         
    {             
        HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_9);    
    }
}
1
2
3
4
5
6
7
8
9

判断句柄是否正确的huart->Instance是它的基地址

# 非阻塞式接收函数

HAL_StatusTypeDef  HAL_UART_Receive(UART_HandleTypeDef *huart, uint8_t *pData,  uint16_t Size, uint32_t Timeout);

//参数1:huart,串口实例的指针
//参数2:*pData,数据接收据缓冲区的指针
//参数3:Size,待接收数据的字节数
//参数4:Timeout,超时时间值
//返回值:HAL_StatusTypeDef,函数执行状态
1
2
3
4
5
6
7

不推荐使用,因为发来数据的对象发送的时间不可控,但你的时间是确定的

# 阻塞式接收函数

HAL_StatusTypeDef  HAL_UART_Receive_IT(UART_HandleTypeDef *huart,uint8_t *pData,uint16_t Size);

//参数1:huart,串口实例的指针。
//参数2:*pData,数据接收据缓冲区的指针。
//参数3:Size,待接收数据的字节数。
//返回值:HAL_StatusTypeDef,函数执行状态。

////串口接收完毕中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef  *huart);
1
2
3
4
5
6
7
8
9
10

接收完之后,会进入接收完中断

运行实例:

//使用中断,非阻塞方式
HAL_UART_Transmit_IT(&huart1,  &dat_Rxd, 1);
  
void HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart)    
{        
    if(huart->Instance == USART1)    
    {       
        if(dat_Rxd == 0x5A)      
            HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
    }
}
1
2
3
4
5
6
7
8
9
10
11

接收完一个字节就中断

# 非 DMA 实例

我们循序渐进的用不同等级的代码来实现板间的串口通信

# 阻塞式发送与非阻塞式接收

/*
  @brief  简单的串口通信
  @type   使用阻塞式发送与非阻塞式接收
  @coding UTF-8
*/

//// main 之前的内容
// 定义简单的LED开闭宏
#define LEDx_ON() HAL_GPIO_WritePin(GPIOx, GPIO_PINx, GPIO_PIN_SET)
#define LEDx_OFF() HAL_GPIO_WritePin(GPIOx, GPIO_PINx, GPIO_PIN_RESET)
// 收发使用的数组与数字
uint8_t Tx_str1[] = "LEDx is opened.\r\n";
uint8_t Tx_str2[] = "LEDx is cloesd.\r\n";
uint8_t Rx_data = 0;

//// 回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if (Rx_data == 0xa1)
	{
		LEDx_ON();
		HAL_UART_Transmit(&huart1, Tx_str1, sizeof(Tx_str1), 10000);
		// @注意1
		HAL_UART_Receive_IT(&huart, &Rx_data, 1);
	}
	else if (Rx_data == 0xa2)
	{
		LEDx_OFF();
		HAL_UART_Transmit(&huart1, Tx_str2, sizeof(Tx_str2), 10000);
		// @注意1
		HAL_UART_Receive_IT(&huart, &Rx_data, 1);
	}
}

//// main中的自定义内容
int main()
{
	// forward
	// 启动接受中断(非阻塞式接收函数)
	HAL_UART_Receive_IT(&huart1, &Rx_data, 1);
	// later
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42

这段函数的作用就是使能了串口1,也就是 UART1 的接收中断,接受传入到串口1的信息,并且每接受一个数据帧(长度可设置,一般为 8bit,也就是两位16进制数)之后发生中断

在中断中,检测这个发来的2位16进制数,如果大小为 0xa1 则打开 LED,并向信息来源发送“LED is opened”;如果大小是 0xa2 则关闭 LED,并向信息来源发送“LED is closed”。

注意解析

  1. 在接收中断,也就是HAL_UART_Receive_IT() 作用过一次之后,他就失效了,也就是这一句函数只管使用一次接收

# DMA 方式

# DMA 是什么

DMA 全称 Direct Memory Access(直接存储器访问),是 STM32 的一个外设,它的特点在于:

在不占用 CPU 的情况下将数据从存储器直接搬运到外设,或者从外设直接搬运到存储器,当然也可以从存储器直接搬运到存储器。

比如在需要串口发送大量数据的时候,CPU 只需要发起 DMA 传输请求,然后就可以去做别的事情了,DMA 会将数据传输到串口发送,DMA 传输完之后会触发中断,CPU 如果有需要,可以对该中断进行处理,这样一来 CPU 的效率就大大提高了

# CubeMX设置

比起串口的设置只需要注意一下几点即可:

# 配置DMA

  1. 可以设置从 FLASH 到外设或者 SRAM 到外设
  2. 优先程度“Priority”也可调
  3. 数据宽度一般默认,在代码中设定
  4. 模式中,我们可以设置为“Circle”或者自己手动在 DMA 接受时像中断接收一样一遍又一遍的使能接收

# 函数部分

# 发送函数

HAL_StatusTypeDef HAL_UART_Transmit_DMA  ( UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size) 
//参数1:huart,串口实例的指针
//参数2:*pData,待发送数据缓冲区的指针
//参数3:Size,待发送数据的字节数
//返回值:HAL_StatusTypeDef,函数执行状态
1
2
3
4
5

# 接收函数

HAL_StatusTypeDef HAL_UART_Receive_DMA  ( UART_HandleTypeDef *huart, uint8_t *pData, uint16_t Size)  
//参数1:huart,串口实例的指针
//参数2:*pData,接收数据缓冲区的指针
//参数3:Size,待接收数据的字节数
//返回值:HAL_StatusTypeDef,函数执行状态
    
////串口接收完毕中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef  *huart);
void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef  *huart);
1
2
3
4
5
6
7
8
9

像中断接收的方法一样,我们在使用这个函数的时候也是只有一次作用的机会,如果没有在模式设置中设为“Circle”时,则必须手动开启

且 DMA 方式接收的时候依然会有接受完中断或者接收一半中断,我们可以通过这个特性设置双缓存区等等

注意

这里的设置中一定要开启UART的全局中断,否则 DMA 的发送将只能工作一次!亲测

# 重定向printf

我们在普通环境下使用C编程时通常会使用 printf 函数打印数据,但是由于 printf函数使用了半主机模式,所以直接使用标准库会导致程序无法运行。我们通常会使用一些方式对 printf 进行重定向来代替 HAL_Transmit_xx 来从串口输出数据,其实有很多方法,这篇文章 (opens new window)讲的不错,这里只记录下最简单的一个

# 通过 MicroLib 重定向

首先要在魔术棒里的“Target”中勾选“Use MicroLib”选项,一定要勾选!

由于在printf的底层,它是不会一个字符一个字符去输出,而是会调用更底层的 I/O 函数:fputc 去逐个字符打印,所以只需要重定向此函数至串口即可:

可以在uart.c中添加如下代码:

/* USER CODE BEGIN 1 */
#if 1
#include <stdio.h>

int fputc(int ch, FILE *stream)
{
    /* 堵塞判断串口是否发送完成且等待 */
    while((USART1->ISR & 0X40) == 0);

    /* 串口发送完成,将该字符发送 */
    USART1->TDR = (uint8_t) ch;

    return ch;
}
#endif

/* USER CODE END 1 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

注意

如果是  STM32F1 系列,上面这段代码不适用,请使用下面的代码

/* USER CODE BEGIN 1 */
#if 1
#include <stdio.h>

int fputc(int ch, FILE *stream)
{
    /* 堵塞判断串口是否发送完成且等待 */
    while((USART1->SR & 0X40) == 0);

    /* 串口发送完成,将该字符发送 */
    USART1->DR = (uint8_t) ch;

    return ch;
}
#endif
/* USER CODE END 1 */
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

我们均使用了寄存器操作以提高效率!

其他两种方法可以了解一下!!

编辑 (opens new window)
上次更新: 2020/11/18, 13:11:00
ADC使用
FLASH 读写

← ADC使用 FLASH 读写→

Theme by Vdoing | Copyright © 2020-2025 Jack :) | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式