熟悉新工程

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-10-08 01:26:37 Views: 1 Likes: 0 Comments: 0

096.1.7熟悉新工程

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */

/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

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

/* USER CODE END PM */

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

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

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

  /* 复位外设并建立 HAL、Flash 与 SysTick 的基础运行环境。 */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* 配置时钟树:外部 8 MHz 晶振经 PLL 产生系统时钟。 */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* 初始化 CubeMX 已启用的 GPIO 外设。 */
  MX_GPIO_Init();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* 主循环;应用逻辑应写在 USER CODE BEGIN 3 保护区内。 */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

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

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; /* 选择外部高速时钟源。 */
  RCC_OscInitStruct.HSEState = RCC_HSE_ON; /* 启用 HSE 晶振。 */
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; /* 使能 PLL。 */
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; /* HSE 作为 PLL 输入。 */
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; /* 8 MHz × 9 = 72 MHz。 */
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler(); /* 振荡器或 PLL 启动失败。 */
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; /* PLL 输出作为 SYSCLK。 */
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; /* HCLK 为 72 MHz。 */
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; /* APB1 降为 36 MHz。 */
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; /* APB2 保持 72 MHz。 */

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler(); /* 总线时钟配置失败。 */
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq(); /* 不可恢复错误时关闭中断并停在此处。 */
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */


一、新工程的使用

1.1 网盘文件下载与解压

配套代码包含 Keil MDK-ARM 工程和 STM32CubeIDE 工作区。建议解压到短、纯英文路径,例如 D:\stm32\code,并保留完整目录结构;不要只复制某个 .c 文件,因为工程还依赖头文件、HAL 驱动、启动文件、链接配置和芯片描述。

打开前先区分三类入口:

文件/目录用途打开方式
*.iocCubeMX 图形化配置STM32CubeMX 或 CubeIDE
MDK-ARM/*.uvprojxKeil 工程Keil µVision
CubeIDE 工作区/.projectEclipse 工程描述在 CubeIDE 选择工作区后导入或打开项目

工程路径、工作区路径和工具链路径是三个概念。路径包含中文、空格或特殊字符时,某些版本的构建脚本或插件可能报错;课程阶段使用全英文短路径最稳妥。

1.2 双击Q8文件

这里的“Q8”是转写中对 Keil 的称呼。Keil 工程应进入 MDK-ARM 目录,双击 .uvprojx,而不是随意双击某个源文件。

1.2.1 CUBE MX使用

双击 .ioc 后可查看芯片引脚、时钟树、外设和代码生成设置。.ioc 是工程配置清单,不是快捷方式,也不是可执行程序。

1.2.1.1 整合到最新版本

CubeMX/CubeIDE 打开较早工程时可能提示迁移固件包或工具版本。迁移前应先复制整个工程或提交到 Git,再阅读提示:

  • Migrate 会按当前工具与固件包调整工程,生成代码可能发生变化;
  • Continue 或保持版本则依赖本机安装相应固件包;
  • 迁移后应重新生成、全量编译,并使用目录比较或 git diff 检查变化;
  • 不要只看 IDE 显示“成功”,还要核对时钟、引脚、链接脚本和用户代码保护区。
1.2.2 Q8DE使用

这里的“Q8DE”指 STM32CubeIDE。推荐做法是:启动 CubeIDE,选择一个纯英文工作区,再通过 File → Import → Existing Projects into Workspace 导入项目;也可以在已关联的环境中打开 .project。

不要把“工程目录”和“工作区”混为一谈。工作区可以管理多个工程,工程目录则保存某个项目的源码与构建配置。

1.2.2.1 工作空间的定义

CubeIDE 工作区根目录下通常有 .metadata,用于保存窗口布局、索引、调试配置等工作区状态;不是 .mate。项目目录中的 .project、.cproject 描述工程,.ioc 保存 CubeMX 配置。

workspace/
├─ .metadata/               IDE 工作区状态,通常不要手改
├─ 01GPIO/
│  ├─ .project              Eclipse 项目描述
│  ├─ .cproject             C/C++ 构建配置
│  ├─ 01GPIO.ioc            CubeMX 配置
│  ├─ Core/
│  ├─ Drivers/
│  └─ STM32F103ZETX_FLASH.ld
└─ 02EXTI/

工具栏“小锤子”构建的是当前工程或当前目标,不是“选中一个 .c 后逐个编译所有文件”。字体缩放、编辑区拆分等快捷键可能随操作系统和键位方案变化,可在 Preferences → General → Keys 中查询。

1.3 单片机代码运行

STM32 复位后的实际启动链路如下:

flowchart TD
    RESET[复位] --> MSP[读取 0x00000000 的初始 MSP]
    MSP --> RH[读取 0x00000004 的 Reset_Handler]
    RH --> INIT[SystemInit]
    INIT --> CRT[C 运行库:复制 .data、清零 .bss]
    CRT --> MAIN[main]
    MAIN --> LOOP[初始化与 while 1]

.s 是汇编启动文件。文件开头的向量表第一项是初始栈顶,第二项是 Reset_Handler,后续是 NMI、HardFault 和各外设中断入口。课程使用的 startup_stm32f103xe.s 中,复位处理通常先调用 SystemInit,再跳转到 C 运行库入口;运行库完成数据段准备后才调用 main()。因此不能笼统说启动文件一定直接执行一条 BL main。

这里也不是 PC 意义上的 Bootloader 必然先运行。BOOT0/BOOT1 决定复位后把哪一片存储器映射到启动区:通常从用户 Flash 启动;选择系统存储器时才进入 ST 固化的 ROM Bootloader;也可选择从 SRAM 启动。

1.4 q工程文件夹

这里的“q工程”对应 CubeIDE 工程。重点目录如下:

目录/文件内容是否常改
Core/Incmain.h、中断头文件、外设头文件会
Core/Srcmain.c、外设初始化、中断与 MSP 代码会
Core/StartupGCC 语法启动汇编通常不改
Drivers/STM32F1xx_HAL_DriverHAL/LL 驱动通常不改
Drivers/CMSISCortex-M 内核接口和器件寄存器定义通常不改
*.ioc图形化配置会
*.ld链接脚本,定义 Flash/SRAM 布局特殊场景修改

Core 不是字母 q,配置文件扩展名是 .ioc,不是 .lc。Inc 存头文件,Src 存源文件,但二者必须通过包含路径和工程构建配置共同工作。

1.5 Q工程文件夹

这里对应 Keil 工程视图。它按工程分组展示文件,不一定与磁盘上的文件夹一一对应:

  • Application/User 一般包含 main.c、gpio.c 等应用文件;
  • Drivers/HAL 包含硬件抽象层源码;
  • CMSIS 包含内核与器件级定义;
  • startup_stm32f103xe.s 提供向量表和复位入口;
  • MDK-ARM 下还保存 .uvprojx、分散加载或链接配置及构建产物。

在 Keil 中看到的是“工程分组”,在资源管理器中看到的是“磁盘路径”。文件能在磁盘上找到,不代表已经加入 Keil 构建目标;反过来,工程分组名也不保证存在同名目录。

1.6 库存在的意义

HAL、CMSIS 和启动代码分别解决不同层次的问题:

应用逻辑
   ↓ 调用
HAL API(初始化、收发、状态与超时)
   ↓ 使用
CMSIS 器件结构体、位掩码和内核接口
   ↓ 映射
寄存器与芯片内部电路

库函数的价值是把重复、易错、具有严格顺序的寄存器操作封装起来,但调试时仍要理解端口时钟、寄存器位和硬件连接。修改 HAL 库本体会影响多个模块,课程阶段通常把自定义逻辑放在 Core 或单独驱动文件中。