news 2026/4/18 8:31:45

零基础学习Keil新建Cortex-M项目的步骤

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
零基础学习Keil新建Cortex-M项目的步骤

手把手教你从零开始用Keil搭建Cortex-M项目

你是不是也经历过这样的时刻:手头有一块STM32开发板,下载好了Keil MDK,点开软件却不知道从哪下手?新建工程时面对一堆选项一头雾水,点了“下一步”又怕配错,不点又没法继续——这几乎是每个嵌入式新手必经的坎。

别担心,今天我们不讲大道理,也不堆术语,就像师傅带徒弟一样,一步步带你把一个能跑起来的Cortex-M工程搭出来。过程中你会明白:为什么要有启动文件?链接脚本到底管什么?宏定义为啥非得加?搞懂这些,以后换芯片、换平台都能举一反三。


一、先搞清楚我们要建的是个啥?

在动手之前,得知道你创建的不是一个简单的.c文件,而是一个“嵌入式系统”的起点。它要完成的任务是:

  • 上电后CPU第一件事做什么?
  • 堆栈放在哪里?
  • 代码烧到Flash哪个位置?
  • main()函数之前发生了什么?

这些问题的答案,都藏在你新建的这个工程里。Keil不是魔法工具,它只是帮你把这些底层配置组织好,最终生成一个可以写进单片机的二进制文件(比如.hex.bin)。

所以,“新建工程”本质上是在告诉编译器和链接器:

“我的芯片长什么样?内存怎么分布?程序从哪儿开始?请按我说的来打包。”

明白了这一点,你就不会再把它当成“点几个按钮就行”的操作了。


二、第一步:打开Keil,真正的新手第一步

  1. 打开Keil µVision(版本推荐V5以上)。
  2. 菜单栏选择Project → New uVision Project
  3. 弹出窗口让你选保存路径,建议专门建个文件夹,比如:
    /MyFirstBlink/ ├── Project/ ← 工程放这里 ├── Src/ ← 后面自己建 ├── Inc/

输入工程名,比如Blink_LED,点击保存。

这时候Keil会立刻跳出来一个对话框:Select Device for Target ‘Target 1’

关键来了:选对芯片型号!

  • 展开厂商列表,找到你的MCU厂家,比如STMicroelectronics
  • 找到具体型号,例如STM32F103C8(常见于蓝pill开发板)。

⚠️ 小提示:如果你不确定型号,先查开发板资料。选错芯片可能导致后续无法下载程序或时钟配置错误。

选完之后,Keil会自动做两件事:
- 加载该芯片的基本参数(Flash/RAM大小、默认时钟等);
- 准备好对应的Flash编程算法(后面烧录要用)。


三、要不要启动文件?必须要有!

接下来弹窗问你:“Copy STM32F10x Standard Peripherals Library files?”之类的提示(不同版本略有差异),但最关键的是是否添加启动文件(Startup File)。

一定要选“Yes”!

Keil会自动给你加入一个类似这样的文件:

startup_stm32f103xb.s

这个名字里的xb对应的是中等容量Flash的F1系列芯片(64KB~128KB)。如果你用的是STM32F103C8,它的Flash是64KB,正好匹配。

启动文件到底干了啥?

你可以把它理解为“单片机起床后的洗漱流程”。上电瞬间,CPU做的第一件事就是读取这个文件中的中断向量表

AREA RESET, DATA, READONLY EXPORT __Vectors EXPORT __Vectors_End EXPORT __Vectors_Size __Vectors DCD __initial_sp ; Top of Stack DCD Reset_Handler ; Reset Handler DCD NMI_Handler ; NMI Handler DCD HardFault_Handler ; Hard Fault Handler ...

注意前两个:
- 第一项是初始堆栈指针(SP),指向RAM最高地址;
- 第二项是复位处理函数(Reset_Handler),CPU接下来就跳到这里执行。

没有这个文件,或者内容不对,你的程序根本跑不起来——连main()都到不了。


四、工程结构怎么组织?别让文件满天飞

现在工程有了,但我们还没写代码。先别急着敲main(),先把目录理清楚。

右键左侧“Source Group 1”,重命名为Src。然后在项目根目录下手动创建两个文件夹:
-Src/—— 放所有.c源文件
-Inc/—— 放所有.h头文件

接着,在Inc/里新建一个空头文件main.h,在Src/里新建main.c,并将其添加到工程中(拖进去或右键Add)。

此时你的工程结构应该是这样:

Project/ ├── Blink_LED.uvprojx ├── Src/ │ ├── main.c │ └── startup_stm32f103xb.s ├── Inc/ │ └── main.h

规范的结构能让后期维护轻松十倍。


五、最关键的一步:配置工程选项(Options for Target)

这是最容易出问题的地方,也是最值得花时间理解的部分。

右键左侧“Target 1” → “Options for Target…”,进入设置界面。我们逐个标签来看:

1. Target 标签页

  • XTAL (MHz):填写外部晶振频率。比如你的板子接的是8MHz晶振,就填8.0。

    这个值会影响后续HAL库中SysTick定时器的计算,务必准确。

  • Use MicroLIB✅ 勾上!

MicroLIB 是Keil提供的轻量级C库,比标准库小很多,适合资源紧张的MCU。而且它支持半主机(semihosting),调试时可以用printf输出到串口。


2. Output 标签页

  • ✅ 勾选Create HEX File

    很多烧录工具(如FlyMCU)只认.hex文件,勾上方便后期独立烧录。

  • 可以修改输出路径为./Output文件夹,整洁管理。


3. C/C++ 标签页

这里是编译器的大脑所在,三个关键设置:

(1)Define 宏定义

添加芯片标识宏,格式如下:

STM32F103xB

注意:不同型号后缀不同。C8CB属于xB系列;RCT6则是xE

为什么要加这个?因为ST的官方库(如HAL)靠这个判断你是哪个芯片,从而包含正确的寄存器定义和初始化代码。

你可以在stm32f1xx.h中看到类似代码:

#if defined(STM32F103xB) #include "stm32f103xb.h" #elif defined(STM32F103xE) #include "stm32f103xe.h" #endif

没定义 → 找不到头文件 → 编译报错。

(2)Include Paths

点击右侧图标,添加以下路径(假设你用了STM32Cube生成的库):

.\Inc Drivers\CMSIS\Include Drivers\CMSIS\Device\ST\STM32F1xx\Include Drivers\STM32F1xx_HAL_Driver\Inc

这样编译器才能找到#include <stm32f1xx.h>#include "main.h"

(3)C99 Mode

✅ 勾选Enable C99

允许使用现代C语法,比如:

for (int i = 0; i < 10; i++) { ... } // C99才支持在for中定义变量

不然编译会报错。


4. Debug 标签页

连接调试器(如ST-Link、J-Link)后,选择对应调试器:
- 点击ST-Link Debugger
- 点击 Settings → Debug → Enable “Run to main()”

这个功能非常实用:每次下载程序后,调试器会自动运行到main()函数开头停下,而不是卡在汇编启动代码里。


5. Utilities 标签页

勾选Use Debug Driver,并确保下面选择了正确的Flash编程算法(Keil通常会自动识别)。

如果出现“No Algorithm Found”,说明没加载Flash算法,检查芯片型号是否选对。


六、链接脚本(Scatter File):内存地图谁说了算?

Keil默认使用内部链接规则,但对于复杂项目,最好自己写一个.sct文件来控制内存布局。

比如你的STM32F103C8有:
- Flash:64KB,起始地址0x08000000
- RAM:20KB,起始地址0x20000000

创建一个linker.sct文件,内容如下:

LR_IROM1 0x08000000 0x00010000 { ; 64KB Flash ER_IROM1 0x08000000 0x00010000 { *.o (RESET, +First) *(InRoot$$Sections) .ANY (+RO) } RW_IRAM1 0x20000000 0x00005000 { ; 20KB SRAM .ANY (+RW +ZI) } }

然后在Options → Linker → Use Memory Layout from Target Dialog不勾选,改为勾选Use Custom Scatter File,指定这个文件。

作用是什么?
- 确保复位向量表在Flash最前面;
- 把全局变量(.data)复制到RAM;
- 零初始化段(.bss)清零;
- 堆栈空间预留足够。

否则可能出现“变量没初始化”、“malloc失败”等问题。


七、写个最简main函数,验证能不能通

main.c中写下最基础的LED闪烁代码:

#include "stm32f1xx.h" #include "main.h" void delay(volatile uint32_t count) { while (count--) __NOP(); } int main(void) { // 使能GPIOA时钟 RCC->APB2ENR |= RCC_APB2ENR_IOPAEN; // PA5设为推挽输出(LED常用引脚) GPIOA->CRL &= ~GPIO_CRL_MODE5; GPIOA->CRL |= GPIO_CRL_MODE5_1; // 输出模式,最大速度10MHz GPIOA->CRL &= ~GPIO_CRL_CNF5; // 推挽输出 while (1) { GPIOA->BSRR = GPIO_BSRR_BR5; // LED off delay(1000000); GPIOA->BSRR = GPIO_BSRR_BS5; // LED on delay(1000000); } }

这段代码直接操作寄存器,不依赖HAL库,编译快、体积小,适合验证工程环境是否正常。


八、编译 → 下载 → 看灯闪!

点击顶部的Build按钮(锤子图标)。

如果没有错误(0 Error(s), 0 Warning(s)),恭喜你,工程结构没问题!

接下来:
1. 用ST-Link将开发板连接电脑;
2. 点击Load按钮(向下箭头),程序就会烧录进Flash;
3. 点击Start/Stop Debug Session(虫子图标),进入调试模式;
4. 观察是否停在main()函数开头;
5. 按F5全速运行,看看LED有没有开始闪烁。

💡 如果灯亮了——你已经跨过了嵌入式开发的第一道门槛!


九、那些年我们都踩过的坑:常见问题与应对

问题表现解决方案
编译报错"identifier 'xxx' is undefined"提示找不到寄存器名检查是否定义了STM32F103xB
程序下载失败显示“No target connected”检查SWD连线、供电、复位电路
LED不闪,程序卡住调试发现停在HardFault_Handler大概率堆栈溢出或非法访问,检查Stack_Size
全局变量始终为0数据没从Flash复制到RAM检查scatter文件中是否有.data段处理
printf不输出串口无打印信息启用MicroLIB,并实现fputc函数

其中最隐蔽的问题之一是HardFault。建议你在工程中保留一份HardFault_Handler的调试版本:

void HardFault_Handler(void) { __disable_irq(); while (1) { // 在这里打断点,查看调用栈 } }

配合调试器,能快速定位非法内存访问或栈溢出。


十、给未来的你留条路:最佳实践建议

当你熟练之后,不妨养成这几个习惯:

  1. 模板化工程结构
    成功跑通一次后,备份成“通用模板”,下次直接复制,省去重复配置时间。

  2. 纳入Git版本控制
    提交.uvprojx,.c/.h,.sct等核心文件,忽略.uvoptx,Objects/,Listings/等临时文件。

  3. 尽量使用CMSIS标准接口
    比如用SystemCoreClock变量代替硬编码时钟值,提升可移植性。

  4. 命令行构建准备
    Keil提供UV4命令行工具,可用于自动化构建:
    bash UV4 -b Project.uvprojx -o build.log
    为将来接入CI/CD流水线打好基础。


最后一句话

你现在搭的不只是一个LED工程,而是通往嵌入式世界的大门钥匙。每一个成功的“新建工程”,都是你对底层机制理解更深一层的结果。

下次当你看到别人几分钟搞定工程搭建时,别羡慕——因为你已经知道,那背后藏着多少看不见的细节。

如果你在实践中遇到其他问题,欢迎留言交流。我们一起把这条路走得更稳、更远。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/18 7:43:09

工业通信协议栈中集成I2C读写EEPROM的深度剖析

工业通信协议栈中集成IC读写EEPROM的实战指南&#xff1a;从底层驱动到系统级设计为什么工业设备离不开本地非易失存储&#xff1f;在一次调试某型PLC扩展模块时&#xff0c;客户反馈&#xff1a;“每次断电重启后&#xff0c;量程配置都恢复成了出厂值。”问题根源很快被定位—…

作者头像 李华
网站建设 2026/4/16 10:49:37

零配置启动IndexTTS2,开箱即用的情感语音工具

零配置启动IndexTTS2&#xff0c;开箱即用的情感语音工具 1. 引言&#xff1a;情感化语音合成的新选择 在当前AI音频生成技术快速发展的背景下&#xff0c;文本转语音&#xff08;TTS&#xff09;系统已不再局限于机械朗读&#xff0c;而是朝着自然、富有情感表达的方向演进。…

作者头像 李华
网站建设 2026/4/17 15:35:50

OpCore Simplify:终极黑苹果EFI配置完整指南

OpCore Simplify&#xff1a;终极黑苹果EFI配置完整指南 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify OpCore Simplify是一款专为黑苹果爱好者设计的…

作者头像 李华
网站建设 2026/3/14 13:23:13

OpCore-Simplify智能选版指南:为你的黑苹果找到完美macOS版本

OpCore-Simplify智能选版指南&#xff1a;为你的黑苹果找到完美macOS版本 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify OpCore-Simplify是一款专业的…

作者头像 李华
网站建设 2026/4/18 1:47:18

Arduino IDE与ESP32串口通信配置:实战案例详解

从“烧录失败”到稳定通信&#xff1a;手把手教你搞定ESP32与Arduino IDE的串口调试你有没有遇到过这样的场景&#xff1f;代码写得信心满满&#xff0c;点击“上传”&#xff0c;结果Arduino IDE底部弹出一行红字&#xff1a;A fatal error occurred: Failed to connect或者&a…

作者头像 李华
网站建设 2026/4/4 6:27:41

OpCore Simplify:智能适配引擎重构Hackintosh配置范式

OpCore Simplify&#xff1a;智能适配引擎重构Hackintosh配置范式 【免费下载链接】OpCore-Simplify A tool designed to simplify the creation of OpenCore EFI 项目地址: https://gitcode.com/GitHub_Trending/op/OpCore-Simplify OpCore Simplify作为一款革命性的Ha…

作者头像 李华