【STM32】BLDC驱动&控制开发笔记 | 09_基于STM32F407的ADC电压采集,多通道ADC+DMA+USART,定时器触发

😶‍🌫️ 0 说在最前面 + 实现功能

在实现电机闭环控制的实验程序中,不可或缺的一部分便是通过ADC功能来实现电压或电流的采集,具体涉及到电机控制的理论部分这里暂不展开。在使用ADC功能的同时,经常搭配DMA进行数据传输,这样能尽可能减轻对CPU的负担。

本文实验目的:为电机控制实验做ADC功能的单独测试。
使用三路ADC进行电压采集,搭配DMA进行数据传输,利用UART串口将采集到的数据打印出来。
为了保证在实际电机实验中ADC采集频率与PWM输出频率一致,需要使用产生PWM波的同一个定时器单独多一路用来触发ADC采样。也就是常说的“定时器触发”,但其实这里没有用定时器中断。

🫥参考/学习资料:
[1] - 【STM32官方参考手册】《STM32F4xx参考手册_中文_RM0090》 (文档ID 018909 第4版)
🧀超链接跳转:英文版-STM32F4xx参考手册-RM0090(官方下载)
🧀超链接跳转:中文版-STM32F4xx参考手册-RM0090(非官方,论坛下载)
P248/1285,CH11 模数转换器 (ADC)。
博主使用的即是第二个链接中的版本。
STM32官方参考手册

[2] -【正点原子开源学习资料】《STM32F1开发指南(精英版)-HAL库版本_V1.0》
P324/742,第22章 ADC实验。
个人觉得原子哥的实验教程写得比野火的更通俗易懂一些。缺点是不够全面,这里仅介绍了单个通道的单次转换模式的实验。正点原子开源学习资料

[3] -【野火开源学习资料】《STM32HAL库开发实战指南——基于野火F4系列开发板》
P748/1626,第29章 ADC-电压采集。
比正点原子的讲得相对更全面一些。
野火开源学习资料
其他参考/学习资料:
🧀STM32G4电机外设配置-TIM1,OPAMP,ADC,UART_bilibili视频@欧拉电子 有关于STM32电机控制的良心博主,推荐!✨
🧀STM32 ADC多通道采集_CSDN博客@Deacde_ZY 操作过程步骤详解,针对ADC多通道采集的阻塞模式和DMA模式两种,可跟着他的操作尝试复现。✨
🧀STM32 多通道ADC采集详解(DMA模式和非DMA模式)_CSDN博客@发呆健将 更多侧重在关于ADC的理论知识的讲解,简洁清晰。
🧀STM32L0 ADC使用HAL库关于校准问题的说明_CSDN博客@矜辰所致 ADC校准的详细讲解,感觉还不错。
🧀STM32CubeMX | HAL库的ADC多通道数据采集(轮训、DMA、DMA+TIM)、读取内部传感器温度_CSDN博客@觉皇嵌入式 几种模式分别实现,清晰明了简单直接。✨
🧀stm32H7 HAL库 定时器触发多通道adc采样 DMA_CSDN博客@_zs_dawn 定时器触发、多通道ADC、DMA。

👀 1 CubeMX中的配置

🕶 1.1 RCC & Clock Configuration时钟配置

🥽 System Core – RCC
高速时钟源 – 外部晶振
1.1 RCC & Clock Configuration时钟配置-1
PH0 – RCC_OSC_IN
PH1 – RCC_OSC_OUT

🥽 Clock Configuration
系统定时器配置Cortex System timer – 168MHz
1.1 RCC & Clock Configuration时钟配置-2
这里重点关注到PCLK2对应的是84MHz,在后面会用到。因为ADC输入时钟ADC_CLK由PCLK2经过分频产生[3]–P755

🕶 1.2 SYS Debug设置

🌠>> SYS Mode and Configuration
1.2 SYS Debug设置
🚦Debug – Serial Wire
🚦Timebase Source – TIM7 (SysTick)
(PA13+PA14 = No.72, 76口)
这里的Timebase Source一般是默认选SysTick的,但如果考虑到后续要使用RTOS,就不能选这个,改用TIM7。

🕶 1.3 TIM定时器设置(TIM8-PWM+TIM4-HALL+TIM6简单定时)

在ADC采样的实验中,这里主要是用到产生PWM波的高级定时器TIM8来作为采样的触发源,当然也可以是别的定时器触发。
这里也算作为顺便了,为了一块儿看看我设置的别的定时器中断会不会对ADC采样有影响,就一块儿配了。

🥽 【TIM4】通用定时器 - 84MHz - 10Hz(T=100ms)的HALL传感器

每隔100ms读取霍尔传感器状态1次。
(TIM4 ~ APB1 Timer clocks – 84MHz)
PD12 – TIM4_CH1 – HALL_W(HALL_3)
PD13 – TIM4_CH2 – HALL_V(HALL_2)
PD14 – TIM4_CH3 – HALL_U(HALL_1)
🌠>> TIM4 – Mode
1.3 TIM定时器设置-1
🚦Clock Source – Internal Clock
🚦Combined Channels – XOR ON/ Hall Sensor Mode

🌠>> TIM4 – Configuration – Parameters Settings
1.3 TIM定时器设置-2
🌠>> TIM4 – NVIC Settings
NVIC使能TIM4中断,中断优先级(1, 0)。
1.3 TIM定时器设置-3
记得要回到这里才能改优先级。1.3 TIM定时器设置-4

🌠>> TIM4 – GPIO Settings
要回到 System Core - GPIO 改这里的设置。
1.3 TIM定时器设置-5
🚦GPIO mode – Alternate Function Push Pull
🚦GPIO Pull-up/Pull-down – Pull-down(别人一般是上拉,但我的理解是下拉Pull-down…not sure…)
🚦Maximum output speed – Low(这里的High和Low暂时没感觉出有啥区别?存疑…)

🥽 【TIM6】基本定时器 - 84MHz - 50Hz(T=20ms)

(TIM6 ~ APB1 Timer clocks – 84MHz)
🌠>> TIM6 – Mode & Configuration
1.3 TIM定时器设置-6
🌠>> TIM6 – NVIC Settings
NVIC使能TIM6中断,中断优先级(2, 0)。
1.3 TIM定时器设置-7

🥽 【TIM8】高级定时器 - 168MHz - 20kHz(T=50us)的PWM输出 及 触发ADC采样

(TIM8 ~ APB2 Timer clocks – 168MHz)

🌠>> TIM8 – Mode
1.3 TIM定时器设置-8
❗需要特别注意,除了原本正常输出的CH1+CH2+CH3三通道PWM波以外,还用到了通道4,生成PWM但不输出,专门用来触发ADC采样,这样才能保证采样的和产生PWM波的定时器是完全同步的。
PC6 – TIM8_CH1
PC7 – TIM8_CH2
PC8 – TIM8_CH3

🌠>> TIM8 – Configuration – Parameters Settings
1.3 TIM定时器设置-9

🚦Auto-reload preload – Disable
当Auto-reload preload设置为Enable, ARR没加满就可变,有危险加过头了。
❗(这里切勿搞反啦!)

  1. auto-reload precload=Disable:自动重装载寄存器写入新值后,计数器立即产生计数溢出,然后开始新的计数周期
  2. auto-reload precload=Enable:自动重装载寄存器写入新值后,计数器完成当前旧的计数后,再开始新的计数周期
    🧀来源:STM32CubeMX配置时钟中的auto-reload precload_CSDN博客@飞由于度

🚦CH Polarity – High(Pulse/ARR部分为高电平)

✨❗其中还需更改 Trigger Output 的设定,这里涉及到ADC采样的触发,选择通道4的compare作为触发事件。
1.3 TIM定时器设置-10
🚦Trigger Event Selection – Output Compare (OC4REF) 或 Update Event (两种均可实现)

✨❗这里PWM的模式设定也需要注意区别,PWM4使用 mode 2,而PWM1+2+3使用 mode 1。
区别在于:对于PWM信号,ccr/arr的部分为有效输出-高电平,即为 mode 1;对于CH4信号,想要到ccr时刻才由低电平变到高电平,即为 mode 2,这样通过上升沿触发ADC采样。
(这里如果想保持PWM的模式设定统一,讲道理CH4也设成 mode 1,然后用下降沿触发ADC应该也可以~)
与PWM输出相关的几个寄存器值,CCR、ARR、…更多详情看这里:🧀STM32F1定时器-PWM输出_CSDN博客@ONE_Day|;🧀笔记:STM32——PWM波形生成以及控制电机_CSDN博客@c-tion,很关键!
1.3 TIM定时器设置-11
🚦Mode – PWM mode 2
🚦Pulse – 1050(这里暂设2100/2=1050,后面遇到PWM的占空比在运行中改变的情况,需要把这里的赋值改成前三个通道pulse对应变量的一半)

🌠>> TIM8 – GPIO Settings
1.3 TIM定时器设置-12
使能了TIM8的PWM模式的通道1、2、3后,STM32CubeMX同步使能了PC6、PC7、PC8。速度暂时用Very High。

🌠>> TIM8 – NVIC Settings
TIM8暂时没有用到中断。
1.3 TIM定时器设置-13

🕶 1.4 USART3通讯设置(收发数据,把ADC采集数据打出来)

PD8 - USART3_TX
PD9 - USART3_RX
🌠>> USART3 – Mode & Parameter Settings
1.4 USART1通讯设置-1

🌠>> USART3 – NVIC Settings
1.4 USART1通讯设置-2

🌠>> USART3 – GPIO Settings
这里调整GPIO设置需要到“System Core - GPIO”里面改。
1.4 USART1通讯设置-3
🚦GPIO mode – Alternate Function Push Pull
🚦GPIO Pull-up/Pull-down – Pull-up

❗建议将UART_Rx上拉,避免在悬空时受电路中某些信号的影响而误触发。
(有关UART端口上拉与否,详情参考:🧀USART RX 不上拉的后果_CSDN博文_小康师兄、🧀# STM32系列-串口-uart-软件引脚内部上拉 或者 外部电阻上拉-原因问题的搜寻_CSDN博文_好奇龙猫

🕶 1.5 GPIO Output - LED设置 输出低电平灯亮

PA1 – LED0 – 核心板自带LED指示灯
PB10 – LED1
PB11 – LED2
PE15 – LED3
端口选择根据自己板子的随便来就好。
🌠>> GPIO – PA1+PB10+PB11+PE15 Configuration
1.5 GPIO Output - LED设置--1
🚦GPIO Pull-up/Pull-down – Pull-up
在默认设置的基础上,把 GPIO Pull-up/Pull-down 改成 Pull-up 模式。
1.5 GPIO Output - LED设置-2
结合外接电路,接有3.3V高电平,当引脚输出低电平时灯亮。因此默认上拉时,灯灭。

🕶 1.6 ADC电压采集+DMA

ADC3_IN2 - PA2 - SOA
ADC3_IN3 - PA3 - SOB
ADC3_IN13 - PC3 – SOC

🌠>> ADC3 Mode
1.6 ADC电压采集+DMA-1
根据自己选的端口来勾选即可。

🌠>> ADC3 Configuration – Parameters Settings
1.6 ADC电压采集+DMA-2
🚦Clock Prescaler – PCLK2 divided by 4
这里至少也是4分频,说明已经是能到达的最快状态了:PCLK2 = 84MHz,84/4=21MHz。
1.6 ADC电压采集+DMA-3

[3] -- P755 CH29.2.5.1 ADC时钟
参考野火开发指南的叙述,我这里时钟树里的PCLK2同样也是84MHz,因此暂设为4分频吧。

🚦Resolution – 12 bits (15 ADC Clock cycles)
使用的能实现的最高精度,12位。

🚦Data Alignment – Right alignment
转换后存储的数据的对齐方式。一般都默认选右对齐,存在寄存器的低12位,对应212=4096。如果选左对齐,则不再对应212=4096,而是有一个左移4位的关系,换算起来麻烦一点。
1.6 ADC电压采集+DMA-4

[1] -- P255 CH11.4 数据对齐

🚦Scan Conversion Mode – Enable
扫描模式,打开。依次扫描该ADC中选中的各个通道。
和后面 Number Of Conversion 有关,如果只选择使用1个通道,则扫描模式的选项只能选Disabled,选择更多通道数后默认就会改变成Enable了。

🚦Continuous Conversion Mode – Disable
连续转换模式,不打开。
如果不打开,则只会进行一次转换便结束。❗但并不意味着整个程序中只能进行一次。这里的连续转换是指,依次采集完各个通道之后,自动回到最开头的通道继续循环。但在实际使用中,可能是希望每 T ms 采集1次,那是通过定时器中断来每 T ms 开启1次的ADC,所以不要打开连续转换模式。

🚦Discontinuous Conversion Mode – Disable
间断模式,保持默认,不打开。

🚦DMA Continuous Requests – Enable
DMA连续请求,打开。这里要用到DMA功能。
1.6 ADC电压采集+DMA-5
默认下是Disable的,需要搭配DMA Setting来进行设置。❗先跳转到下文 “🌠>> ADC3 Configuration – DMA Settings” 部分,设完了再回来选中Enable。

🚦End Of Conversion Selection – EOC flag at the end of all conversions
默认选项EOC flag at the end of single channel conversion意思是每个单独通道转换完成就视作为结束。因为这里用到了3个channel,所以需要所有转换都完成了,才视为结束。

1.6 ADC电压采集+DMA-6
ADC采样有 规则通道注入通道 两种,有点类似于 顺序/中断 的对应关系。
我这里暂时没用到注入通道的模式,所以3个规则通道转换模式、0个注入通道转换模式。
1.6 ADC电压采集+DMA-7
🚦Number Of Conversion – 3 (用到了3个通道,都要来转换)
🚦External Trigger Conversion Source – Timer 8 Trigger Out Event(对应前面的设置,TIM8的CH4)
🚦External Trigger Conversion Edge – Trigger detection on the rising edge(和前一项关联,选择后会自动改变,注意一下)

分别选对应的3个通道。采样周期最小是3个,即是能设置的最快的模式。
1.6 ADC电压采集+DMA-8

[3] -- P755 CH29.2.5.2 采样时间

根据目前我的设置,也就是野火提到最常用的情况,PCLK2为84MHz,ADC时钟为4分频,故ADC_CLK=84M/4Hz=21MHz。Tconv=3+12=15个周期,1个周期1/21M,故15个周期15/21M=0.7143us。

再解释一下Tconv=3+12=15个周期这个公式。ADC端口本质上是有一个电容,通过对其充电+读取来知道电压大小,所以整个ADC采样时间包括给电容充电的采样时间3个周期(这里时间越长当然采到的数据会越准,但设置得过长了其实早就稳了也没有多大意义)和读取的转换时间12个周期(ADC固有的转换时间,将电压转换为数字量)。此处的周期是针对ADC的主频来,因此是根据4分频得到的21MHz。

🌠>> ADC3 Configuration – DMA Settings
1.6 ADC电压采集+DMA-9
🚦Stream:DMA2 Stream 0(数据流0和数据流1都对应到ADC3,这里暂时就用DMA2 Stream 0了。)
🚦Direction:Peripheral To Memory
🚦Priority:Very High(尽量把这个优先级设高一点)
🚦Mode:Circular(循环模式,不然传输一次就结束了呗)
🚦Memory:打钩,表示存储ADC值的内存地址(数组)会自增
🚦Data Width:Half Word(u16)

参考 [1] – P206/1285 & [3] – P349/1626 ——
1.6 ADC电压采集+DMA-10

[1] -- P205 CH9.3.3 通道选择

每个DMA控制器具有 ( 0 ~ 7 ) 8个数据流,每个数据流对应8个外设请求。
在实现 DMA传输之前,DMA 控制器会通过DMA数据流 x 配置寄存器DMA_SxCR (x为0 ~ 7) 的 CHSEL[2:0] 位选择对应的通道作为该数据流的目标外设。[3]

❗每个外设请求都占用一个数据流通道,相同外设请求可以占用不同数据流通道。
❗但是,1个通道同时只能使用1个外设请求。
1.6 ADC电压采集+DMA-11
可以看到ADC3对应的是通道2,数据流0和数据流1,即对应这里DMA设置中,Stream项可以选择DMA2 Stream 0和DMA2 Stream 1。目前看起来这两者没什么区别,因此就先设为Stream 0了。

🌠>> ADC3 - GPIO Settings
暂时保持默认即可。
1.6 ADC电压采集+DMA-12

🕶 1.7 Project Manager 设置工程(自动生成C语言工程文件)

🌠>> Project Manager – Project

08
🌠>> Project Manager – Code Generator
09
🌠>> Project Manager – Advanced Settings
10
最后从CubeMX界面右上角,点 GENERATE CODE 按钮,即可。
11

👀 2 Keil 中的配置

🕶 2.1 下载程序前,记得改一下的设置

找到这个小魔术棒。2-10
>>Target – Use MicroLIB
2-11
>>Debug – Settings
2-12
2-13
2-14
2-15
2-16
OK,关掉。

🕶 2.2 Keil - UART通讯设置

🥽2.2.1 在【main.c】插入这些

这里实现ADC功能的实验中,用到串口只要是为了打印出数据,所以虽然照理打开了串口中断,但却并没用到。实际上的实验中,可能会有根据输入来在中断回调函数中执行相对应操作的,这部分根据自己后续的功能需要修改即可。
这里把串口接收中断回调函数的重定义放在了main.c的末尾。我为了省事,只判断1位输入,所以用的是一个 uint8_t 类型变量 uart3Rx 。

开头声明extern变量,免得因为声明在usart.c中,这里用不了。

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */
extern uint8_t uart3Rx;
/* USER CODE END PV */

末尾针对自己对功能的设想,修改中断回调函数的重定向。(这部分对本文ADC功能测试无影响!)

❗注意switch中的判断需要加单引号,如果不用引号括起来,会出错。

/* USER CODE BEGIN 4 */

// 弱声明函数的重定义,接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(huart->Instance == USART3)  // 如果是串口3
	{
		switch (uart3Rx)
		{
			case '0':
				//printf("case 0...\r\n");
			  break;
			case '1':
				//printf("case 1...\r\n");
			  break;
			case '2':
				//printf("case 2...\r\n");
			  break;
			default:
				//printf("Error! Please input again.\r\n");
				break;
		}
		//printf("...\r\n");
		HAL_UART_Receive_IT(&huart3, &uart3Rx, 1);   //再开启接收中断
	}
}

/* USER CODE END 4 */

可以用LED0的持续闪烁指示程序在正常运行,而没有卡在某个中断里。
在主函数的while循环里加上LED0的电平翻转,延时500ms。

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
		HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */

🥽2.2.2 在【usart.c】插入这些

开头声明存储接收量的变量uart3Rx,这里因为简化情形,就用一个变量来判断而非一整句话了。

/* USER CODE BEGIN 0 */
uint8_t uart3Rx;
/* USER CODE END 0 */

相对应的,UART3的初始化函数 void MX_USART3_UART_Init(void) 中,开启接收中断的语句也作相应调整,改成这个变量。

void MX_USART3_UART_Init(void)
{

  /* USER CODE BEGIN USART3_Init 0 */

  /* USER CODE END USART3_Init 0 */

  /* USER CODE BEGIN USART3_Init 1 */

  /* USER CODE END USART3_Init 1 */
  huart3.Instance = USART3;
  huart3.Init.BaudRate = 115200;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART3_Init 2 */
	//HAL_UART_Receive_IT(&huart3, (uint8_t *)&RxData, 1);//开启接收中断,将接收数据存储到RxData
	HAL_UART_Receive_IT(&huart3, &uart3Rx, 1); // !删掉了上一行,加上了这行!
  /* USER CODE END USART3_Init 2 */

}

末尾关于printf的重定向不变。

/* USER CODE BEGIN 1 */
//函数功能: 重定向c库函数printf到USART3
int fputc(int ch, FILE *f)
{
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xffff);
  return ch;
}
//函数功能: 重定向c库函数getchar,scanf到USART3
int fgetc(FILE *f)
{
  uint8_t ch = 0;
  HAL_UART_Receive(&huart3, &ch, 1, 0xffff);
  return ch;
}

/* USER CODE END 1 */

🥽2.2.3 在【usart.h】插入这些

因为情况简化了,之前关于存储接收数据的数组的一系列东西都可以省略了。因此在usart.h开头include和声明变量的地方,针对CubeMX生成的版本仅用加上这两个头文件。别的那堆extern都可以删掉啦。

/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <string.h>
/* USER CODE END Includes */

🕶 2.3 Keil - ADC设置

🥽2.3.1 在【adc.c】改这些

定义一个数组 ADC_Values 存放每次采样得到的数值。这里每次采样3个通道,所以大小为3。
需要注意,如果想类似采样100次取平均,之类的操作,数组大小不能太大,否则会超内存(我试的时候1000就💥了,100还行),这个也和自己在魔术棒里的ROM、RAM设置有关。
定义为volatile是因为采样频率比较大,担心因为经常改变被系统优化掉…

/* Includes ------------------------------------------------------------------*/
#include "adc.h"

/* USER CODE BEGIN 0 */
#define ADC_CHANNELS  3
volatile uint16_t ADC_Values[ADC_CHANNELS] = {0};
/* USER CODE END 0 */

🥽2.3.2 在【main.c】改这些

刚才声明关于串口的extern变量的位置,再加点东西。
新声明一个数组 ADC_Voltages,用来保存转换后得到的真实电压值。

/* USER CODE BEGIN PV */
//接下来一部分是和 UART 串口功能有关的 
extern uint8_t uart3Rx;

//接下来一部分是和 ADC相关
#define ADC_CHANNELS  3
extern uint16_t ADC_Values[ADC_CHANNELS];
volatile double ADC_Voltages[ADC_CHANNELS];
/* USER CODE END PV */

❗在主函数中,虽然有关于定时器的配置已经由CubeMX自动生成,但是还需要我们手动打开。
刚开始忽略了 这个小细节,导致自己的测量值是0.000且打印不出来…
与此同时,还需要调用ADC开启的函数,这里用到了DMA,所以用的是末尾有DMA的这个。

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* 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_USART1_UART_Init();
  MX_TIM4_Init();
  MX_TIM6_Init();
  MX_ADC3_Init();
  MX_TIM8_Init();
  MX_USART3_UART_Init();
  /* USER CODE BEGIN 2 */
//	__HAL_TIM_ENABLE_IT(&htim4,TIM_IT_TRIGGER);  //开启编码器定时器 触发中断 
//  __HAL_TIM_ENABLE_IT(&htim4,TIM_IT_UPDATE);   //开启编码器定时器 更新中断
//	HAL_TIMEx_HallSensor_Start_IT(&htim4);
	HAL_TIM_Base_Start_IT(&htim6);
	HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_1);    // 使能TIM8 PWM通道,准备开始输出PWM
	HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_2);
	HAL_TIM_PWM_Start(&htim8,TIM_CHANNEL_3);
	HAL_ADC_Start_DMA(&hadc3, (uint32_t *)ADC_Values, ADC_CHANNELS);  // ADC 启动函数
  /* USER CODE END 2 */

然后是在while(1)中LED0的闪烁与数据的打出。

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		printf("-----*-----*---NEW MEASURE---*-----*----- \r\n");
			printf("电压1:%.4f V\r\n",ADC_Voltages[0]);
			printf("电压2:%.4f V\r\n",ADC_Voltages[1]);
			printf("电压3:%.4f V\r\n",ADC_Voltages[2]);
		printf("\r\n");
		HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
		HAL_Delay(500);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

本来想直接在定时器6(20ms)的中断回调函数里面实现打印的,但串口打印耗时较长,这样可能会来不及打。

// TIM6定时器-更新中断-回调函数  定时中断(20ms)
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
	if(htim == (&htim4))  //TIM4-100ms-霍尔传感器的定时器
	{
    
	}else	if(htim == (&htim6))  //TIM6-20ms-基本定时器
	{
//		printf("-----*-----*---NEW MEASURE---*-----*----- \r\n");
//			printf("电压1:%.4f V\r\n",ADC_Voltages[0]);
//			printf("电压2:%.4f V\r\n",ADC_Voltages[1]);
//			printf("电压3:%.4f V\r\n",ADC_Voltages[2]);
//		printf("---------------MEASURE END--------------- \r\n");
//		printf("\r\n");
	}
}

我这里的现象是,下载好程序打开串口的那一下,劈里啪啦打出来一大堆,然后就卡在那里没有后续输出了TAT。
失败的截图...
最后是DMA采集完成的中断回调函数,也跟串口的接收中断一块儿放最后。

/* USER CODE BEGIN 4 */
//DMA采集完成中断回调函数
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* hadc)
{
	ADC_Voltages[0] = ADC_Values[0]*3.3/4096;
	ADC_Voltages[1] = ADC_Values[1]*3.3/4096;
	ADC_Voltages[2] = ADC_Values[2]*3.3/4096;

	//HAL_ADC_Stop_DMA(&hadc3);//关闭DMA的ADC采集
}

// 弱声明函数的重定义,接收中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{    /*...同前...*/    }

/* USER CODE END 4 */

🕶 2.4 我的实验结果

与此同时板载小灯每0.5秒亮/灭一次。这里我是前两个通道接的GND,第三个通道接3.3V。
成功的截图

👀 3 调试过程中遇到的问题

🕶 3.1 测量值是0.000

可能的原因:没有手动打开定时器、使能定时器通道。

🕶 3.2 测量结果打印不出来

可能的原因:在中断中打印,但周期太短,如20ms。

  • 4
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值