Keil代码移植
105.2.8Keil代码移植
/* ==================== Core\Inc\main.h ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.h
* @brief : Header for main.c file.
* This file contains the common defines of the application.
******************************************************************************
* @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 */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"
#define KEY1_Pin GPIO_PIN_3
#define KEY1_GPIO_Port GPIOE
#define KEY2_Pin GPIO_PIN_4
#define KEY2_GPIO_Port GPIOE
#define LED2_Pin GPIO_PIN_5
#define LED2_GPIO_Port GPIOE
#define SUN_Pin GPIO_PIN_11
#define SUN_GPIO_Port GPIOB
#define LED1_Pin GPIO_PIN_5
#define LED1_GPIO_Port GPIOB
#define BEEP_Pin GPIO_PIN_8
#define BEEP_GPIO_Port GPIOB
/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);
/* USER CODE BEGIN EFP */
/* USER CODE END EFP */
/* Private defines -----------------------------------------------------------*/
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
#ifdef __cplusplus
}
#endif
#endif /* __MAIN_H */
/* ==================== Core\Inc\gpio.h ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file gpio.h
* @brief This file contains all the function prototypes for
* the gpio.c file
******************************************************************************
* @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 */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __GPIO_H__
#define __GPIO_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MDK_GPIO_Init(void);
/* USER CODE BEGIN Prototypes */
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /*__ GPIO_H__ */
/* ==================== Core\Src\main.c ==================== */
/* 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"
void SystemClock_Config(void);
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* Configure the system clock */
SystemClock_Config();
/* Initialize all configured peripherals */
MDK_GPIO_Init();
/* Infinite loop */
while (1)
{
if(0 == HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin))
{
HAL_Delay(120);
if(0 == HAL_GPIO_ReadPin(KEY1_GPIO_Port, KEY1_Pin))
{
HAL_GPIO_TogglePin(BEEP_GPIO_Port, BEEP_Pin);
}
}
if(0 == HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin))
{
HAL_Delay(120);
if(0 == HAL_GPIO_ReadPin(KEY2_GPIO_Port, KEY2_Pin))
{
HAL_GPIO_TogglePin(LED2_GPIO_Port, LED2_Pin);
}
}
if(0 == HAL_GPIO_ReadPin(SUN_GPIO_Port, SUN_Pin))
{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, 1);
}else{
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, 0);
}
}
}
/**
* @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 */
/* ==================== Core\Src\gpio.c ==================== */
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file gpio.c
* @brief This file provides code for the configuration
* of all used GPIO pins.
******************************************************************************
* @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 "gpio.h"
/* USER CODE BEGIN 0 */
/* USER CODE END 0 */
/*----------------------------------------------------------------------------*/
/* Configure GPIO */
/*----------------------------------------------------------------------------*/
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/** Configure pins as
* Analog
* Input
* Output
* EVENT_OUT
* EXTI
*/
void MDK_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED2_GPIO_Port, LED2_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(LED1_GPIO_Port, LED1_Pin, GPIO_PIN_SET);
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(BEEP_GPIO_Port, BEEP_Pin, GPIO_PIN_RESET);
/*Configure GPIO pins : PEPin PEPin */
GPIO_InitStruct.Pin = KEY1_Pin|KEY2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = LED2_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(LED2_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : PtPin */
GPIO_InitStruct.Pin = SUN_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(SUN_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : PBPin PBPin */
GPIO_InitStruct.Pin = LED1_Pin|BEEP_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
/* USER CODE BEGIN 2 */
/* USER CODE END 2 */
一、寄存器地址相关概念
课程先用 51 单片机的 reg52.h 帮助理解“头文件如何把名字映射到硬件”。在 8051 中,特殊功能寄存器和位地址常写成:
sfr P0 = 0x80;
sbit P0_0 = P0^0;
这表示 P0 对应特殊功能寄存器地址 0x80,P0_0 对应该寄存器的第 0 位。它不是在 RAM 中创建一个普通变量,而是让编译器生成对固定硬件地址的访问。
STM32 使用 CMSIS 结构体完成同一类映射:
#define GPIOB_BASE 0x40010C00UL
#define GPIOB ((GPIO_TypeDef *)GPIOB_BASE)
访问 GPIOB->ODR 时,编译器根据 GPIO_TypeDef 中 ODR 的 0x0C 偏移,最终访问 0x40010C0C。两种写法语法不同,核心思想相同:
可读名称 ──> 固定地址/位 ──> 总线访问 ──> 真实硬件寄存器
Keil C51 的 sfr/sbit 是 8051 编译器扩展,不能直接用于 ARM;STM32 工程应使用设备头文件中的 CMSIS 类型、宏和 HAL 接口。
二、代码移植
这里的“移植”不是把整个 CubeIDE 工程目录复制到 Keil,而是把同一芯片、同一 HAL 版本下的用户层功能合入已经能编译的 Keil 工程。课程移植的是 GPIO 实验,目标保持:
- PB5、PE5 控制两只低电平有效 LED;
- PE3、PE4 读取两只低电平有效按键;
- PB11 读取传感器 DO;
- PB8 控制高电平有效蜂鸣器;
- 使用 8 MHz HSE,经 PLL×9 得到 72 MHz 系统时钟。
移植前必须核对芯片型号、启动文件、链接地址、HAL 驱动版本和编译器。若工程目标芯片不同,即使函数名称相同,启动文件、中断向量和可用外设也可能不匹配。
三、网盘模板介绍
课程配套模板已经包含 Keil 工程框架、CMSIS、STM32F1 HAL 驱动、启动文件和芯片选择。模板的作用是提供可编译的基础设施,用户仍要理解各文件的职责:
| 目录/文件 | 职责 |
|---|---|
| Core/Inc | 应用头文件、引脚宏和函数声明 |
| Core/Src | main、GPIO 初始化、系统中断等应用源码 |
| Drivers/CMSIS | Cortex-M 内核接口与芯片寄存器定义 |
| Drivers/STM32F1xx_HAL_Driver | HAL 外设驱动 |
| MDK-ARM/startup_stm32f103xe.s | 中断向量表、栈、复位入口 |
| .uvprojx | Keil 工程分组、文件与目标设置 |
模板只能解决工程组织问题,不能替代引脚核对。开发板电路和 main.h 中的宏必须一致。
四、移植内容
课程操作的核心内容包括:
- 从 CubeIDE 的 GPIO 工程复制 main.c 中的用户逻辑和 SystemClock_Config;
- 将 CubeMX 生成的
MX_GPIO_Init改名为MDK_GPIO_Init,避免与模板中已有符号或课程约定冲突; - 从 main.h 复制按键、LED、传感器和蜂鸣器的引脚宏;
- 将 gpio.c/gpio.h 加入 Keil 工程并保证函数声明一致;
- 清理只用于 CubeMX 再生成保护的 USER CODE 注释,不改变有效语句;
- 确认 Keil 工程包含所需 HAL 源文件和头文件路径。
函数改名必须同时修改声明、定义和调用:
Core/Inc/gpio.h void MDK_GPIO_Init(void);
Core/Src/gpio.c void MDK_GPIO_Init(void) { ... }
Core/Src/main.c MDK_GPIO_Init();
三处只改一处会分别产生“隐式声明”“未定义符号”或链接失败。
五、gpioc文件配置
GPIO 初始化的顺序应保持:
flowchart TD
CLK[开启 GPIOE/GPIOA/GPIOB 时钟] --> LEVEL[预置 LED 与蜂鸣器输出电平]
LEVEL --> INPUT[配置 PE3/PE4、PB11 输入]
INPUT --> OUTPUT[配置 PB5/PE5、PB8 推挽输出]
先预置输出电平,再切换成输出模式,可以减少上电瞬间的错误闪烁或蜂鸣。课程电路中:
| 网络 | 引脚 | 模式 | 有效电平 |
|---|---|---|---|
| KEY1 | PE3 | 上拉输入 | 低 |
| KEY2 | PE4 | 上拉输入 | 低 |
| SUN | PB11 | 上拉输入 | 取决于模块 DO |
| LED1 | PB5 | 推挽输出 | 低 |
| LED2 | PE5 | 推挽输出 | 低 |
| BEEP | PB8 | 推挽输出 | 高 |
按键松开由内部上拉保持为 1,按下后接地变为 0。LED 接在 3.3 V 与 GPIO 之间,需要 GPIO 拉低吸收电流;蜂鸣器由 S8050 三极管驱动,PB8 经 1 kΩ 使三极管导通,因此高电平响。
六、主函数配置
主函数的必要顺序:
HAL_Init();
SystemClock_Config();
MDK_GPIO_Init();
while (1)
{
/* 应用逻辑 */
}
HAL_Init()初始化 HAL、SysTick 和默认中断优先级分组;SystemClock_Config()配置 HSE、PLL、AHB/APB 分频;MDK_GPIO_Init()开启端口时钟并配置引脚;- 无限循环中再进行按键检测、LED、传感器与蜂鸣器控制。
若在 HAL_Init 之前调用依赖 HAL 时基的函数,或在端口时钟开启前访问 GPIO,都会破坏初始化前提。
七、编译运行
编译和下载按以下顺序检查:
- 在 Options for Target 的 Device 中确认实际 STM32F103 型号;
- 检查 C/C++ 的 Include Paths,确保 Core/Inc、CMSIS 和 HAL Inc 可见;
- 检查预处理宏,尤其是目标器件宏和 USE_HAL_DRIVER;
- 确认 startup_stm32f103xe.s 与芯片容量匹配;
- Build 后先处理第一条编译错误,后续错误常由它连锁产生;
- 在 Debug 中选择实际下载器,在 Flash Download 中加入正确算法;
- 需要下载后自动运行时勾选 Reset and Run;
- 下载后按开发板复位键,验证 LED、按键和蜂鸣器。
常见报错定位:
| 现象 | 优先检查 |
|---|---|
| 找不到 main.h/gpio.h | Include Paths |
| 找不到 HAL_GPIO_Init | HAL GPIO 源文件是否加入工程 |
| 找不到 MDK_GPIO_Init | 声明、定义、调用是否同名,gpio.c 是否参与编译 |
| 大量芯片类型未定义 | 设备宏、stm32f1xx.h 与目标型号 |
| 可编译但下载失败 | 下载器、驱动、SWD 接线、供电、Flash 算法 |
| 下载成功但不运行 | Reset and Run、时钟源、启动地址、Error_Handler |
不要通过随意关闭调试选项来掩盖问题。最可靠的顺序是先让空模板可编译、可下载、可停在 main,再逐项合入 GPIO 功能;每加入一组文件就重新编译一次,便于缩小故障范围。