本文采用图形化配置+专业编译+轻量编辑的协同开发模式:通过 STM32CubeMX 完成外设配置与基础代码生成,利用 Keil MDK 进行工程构建、编译调试及烧录验证,最终在 VSCode 中完成核心业务逻辑开发。这种分工明确、优势互补的工具链组合已成为当前STM32工程开发的黄金实践方案。

在编程实现上,本文选用HAL库作为开发框架。相较于传统寄存器操作与标准库开发,HAL库凭借其硬件抽象层设计、跨系列兼容性STM32官方长期维护等优势,已成为工业现场的主流选择。

实验部分重点实现两大功能:

  1. 片上温度监测:通过ADC读取内部温度传感器通道信号,经线性转换公式计算实时芯片温度
  2. 多通道数据采集:同步扫描外部引脚电压信号,采用DMA双缓冲机制实现无阻塞传输,最终通过UART将温度值(℃)与电压值(V)格式化输出至终端。

项目完整演示了从硬件配置到算法实现的全流程开发,特别适合作为:

  • STM32多工具链协同开发入门案例
  • HAL库高级应用实践参考
  • ADC+DMA+UART组合外设开发范本

1. 图形化配置

1.1 SYS配置

作者选择的是JLink V8下载调试器,所以选择serial wire调试,如果用STlink 同样也是这个选项。

时钟源选择系统时钟systick

1.2 RCC 配置 

使用外部晶振,市面上几乎所以STM32的板子都有外部8M晶振

按如下图形配置时钟源,固定不变的,虽然可以有N种配置方案,但几乎所有软件代码都是基于这套方案配置的,高速时钟72MHz 低速时钟36MHz

注意:因为此时还没配置ADC 所以下面ADC时钟应该是灰色无法配置的(作者截图是已经配置好ADC的样子,所以ADC不是灰色的) 

1.3 USART1配置

为了方便看打印结果,配置一个串口输出结果。

注:硬件要使用串口连接电脑,并打开串口调试工具才能看到

1.4 GPIO配置

为了显示系统是否在运行,配置一个LED灯进行翻转指示芯片是否在运行while程序

 1.5 ADC1配置

采样周期尽量选最大,这样才有充分的时间进行温度采集和转换,同时我还配置了通道1同时进行电压测试。

因为此时有不止一个通道,所以要开启扫描模式,多通道顺序扫描,挨个转换

Scan conversion Mode: enabled

打开循环模式,关掉间断模式,通道组内转换完一轮后继续转换下一轮,让ADC不断进行信号采集转换,不要停

为了减少CPU的负担,采用了DMA传输转换数据

 1.6 NVIC配置

取消DMA中断,因为DMA中断是默认打开的,因为ADC转换采集频率相对较高,为了不必要的高频中断,这里进行取消

 1.6 project配置

 最后点击右上角 generate code 生成代码 并打开keil 软件

2. 工程设置

2.1 target配置

为了实现串口打印重定向功能,引入一个包

 2.2 debug配置

我用的Jlink 所以这里选择这个选项,如果用的stlink 就选择对应选项即可 

下载速率搞到最大,选择JTAG端口 

 避免每次下载程序后还要按复位键才执行最新代码,一定要选那个红色选项

 配置完后,关闭keil 这些配置就自动保存了,并生成对应配置文件,此时再使用VScode打开即可

注:一定要关掉keil再打开VScode,如果不关掉keil,那些配置就不会生成

3. 代码编写

3.1 USSRT1打印重定向

只需要写下如图几行代码即可实现串口打印重定向,重定向的意思就是,串口输出打印内容不再使用复杂的函数,只需要用 printf() 加上要打印的内容即可,非常方便调试,这也是除了仿真以外最重要的一种调试手段了。

/* USER CODE BEGIN 1 */
int fputc(int ch, FILE * file){
  HAL_UART_Transmit(&huart1,(uint8_t *)&ch, 1, 1000);
  return ch;
}
/* USER CODE END 1 */

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

注:include代码是写在usart.h文件中的,看截图所示,如果刚打开工程,只有usart.c文件,没有usart.h文件,只需要编译一次工程,即可看到 

 3.2 参数定义

在main函数中进行编码,先定义温度计算相关的参数,和dma的变量

/* USER CODE BEGIN PD */
// 温度传感器参数(参考 STM32F103 手册)
#define V25        1.43f   // 25°C 时的电压(典型值)
#define AVG_SLOPE  0.0043f // 平均斜率(4.3 mV/°C)
#define VREF       3.3f    // ADC 参考电压(默认 3.3V)
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

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

/* USER CODE BEGIN PV */
uint16_t adc_values[2] = {0};
/* USER CODE END PV */

3.3 开启ADC

  /* USER CODE BEGIN 2 */
  HAL_ADCEx_Calibration_Start(&hadc1);
  HAL_ADC_Start_DMA(&hadc1,(uint32_t *)adc_values,2);
  /* USER CODE END 2 */

第一行代码是校验ADC,第二行是开启ADC 

 3.4 计算温度并循环打印

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    printf("Hello World!\n");  // 打印调试信息
 
    // 切换LED状态
    HAL_GPIO_TogglePin(LED0_GPIO_Port, LED0_Pin);
 
    // 读取 ADC 值(DMA 自动更新 adc_values)
    uint16_t adc_voltage = adc_values[0]; // 假设第一个通道是电压
    uint16_t adc_temp = adc_values[1];    // 假设第二个通道是温度

    // 计算电压(假设第一个通道测量外部电压)
    float voltage = (adc_voltage * VREF) / 4095.0f;

    // 计算温度(第二个通道是温度传感器)
    float v_sense = (adc_temp * VREF) / 4095.0f;
    float temperature = (V25 - v_sense) / AVG_SLOPE + 25.0f;

    // 打印结果
    printf("Voltage Channel: %u, Voltage: %.3f V \r\t\n", adc_voltage, voltage);
    printf("Temp Channel: %u, V_sense: %.3f V, Temperature: %.2f C \r\t\n", 
            adc_temp, v_sense, temperature);
 
    HAL_Delay(2000);  // 2秒延迟(若需更高实时性,改用定时器中断)
  }

分别从DMA中取出两个通道的值,然后一个转换成电压,一个转换成温度,这个公式就是固定的。 

4. 编译下载验证

因为内部集成的温度传感器精度不高,误差在1.5度左右,所以每秒钟测量的值在这个区间抖动,用手去摸芯片,能明显看到温度上升。

 ADC1通道1对应的引脚是PA1,可以用杜邦线把这个引脚连接0V和3.3V之间的任意电压,都可以测量出来,注意不要超过3.3V,否则烧芯片。

注意:

在做这个实验的时候,作者困扰了好久,测量出来的温度是一个负数,打印出来的DMA值一直都是4095不变,而对比通道1却能够正常测量电压。

原因是硬件方便,参考电压VREF没有接地和接电源导致的,一定要注意这两个引脚分别接上正负极,一般的板子会空出来,需要用短路帽或者杜邦线进行连接。

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐