本文将介绍如何使用 SDCC + CMake 构建一个适用于 STC8 系列(8051 架构)MCU 的嵌入式工程,从软件准备到最终生成 .hex
文件,适合初学者逐步上手。
工具 | 作用 | 要求 |
---|---|---|
SDCC | 编译器,支持 8051 | 添加到系统 PATH |
CMake(3.15+) | 构建系统生成工具 | 必须安装 |
make / ninja | 构建执行器 | Windows 推荐 MinGW 的 make,或 ninja |
VSCode / Neovim | 编辑器,任选其一 | 推荐带 C 插件支持 |
工程根目录形如:
your-project/
├── CMakeLists.txt # CMake 配置文件(顶层构建脚本)
├── README.md # 项目说明文档
│
├── build/ # 构建输出目录(由 CMake 自动生成)
│ └── MySDCCProject.hex # 最终生成的十六进制固件文件
│
├── cmake/ # 自定义 CMake 脚本目录
│ └── convert_hex.cmake # 用于将 .ihx 转换为 .hex 的脚本
│
├── include/ # 公共头文件目录
│ ├── delay.h # 延时函数头文件
│ └── stc8h.h # STC8H 寄存器定义头文件
│
└── src/ # 源代码目录
├── delay.c # 延时函数实现
└── main.c # 主程序,控制 LED 闪烁
src/main.c
#include "stc8h.h"
#include "delay.h"
void main(void)
{
P1M0 = 0x01; // Set P1.0 as push-pull output mode
P1M1 = 0x00;
while(1)
{
P10 ^= 0x01;
delay_ms(500);
}
}
src/delay.c
void delay_ms(unsigned int ms) {
volatile unsigned int i, j;
for (i = 0; i < ms; i++)
{
for (j = 0; j < 123; j++);
}
}
include/delay.h
#ifndef DELAY_H
#define DELAY_H
void delay_ms(unsigned int ms);
#endif
include/stc8h.h
打开STC-ISP软件,选择头文件,选择SDCC格式,复制代码即可。
本项目使用 CMake 构建系统来组织源代码、配置编译器(如 SDCC)、管理构建过程,并生成最终的十六进制(.hex
)固件文件。整个构建过程高度自动化,便于在不同平台下统一使用命令行进行构建。
CMakeLists.txt
这个文件是 CMake 的核心配置脚本,用于定义整个项目的构建逻辑。主要完成了以下几件事:
sdcc
进行编译。include/
和 src/
目录下的相关文件。.c
为 .rel
文件:为每个源文件生成中间目标文件。.ihx
文件:将多个 .rel
链接为 SDCC 的 Intel HEX 格式文件(.ihx
)。.hex
文件:调用自定义 CMake 脚本 convert_hex.cmake
进行 .ihx → .hex
转换。文件内容示例:
# CMakeLists.txt
cmake_minimum_required(VERSION 3.15)
project(MySDCCProject NONE)
# === Step 1: Find SDCC ===
if(NOT DEFINED SDCC_PATH)
find_program(SDCC_EXECUTABLE sdcc)
if(SDCC_EXECUTABLE)
get_filename_component(SDCC_BIN_DIR "${SDCC_EXECUTABLE}" DIRECTORY)
if(WIN32)
get_filename_component(SDCC_ROOT "${SDCC_BIN_DIR}/.." ABSOLUTE)
set(SDCC_PATH "${SDCC_ROOT}/bin")
set(SDCC_INCLUDE_PATH "${SDCC_ROOT}/include")
else()
set(SDCC_PATH "${SDCC_BIN_DIR}")
if(APPLE)
set(SDCC_INCLUDE_PATH "/usr/local/share/sdcc/include")
else()
set(SDCC_INCLUDE_PATH "/usr/share/sdcc/include")
endif()
endif()
else()
message(FATAL_ERROR "SDCC not found. Please install SDCC or set -DSDCC_PATH manually.")
endif()
else()
if(NOT IS_ABSOLUTE "${SDCC_PATH}")
set(SDCC_PATH "${CMAKE_SOURCE_DIR}/${SDCC_PATH}")
endif()
if(NOT DEFINED SDCC_INCLUDE_PATH)
if(WIN32)
set(SDCC_INCLUDE_PATH "${SDCC_PATH}/../include")
elseif(APPLE)
set(SDCC_INCLUDE_PATH "/usr/local/share/sdcc/include")
else()
set(SDCC_INCLUDE_PATH "/usr/share/sdcc/include")
endif()
endif()
endif()
file(TO_CMAKE_PATH "${SDCC_PATH}" SDCC_PATH)
file(TO_CMAKE_PATH "${SDCC_INCLUDE_PATH}" SDCC_INCLUDE_PATH)
if(NOT EXISTS "${SDCC_PATH}/sdcc" AND NOT EXISTS "${SDCC_PATH}/sdcc.exe")
message(FATAL_ERROR "SDCC executable not found at ${SDCC_PATH}")
endif()
message(STATUS "Using SDCC from: ${SDCC_PATH}")
message(STATUS "Using SDCC include: ${SDCC_INCLUDE_PATH}")
# === Step 2: Include paths ===
include_directories(${SDCC_INCLUDE_PATH} ${CMAKE_SOURCE_DIR}/include)
# === Step 3: Source files ===
set(SRC_DIR ${CMAKE_SOURCE_DIR}/src)
set(SRC_FILES
${SRC_DIR}/main.c
${SRC_DIR}/delay.c
)
# === Step 4: Compile .c to .rel ===
set(REL_FILES "")
foreach(SRC_FILE ${SRC_FILES})
get_filename_component(FILE_NAME ${SRC_FILE} NAME_WE)
set(REL_FILE "${CMAKE_CURRENT_BINARY_DIR}/${FILE_NAME}.rel")
if(FILE_NAME STREQUAL "main")
set(MAIN_REL ${REL_FILE})
else()
list(APPEND REL_FILES ${REL_FILE})
endif()
add_custom_command(
OUTPUT ${REL_FILE}
COMMAND ${SDCC_PATH}/sdcc
-mmcs51
--std-c99
--opt-code-size
--debug
-I${SDCC_INCLUDE_PATH}
-I${CMAKE_SOURCE_DIR}/include
-c ${SRC_FILE}
DEPENDS ${SRC_FILE}
COMMENT "[Compiling] ${FILE_NAME}.c -> ${FILE_NAME}.rel"
VERBATIM
)
endforeach()
# === Step 5: Link to .ihx ===
set(IHX_FILE main.ihx)
add_custom_command(
OUTPUT ${IHX_FILE}
COMMAND ${SDCC_PATH}/sdcc
-mmcs51
--std-c99
--opt-code-size
--debug
${MAIN_REL} ${REL_FILES}
DEPENDS ${MAIN_REL} ${REL_FILES}
COMMENT "[Linking] .rel files -> ${IHX_FILE}"
VERBATIM
)
# === Step 6: Convert IHX to HEX ===
set(HEX_FILE ${PROJECT_NAME}.hex)
add_custom_command(
OUTPUT ${HEX_FILE}
COMMAND ${CMAKE_COMMAND}
-DINPUT=${IHX_FILE}
-DOUTPUT=${HEX_FILE}
-DSDCC_PATH=${SDCC_PATH}
-P ${CMAKE_SOURCE_DIR}/cmake/convert_hex.cmake
DEPENDS ${IHX_FILE}
COMMENT "[Post] Converting ${IHX_FILE} -> ${HEX_FILE}"
)
# === Step 7: Final build target ===
add_custom_target(${PROJECT_NAME} ALL
DEPENDS ${HEX_FILE}
COMMENT "[Target] Building project: ${PROJECT_NAME}"
)
convert_hex.cmake
这是一个自定义的 CMake 脚本,用于在构建过程的最后一步,将 SDCC 生成的 .ihx
文件转换为 .hex
文件。使用的是 SDCC 自带的 packihx
工具。
该脚本主要做了什么:
.ihx
文件路径和输出 .hex
文件路径。packihx
工具进行格式转换。.hex
固件文件。文件内容示例:
# convert_hex.cmake
cmake_minimum_required(VERSION 3.10)
# === Validate Required Variables ===
if(NOT DEFINED INPUT OR NOT DEFINED OUTPUT)
message(FATAL_ERROR "Missing required variables: INPUT or OUTPUT")
endif()
# === Normalize Paths to Absolute ===
get_filename_component(INPUT "${INPUT}" ABSOLUTE)
get_filename_component(OUTPUT "${OUTPUT}" ABSOLUTE)
# === Validate Input File Existence ===
if(NOT EXISTS "${INPUT}")
message(FATAL_ERROR "Input file not found: ${INPUT}")
endif()
# === Find packihx ===
if(DEFINED SDCC_PATH)
find_program(PACKIHX_EXECUTABLE packihx PATHS "${SDCC_PATH}" NO_DEFAULT_PATH)
else()
find_program(PACKIHX_EXECUTABLE packihx)
endif()
if(NOT PACKIHX_EXECUTABLE)
message(FATAL_ERROR "Could not find 'packihx'. Please set SDCC_PATH or ensure it is in the system PATH.")
endif()
# === Logging (Optional Debug Info) ===
message(STATUS "Converting IHX to HEX")
message(STATUS "Input IHX: ${INPUT}")
message(STATUS "Output HEX: ${OUTPUT}")
message(STATUS "Using packihx: ${PACKIHX_EXECUTABLE}")
# === Execute Conversion ===
execute_process(
COMMAND "${PACKIHX_EXECUTABLE}" "${INPUT}"
OUTPUT_FILE "${OUTPUT}"
RESULT_VARIABLE result
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
ERROR_VARIABLE error_output
)
# === Handle Result ===
if(NOT result EQUAL 0)
message(FATAL_ERROR "packihx failed with exit code: ${result}\nError output:\n${error_output}")
else()
message(STATUS "Successfully generated HEX file: ${OUTPUT}")
endif()
确保你已经将 SDCC 和 MinGW 的路径添加到系统环境变量 PATH
中。
cmake -B build -G "MinGW Makefiles" -DSDCC_PATH="D:/sdcc/bin"
cmake --build build
cmake -B build -G "Unix Makefiles" -DSDCC_PATH="/usr/bin"
cmake --build build
cmake -B build -G "Ninja" -DSDCC_PATH="..."
cmake --build build
在项目完成构建后,系统会自动生成一个可用于烧录的十六进制固件文件(.hex
)。这个文件是通过以下几个步骤生成的:
cmake -B build -G "Ninja"
cmake --build build
PS D:\B> cmake -B build -G "Ninja"
-- Using SDCC from: D:/sdcc/bin
-- Using SDCC include: D:/sdcc/include
-- Configuring done (0.3s)
-- Generating done (0.1s)
-- Build files have been written to: D:/B/build
PS D:\B> cmake --build build
[4/4] [Post] Converting main.ihx -> MySDCCProject.hex
-- Converting IHX to HEX
-- Input IHX: D:/B/build/main.ihx
-- Output HEX: D:/B/build/MySDCCProject.hex
-- Using packihx: D:/sdcc/bin/packihx.exe
-- Successfully generated HEX file: D:/B/build/MySDCCProject.hex
.c
源文件编译为 .rel
文件。.rel
文件被链接生成 .ihx
(Intel Hex)格式中间文件。packihx
工具将 .ihx
转换为标准的 .hex
文件。.hex
文件即为可烧录到 MCU 的最终固件。build/MySDCCProject.hex
你可以使用如 STC-ISP 工具,将该 .hex
文件烧录到你的 STC8H 单片机中。
.hex
文件写入 STC8 系列 MCU为更好地理解该项目,建议掌握以下知识:
类别 | 推荐学习内容 |
---|---|
C语言基础 | 函数、变量、循环、头文件 |
嵌入式入门 | GPIO 控制、MCU 时序 |
Makefile / CMake | 编译流程、构建系统基础 |
SDCC | 编译参数、优化选项 |
8051架构 | I/O 控制、寄存器操作(如 P1M0, P10) |
这个项目提供了一个完整的、可跨平台构建的 SDCC 工程模板,适合用于 STC8 系列 MCU 的学习与实践。使用现代工具链(如 CMake)可以大幅提高开发效率,也利于自动化部署与集成。
如果你刚开始学习 8051 嵌入式开发,本项目是一个非常好的起点。
sdcc
有了更深入的理解和认识。