西门子嵌入式第一讲 蓝桥杯过渡
本文档用于帮助从蓝桥杯嵌入式竞赛过渡到西门子嵌入式开发课程,梳理嵌入式C语言开发中的基础规范与常见开发方式。
目录:
1、嵌入式开发中的代码命名规范
2、命名注意事项
3、嵌入式开发方式(寄存器 / 标准库 / HAL库)
1、变量命名方法
(1)蛇形命名法
单词用下划线分隔,所有字母小写
应用场景:常用于全局变量、静态变量或文件名
//变量命名
uint16_t sensor_data_count;//传感器数据统计
//函数命名
void update_system_status();//更新系统状态
(2)驼峰命名法
首单词小写,后续单词首字母大写,无分隔符
应用场景:适用于变量名和函数名
//变量命名
uint32_t systemTick;//系统滴答计数
//函数命名
int readTemperature();//读取温度
(3)帕斯卡命名法
每个单词首字母大写,无分隔符
应用场景:常用于类型名、结构体名或模块名
//结构体定义
typedef struct{
int16_t x;
int16_t y;
}PointCoordinate; //坐标点结构体
//函数命名
void InitializePeriph(); //初始化外设
(4)匈牙利命名法
以类型或用途前缀开头,后接具体名称
应用场景:早期嵌入式开发多,后期嵌入式开发逐渐减少
// 类型前缀
uint8_t bStatus; //b表示byte,状态字节
char* pszErrorMessage; //psz表示指向以零结尾的字符串的执政
(5)全大写命名法
所有字母大写,单词间用下划线分隔
应用场景:用于宏定义和常量
//宏定义
#define MAX_DATA_LENGTH 128 //最大数据长度
//常量
const uint32_t SYS_FREQ = 72000000; //系统频率(HZ)
2、命名注意事项
(1)避免与系统保留字冲突
如register、volatile、interrupt等具有特殊含义的关键字,不能用作变量名或函数名。
此外,还应避免使用标准库中已有的函数名,如printf、malloc等,防止产生重定义冲突。
// 错误示范
uint8_t register = 0; // ❌ register是C语言关键字
uint8_t volatile = 1; // ❌ volatile是C语言关键字
// 正确示范
uint8_t reg_value = 0; // ✅ 用缩写或语义名代替
uint8_t vol_flag = 1; // ✅
(2)长度限制
变量名长度虽然在C99标准中支持至少63个有效字符,但在实际嵌入式开发中建议控制在20个字符以内。
过短的命名(如a、b、x)缺乏语义,过长的命名(如the_current_temperature_sensor_reading_value)影响可读性。
推荐使用常见缩写:temp(temperature)、cnt(count)、buf(buffer)、idx(index)、len(length)、msg(message)等。
// 过短 — 语义不明
uint8_t a; // ❌ 不知道存什么
int t; // ❌ 不知道是温度还是时间
// 过长 — 影响阅读
uint32_t the_system_total_running_time_in_milliseconds; // ❌
// 推荐 — 简洁且语义明确
uint8_t adc_value; // ✅ ADC采样值
int temp_celsius; // ✅ 摄氏温度
uint32_t sys_run_ms; // ✅ 系统运行毫秒数
(3)语义清晰
变量名和函数名应直接反映其用途或含义,做到"见名知意",减少对注释的依赖。
布尔类型变量推荐使用is_、has_、can_等前缀,函数名推荐使用"动词+名词"结构。
// 语义不清
uint8_t flag; // ❌ 什么标志?
void process(); // ❌ 处理什么?
// 语义清晰
uint8_t is_data_ready; // ✅ 数据是否就绪
void process_uart_data(); // ✅ 处理串口数据
uint8_t has_new_message; // ✅ 是否有新消息
void reset_watchdog(); // ✅ 喂狗/复位看门狗
(4)统一命名风格
一个项目中应统一使用一种命名风格,不要混用蛇形命名和驼峰命名。
团队开发时应事先约定命名规范,并在代码审查中严格执行。
(5)模块化前缀
在嵌入式开发中,建议为不同模块的函数和全局变量添加模块前缀,避免命名冲突。
// UART模块
void UART_Init(void);
void UART_SendByte(uint8_t data);
// LED模块
void LED_Init(void);
void LED_Toggle(uint8_t led_num);
// ADC模块
void ADC_Init(void);
uint16_t ADC_GetValue(uint8_t channel);
3、嵌入式开发方式
(1)寄存器开发
方法:直接操作MCU内部寄存器的地址来配置外设功能。开发者需要查阅芯片参考手册(Reference Manual),
找到对应寄存器的地址和各位的定义,通过位操作来完成配置。
优点:
- 执行效率最高,生成的代码体积最小,运行速度最快
- 对硬件的控制最为精确和灵活,不受库函数封装的限制
- 有助于深入理解MCU的底层工作原理和硬件架构
缺点:
- 开发效率低,代码可读性差,每一步都需要查阅参考手册
- 代码移植性差,换一款芯片几乎需要全部重写
- 容易出错,一个位操作写错就可能导致外设无法正常工作
- 不适合大型项目和团队协作
/* 寄存器方式:配置GPIOA Pin5为推挽输出(以STM32F103为例) */
// 1. 开启GPIOA的时钟(RCC_APB2ENR寄存器的第2位)
RCC->APB2ENR |= (1 << 2);
// 2. 配置PA5为推挽输出,最大速度50MHz
// PA5对应CRL寄存器的[20:23]位
// MODE5[1:0] = 11(输出模式,50MHz)
// CNF5[1:0] = 00(通用推挽输出)
GPIOA->CRL &= ~(0xF << 20); // 先清零[20:23]位
GPIOA->CRL |= (0x3 << 20); // 设置MODE5 = 11
// 3. 输出高电平,点亮LED
GPIOA->ODR |= (1 << 5); // PA5输出高电平
// 4. 输出低电平,熄灭LED
GPIOA->ODR &= ~(1 << 5); // PA5输出低电平
(2)标准库开发
方法:使用ST官方提供的标准外设库(Standard Peripheral Library,简称SPL/StdLib),
通过调用封装好的API函数来配置外设。库函数内部完成了对寄存器的操作,
开发者只需传入配置参数即可。蓝桥杯嵌入式竞赛主要使用此方式。
优点:
- 开发效率较高,函数接口清晰,参数含义明确
- 代码可读性好,便于理解各外设的配置过程
- 在同系列芯片间具有一定的移植性
- 蓝桥杯竞赛指定使用,竞赛选手较为熟悉
缺点:
- ST官方已停止维护标准库,不再更新和支持新型号芯片
- 仅支持STM32F1/F2/F4等较早系列,不支持STM32G0/G4/H7等新系列
- 没有图形化配置工具支持,外设初始化需要手写代码
- 相比寄存器方式,运行效率略低(函数调用开销)
/* 标准库方式:配置GPIOA Pin5为推挽输出(以STM32F103为例) */
#include "stm32f10x.h"
#include "stm32f10x_gpio.h"
#include "stm32f10x_rcc.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
// 1. 开启GPIOA时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 2. 配置GPIO参数
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5; // 选择Pin5
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; // 输出速度50MHz
// 3. 初始化GPIOA
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 4. 点亮LED(输出高电平)
GPIO_SetBits(GPIOA, GPIO_Pin_5);
// 5. 熄灭LED(输出低电平)
GPIO_ResetBits(GPIOA, GPIO_Pin_5);
}
(3)HAL库开发
方法:使用ST官方推出的硬件抽象层(Hardware Abstraction Layer,简称HAL库),
配合STM32CubeMX图形化配置工具进行开发。CubeMX可自动生成初始化代码,
开发者在此基础上编写应用逻辑。这是目前ST官方主推的开发方式,
也是西门子嵌入式课程采用的主要开发方式。
优点:
- ST官方持续维护,支持所有STM32系列芯片(F0/F1/F4/G0/G4/H7/U5等)
- 配合CubeMX图形化工具,可快速完成引脚配置、时钟树设置、外设初始化
- 跨系列移植性最好,API接口在不同系列间高度统一
- 支持中间件集成(FreeRTOS、USB、LWIP、FatFS等),适合复杂项目
- 回调机制(Callback)使得中断处理结构更加清晰
缺点:
- 库体积较大,对Flash和RAM资源占用相对较多
- 层层封装导致运行效率比寄存器和标准库方式稍低
- 初学者可能因过度依赖CubeMX而忽视底层原理
- HAL库部分API设计冗余,某些简单操作也需较多代码
/* HAL库方式:配置GPIOA Pin5为推挽输出(以STM32F103为例) */
/* 注意:以下初始化代码通常由CubeMX自动生成 */
#include "stm32f1xx_hal.h"
void LED_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 1. 开启GPIOA时钟(HAL库宏定义方式)
__HAL_RCC_GPIOA_CLK_ENABLE();
// 2. 配置GPIO参数
GPIO_InitStruct.Pin = GPIO_PIN_5; // 选择Pin5
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStruct.Pull = GPIO_NOPULL; // 无上下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; // 高速
// 3. 初始化GPIOA
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
// 4. 点亮LED(输出高电平)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
// 5. 熄灭LED(输出低电平)
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
// 6. 翻转LED状态
HAL_GPIO_TogglePin(GPIOA, GPIO_PIN_5);
}
三种开发方式对比总结
| 对比项 | 寄存器开发 | 标准库开发 | HAL库开发 |
|---|---|---|---|
| 开发效率 | 低 | 中 | 高 |
| 执行效率 | 最高 | 较高 | 中等 |
| 代码可读性 | 差 | 良好 | 良好 |
| 移植性 | 差 | 同系列可移植 | 跨系列可移植 |
| 官方维护 | — | 已停止维护 | 持续更新 |
| 工具支持 | 无 | 无 | CubeMX图形化 |
| 学习价值 | 深入理解底层 | 蓝桥杯竞赛必备 | 工业开发主流 |
| 适用场景 | 底层驱动/极致优化 | 蓝桥杯竞赛/老项目维护 | 新项目开发/产品开发 |
从蓝桥杯到西门子嵌入式的过渡要点
-
开发库的切换:蓝桥杯竞赛使用寄存器,西门子嵌入式课程使用HAL库。两者API风格不同,但核心思路一致——都是通过结构体配置参数,再调用初始化函数。
-
开发工具的变化:HAL库开发通常配合STM32CubeMX进行引脚和时钟配置,自动生成初始化代码,IDE一般使用Keil MDK或STM32CubeIDE。
-
中断处理方式的区别:
- 标准库:在中断服务函数(如
EXTI0_IRQHandler)中直接编写处理逻辑 - HAL库:中断服务函数调用
HAL_GPIO_EXTI_IRQHandler(),再通过回调函数HAL_GPIO_EXTI_Callback()处理逻辑
- 标准库:在中断服务函数(如
-
命名风格的差异:
- 标准库函数:
GPIO_SetBits()、RCC_APB2PeriphClockCmd() - HAL库函数:
HAL_GPIO_WritePin()、__HAL_RCC_GPIOA_CLK_ENABLE()
- 标准库函数:
-
共通的基础知识:GPIO、UART、TIM、ADC、I2C、SPI等外设原理不变,掌握蓝桥杯中的外设知识可以直接迁移到HAL库开发中。