从零到一:用STM32F405RGT6和Keil5打造你的第一个嵌入式‘Hello World’
当你第一次拿到STM32F405RGT6开发板时,面对密密麻麻的引脚和陌生的开发环境,可能会感到无从下手。别担心,这篇文章将带你从零开始,一步步完成第一个嵌入式程序的开发。我们将使用Keil MDK-ARM(简称Keil5)作为开发工具,通过标准外设库(Standard Peripheral Library)来实现一个简单的"Hello World"程序——点亮开发板上的LED灯。
1. 开发环境搭建
在开始编写代码之前,我们需要准备好开发环境。这包括安装必要的软件工具和获取相关的库文件。
首先,确保你已经安装了Keil MDK-ARM(Keil5)。如果没有安装,可以从Keil官网下载并安装。安装过程中需要注册一个Keil账号,这是免费的。安装完成后,你还需要安装STM32F4系列的设备支持包(Device Family Pack)。
接下来,我们需要从ST官网下载标准外设库。访问ST官网的标准外设库下载页面,找到STM32F4系列的标准外设库并下载。下载完成后,解压到一个合适的目录。
提示:ST官网下载可能需要注册账号,建议使用常用邮箱注册,方便后续获取其他资源。
2. 项目目录结构规划
一个良好的项目目录结构能让你的开发工作更加有序。我们建议按照以下结构组织你的项目文件:
ProjectTemplate/ ├── Core/ # 核心文件(启动文件、CMSIS核心文件) ├── Libraries/ # 标准外设库文件 ├── System/ # 系统级文件(时钟配置、延时函数等) └── User/ # 用户代码(主程序、配置文件等)让我们一步步填充这些目录:
Core目录:从下载的标准外设库中,将以下文件复制到Core目录:
Libraries/CMSIS/Include/下的所有文件Libraries/CMSIS/Device/ST/STM32F4xx/Source/Templates/arm/下的启动文件(选择startup_stm32f40xx.s)
Libraries目录:将
Libraries/STM32F4xx_StdPeriph_Driver/下的inc和src文件夹复制到这里。User目录:复制以下文件:
Libraries/CMSIS/Device/ST/STM32F4xx/Include/下的stm32f4xx.h和system_stm32f4xx.hProject/STM32F4xx_StdPeriph_Templates/下的main.c、stm32f4xx_conf.h、stm32f4xx_it.c和stm32f4xx_it.h
3. 创建Keil项目
现在我们可以开始创建Keil项目了:
- 打开Keil MDK-ARM,点击"Project" → "New μVision Project"
- 选择项目保存位置(建议放在User目录下)
- 在弹出的设备选择窗口中,搜索并选择"STM32F405RGTx"
- 点击"OK"后,会弹出运行时环境管理窗口,直接点击"Cancel"即可
接下来,我们需要配置项目结构:
- 右键点击"Target 1",选择"Manage Project Items"
- 创建以下组(Groups):
- Core
- Libraries
- User
- 向各组添加相应文件:
- Core组:添加Core目录下的启动文件(.s文件)
- Libraries组:添加Libraries/src下的所有.c文件
- User组:添加User目录下的所有.c文件
注意:添加完Libraries/src下的所有.c文件后,需要删除
stm32f4xx_fmc.c文件,因为它与STM32F405RGT6不兼容,会导致编译错误。
4. 项目配置
项目创建完成后,我们需要进行一些必要的配置:
点击"Options for Target"(魔术棒图标)打开配置窗口
在"Output"选项卡中,勾选"Create HEX File"
在"C/C++"选项卡中:
- 在"Define"框中添加:
USE_STDPERIPH_DRIVER,STM32F40_41xxx - 在"Include Paths"中添加以下路径:
../Core../Libraries/inc../User- Keil安装目录下的
ARM/CMSIS/Include - Keil安装目录下的
ARM/RV31/LIB
- 在"Define"框中添加:
在"Debug"选项卡中,选择你的调试工具(如ST-Link Debugger)
勾选"Reset and Run",这样程序烧录后会自动运行
5. 编写第一个程序
现在,我们可以开始编写第一个程序了。打开User目录下的main.c文件,替换为以下代码:
#include "stm32f4xx.h" #include "stm32f4xx_gpio.h" #include "stm32f4xx_rcc.h" void Delay(__IO uint32_t nCount) { while(nCount--) { } } int main(void) { GPIO_InitTypeDef GPIO_InitStructure; // 使能GPIOA时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 配置PA5引脚(通常连接LED) GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; GPIO_Init(GPIOA, &GPIO_InitStructure); while(1) { // 点亮LED GPIO_SetBits(GPIOA, GPIO_Pin_5); Delay(0x7FFFFF); // 熄灭LED GPIO_ResetBits(GPIOA, GPIO_Pin_5); Delay(0x7FFFFF); } }这段代码实现了以下功能:
- 初始化GPIOA的时钟
- 配置PA5引脚为推挽输出模式
- 在主循环中交替点亮和熄灭LED(通过PA5引脚)
6. 编译与调试
代码编写完成后,我们可以进行编译和调试:
- 点击"Build"按钮(或按F7)编译项目
- 如果没有错误,点击"Load"按钮(或按F8)将程序烧录到开发板
- 观察开发板上的LED是否开始闪烁
如果遇到编译错误,常见的问题及解决方法包括:
- main.h找不到:这是因为标准库模板中的
stm32f4xx_it.c文件引用了main.h,但我们的项目中没有这个文件。可以注释掉相关代码或创建一个空的main.h文件。 - 重复定义警告:这是由于
stm32f4xx.h中为了兼容性保留了一些旧的定义。可以忽略这些警告,或者修改文件属性为可写后删除重复定义。
7. 进阶:使用串口打印"Hello World"
除了点亮LED,我们还可以通过串口实现真正的"Hello World"输出。以下是实现步骤:
- 首先,我们需要初始化USART外设。在main.c中添加以下代码:
#include "stm32f4xx_usart.h" void USART1_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; // 使能GPIOA和USART1时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // 配置PA9为USART1_TX,PA10为USART1_RX GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9 | GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 将PA9和PA10引脚映射到USART1 GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1); GPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); // 配置USART1参数 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; USART_InitStructure.USART_StopBits = USART_StopBits_1; USART_InitStructure.USART_Parity = USART_Parity_No; USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_Init(USART1, &USART_InitStructure); // 使能USART1 USART_Cmd(USART1, ENABLE); } void USART1_SendChar(char ch) { while(!(USART1->SR & USART_SR_TXE)); USART1->DR = (ch & 0xFF); } void USART1_SendString(char *str) { while(*str) { USART1_SendChar(*str++); } }- 修改main函数:
int main(void) { USART1_Init(); USART1_SendString("Hello World!\r\n"); while(1) { } }- 连接开发板的USART1到电脑的串口(通常通过USB转串口工具),使用串口调试工具(如Putty、Tera Term等)查看输出。
8. 常见问题与解决方案
在实际开发过程中,你可能会遇到以下问题:
程序无法烧录
- 检查调试器连接是否正确
- 确认开发板供电正常
- 检查"Debug"配置中的调试器选择是否正确
LED不闪烁
- 确认LED连接的GPIO引脚是否正确(有些开发板LED连接的是其他引脚)
- 检查GPIO初始化代码是否正确
- 使用调试器单步执行,查看程序是否正常运行
串口无输出
- 检查串口线连接是否正确(TX接RX,RX接TX)
- 确认串口调试工具的波特率设置与程序一致
- 检查USART初始化代码是否正确
编译错误
- 确保所有必要的头文件路径已添加
- 检查是否有文件缺失或重复包含
- 确认宏定义是否正确
通过这个完整的开发流程,你应该已经成功地在STM32F405RGT6开发板上实现了第一个嵌入式程序。无论是点亮LED还是通过串口输出"Hello World",这都是嵌入式开发的重要第一步。