文章概览

  • 😶‍🌫️ 0 说在最前面 + 实现功能
  • 👀 1 CubeMX中的配置
  • 🕶 1.1 RCC & Clock Configuration时钟配置
  • 🕶 1.2 SYS Debug设置
  • 🕶 1.3 TIM定时器设置(TIM8-PWM+TIM4-HALL+TIM6简单定时)
  • 🥽 【TIM4】通用定时器 – 84MHz – 10Hz(T=100ms)的HALL传感器
  • 🥽 【TIM6】基本定时器 – 84MHz – 50Hz(T=20ms)
  • 🥽 【TIM8】高级定时器 – 168MHz – 20kHz(T=50us)的PWM输出 及 触发ADC采样
  • 🕶 1.4 USART3通讯设置(收发数据,把ADC采集数据打出来)
  • 🕶 1.5 GPIO Output – LED设置 输出低电平灯亮
  • 🕶 1.6 ADC电压采集+DMA
  • 🕶 1.7 Project Manager 设置工程(自动生成C语言工程文件)
  • 👀 2 Keil 中的配置
  • 🕶 2.1 下载程序前,记得改一下的设置
  • 🕶 2.2 Keil – UART通讯设置
  • 🥽2.2.1 在【main.c】插入这些
  • 🥽2.2.2 在【usart.c】插入这些
  • 🥽2.2.3 在【usart.h】插入这些
  • 🕶 2.3 Keil – ADC设置
  • 🥽2.3.1 在【adc.c】改这些
  • 🥽2.3.2 在【main.c】改这些
  • 🕶 2.4 我的实验结果
  • 👀 3 调试过程中遇到的问题
  • 🕶 3.1 测量值是0.000
  • 🕶 3.2 测量结果打印不出来
  • 😶‍🌫️ 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。

    物联沃分享整理
    物联沃-IOTWORD物联网 » STM32 BLDC驱动器详解

    发表评论