【我的stm32开发之路-实践篇-嵌入式的hello-world】原创

我的stm32开发之路-实践篇-嵌入式的hello-world

前言

俗话说,万事开头难,可能当一个想接触嵌入式的开发者实现了第一个"hello-world"时才是真正的接触到入门的门槛。本文的目标就是跨过这个开头难,配置一个基于命令行的构建系统(使用GCC ARM工具链)和openocd来编译、烧录和调试一个LED点亮的项目。

本文改造了野火开发板配套的资料(请自行到 野火官方网站 下载STM32F103ZE_霸道开发板的《野火【STM32F103开发板-霸道】资料》)的点亮LED的项目配置。本文最大特点是真正从0开始指导新手开发者不使用Keil MDK将开发板上的LED灯点亮。因为嵌入式开发都是针对特定的物理设备,如果你的硬件设备与本人不同,且也想只使用命令行进行开发,也是可以参考本文中涉及的方法的,相信会有一定的帮助。

下面是本文的开发配置:

  1. 硬件: 野火stm32f103霸道v2开发板 + fileDAP(CMSIS-DAP设备)
  2. 操作系统: ubuntu24.04
  3. 初始工具: bash + vim
  4. 基础项目: 野火【STM32F103开发板-霸道】资料/0-开机例程源码/开机测试例程_20211130/开机测试例程/GPIO输出—使用固件库点亮LED灯/12-GPIO输出—使用固件库点亮LED灯

预备知识

术语

以下的术语有的并不会在此文中涉及,可后面自行了解。
ARM=Advanced RISC Machines
gcc=GNU compiler collectiono
gdb=GNU debugger
eabi=Embedded Application Binary Interface
openocd=Open On-Chip Debugger
CMSIS=Cortex Microcontroller Software Interface Standard CMSIS是ARM公司制定的统一软件开发框架
DAP=Debug Access Port 是硬件调试接口的具体实现,通过USB提供标准化的调试通道,支持SWD和JTAG两种协议
NXP=Next Experience
SWD=Serial Wire Debug
JTAG=Joint Test Action Group
SWDIO=Serial Wire Data Input/Output 是数据线
SWCLK=Serial Wire Clock 是时钟线
tcl=tool command language
srst=Synchronous Reset
arst=Asynchronous Reset
TCK=Test Clock
TMS=Test Mode Select
TDI=Test Data In
TDO=Test Data Out
DP=Debug Port ARM架构的调试接口(如SWD接口)的根寄存器组,包含控制、状态和识别寄存器
IDR=Identification Register 位于DP中的寄存器,用于读取目标设备的调试接口标识(如厂商、版本号等)
BSP=Board Support Package

关键概念对比

术语物理位置功能描述示例
Debugger开发PC上调试控制软件OpenOCD/GDB
ProbeUSB调试硬件协议转换器CMSIS-DAP/J-Link
On-Chip目标处理器内部芯片级调试硬件单元ARM CoreSight, JTAG TAP

开发环境搭建

安装工具链

# ARM交叉编译工具链及openocd
sudo apt install gcc-arm-none-eabi binutils-arm-none-eabi gdb-multiarch openocd

物理连接(应有CMSIS-DAP设备)及检查DAP识别

DAP使用排线连接开发板,DAP使用usb连接PC,连接PC后DAP的绿灯会常亮。

通过在PC上敲入如下命令检查设备:

lsusb | grep -i "CMSIS"

从上面的输出中可获得由冒号分开的vid:pid,即vendor id和product id,这里先记录在纸上,后面会用到。


项目组织

目录结构

目录结构请根据我如下展示的目录结构自行调整,注意注释说明,会涉及将原目录下的一些文件进行替换。注意后面的注释,非常重要。

├── Libraries # 此目录中的文件应全部在STM32的官方标准库STM32F10x_StdPeriph_Lib_V3.5.0中寻找并放到对应的路径下。
│   ├── CMSIS # 核心,提供系统级支持,具有通用性。STM32 开发的核心部分
│   │   ├── core_cm3.c # ARM Cortex-M3 内核的核心支持文件。注意此文件版本在项目中有处源码bug需修改,即__STREXB和__STREXH函数里面的"=r"要改成"=&r",否则make时会报错Error:registers may not be the same--`strexbr0,r0,[r1]'
│   │   ├── core_cm3.h
│   │   ├── startup # 包含针对不同STM32F10x型号的启动文件,注意我们的项目使用官方的startup/gcc_ride7目录而不是startup/arm目录,否则make时会报错Error: junk at end of line, first unrecognized character is `*‘等
│   │   │   ├── startup_stm32f10x_cl.s
│   │   │   ├── startup_stm32f10x_hd.s # 本应用使用此汇编代码文件来启动芯片
│   │   │   ├── startup_stm32f10x_hd_vl.s
│   │   │   ├── startup_stm32f10x_ld.s
│   │   │   ├── startup_stm32f10x_ld_vl.s
│   │   │   ├── startup_stm32f10x_md.s
│   │   │   ├── startup_stm32f10x_md_vl.s
│   │   │   └── startup_stm32f10x_xl.s
│   │   ├── stm32f10x.h # STM32F10x 系列微控制器的主头文件
│   │   ├── system_stm32f10x.c # 系统初始化文件,SystemInit()函数在此
│   │   └── system_stm32f10x.h
│   └── FWlib # 驱动,用于外设访问,具有通用性。STM32 标准外设库(Standard Peripheral Library)的核心部分
│       ├── inc
│       │   ├── misc.h
│       │   ├── stm32f10x_adc.h
│       │   ├── stm32f10x_bkp.h
│       │   ├── stm32f10x_can.h
│       │   ├── stm32f10x_cec.h
│       │   ├── stm32f10x_crc.h
│       │   ├── stm32f10x_dac.h
│       │   ├── stm32f10x_dbgmcu.h
│       │   ├── stm32f10x_dma.h
│       │   ├── stm32f10x_exti.h
│       │   ├── stm32f10x_flash.h
│       │   ├── stm32f10x_fsmc.h
│       │   ├── stm32f10x_gpio.h
│       │   ├── stm32f10x_i2c.h
│       │   ├── stm32f10x_iwdg.h
│       │   ├── stm32f10x_pwr.h
│       │   ├── stm32f10x_rcc.h
│       │   ├── stm32f10x_rtc.h
│       │   ├── stm32f10x_sdio.h
│       │   ├── stm32f10x_spi.h
│       │   ├── stm32f10x_tim.h
│       │   ├── stm32f10x_usart.h
│       │   └── stm32f10x_wwdg.h
│       └── src
│           ├── misc.c
│           ├── stm32f10x_adc.c
│           ├── stm32f10x_bkp.c
│           ├── stm32f10x_can.c
│           ├── stm32f10x_cec.c
│           ├── stm32f10x_crc.c
│           ├── stm32f10x_dac.c
│           ├── stm32f10x_dbgmcu.c
│           ├── stm32f10x_dma.c
│           ├── stm32f10x_exti.c
│           ├── stm32f10x_flash.c
│           ├── stm32f10x_fsmc.c
│           ├── stm32f10x_gpio.c
│           ├── stm32f10x_i2c.c
│           ├── stm32f10x_iwdg.c
│           ├── stm32f10x_pwr.c
│           ├── stm32f10x_rcc.c
│           ├── stm32f10x_rtc.c
│           ├── stm32f10x_sdio.c
│           ├── stm32f10x_spi.c
│           ├── stm32f10x_tim.c
│           ├── stm32f10x_usart.c
│           └── stm32f10x_wwdg.c
├── Makefile # 构建系统的核心配置文件
├── openocd.cfg # OpenOCD 调试工具的配置文件,调试器配置
├── stm32_flash.ld # 链接器脚本文件,内存布局定义,芯片特定,确保程序正确加载到微控制器的存储器中
├── README.md
└── User # 应用,项目特定
    ├── led # BSP的LED驱动
    │   ├── bsp_led.c # LED初始化、控制函数实现
    │   └── bsp_led.h # LED控制函数声明及宏定义
    ├── main.c
    ├── stm32f10x_conf.h # 外设库配置文件,来自官方STM32F10x_StdPeriph_Lib_V3.5.0/Project/STM32F10x_StdPeriph_Template
    ├── stm32f10x_it.c # 中断服务程序(Interrupt Service Routines),来自官方STM32F10x_StdPeriph_Lib_V3.5.0/Project/STM32F10x_StdPeriph_Template
    └── stm32f10x_it.h

配置OpenOCD支持DAP仿真器

下面为openocd.cfg的配置

# 配置调试探头(Debugger硬件接口),文件位置/usr/share/openocd/scripts/interface/cmsis-dap.cfg,可自行查看
source [find interface/cmsis-dap.cfg]

# 选择调试协议为SWD
transport select swd

# 指定调试器的USB VID/PID,
# 替换为 CMSIS-DAP 实际ID,使用lsusb根据实际情况配置,0x1fc9是NXP的VID
cmsis_dap_vid_pid 0x1fc9 0x5601

# 设置调试时钟速度,单位kHz
adapter speed 1000

# 配置On-Chip调试模块(目标芯片),文件位置/usr/share/openocd/scripts/target/stm32f1x.cfg,可自行查看
source [find target/stm32f1x.cfg]

# 复位配置,禁用硬件复位,使用软件复位
reset_config none

# 初始化调试会话
init

创建Makefile

# 目标名称
TARGET = BH-F103

# 工具链配置
CC = arm-none-eabi-gcc
AS = arm-none-eabi-as
LD = arm-none-eabi-ld
OBJCOPY = arm-none-eabi-objcopy
SIZE = arm-none-eabi-size

# MCU配置
MCU = -mcpu=cortex-m3 -mthumb
DEFS = -DSTM32F10X_HD -DUSE_STDPERIPH_DRIVER

# 包含路径
INCLUDES = -ILibraries/CMSIS \
           -IUser \
           -ILibraries/FWlib/inc \
           -IUser/led

# 编译选项
CFLAGS = $(MCU) $(DEFS) $(INCLUDES) \
         -Os -Wall -fdata-sections -ffunction-sections

# 链接脚本(创建新的链接脚本)
LDSCRIPT = stm32_flash.ld
LDFLAGS = $(MCU) -T$(LDSCRIPT) -Wl,--gc-sections \
          -Wl,-Map=$(TARGET).map -specs=nano.specs

# 源文件
SRC_C = $(wildcard Libraries/CMSIS/*.c) \
        $(wildcard Libraries/FWlib/src/*.c) \
        User/main.c \
        User/stm32f10x_it.c \
        User/led/bsp_led.c
        
SRC_S = Libraries/CMSIS/startup/startup_stm32f10x_hd.s

# 构建目录
BUILD_DIR = build
OBJS = $(addprefix $(BUILD_DIR)/,$(notdir $(SRC_C:.c=.o))) \
       $(addprefix $(BUILD_DIR)/,$(notdir $(SRC_S:.s=.o)))

vpath %.c $(sort $(dir $(SRC_C)))
vpath %.s $(sort $(dir $(SRC_S)))

all: $(BUILD_DIR)/$(TARGET).elf

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

$(BUILD_DIR)/%.o: %.s | $(BUILD_DIR)
	$(AS) $(MCU) $< -o $@

$(BUILD_DIR)/$(TARGET).elf: $(OBJS)
	$(CC) $(LDFLAGS) $^ -o $@
	$(SIZE) $@

$(BUILD_DIR):
	mkdir -p $@

flash: $(BUILD_DIR)/$(TARGET).elf
	openocd -f openocd.cfg -c "program $< verify reset exit"

clean:
	rm -rf $(BUILD_DIR)

创建链接脚本stm32_flash.ld

MEMORY
{
    FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
    RAM (xrw)  : ORIGIN = 0x20000000, LENGTH = 64K
}

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

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

    _sidata = .;
    .data : AT(_sidata)
    {
        . = ALIGN(4);
        _sdata = .;
        *(.data*)
        . = ALIGN(4);
        _edata = .;
    } >RAM

    .bss :
    {
        . = ALIGN(4);
        _sbss = .;
        *(.bss*)
        *(COMMON)
        . = ALIGN(4);
        _ebss = .;
    } >RAM

    _estack = ORIGIN(RAM) + LENGTH(RAM);
}

其他重要代码

Libraries中的代码和User中的部分代码全是官方提供的,不需要我们自行编写,但User目录下中此应用重要的三个文件代码还是有必要放在这里展示的。

main.c文件
// main.c

#include "stm32f10x.h"
#include "bsp_led.h"

#define SOFT_DELAY Delay(0x0FFFFF);

void Delay(__IO u32 nCount); 

int main(void)
{	
	LED_GPIO_Config();	 

	while (1)
	{
		LED1_ON;
		SOFT_DELAY;
		LED1_OFF;

		LED2_ON;
		SOFT_DELAY;
		LED2_OFF;

		LED3_ON;
		SOFT_DELAY;
		LED3_OFF;

		LED_RED;
		SOFT_DELAY;
		
		LED_GREEN;
		SOFT_DELAY;
		
		LED_BLUE;
		SOFT_DELAY;
		
		LED_YELLOW;
		SOFT_DELAY;
		
		LED_PURPLE;
		SOFT_DELAY;
				
		LED_CYAN;
		SOFT_DELAY;
		
		LED_WHITE;
		SOFT_DELAY;
		
		LED_RGBOFF;
		SOFT_DELAY;		
	}
}

void Delay(__IO uint32_t nCount)
{
	for(; nCount != 0; nCount--);
}
led/bsp_led.h文件
// led/bsp_led.h

#ifndef __LED_H
#define	__LED_H

#include "stm32f10x.h"

#define LED1_GPIO_PORT    	GPIOB
#define LED1_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define LED1_GPIO_PIN		GPIO_Pin_5

#define LED2_GPIO_PORT    	GPIOB
#define LED2_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define LED2_GPIO_PIN		GPIO_Pin_0

#define LED3_GPIO_PORT    	GPIOB
#define LED3_GPIO_CLK 	    RCC_APB2Periph_GPIOB
#define LED3_GPIO_PIN		GPIO_Pin_1

#define ON  0
#define OFF 1

#define LED1(a)	if (a)	\
					GPIO_SetBits(LED1_GPIO_PORT,LED1_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2(a)	if (a)	\
					GPIO_SetBits(LED2_GPIO_PORT,LED2_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3(a)	if (a)	\
					GPIO_SetBits(LED3_GPIO_PORT,LED3_GPIO_PIN);\
					else		\
					GPIO_ResetBits(LED3_GPIO_PORT,LED3_GPIO_PIN)


#define	digitalHi(p,i)		 {p->BSRR=i;}
#define digitalLo(p,i)		 {p->BRR=i;}
#define digitalToggle(p,i) {p->ODR ^=i;} 


#define LED1_TOGGLE		 digitalToggle(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_OFF		   digitalHi(LED1_GPIO_PORT,LED1_GPIO_PIN)
#define LED1_ON			   digitalLo(LED1_GPIO_PORT,LED1_GPIO_PIN)

#define LED2_TOGGLE		 digitalToggle(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_OFF		   digitalHi(LED2_GPIO_PORT,LED2_GPIO_PIN)
#define LED2_ON			   digitalLo(LED2_GPIO_PORT,LED2_GPIO_PIN)

#define LED3_TOGGLE		 digitalToggle(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_OFF		   digitalHi(LED3_GPIO_PORT,LED3_GPIO_PIN)
#define LED3_ON			   digitalLo(LED3_GPIO_PORT,LED3_GPIO_PIN)


#define LED_RED  \
					LED1_ON;\
					LED2_OFF\
					LED3_OFF

#define LED_GREEN		\
					LED1_OFF;\
					LED2_ON\
					LED3_OFF

#define LED_BLUE	\
					LED1_OFF;\
					LED2_OFF\
					LED3_ON

					
#define LED_YELLOW	\
					LED1_ON;\
					LED2_ON\
					LED3_OFF
#define LED_PURPLE	\
					LED1_ON;\
					LED2_OFF\
					LED3_ON

#define LED_CYAN \
					LED1_OFF;\
					LED2_ON\
					LED3_ON
					
#define LED_WHITE	\
					LED1_ON;\
					LED2_ON\
					LED3_ON
					
#define LED_RGBOFF	\
					LED1_OFF;\
					LED2_OFF\
					LED3_OFF

void LED_GPIO_Config(void);

#endif /* __LED_H */
led/bsp_led.c文件
// led/bsp_led.c

#include "bsp_led.h"   

void LED_GPIO_Config(void)
{		
		GPIO_InitTypeDef GPIO_InitStructure;

		RCC_APB2PeriphClockCmd( LED1_GPIO_CLK | LED2_GPIO_CLK | LED3_GPIO_CLK, ENABLE);
		GPIO_InitStructure.GPIO_Pin = LED1_GPIO_PIN;	
		GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   
		GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; 
		GPIO_Init(LED1_GPIO_PORT, &GPIO_InitStructure);	
		GPIO_InitStructure.GPIO_Pin = LED2_GPIO_PIN;
		GPIO_Init(LED2_GPIO_PORT, &GPIO_InitStructure);
		GPIO_InitStructure.GPIO_Pin = LED3_GPIO_PIN;
		GPIO_Init(LED3_GPIO_PORT, &GPIO_InitStructure);
		GPIO_SetBits(LED1_GPIO_PORT, LED1_GPIO_PIN);
		GPIO_SetBits(LED2_GPIO_PORT, LED2_GPIO_PIN);	 
		GPIO_SetBits(LED3_GPIO_PORT, LED3_GPIO_PIN);
}

编译及烧录

编译程序

make后会生成一个elf文件

make clean
make

在一个单独的终端启动OpenOCD以烧录及调试

openocd -f openocd.cfg

烧录到flash

可以通过telnet或gdb两种方法国进行烧录

两种方法的对比
特性TelnetGDB
适用场景快速烧录、简单调试(无需源码级调试)。源码级调试(设置断点、单步执行、查看变量等)。
操作复杂度简单,直接输入命令(如 flash write_image)。稍复杂,需通过 GDB 客户端连接并加载固件。
灵活性仅支持 OpenOCD 提供的命令(如擦除、烧录、复位)。支持 GDB 调试命令 + OpenOCD 的 Telnet 命令(通过 monitor 执行)。
是否需要固件文件需要 .bin.elf 文件。需要 .elf 文件(包含调试信息)。
是否需要调试器支持是(需配置调试器接口,如 ST-Link、J-Link)。是(需调试器支持 GDB 协议)。

本文选择gdb的方法进行烧录

新开一个终端使用GDB烧录
  1. 启动GDB并连接openocd:
gdb-multiarch build/BH-F103.elf
  1. 烧录固件,在GDB中执行:
target extended-remote :3333 # 连接到openocd的gdb服务
monitor reset halt # 复位并暂停目标设备
load # 加载.elf文件到目标设备,load命令会自动erase Flash并烧录.elf文件
monitor reset run # 这一步执行后LED灯就会点亮
detach
quit

若需手动控制烧录地址,可使用flash write_image(通过monitor命令)

总结

本文详细介绍了如何只使用命令行来点亮一个LED灯,可供感兴趣的朋友参考。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值