一、串口中断+超时解析

1. CubeMX配置

1.1 属性配置

主要配置波特率,其余默认

image-20240919224239733

中断配置

Preemption Priority:抢占优先级

Sub Priority: 子优先级

串口的DMA设置

只开接收DMA即可

DMA的模式:

  • Normol
  • Circual

2. 驱动程序编写

2.1 串口重定向

在uasrt.c中进行修改

1
2
3
4
5
int fputc(int ch, FILE * stc)
{
HAL_UART_Transmit(&huart1,(uint8_t *)&ch,1,10);
return ch;
}

2.2 app_uart.c 变量定义

1
2
3
uint16_t uart_rx_index = 0;
uint16_t uart_tx_ticks = 0;
uint8_t uart_rx_buffer[128]={0};

2.3 中断初始化

放入Core->Src->usart.c中

在初始化中使能串口中断,往buffer中每次填充一个字节,触发中断回调

1
HAL_UART_Receive_IT(&huart1,uart_rx_buffer,1);
  1. Hal库——中断回调函数

2.4 回调函数声明

弱定义

自定义回调函数

可以自行声明与弱定义回调函数同名的函数,会优先执行自定义的函数

Hal库中各种弱定义都是用__weak修饰的

过程:串口接收->触发回调->进入回调函数

PS: void HAL_UART_RxCpliCallback(UART_HandleTypeDef *huart) 不要用成 void HAL_UART_TxCpliCallback(UART_HandleTypeDef *huart)

1
2
3
4
5
6
7
8
9
10
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if(huart->Instance == USART1)
{
uart_rx_ticks = uwTick;
uart_rx_index++;//索引自增
//每次触发回调,都要重新初始化接收中断,定义接收的位置
HAL_UART_Receive_IT(&huart1,&uart_r_buffer[uart_rx_index],1);
}
}

2.5 串口解析

超时解析

1
2
3
4
5
6
7
8
9
10
11
12
13
void uart_proc(void)
{
if(uart_rx_index == 0) return;

if(uwTick - uart_rx_ticks > 100)//时间超过100
{
printf("uart data:%s\n",uart_rx_buffer);//发送串口接收内容

memset (uart_rx_buffer,0,uart_rx_index);//清空
uart_rx_index = 0;//指针指令
huart1.pRxBuffPtr = uart_rx_buffer;//uart1缓存区指针指向buffer
}
}

# 无DMA和环形缓冲区的问题

当串口接收速率过快时,如视觉上位机频繁向单片机发送识别到的坐标数据,可能会导致单片机程序阻塞

1. 串口阻塞的解决方案

DMA:数据转运

RingBuffer:环形缓存区

2. # 环形缓冲区的概念:

  • 头指针
  • 尾指针

# 现象:

1. 串口无解析发送上位机

CubeMX未定义串口引脚,未注意STM32外设引脚可复用问题

2. 回调函数名称错误

二、DMA+空闲中断

# DMA的作用

无DMA:数据->Uart寄存器->CPU访问Uart寄存器->执行其他程序部分

​ -------如果串口通信速率过快------>CPU频繁访问Uart寄存器-------->程序阻塞

有DMA:数据->Uart->DMA访问Uart数据->存放到单片机内存地址

​ CPU与DMA并行工作

在上述配置的基础上对程序文件进行进一步修改。

1. 变量声明

声明 uart_rx_dma_buffer变量,用于数据转运

2. 中断初始化

启用DMA相关中断

关闭DMA半中断

PS: 不再适用串口回调,改用DMA的方法

3. 串口中断函数

每次触发串口中断,触发DMA中断

取消使用串口中断回调函数

改用空闲中断回调函数

PS: 不再需要串口超时解析

# 现象:

# 补充——中断函数与回调函数的区别

在嵌入式编程中,HAL(硬件抽象层)库的中断函数和回调函数是常见的机制,尤其是在处理外设操作时。这两者的作用有时容易混淆,但它们的概念和使用场景有所不同。下面详细解释它们的区别:

1. 中断函数(Interrupt Service Routine, ISR)

中断函数是一段处理硬件中断的代码。当外设或处理器触发中断时,处理器会暂停当前的代码执行,转而执行与该中断对应的ISR。一旦中断被处理完毕,程序会恢复到原来的执行状态。

  • 执行方式:硬件触发,直接由处理器执行,通常是高优先级。
  • 响应时间:要求短小精悍,不能执行耗时的任务,因为会阻塞其他中断。
  • 位置:ISR通常定义在HAL库或用户代码中,是一个固定的函数(如TIM_IRQHandler等)。
  • 调用方式:自动触发,由硬件中断控制器(NVIC)决定何时调用中断处理函数。

2. 回调函数(Callback Function)

回调函数是一个函数指针,通过预先注册到某个模块或API中,等到某个事件发生时,由该模块或API负责调用。HAL库中的回调函数通常是在中断处理完毕后,由ISR或HAL库内部调用,用来进一步处理用户逻辑。

  • 执行方式:由程序代码(比如ISR或定时器事件)调用,响应某个事件。
  • 响应时间:回调函数不要求像中断处理函数那样必须快速完成,往往用于处理稍复杂的业务逻辑。
  • 位置:回调函数通常由用户实现,并由HAL库的中断处理函数或其他机制调用(如HAL_TIM_PeriodElapsedCallback)。
  • 调用方式:回调函数不是直接由硬件触发,而是由软件触发,即当中断函数处理完硬件中断后,再调用用户注册的回调函数。

简单总结区别:

  • 触发机制:中断函数是由硬件事件(如定时器溢出、外部信号等)直接触发,而回调函数是由软件(如ISR)触发。
  • 职责范围:中断函数负责处理硬件中断,通常需要快速执行;回调函数则处理用户定义的业务逻辑,通常可以有更多的处理空间和时间。
  • 优先级:中断函数的优先级较高,回调函数的执行时间不受硬件中断控制,通常在中断函数结束之后才执行。

典型应用场景

以定时器为例:

  • 当定时器溢出时,触发一个中断,执行定时器的中断函数TIM_IRQHandler
  • 在中断函数内部,可能会调用HAL库的定时器回调函数HAL_TIM_PeriodElapsedCallback,用于用户自定义的定时器周期性任务处理。

这就是中断函数和回调函数的核心区别。

三、环形缓冲区

# 环形缓冲区的简介

待补充

1. 移植环形缓冲区驱动文件

1
2
3
ringbuffer_t usart_rb; //定义ringbuffer_t类型结构体变量

uint8_t usart_read_buffer[128];//定义环形缓存区数组
  • 判断ringbuffer是否满
  • 写入数据
  • 清空结构体

2. 修改串口解析

1
2
3
4
5
6
7
void uart_proc()
{
if(ringbuffer_is_empty(&usart_rb))return;
ringbuffer_read(&usart_rb,usart_read_buffer,usart_rb.itemCount);
printf("ringbuffer data:%s\n",usart_read_buffer);
meset(usart_read_buffer,0,sizeof(uint8_t)*128);
}

STM32串口通信方法总结:

  • 超时解析

  • DMA空闲中断

  • 环形缓存区

四、ADC和DMA

STM32的ADC(模数转换器)通道IN11指的是STM32微控制器上一个特定的ADC输入通道。每个STM32芯片的ADC都有多个模拟输入引脚,这些引脚标记为INx(例如IN0、IN1、IN2等),对应不同的GPIO引脚。

具体到IN11,它是ADC的第11个输入通道,通常与一个特定的GPIO引脚连接。该引脚用于将模拟信号输入到ADC进行模数转换。

CT117E原理图:

1. CubeMX配置

1.1 ADC通道分配:

  • ADC1: IN11
  • ADC2: IN15

1.2 配置DMA

1.2.1 配置DMA通道

1.2.2 配置为循环模式

1.2.3 配置DMA速度

设置为中、高均可

1.3 配置ADC属性

  • 四分频
  • DMA使能
  • 循环使能

1.4 配置ADC中断

优先级为2即可

2. 驱动程序编写

2.1 创建adc_app.c

变量声明

1
2
3
4
#include "adc_app.h"

uint32_t dma_buff[2][30];//双通道DMA
float adc_value[2];

在主程序初始化启用DMA 转运 ADC 数据

2.2 定义ADC进程

  • 读取电压dma储存数据
  • 转换为模拟电压值

同样的,记得在任务调度器中添加proc

2.3 lcd显示

# 动态窗口

  • 使用环形缓存区
  • 定义结构体