库函数怎么配置寄存器

Tutorial: 电子电路 Category: 嵌入式 Published: 2026-08-29 08:09:36 Views: 0 Likes: 0 Comments: 0

095.1.6库函数怎么配置寄存器

/* 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--------------------------------------------------------*/

  /* 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();
  /* USER CODE BEGIN 2 */

  /* USER CODE END 2 */

  /* Infinite loop */
  /* 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;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** 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;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  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 寄存器配置

以打开 HSE 为例,代码会逐层展开为:

SET_BIT(RCC->CR, RCC_CR_HSEON);

#define SET_BIT(REG, BIT) ((REG) |= (BIT))
#define RCC_CR_HSEON_Pos  (16U)
#define RCC_CR_HSEON_Msk  (0x1UL << RCC_CR_HSEON_Pos)
#define RCC_CR_HSEON      RCC_CR_HSEON_Msk

代入宏以后,核心运算就是:

RCC->CR |= (1UL << 16);

1UL << 16 的结果为:

二进制:0000 0000 0000 0001 0000 0000 0000 0000
十六进制:0x0001 0000
位号:bit16(若按“第几个”从 1 数,则是第 17 位)

按位或 |= 能把 bit16 置 1,同时保持其他位的当前值。它本质上是“先读整个寄存器、按位或、再写回”的读—改—写操作。对硬件自动变化的状态位、写 1 清零位或并发访问场景,不能不加分析地套用这种形式,必须服从手册规定。

常见位操作宏:

宏/写法等价含义作用
SET_BIT(REG, MASK)`REG= MASK`
CLEAR_BIT(REG, MASK)REG &= ~MASK把掩码覆盖的位清为 0
READ_BIT(REG, MASK)REG & MASK读取指定位置;结果不一定等于 1
MODIFY_REG(REG,C,S)先清 C 再置 S修改一个多位字段而保留其余位

1.2 寄存器手册

STM32F103 是 32 位 MCU,但“所有寄存器都是 32 位”并不严谨。外设通常通过 32 位地址映射,参考手册也常用 32 位寄存器图表示;某些寄存器只有低 16 位实现,某些位保留,访问宽度也应按手册要求执行。

RCC 时钟控制寄存器 RCC_CR 的关键位如下:

名称访问含义
0HSION读写1:打开内部 8 MHz HSI
1HSIRDY只读1:HSI 已稳定
7:3HSITRIM读写HSI 用户微调值
15:8HSICAL只读HSI 出厂校准值
16HSEON读写1:打开 HSE
17HSERDY只读1:HSE 已稳定
18HSEBYP读写1:外部有源时钟旁路;0:晶体/谐振器模式
19CSSON读写时钟安全系统使能
24PLLON读写1:打开 PLL
25PLLRDY只读1:PLL 已锁定并稳定

HSEON=1 只是发起振荡器启动;代码还应等待 HSERDY=1。若晶体未焊好、负载不匹配或超时,HAL 会返回错误,应用层随后进入 Error_Handler()

1.3 库函数意义

库函数不是“绕过寄存器”,而是把正确的寄存器配置顺序封装起来:

flowchart LR
    APP[应用代码] --> API[HAL_RCC_OscConfig]
    API --> CHECK[参数检查/状态判断]
    CHECK --> BITS[SET_BIT / CLEAR_BIT]
    BITS --> RCCCR[RCC_CR]
    RCCCR --> OSC[HSE/PLL 电路]
    OSC --> FLAG[HSERDY/PLLRDY]
    FLAG --> API

封装带来的价值包括:统一接口、参数合法性检查、等待就绪与超时处理、跨同系列型号复用。代价是代码层次更深、占用量通常大于极简寄存器代码。调试时仍需会沿调用链追到寄存器。

1.4 RCC 寄存器描述

RCC 负责三个彼此相关但不同的动作:

  • 时钟源与系统分频CRCFGR 等寄存器决定 HSI/HSE/PLL 和各总线频率。
  • 外设时钟使能AHBENRAPB1ENRAPB2ENR 的位像时钟门,打开后外设才获得时钟。
  • 外设复位APB1RSTRAPB2RSTR 等寄存器用相应位把外设保持在复位状态;通常置 1 后还要清 0,才能释放复位。

时钟使能与复位不要混淆。例如 GPIOB 初始化前常见的是设置 RCC_APB2ENR.IOPBEN,不是持续设置 RCC_APB2RSTR.IOPBRST

1.4.1 APB2外设复位寄存器

STM32F103 高密度器件的 RCC_APB2RSTR 是 APB2 外设复位控制寄存器。常用位如下:

名称对应模块写 1 的效果
0AFIORST复用功能 I/O复位 AFIO
2IOPARSTGPIOA复位 GPIOA
3IOPBRSTGPIOB复位 GPIOB
4IOPCRSTGPIOC复位 GPIOC
5IOPDRSTGPIOD复位 GPIOD
6IOPERSTGPIOE复位 GPIOE
7IOPFRSTGPIOF复位 GPIOF
8IOPGRSTGPIOG复位 GPIOG
9ADC1RSTADC1复位 ADC1
10ADC2RSTADC2复位 ADC2
11TIM1RST高级定时器 1复位 TIM1
12SPI1RSTSPI1复位 SPI1
13TIM8RST高级定时器 8复位 TIM8
14USART1RSTUSART1复位 USART1
15ADC3RSTADC3复位 ADC3

bit1 和 bit31:16 为保留位,必须保持复位值。ADC3、TIM8 等模块只在具有相应外设的型号上有效;STM32F103ZET6 属于高密度产品,具备 ADC3 和 TIM8。换用其他容量或产品线时要查对应型号章节,不能直接套用这张表。

典型的软件复位操作是脉冲式的:

SET_BIT(RCC->APB2RSTR, RCC_APB2RSTR_IOPBRST);   /* 使 GPIOB 进入复位 */
CLEAR_BIT(RCC->APB2RSTR, RCC_APB2RSTR_IOPBRST); /* 释放 GPIOB 复位 */

二、结束

2.1 图形化配置与代码生成

图形界面每次生成代码后,应检查 .ioc、初始化函数和用户代码保护区。想知道一个选项影响了什么,最有效的方法是保存一份可编译基线,只改一个选项,再比较两个工程。

这种“单变量实验”能建立清晰对应关系。例如只关闭 LSE,差异通常会集中在振荡器类型、LSE 状态、RTC 时钟选择和 rtc.c/.h 是否生成等位置。

2.2 代码对比工具的使用方法

安全做法是把两个独立副本放在不含中文和特殊字符的短路径下,并排比较同名文件。也可以使用 Git:每次配置前提交一次,生成代码后运行 git diff。这样不必手工复制大量目录,也能准确还原每次变化。

比较时先过滤 DebugObjects、构建产物与 IDE 缓存,优先关注:

  • .ioc 配置;
  • Core/IncCore/Src
  • 工程描述和链接脚本;
  • 新增或删除的外设初始化文件。

2.3 例题1: 如何对比新旧代码差异

以启用 RTC 与 LSE 为例,可按以下顺序分析差异:

  1. .ioc 是否新增 RTC,并把 LSE 设为晶体模式;
  2. main.c 是否新增 #include "rtc.h"MX_RTC_Init()
  3. 是否出现完整的 rtc.crtc.h
  4. SystemClock_Config() 中的振荡器与 RTC 时钟源字段是否变化;
  5. 工程配置是否把新增文件加入编译。

一次只改变一个图形选项,差异才容易归因。若同时改变时钟、GPIO 和 RTC,工具虽然能显示不同,却很难判断每一行由哪个选择产生。

2.4 Beyond Compare软件介绍

Beyond Compare 可以进行目录和文本并排比较,颜色用于标识新增、删除或不同内容。使用时应安装并遵守其许可条款;也可以使用 Git、VS Code 的 Select for Compare、WinMerge 等合法工具完成同类任务。

比较结果本身不是错误结论:时间戳、自动排序、生成器版本变化也会造成差异,仍需结合 .ioc 和代码语义判断。

2.5 代码对比工具的其他应用场景

  • 自己的工程无法运行时,与课程中可运行的工程对比,定位漏掉的初始化、宏定义或中断入口。
  • 修改 CubeMX 配置后,检查用户代码是否仍位于保护区内。
  • 升级 HAL 或 IDE 后,识别生成代码和库版本变化。
  • 代码移植到 Keil 时,核对文件、包含路径、宏和启动文件是否齐全。

对比工具负责指出“哪里不同”,编译器、调试器、寄存器状态和原理图负责回答“哪一个差异导致了问题”。