先没管天空星那块开发板,这几天的精力主要放在了地阔星上。
不知不觉过去了这么多天了,终于把linux下的开发环境搞好了。
按照官网的方法的确是很快就完成了第一个试验(这里也许会有人跟我说应该用“实验”而不是“试验”,对也许对你来说仅仅是实践别人的程序,而对我来说却是试着去理解这个程序的过程,所以你去用你的“实验”就行,我是我,你是你),就是让开发板上的那个灯亮起来。
但对我来说这仅仅是半步,因为我日常都是工作在linux下的,windows仅仅跑在虚拟机里,我不想以后都依赖于虚拟机,所以我必须想办法把整个开发环境部署在linux上。
然而,简体中文圈里一说嵌入式就离不开keil的那个集成式开发环境(IDE),虽然那是arm出的软件,对于开发arm处理器的程序而言无疑它是很好用的,但是那货却只有windows版(这么说也不太公平,因为它有一个vscode的扩展,还有一个cloud版),但我却是希望用我习惯的编辑器vim来编辑的,所以那些我都用不上。
我在搜索时发现了意法半导体(ST)出了一系列的软件:
STM32CubeProgrammer 是把编译好的程序写进单片机的,支持 ST-Link、 J-Link/Flasher、UART (这个就是我用的usb转ttl模块要使用的模式)、USB 四种模式。
STM32CubeMX 是在一开始的创建整个代码工程的,它能导出成 Makefile 工程,这个工程就可以在linux上用make命令直接编译。这里要说明的是:它导出的是使用HAL库的,这个库对于商业开发来说可能很快捷,但对于像我这样的初学者来说就屏蔽掉了所有的学习底层的机会~~ 所以我现在还用不上它。
STM32CubeIDE 这也是一个集成开发环境,类似于keil的那个,可以使用STM32CubeMX专门为其导出的工程项目。
这貌似是解决了整个链条,但并不完全适用于我的情况。
对于我来说只解决了把编译好的程序写进单片机的过程,写程序和编译的过程还没解决。
编译器要使用 arm-none-eabi-gcc 我所使用的操作系统 archlinux 上可以直接安装,它在 extra 库里,所以运行
就能安装好了,然后运行
就能看到如下的输出
Code: Select all
extra/arm-none-eabi-gcc 14.2.0-1 (237.8 MiB 1.7 GiB) (Installed)
The GNU Compiler Collection - cross compiler for ARM EABI (bare-metal) target
或者
Code: Select all
extra/arm-none-eabi-gcc 14.2.0-1 [installed]
The GNU Compiler Collection - cross compiler for ARM EABI (bare-metal)
target
大体的使用方式是
Code: Select all
arm-none-eabi-gcc -c main.c -o main.o
我现在用的是地阔星的开发板,LED接到了 PC13 上

-->完整版的原理图可以通过下面的链接查看
https://pro.lceda.cn/editor#id=192ba5bf ... f633b59b3e
这个网址需要
打开后放一会儿,才能显示地阔星的原理图。不要问我为什么,去问嘉立创。
所以要让开发板上的LED亮起来的话就得让 PC13 输出高电平,并且还得维持住。
于是我写了以下的 main.c
Code: Select all
/*
* author timeline.menu@gmail.com
* changelog
* UTC+0800 2025-02-27 20:11:25 021150737 058 09 4
* Create this file.
*
*/
typedef unsigned int uint32_t;
typedef unsigned short uint16_t;
#define PERIPH_BASE ((uint32_t)0x40000000) /*!< Peripheral base address in the alias region */
#define AHBPERIPH_BASE (PERIPH_BASE + 0x20000)
#define APB2PERIPH_BASE (PERIPH_BASE + 0x10000)
// /*RCC外设基地址*/
#define RCC_BASE (AHBPERIPH_BASE + 0x1000)
/*RCC的AHB1时钟使能寄存器地址,强制转换成指针*/
#define RCC_APB2ENR *(unsigned int*)(RCC_BASE+0x18)
// GPIOC 基地址
#define GPIOC_BASE (APB2PERIPH_BASE + 0x1000)
/* GPIOC寄存器地址,强制转换成指针 */
#define GPIOC_CRL *(unsigned int*)(GPIOC_BASE+0x00)
#define GPIOC_CRH *(unsigned int*)(GPIOC_BASE+0x04)
#define GPIOC_IDR *(unsigned int*)(GPIOC_BASE+0x08)
#define GPIOC_ODR *(unsigned int*)(GPIOC_BASE+0x0C)
#define GPIOC_BSRR *(unsigned int*)(GPIOC_BASE+0x10)
#define GPIOC_BRR *(unsigned int*)(GPIOC_BASE+0x14)
#define GPIOC_LCKR *(unsigned int*)(GPIOC_BASE+0x18)
/*
* 延时函数
*/
void delay ( volatile uint32_t count )
{
while ( count -- )
{
}
}
int main ( void )
{
// 开启GPIO的端口时钟
// 开启GPIOC时钟
RCC_APB2ENR |= ( 0x01 << 4 ) ;
// -- 配置GPIO的模式 -- begin --
// 13端口的CNF位,置0
GPIOC_CRH &= ~( 0x03 << 22 ) ;
// 3的二进制为11再取反为00
// 查文档得知 13端口的CNF为 22 23 两个数据位
// 13端口的MODE位,置0
GPIOC_CRH &= ~( 0x03 << 20 ) ;
// 查文档得知 13端口的MODE为 20 21 两个数据位
// 设置为通用推挽输出模式
//00:通用推挽输出模式
//01:通用开漏输出模式
//10:复用功能推挽输出模式
//11:复用功能开漏输出模式
GPIOC_CRH |= ( 0x00 << 22 ) ;
// 设置为 50MHz
//00:输入模式(复位后的状态)
//01:输出模式,最大速度10MHz
//10:输出模式,最大速度2MHz
//11:输出模式,最大速度50MHz
GPIOC_CRH |= ( 0x03 << 20 ) ;
// -- 配置GPIO的模式 -- end --
while(1)
{
// PC13输出高电平
GPIOC_ODR |= ( 0x01 << 13 ) ;
delay(1000000); // 延时
// PC13输出低电平
GPIOC_ODR &= ~( 0x01 << 13 ) ;
delay(1000000); // 延时
delay(1000000); // 延时
}
return 0 ;
}
单片机不同于纯软件的编程,还需要两个文件才能为实体硬件生成可执行的程序 一个启动文件和一个链接文件。
启动文件是一个汇编文件,stm32芯片启动流程如下:
1、用代码段的第1个32位数初始化堆栈指针SP
2、用代码段的第2个32位数初始化程序指针PC(也就是程序跳转到第2个32位数的数值指向的地址)
我们需要定义一个g_pfnVectors向量表,向量表中的 第一个值为SP初始化值, 第二个值为复位Reset_Handler函数地址。
在复位函数Reset_Handler中跳转到了mian函数。
为此我们可以这样写这个汇编文件,比如我们命名为 startup_stm32f10x_md.s
Code: Select all
/*
* author timeline.menu@gmail.com
* changelog
* UTC+0800 2025-02-27 21:34:28 530777452 058 09 4
* Create this file.
*
*/
.syntax unified
.cpu cortex-m3
.fpu softvfp
.thumb
/******************************************************************************
复位启动函数Reset_Handler 执行跳转到main函数
******************************************************************************/
.section .text.Reset_Handler
.weak Reset_Handler
.type Reset_Handler, %function
Reset_Handler:
bl main
bx lr
.size Reset_Handler, .-Reset_Handler
/******************************************************************************
向量表 第一个值为SP 第二个值为复位地址
******************************************************************************/
.global g_pfnVectors
.section .isr_vector,"a",%progbits
.type g_pfnVectors, %object
.size g_pfnVectors, .-g_pfnVectors
g_pfnVectors:
.word _estack
.word Reset_Handler
再来就是链接文件了,它定义了代码应该下载到stm32f103rb芯片的什么位置。
链接可以将多个目标代码整合成最终的可执行程序,在链接过程中有个重要的步骤就是给程序指定一个起始位置。
因此我们编写一个简单的链接文件:比如我们命名为 stm32_flash.ld
Code: Select all
/*
* author timeline.menu@gmail.com
* changelog
* UTC+0800 2025-02-27 21:38:35 969741863 058 09 4
* Create this file.
*
*/
/* 定义flash 和 ram 区域 */
MEMORY
{
flash (rx) : ORIGIN = 0x08000000, LENGTH = 64K
ram (rwx) : ORIGIN = 0x20000000, LENGTH = 20K
}
ENTRY(Reset_Handler)
_heap_size = 0; /* required amount of heap */
_stack_size = 0; /* required amount of stack */
/* The stack starts at the end of RAM and grows downwards. Full-descending*/
_estack = ORIGIN(ram) + LENGTH(ram);
SECTIONS
{
/* Reset and ISR vectors */
.isr_vector :
{
__isr_vector_start__ = .;
KEEP(*(.isr_vector)) /* without 'KEEP' the garbage collector discards this section */
ASSERT(. != __isr_vector_start__, "The .isr_vector section is empty");
} >flash
/* Text section (code and read-only data) */
.text :
{
. = ALIGN(4);
_stext = .;
*(.text*) /* code */
*(.rodata*) /* read only data */
/*
* NOTE: .glue_7 and .glue_7t sections are not needed because Cortex-M
* only supports Thumb instructions, no ARM/Thumb interworking.
*/
/* Static constructors and destructors */
KEEP(*(.init))
KEEP(*(.fini))
. = ALIGN(4);
_etext = .;
} >flash
/*
* Initialized data section. This section is programmed into FLASH (LMA
* address) and copied to RAM (VMA address) in startup code.
*/
_sidata = .;
.data : AT(_sidata) /* LMA address is _sidata (in FLASH) */
{
. = ALIGN(4);
_sdata = .; /* data section VMA address */
*(.data*)
. = ALIGN(4);
_edata = .;
} >ram
/* Uninitialized data section (zeroed out by startup code) */
.bss :
{
. = ALIGN(4);
_sbss = .;
*(.bss*)
*(COMMON)
. = ALIGN(4);
_ebss = .;
} >ram
/*
* Reserve memory for heap and stack. The linker will issue an error if
* there is not enough memory.
*/
._heap :
{
. = ALIGN(4);
. = . + _heap_size;
. = ALIGN(4);
} >ram
._stack :
{
. = ALIGN(4);
. = . + _stack_size;
. = ALIGN(4);
} >ram
}
/* Nice to have */
__isr_vector_size__ = SIZEOF(.isr_vector);
__text_size__ = SIZEOF(.text);
__data_size__ = SIZEOF(.data);
__bss_size__ = SIZEOF(.bss);
基础的代码都有了,下面就是编译规则了,当然写成shell也行,但大多数公司都喜欢用 makefile ,它的方便性就是可以一个make命令就解决(貌似这点shell也可以办到~~,但makefile就是这么宣传的~~)
我写的Makefile如下
Code: Select all
#
# author timeline.menu@gmail.com
# changelog
# UTC+0800 2025-02-27 21:44:38 496632456 058 09 4
# Create this file.
#
#
# toolchain
CC = arm-none-eabi-gcc
CP = arm-none-eabi-objcopy
AS = arm-none-eabi-gcc -x assembler-with-cpp
# all the files will be generated with this name
PROJECT_NAME = stm32f10x_project
# 这个是生成的文件的名字,需要修改的话就在这里改
# user specific
SRC += ./main.c
# 如果有其他源文件可以附在后面,以半角空格分隔
# startup
ASM_SRC += ./startup_stm32f10x_md.s
OBJECTS = $(ASM_SRC:.s=.o) $(SRC:.c=.o)
# Define optimisation level here
MC_FLAGS = -mcpu=cortex-m3
AS_FLAGS = $(MC_FLAGS) -g -mthumb
CP_FLAGS = $(MC_FLAGS) -g -mthumb -Wall -fverbose-asm
LD_FLAGS = $(MC_FLAGS) -g -mthumb -Xlinker --gc-sections -T stm32_flash.ld
# build
all: $(OBJECTS) $(PROJECT_NAME).elf $(PROJECT_NAME).hex $(PROJECT_NAME).bin
arm-none-eabi-size $(PROJECT_NAME).elf
%.o: %.c
$(CC) -c $(CP_FLAGS) -I . $(INC_DIR) $< -o $@
%.o: %.s
$(AS) -c $(AS_FLAGS) $< -o $@
%.elf: $(OBJECTS)
$(CC) $(OBJECTS) $(LD_FLAGS) -o $@
%.hex: %.elf
$(CP) -O ihex $< $@
%.bin: %.elf
$(CP) -O binary -S $< $@
# clean
clean:
rm -v $(PROJECT_NAME).elf $(PROJECT_NAME).hex $(PROJECT_NAME).bin *.o
这样一个基本的程序就完成了。
在这个路径下可以通过执行
就可以生成了,执行后就可以在路径下看到生成的一堆 .o 文件,还有 stm32f10x_project.elf stm32f10x_project.bin stm32f10x_project.hex 这几个文件,其中 stm32f10x_project.hex 就是我要写进单片机里的程序。
这时就可以让STM32CubeProgrammer出场了,具体的操作方式可以参考
https://wiki.lckfb.com/zh-hans/dkx-stm3 ... B%E5%BA%8F 。
需要注意的是在linux下起动这个程序需要给予权限,这与在windows下不同。
我是用的
Code: Select all
sudo ~/STMicroelectronics/STM32Cube/STM32CubeProgrammer/bin/STM32CubeProgrammer
来起动它的。
选择UART后我的ttl模块是以 /dev/ttyUSB0 的名称出现的,也许你的会有所不同,请根据你的实际情况修改。
“烧录”完成后,终于看到了那个小蓝灯在闪了,频率就是 main.c 里面的延时所定义的。
消化消化后,下一步要开始玩ST的标准库了。
之后会再更新。