C++ 裸机编程:脱离操作系统直接与硬件交互

哈喽,各位好!欢迎来到“C++ 裸机编程:直接跟硬件唠嗑”的讲座。今天咱们不搞那些花里胡哨的框架,直接撸起袖子,用C++跟硬件“亲密接触”,聊聊裸机编程那些事儿。

啥是裸机编程?

简单来说,就是你的C++代码不运行在操作系统之上,而是直接跑在硬件上。就像原始人直接用石头砸坚果,没有开瓶器、没有核桃夹子,简单粗暴。

  • 操作系统: 就像一个大管家,帮你管理硬件资源,分配内存,处理中断等等。
  • 裸机编程: 你就是那个管家,所有事情都得自己来。

为啥要裸机编程?

可能你会问,现在操作系统这么发达,为啥还要费劲搞裸机?原因很简单:

  • 极致性能: 没有操作系统的开销,运行速度嗖嗖的,对于实时性要求高的应用(比如无人机、机器人、嵌入式系统),裸机编程是首选。
  • 完全掌控: 你可以完全控制硬件,想怎么玩就怎么玩,不受操作系统限制。
  • 深入理解硬件: 逼着你去了解硬件的底层细节,绝对让你变成硬件专家。
  • 体积小巧: 不需要庞大的操作系统,代码体积可以很小,适合资源受限的设备。

裸机编程的“装备”

要玩裸机编程,你需要一些“装备”:

  • 硬件平台: 比如STM32开发板、树莓派 Pico等等。选择哪个取决于你的项目需求。
  • 交叉编译工具链: 因为你的代码不是在你的电脑上运行,而是在目标硬件上运行,所以需要交叉编译工具链将代码编译成目标硬件能执行的指令。比如arm-none-eabi-gcc (针对ARM架构)。
  • 调试工具: 比如J-Link、ST-Link等等,用来将你的代码烧录到硬件,并进行调试。
  • 文本编辑器/IDE: Visual Studio Code, Eclipse, Keil等等,用来编写代码。
  • 耐心、细心、恒心: 裸机编程不是一蹴而就的,需要不断尝试、调试、学习。

裸机编程的“语言”

C++ 是裸机编程的常用语言,但有一些需要注意的地方:

  • 标准库受限: 很多标准库函数在裸机环境下是不能直接使用的,因为它们依赖于操作系统。你需要自己实现一些基本的功能。
  • 内存管理: 你需要自己管理内存,防止内存泄漏、野指针等问题。
  • 中断处理: 中断是硬件和软件交互的重要方式,你需要编写中断处理函数来响应硬件事件。
  • 硬件寄存器: 你需要直接操作硬件寄存器来控制硬件的行为。

开始你的第一次裸机之旅(以STM32为例)

咱们以STM32开发板为例,来做一个简单的LED闪烁程序。

1. 硬件准备:

  • STM32开发板 (比如STM32F103C8T6 "蓝 pill")
  • ST-Link 调试器
  • 连接线

2. 软件准备:

  • arm-none-eabi-gcc (交叉编译工具链)
  • OpenOCD (调试工具)
  • Visual Studio Code (编辑器)

3. 工程结构:

一个简单的裸机工程通常包含以下文件:

  • startup.s: 启动文件,负责初始化堆栈、中断向量表等。
  • system.c: 系统初始化文件,负责初始化时钟、GPIO等。
  • main.c: 主程序文件,包含主循环和应用程序逻辑。
  • linker.ld: 链接脚本,告诉链接器如何将代码和数据放置到内存中。
  • Makefile: 自动化编译和烧录的脚本。

4. 代码实现:

  • startup.s (启动文件):
.syntax unified
.arch armv7-m

.section .stack
    .align 3
    .global stack_top
stack_top:
    .space 0x400  /* 1KB Stack */
    .global stack_bottom
stack_bottom:

.section .isr_vector
    .align 2
    .global isr_vector
isr_vector:
    .word stack_top         /* Top of Stack */
    .word Reset_Handler     /* Reset Handler */
    .word NMI_Handler       /* NMI Handler */
    .word HardFault_Handler /* Hard Fault Handler */
    .word MemManage_Handler /* MPU Fault Handler */
    .word BusFault_Handler  /* Bus Fault Handler */
    .word UsageFault_Handler/* Usage Fault Handler */
    .word 0                  /* Reserved */
    .word 0                  /* Reserved */
    .word 0                  /* Reserved */
    .word 0                  /* Reserved */
    .word SVC_Handler       /* SVCall Handler */
    .word DebugMon_Handler  /* Debug Monitor Handler */
    .word 0                  /* Reserved */
    .word PendSV_Handler    /* PendSV Handler */
    .word SysTick_Handler   /* SysTick Handler */

    /* External Interrupts */
    .word WWDG_IRQHandler       /* Window WatchDog Interrupt */
    .word PVD_IRQHandler        /* PVD through EXTI Line detect Interrupt */
    .word TAMPER_IRQHandler     /* Tamper Interrupt */
    .word RTC_IRQHandler        /* RTC Interrupt */
    .word FLASH_IRQHandler      /* Flash Interrupt */
    .word RCC_IRQHandler        /* RCC Interrupt */
    .word EXTI0_IRQHandler      /* EXTI Line 0 Interrupt */
    .word EXTI1_IRQHandler      /* EXTI Line 1 Interrupt */
    .word EXTI2_IRQHandler      /* EXTI Line 2 Interrupt */
    .word EXTI3_IRQHandler      /* EXTI Line 3 Interrupt */
    .word EXTI4_IRQHandler      /* EXTI Line 4 Interrupt */
    .word DMA1_Channel1_IRQHandler /* DMA1 Channel 1 Interrupt */
    .word DMA1_Channel2_IRQHandler /* DMA1 Channel 2 Interrupt */
    .word DMA1_Channel3_IRQHandler /* DMA1 Channel 3 Interrupt */
    .word DMA1_Channel4_IRQHandler /* DMA1 Channel 4 Interrupt */
    .word DMA1_Channel5_IRQHandler /* DMA1 Channel 5 Interrupt */
    .word DMA1_Channel6_IRQHandler /* DMA1 Channel 6 Interrupt */
    .word DMA1_Channel7_IRQHandler /* DMA1 Channel 7 Interrupt */
    .word ADC1_2_IRQHandler     /* ADC1 & ADC2 Interrupt */
    .word EXTI9_5_IRQHandler    /* EXTI Line 9 to 5 Interrupt */
    .word TIM1_BRK_TIM15_IRQHandler /* TIM1 Break and TIM15 Interrupt */
    .word TIM1_UP_TIM16_IRQHandler  /* TIM1 Update and TIM16 Interrupt */
    .word TIM1_TRG_COM_TIM17_IRQHandler /* TIM1 Trigger and Commutation and TIM17 Interrupt */
    .word TIM1_CC_IRQHandler      /* TIM1 Capture Compare Interrupt */
    .word TIM2_IRQHandler       /* TIM2 Interrupt */
    .word TIM3_IRQHandler       /* TIM3 Interrupt */
    .word TIM4_IRQHandler       /* TIM4 Interrupt */
    .word I2C1_EV_IRQHandler    /* I2C1 Event Interrupt */
    .word I2C1_ER_IRQHandler    /* I2C1 Error Interrupt */
    .word I2C2_EV_IRQHandler    /* I2C2 Event Interrupt */
    .word I2C2_ER_IRQHandler    /* I2C2 Error Interrupt */
    .word SPI1_IRQHandler       /* SPI1 Interrupt */
    .word SPI2_IRQHandler       /* SPI2 Interrupt */
    .word USART1_IRQHandler     /* USART1 Interrupt */
    .word USART2_IRQHandler     /* USART2 Interrupt */
    .word USART3_IRQHandler     /* USART3 Interrupt */
    .word EXTI15_10_IRQHandler   /* EXTI line 15 to 10 Interrupt */
    .word RTCAlarm_IRQHandler   /* RTC Alarm through EXTI Line Interrupt */
    .word USBWakeUp_IRQHandler   /* USB Wake-up from suspend Interrupt */
    .word TIM8_BRK_IRQHandler    /* TIM8 Break Interrupt */
    .word TIM8_UP_IRQHandler     /* TIM8 Update Interrupt */
    .word TIM8_TRG_COM_IRQHandler /* TIM8 Trigger and Commutation Interrupt */
    .word TIM8_CC_IRQHandler     /* TIM8 Capture Compare Interrupt */
    .word ADC3_IRQHandler       /* ADC3 Interrupt */
    .word FSMC_IRQHandler       /* FSMC Interrupt */
    .word SDIO_IRQHandler       /* SDIO Interrupt */
    .word TIM5_IRQHandler       /* TIM5 Interrupt */
    .word SPI3_IRQHandler       /* SPI3 Interrupt */
    .word UART4_IRQHandler      /* UART4 Interrupt */
    .word UART5_IRQHandler      /* UART5 Interrupt */
    .word TIM6_IRQHandler       /* TIM6 Interrupt */
    .word TIM7_IRQHandler       /* TIM7 Interrupt */
    .word DMA2_Channel1_IRQHandler /* DMA2 Channel 1 Interrupt */
    .word DMA2_Channel2_IRQHandler /* DMA2 Channel 2 Interrupt */
    .word DMA2_Channel3_IRQHandler /* DMA2 Channel 3 Interrupt */
    .word DMA2_Channel4_5_IRQHandler /* DMA2 Channel 4 & 5 Interrupt */

.thumb_func
.global Reset_Handler
Reset_Handler:
    /* Copy .data section from FLASH to SRAM */
    ldr r0, =_sdata
    ldr r1, =_edata
    ldr r2, =_sidata
copy_data:
    cmp r0, r1
    bge data_copied
    ldr r3, [r2], #4
    str r3, [r0], #4
    b copy_data
data_copied:

    /* Zero fill .bss section */
    ldr r0, =_sbss
    ldr r1, =_ebss
zero_bss:
    cmp r0, r1
    bge bss_zeroed
    mov r2, #0
    str r2, [r0], #4
    b zero_bss
bss_zeroed:

    /* Call the application's entry point */
    bl main
    bx lr

.thumb_func
.weak NMI_Handler
NMI_Handler:
    b .
.thumb_func
.weak HardFault_Handler
HardFault_Handler:
    b .
.thumb_func
.weak MemManage_Handler
MemManage_Handler:
    b .
.thumb_func
.weak BusFault_Handler
BusFault_Handler:
    b .
.thumb_func
.weak UsageFault_Handler
UsageFault_Handler:
    b .
.thumb_func
.weak SVC_Handler
SVC_Handler:
    b .
.thumb_func
.weak DebugMon_Handler
DebugMon_Handler:
    b .
.thumb_func
.weak PendSV_Handler
PendSV_Handler:
    b .
.thumb_func
.weak SysTick_Handler
SysTick_Handler:
    b .

/* External Interrupt Handlers */
.thumb_func
.weak WWDG_IRQHandler
WWDG_IRQHandler:
    b .
.thumb_func
.weak PVD_IRQHandler
PVD_IRQHandler:
    b .
.thumb_func
.weak TAMPER_IRQHandler
TAMPER_IRQHandler:
    b .
.thumb_func
.weak RTC_IRQHandler
RTC_IRQHandler:
    b .
.thumb_func
.weak FLASH_IRQHandler
FLASH_IRQHandler:
    b .
.thumb_func
.weak RCC_IRQHandler
RCC_IRQHandler:
    b .
.thumb_func
.weak EXTI0_IRQHandler
EXTI0_IRQHandler:
    b .
.thumb_func
.weak EXTI1_IRQHandler
EXTI1_IRQHandler:
    b .
.thumb_func
.weak EXTI2_IRQHandler
EXTI2_IRQHandler:
    b .
.thumb_func
.weak EXTI3_IRQHandler
EXTI3_IRQHandler:
    b .
.thumb_func
.weak EXTI4_IRQHandler
EXTI4_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel1_IRQHandler
DMA1_Channel1_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel2_IRQHandler
DMA1_Channel2_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel3_IRQHandler
DMA1_Channel3_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel4_IRQHandler
DMA1_Channel4_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel5_IRQHandler
DMA1_Channel5_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel6_IRQHandler
DMA1_Channel6_IRQHandler:
    b .
.thumb_func
.weak DMA1_Channel7_IRQHandler
DMA1_Channel7_IRQHandler:
    b .
.thumb_func
.weak ADC1_2_IRQHandler
ADC1_2_IRQHandler:
    b .
.thumb_func
.weak EXTI9_5_IRQHandler
EXTI9_5_IRQHandler:
    b .
.thumb_func
.weak TIM1_BRK_TIM15_IRQHandler
TIM1_BRK_TIM15_IRQHandler:
    b .
.thumb_func
.weak TIM1_UP_TIM16_IRQHandler
TIM1_UP_TIM16_IRQHandler:
    b .
.thumb_func
.weak TIM1_TRG_COM_TIM17_IRQHandler
TIM1_TRG_COM_TIM17_IRQHandler:
    b .
.thumb_func
.weak TIM1_CC_IRQHandler
TIM1_CC_IRQHandler:
    b .
.thumb_func
.weak TIM2_IRQHandler
TIM2_IRQHandler:
    b .
.thumb_func
.weak TIM3_IRQHandler
TIM3_IRQHandler:
    b .
.thumb_func
.weak TIM4_IRQHandler
TIM4_IRQHandler:
    b .
.thumb_func
.weak I2C1_EV_IRQHandler
I2C1_EV_IRQHandler:
    b .
.thumb_func
.weak I2C1_ER_IRQHandler
I2C1_ER_IRQHandler:
    b .
.thumb_func
.weak I2C2_EV_IRQHandler
I2C2_EV_IRQHandler:
    b .
.thumb_func
.weak I2C2_ER_IRQHandler
I2C2_ER_IRQHandler:
    b .
.thumb_func
.weak SPI1_IRQHandler
SPI1_IRQHandler:
    b .
.thumb_func
.weak SPI2_IRQHandler
SPI2_IRQHandler:
    b .
.thumb_func
.weak USART1_IRQHandler
USART1_IRQHandler:
    b .
.thumb_func
.weak USART2_IRQHandler
USART2_IRQHandler:
    b .
.thumb_func
.weak USART3_IRQHandler
USART3_IRQHandler:
    b .
.thumb_func
.weak EXTI15_10_IRQHandler
EXTI15_10_IRQHandler:
    b .
.thumb_func
.weak RTCAlarm_IRQHandler
RTCAlarm_IRQHandler:
    b .
.thumb_func
.weak USBWakeUp_IRQHandler
USBWakeUp_IRQHandler:
    b .
.thumb_func
.weak TIM8_BRK_IRQHandler
TIM8_BRK_IRQHandler:
    b .
.thumb_func
.weak TIM8_UP_IRQHandler
TIM8_UP_IRQHandler:
    b .
.thumb_func
.weak TIM8_TRG_COM_IRQHandler
TIM8_TRG_COM_IRQHandler:
    b .
.thumb_func
.weak TIM8_CC_IRQHandler
TIM8_CC_IRQHandler:
    b .
.thumb_func
.weak ADC3_IRQHandler
ADC3_IRQHandler:
    b .
.thumb_func
.weak FSMC_IRQHandler
FSMC_IRQHandler:
    b .
.thumb_func
.weak SDIO_IRQHandler
SDIO_IRQHandler:
    b .
.thumb_func
.weak TIM5_IRQHandler
TIM5_IRQHandler:
    b .
.thumb_func
.weak SPI3_IRQHandler
SPI3_IRQHandler:
    b .
.thumb_func
.weak UART4_IRQHandler
UART4_IRQHandler:
    b .
.thumb_func
.weak UART5_IRQHandler
UART5_IRQHandler:
    b .
.thumb_func
.weak TIM6_IRQHandler
TIM6_IRQHandler:
    b .
.thumb_func
.weak TIM7_IRQHandler
TIM7_IRQHandler:
    b .
.thumb_func
.weak DMA2_Channel1_IRQHandler
DMA2_Channel1_IRQHandler:
    b .
.thumb_func
.weak DMA2_Channel2_IRQHandler
DMA2_Channel2_IRQHandler:
    b .
.thumb_func
.weak DMA2_Channel3_IRQHandler
DMA2_Channel3_IRQHandler:
    b .
.thumb_func
.weak DMA2_Channel4_5_IRQHandler
DMA2_Channel4_5_IRQHandler:
    b .

.size isr_vector, .-isr_vector

.equ _sdata,  __data_start__
.equ _edata,  __data_end__
.equ _sidata, __data_load__
.equ _sbss,   __bss_start__
.equ _ebss,   __bss_end__

.end
  • system.c (系统初始化文件):
#include <stdint.h>

// 定义寄存器地址 (根据你的STM32型号修改)
#define RCC_APB2ENR   ((volatile uint32_t *)0x40021018)
#define GPIOC_CRH     ((volatile uint32_t *)0x40011004)
#define GPIOC_ODR     ((volatile uint32_t *)0x4001100C)

void system_init() {
    // 1. 使能 GPIOC 时钟
    *RCC_APB2ENR |= (1 << 4);  // GPIOCEN = 1

    // 2. 配置 GPIOC Pin 13 为输出模式
    *GPIOC_CRH &= ~(0xF << 20); // 清除 GPIOC Pin 13 配置
    *GPIOC_CRH |= (0x1 << 20);  // 配置 GPIOC Pin 13 为通用推挽输出,最大速度 10MHz
}
  • main.c (主程序文件):
#include <stdint.h>

// 定义寄存器地址 (需要和 system.c 中的定义一致)
#define GPIOC_ODR     ((volatile uint32_t *)0x4001100C)

// 简单的延时函数 (裸机环境下没有现成的延时函数,需要自己实现)
void delay(uint32_t count) {
    for (uint32_t i = 0; i < count; i++) {
        asm volatile ("nop"); // 空指令,消耗时间
    }
}

extern void system_init(); // 声明 system_init 函数

int main() {
    system_init(); // 初始化系统

    while (1) {
        // LED 亮
        *GPIOC_ODR |= (1 << 13);  // 设置 GPIOC Pin 13 为高电平
        delay(100000);

        // LED 灭
        *GPIOC_ODR &= ~(1 << 13); // 设置 GPIOC Pin 13 为低电平
        delay(100000);
    }

    return 0;
}
  • linker.ld (链接脚本):
MEMORY
{
  FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 64K
  RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}

SECTIONS
{
  .isr_vector : {
    . = ALIGN(4);
    KEEP(*(.isr_vector))
    . = ALIGN(4);
  } >FLASH

  .text : {
    . = ALIGN(4);
    *(.text*)
    *(.rodata*)
    . = ALIGN(4);
  } >FLASH

  .data : {
    . = ALIGN(4);
    __data_start__ = .;
    *(.data*)
    . = ALIGN(4);
    __data_end__ = .;
  } >RAM AT >FLASH

  .bss : {
    . = ALIGN(4);
    __bss_start__ = .;
    *(.bss*)
    *(COMMON)
    . = ALIGN(4);
    __bss_end__ = .;
  } >RAM

  /* Stabs debugging sections.  */
  .stab 0 : { *(.stab) }
  .stabstr 0 : { *(.stabstr) }
  .symtab 0 : { *(.symtab) }
  .strtab 0 : { *(.strtab) }
  .debug 0 : { *(.debug) }

  /* DWARF debugging sections.
   * Symbols in the .debug sections are not relocated, so that using
   * -ffixed-address is supported.  */
  .debug_info 0 : { *(.debug_info .gnu.linkonce.wi.*) }
  .debug_abbrev 0 : { *(.debug_abbrev) }
  .debug_line 0 : { *(.debug_line) }
  .debug_frame 0 : { *(.debug_frame) }
  .debug_str 0 : { *(.debug_str) }
  .debug_loc 0 : { *(.debug_loc) }
  .debug_macinfo 0 : { *(.debug_macinfo) }

  /* Section information for GNU libgcc */
  .ARM.exidx : { *(.ARM.exidx*) } >FLASH
  .ARM.extab : { *(.ARM.extab*) } >FLASH

  __data_load__ = LOADADDR(.data);

  . = ALIGN(4);
  _end = .;
}
  • Makefile (自动化编译和烧录脚本):
# 项目名称
PROJECT = blink

# 编译器
CC = arm-none-eabi-gcc
OBJCOPY = arm-none-eabi-objcopy
OBJDUMP = arm-none-eabi-objdump

# 编译选项
CFLAGS = -g -O0 -Wall -mthumb -mcpu=cortex-m3 -I.
LDFLAGS = -T linker.ld -mthumb -mcpu=cortex-m3 -specs=nosys.specs

# 源文件
SRCS =  startup.s system.c main.c

# 目标文件
OBJS = $(SRCS:.c=.o)

# 生成的可执行文件
TARGET = $(PROJECT).elf
BIN = $(PROJECT).bin
HEX = $(PROJECT).hex

all: $(BIN)

$(TARGET): $(OBJS)
    $(CC) $(LDFLAGS) $(OBJS) -o $@

%.o: %.c
    $(CC) $(CFLAGS) -c $< -o $@

%.o: %.s
    $(CC) $(CFLAGS) -c $< -o $@

$(BIN): $(TARGET)
    $(OBJCOPY) -O binary $< $@

$(HEX): $(TARGET)
    $(OBJCOPY) -O ihex $< $@

flash: $(BIN)
    openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg -c "program $(BIN) 0x08000000 verify reset exit"

clean:
    rm -f $(OBJS) $(TARGET) $(BIN) $(HEX)

5. 编译和烧录:

  1. 打开终端,进入工程目录。
  2. 运行 make 命令编译代码。
  3. 运行 make flash 命令将代码烧录到 STM32 开发板。 (需要确保 OpenOCD 已经正确安装和配置)

如果一切顺利,你就会看到STM32开发板上的LED开始闪烁了!

代码解释:

  • startup.s: 这个文件是程序的入口,它定义了中断向量表,并初始化了堆栈。Reset_Handler 函数是复位中断的处理函数,它负责将数据从 Flash 拷贝到 RAM,清空 BSS 段,并最终调用 main 函数。
  • system.c: 这个文件负责初始化系统,比如使能 GPIO 时钟,配置 GPIO 引脚的模式。这里我们将 GPIOC 的 13 号引脚配置为输出模式,用来控制 LED。
  • main.c: 这个文件是主程序,它包含了 main 函数,也就是程序的入口点。在 main 函数中,我们调用 system_init 函数初始化系统,然后在无限循环中,不断地设置和清除 GPIOC 的 13 号引脚,从而控制 LED 的亮灭。
  • linker.ld: 这个文件告诉链接器如何将代码和数据放置到 Flash 和 RAM 中。
  • Makefile: 这个文件定义了编译和烧录的规则,可以简化编译和烧录的过程。

一些关键点:

  • 寄存器地址: 你需要根据你的 STM32 型号,修改 system.cmain.c 中的寄存器地址。不同型号的 STM32,寄存器地址可能不同。
  • 延时函数: 裸机环境下没有现成的延时函数,你需要自己实现。这里的 delay 函数只是一个简单的空循环,精度不高,但在简单应用中可以使用。
  • OpenOCD 配置: make flash 命令需要 OpenOCD 的支持,你需要确保 OpenOCD 已经正确安装和配置,并且 interface/stlink-v2.cfgtarget/stm32f1x.cfg 文件存在且配置正确。
  • 错误处理: 裸机环境下没有操作系统的错误处理机制,你需要自己处理错误。比如,如果 GPIO 初始化失败,你需要采取相应的措施,防止程序崩溃。

裸机编程的进阶之路

当你成功点亮了第一个LED,就迈出了裸机编程的第一步。接下来,你可以尝试:

  • 学习更多硬件外设: 比如UART、SPI、I2C、ADC等等。
  • 编写更复杂的中断处理函数: 比如定时器中断、串口中断等等。
  • 实现自己的驱动程序: 比如LCD驱动、触摸屏驱动等等。
  • 学习RTOS (Real-Time Operating System): 虽然是裸机编程,但也可以使用轻量级的RTOS来管理任务和资源。
  • 参与开源项目: 学习别人的代码,贡献自己的代码。

裸机编程的坑

裸机编程虽然自由,但也充满了“坑”:

  • 硬件知识不足: 不了解硬件原理,寸步难行。
  • 调试困难: 没有操作系统的支持,调试起来非常痛苦。
  • 代码复杂: 所有事情都要自己做,代码量会很大。
  • 兼容性问题: 代码只能在特定的硬件平台上运行,移植性差。

一些建议:

  • 多看数据手册: 数据手册是硬件的“圣经”,一定要仔细阅读。
  • 善用调试工具: 调试工具可以帮助你了解程序的运行状态,快速定位问题。
  • 多做实验: 实践是最好的老师,多做实验才能真正掌握裸机编程。
  • 加入社区: 和其他裸机编程爱好者交流经验,共同进步。

总结

裸机编程是一项充满挑战但也非常有意思的技术。它可以让你深入了解硬件的底层细节,并构建高性能、低功耗的嵌入式系统。希望今天的讲座能帮助你入门裸机编程,开启你的硬件探索之旅!

一些常用的寄存器(以STM32为例,需要根据具体的芯片型号进行调整):

寄存器名称 地址 描述
RCC_CR 0x40021000 RCC时钟控制寄存器
RCC_CFGR 0x40021004 RCC时钟配置寄存器
RCC_APB2ENR 0x40021018 RCC APB2外设时钟使能寄存器
GPIOx_CRL 0x40010800 + (0x400 * x) GPIOx端口配置低位寄存器 (x = A, B, C, …)
GPIOx_CRH 0x40010804 + (0x400 * x) GPIOx端口配置高位寄存器 (x = A, B, C, …)
GPIOx_IDR 0x40010808 + (0x400 * x) GPIOx端口输入数据寄存器 (x = A, B, C, …)
GPIOx_ODR 0x4001080C + (0x400 * x) GPIOx端口输出数据寄存器 (x = A, B, C, …)
GPIOx_BSRR 0x40010810 + (0x400 * x) GPIOx端口位设置/清除寄存器 (x = A, B, C, …)
USARTx_SR 0x40004400 + (0x400 * x) USARTx状态寄存器 (x = 1, 2, 3, …)
USARTx_DR 0x40004404 + (0x400 * x) USARTx数据寄存器 (x = 1, 2, 3, …)
USARTx_BRR 0x40004408 + (0x400 * x) USARTx波特率寄存器 (x = 1, 2, 3, …)
USARTx_CR1 0x4000440C + (0x400 * x) USARTx控制寄存器1 (x = 1, 2, 3, …)
TIMx_CR1 0x40000000 + (0x400 * x) TIMx控制寄存器1 (x = 1, 2, 3, …)
TIMx_PSC 0x40000010 + (0x400 * x) TIMx预分频器寄存器 (x = 1, 2, 3, …)
TIMx_ARR 0x4000002C + (0x400 * x) TIMx自动重载寄存器 (x = 1, 2, 3, …)
TIMx_CCR1 0x40000034 + (0x400 * x) TIMx捕获/比较寄存器1 (x = 1, 2, 3, …)

注意: 上面的地址和描述是通用的,但具体的芯片可能有所不同。请务必参考芯片的数据手册。 x 代表不同的外设编号,例如 GPIOAGPIOBUSART1TIM2 等。

祝大家玩得开心!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注