从0开始linux点灯
从0开始linux点灯🐤
大家好啊📲
最近我在学习使用STM32系统板, 起初跟着网上的课程学, 做了很多小实验, 自以为学懂了, 自信去上课, 结果课上老师布置作业让我们用汇编语言点灯, 给我干傻了, 我去, 我看网上教程都是用c调函数实现功能的啊, 咋到你这就要汇编点灯了呢.
于是我就去尝试汇编点灯, 网上找了很多经验, 代码也写得差不多了, 喜闻乐见放到大家最喜欢的keil5里面编译, 结果因为各种文件配置, 和arm编译器的种种问题吧, 一直无法成功编译.
后来有人建议嫌keil5麻烦的话, 直接转linux不就行了. 我一想还真是吧, linux下点灯, 不仅我们可以实操更多的底层配置, 而且还有利于以后的项目开发, 终身摆脱麻烦的keil5, 无敌了!
直接开始...开始吗?😢
在开始之前, 我想我们需要先了解一下我们到底要干什么:
- 配置linux下的嵌入式开发环境
- 配置linux下的嵌入式开发工具链
- 以wsl + vscode 进行开发
- 编写一系列代码以完成点灯
同时, 我们还应确保电脑上已经有了一定的环境支持:
- WSL2 Ubuntu
- vscode
- GNU
- zsh
Step1 : 安装工具链⛓️
先说说工具链和交叉编译
工具链(Toolchain)是一组开发工具的集合,这些工具共同工作,帮助开发人员编写、编译、调试和打包软件, 也就是说, 一个程序从被你编写出来, 便会沿着这个工具链前进, 并被不断加工, 直到它最终被运行.
交叉编译(Cross compiling)是指在一个平台上编译, 在另一个平台上运行可执行程序的过程, 由于嵌入式开发时, 程序总在性能没那么强大的板子上运行, 所以如果我们把编译的工作转移到电脑上, 可以更完全地利用电脑的资源, 也为板子剩下了更多资源用于跑程序
接下来我们安装ARM GNU Toolchain:
结果发现Ubuntu 官方 APT 源似乎对 gcc-arm-none-eabi
已经停止维护了
但是没有关系, 我查阅网上资料, 发现有哥们是采取官网下载文件然后安装进ubuntu里面
Arm GNU Toolchain Downloads – Arm Developer
我下载的是 arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi.tar.xz
![img](file:///C:\Users\范zs\Documents\Tencent Files\1017327044\nt_qq\nt_data\Pic\2024-10\Ori\5a7b9df04fe9acca386dbe1d7f4e570b.png)
但是这样做的话我们得手动安装, 将此文件传入你ubuntu的文件夹里面, 解压到 /opt/gcc-arm-none-eabi
中, 并在~/.zshrc
中添加这一行指令以在每次启动zsh的时候都可以加入gcc-arm-none-eabi环境变量:
export PATH=$PATH:/opt/gcc-arm-none-eabi/bin
你可能会用到:
opt目录: 主要存放可选的程序。想删掉程序的时候,你就可 以直接删除它,而不影响系统其他任何设置。
linux cp: /opt目录需要sudo权限才能访问, 在移动文件时记得使用
sudo cp
来移动由于我们下载了压缩文件.xz, 还要上网学习一下linux的解压命令
在windows文件夹中可以更直观地看到可视化的ubuntu文件结构, 并进行操作![img](file:///C:\Users\范zs\Documents\Tencent Files\1017327044\nt_qq\nt_data\Pic\2024-10\Ori\902ae58e1193fcd4ffcef85abaafa847.png)
然后为了应对可能的报错, 还额外下载了一些东西(比如, ARM GNU Toolchain 还需要 Python3.8 支持)
sudo apt install -y libncursesw5
sudo apt install -y python3.8
最后我们用version验证一下安装成功没
![img](file:///C:\Users\范zs\Documents\Tencent Files\1017327044\nt_qq\nt_data\Pic\2024-10\Ori\cb905dd6f1b656b0c360688544641448.png)
Step2: 安装usbipd⚒️
usbipd用于让我们得以在ubuntu中操作windows本地连接的usb设备
它可以将Windows 本地连接的 USB 设备共享给其他机器的开源项目,包括 Hyper-V 虚拟机和 WSL 2
这里直接跟着微软的手册操作就行, 就不多说了
Step3: 安装 OpenOCD⚖️
OpenOCD
(Open On-Chip Debugger)是一个开源的调试和编程工具,用于嵌入式系统开发中与芯片上调试接口(如 JTAG、SWD 等)通信并进行调试、编程和仿真操作。
OpenOCD
提供了一种通用的接口和协议,可以与多种嵌入式芯片和调试接口进行交互。它支持各种处理器架构(如 ARM、RISC-V 等)和调试接口(如 JTAG、SWD、BDM 等),可以与目标系统上的芯片进行连接,并通过调试接口与芯片进行通信。
主要功能有两个:
- 调试功能:OpenOCD 支持寄存器读写、内存读写、断点设置、单步执行等调试操作,允许开发人员在目标系统上进行调试。它与 GDB 调试器紧密集成,提供了与 GDB 之间的通信接口。
- 编程功能:OpenOCD 支持对芯片进行编程,包括烧录程序代码、擦除芯片、写入 Flash 存储器等操作。它能够与多种烧录器(如 J-Link、ST-Link 等)集成,实现对芯片的编程。
总的来说, 我们这次要用到OpenOCD来将我们的文件烧录到板子上面去
sudo apt install openocd
直接用包管理器安装即可, 注意一下安装之后/usr/share/openocd/scripts/
内的 interface
和 target
文件夹, 可以看看里面是什么
时间差不多咯😃
安装的差不多了, 我们可以开始写代码的部分了
当然, wsl + vscode 开发的话, 其实还有很多vscode的拓展没有安装, 但是此处我们只考虑基础的点灯, 拓展可以暂时不安装
当然能先安装好是最好的, 可以参考一下文章最后列出的其他人的 blog
大家在keil5点灯的时候肯定也配过环境, 把官方的固件包里文件复制到项目文件中即可, 或者直接套用模版工程就行, 如果在linux里面写的话, 可能我们自己写的东西要稍微多一点. 有无这次只是简单的点灯工程, 我们只需完成两样东西, 一个startup文件, 和一个linker文件.
什么, 居然要写一个startup文件出来??!!
当然不是, startup文件不用我们写, 我们只需要在里面添加一些代码即可.
startup文件位于固件库中, 下载STM32固件库的操作不难, 大家可以自行上网查找一下方法
这里需要注意的是, 由于我们在linux环境中使用的是gcc编译器, 所以我们需要找一个支持gcc的startup文件, 我选择的是\STM32F10x_StdPeriph_Lib_V3.5.0\Libraries\CMSIS\CM3\DeviceSupport\ST\STM32F10x\startup\TrueSTUDIO
中的startup_stm32f10x_md.s(因为我是F103, 这个文件选什么根据不同的板子型号选就行)
我们打开startup函数, 里面大多是针对板子启动的一些操作, 由于我是才学, 对汇编语言, 链接器, 和板子启动的逻辑了解不多, 这里就不深入研究原理(另一个原因是我现在在飞机上, 不能上网搜TAT)
找到Reset_Handler函数, 这是板子启动后执行的函数, 由于点灯项目较为简单, 我们直接把项目代码写到这个函数中即可. 这里我让程序直接跳转到init_led, init_led是初始化函数, 将我们点灯的各个部分进行初始化, 便于点灯操作:
init_led:
ldr r0, = 0x40021018
ldr r0, [r0]
orr r0, r0, #0x10
ldr r1, = 0x40021018
str r0, [r1]
ldr r0, = 0x40011004
ldr r0, [r0]
orr r0, r0, #0x100000
ldr r1, = 0x40011004
str r0, [r1]
b lights_loop
初看这段代码可以觉得有点抽象了, 这是在干什么? 怎么到处都是16进制的玩意, 在此我简述一下这段代码的内容
我们所做的初始化操作, 其实就是赋值, 将对应地址的寄存器赋值, 从而控制电路的通断, 达到使能的目的.
而上面这段代码, 和我下面给出的led_init代码的作用是一样的:
void LED_Init(void)
{
//1.打开控制GPIOC的时钟(APB2)
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);
//2.配置结构体
GPIO_InitTypeDef led_init;
led_init.GPIO_Pin = GPIO_Pin_13; //GPIOC13引脚
led_init.GPIO_Mode = GPIO_Mode_Out_PP; //推挽输出
led_init.GPIO_Speed = GPIO_Speed_10MHz; //10MHz
//3.对成员进行初始化
GPIO_Init(GPIOC, &led_init);
}
- 首先我们找到GPIOC时钟的地址0x40021018(这个通过看手册得到, 网上的很多教程都教了如何读手册找地址)
- 然后, 通过一系列的汇编操作将这一组寄存器的第四位设为1, 这第四位就象征着GPIOC时钟的使能(这段汇编指令使用了ldr, orr, str等, 可以查看一下常见汇编代码)
- 接下来是一样的, 我们初始化完了GPIOC的时钟, 之后初始化一下PC13引脚, 可以看到, 我们将GPIOC端口配置储存器的23 - 20位分别配置为了0001, 这意味着将编号13的引脚设置为10MHz输出, 通用输出推挽
- 这些操作同时还包含的16进制和2进制之间的计算, 如果我们试着将代码中的16进制数据转换为二进制, 则会发现规律
在代码的最后, 我们在完成初始化之后进入了lights_loop函数中, 这就是操作闪灯的函数了, 我们需要它不断循环, 使得板子不断闪灯
lights_loop:
bl lightsoff
bl delay
bl lightson
bl delay
b lights_loop
当然这肯定不是函数的全貌, 我们在lightson和lightsoff函数中把控制高低电平输出位的寄存器置1 or 置0, 从而控制灯的亮灭, 当然找地址和找寄存器配置仍然是通过查手册实现, 如果是初次编写汇编点灯程序, 可以看看文末的一篇blog, 会带你查手册 ,讲得很细.
下面是lightson函数, lightsoff是差不多的:
lightson:
ldr r0, = 0x4001100C
ldr r0, [r0]
mov r2, ~(1<<13)
and r0, r0, r2
ldr r1, = 0x4001100C
str r0, [r1]
bx lr
最后我们在on 与 off 之间插入delay, 它会让的灯有持续亮/灭的时间, 不会闪得太快, 超过一定频率的话人眼都看不到了. 延迟的逻辑是, 在延迟函数中不断循环执行n调指令, 因为每条指令的执行都有耗时(比如说是t), 那么执行n次之后我们就会延时n*t的时间了(此处延时的计算我还不太清楚, 一次指令延迟多久, 执行了多少次循环这些都有待深入研究, 毕竟我之前抄了一个延时一秒的代码, 结果延时了10多秒, 给我干麻了)
/* 延时, 简单的循环减一 */
delay:
mov r2, #0x100000
mk:
cmp r2, #0
IT NE
subne r2, r2, #1
bne mk
bx lr
我们还需要写一个链接器⛸️
链接器的代码感觉很多项目里面都是找别的工具自动生成来用的, 我们这次写一个简单的, 所以可以自己写写, 毕竟链接器的代码和汇编那种神人代码比起来相对好读, 容易理解(不过其实也有点神人)
MEMORY
{
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 512K
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 64K
}
ENTRY(Reset_Handler)
_system_stack_size = 0x400;
SECTIONS {
.text :
{
. = ALIGN(4);
*startup_stm32f10x_md.o (.text)
} > FLASH
.rodata :
{
. = ALIGN(4);
KEEP(*(.rodata))
. = ALIGN(4);
} >FLASH
.data :
{
. = ALIGN(4);
_sdata = .; /* create a global symbol at data start */
**startup_stm32f10x_md.o (.data)
. = ALIGN(4);
_edata = .; /* define a global symbol at data end */
} >RAM
. = ALIGN(4);
.bss :
{
/* This is used by the startup in order to initialize the .bss secion */
_sbss = .; /* define a global symbol at bss start */
__bss_start__ = _sbss;
*start.o (.bss)
. = ALIGN(4);
_ebss = .; /* define a global symbol at bss end */
__bss_end__ = _ebss;
} >RAM
}
这段链接器代码又短又基础, 相信大家都能读懂, MEMORY中设置了堆栈, SECTION中将代码和数据链接起来, 同时还规定了它们存储到flash还是ram中, 可以看到, 由于我整个项目里只有一个startup_stm32f10x_md.o文件, 所以我让链接器代码在获取.text时直接看startup_stm32f10x_md.o就行, 其他同理. 只要看懂了各个代码的原理就行, 代码本身让chatGPT生成都行.
代码写完了, 但很明显任务还没完成😵
接下来, 我们要编译该代码得到.o文件, 然后还要拿到elf文件, bin文件烧写到机器上进行运行.
arm-none-eabi-gcc -c -mthumb -mcpu=cortex-m3 -g -Wa,--warn -o startup_stm32f10x_md.o startup_stm32f10x_md.s
如果之前的操作一切正常, 我们可以得到.o文件, 但是.o文件不能直接被机器执行, 我们需要进一步进行处理
arm-none-eabi-gcc -o test.elf startup_stm32f10x_md.o -mthumb -mcpu=cortex-m3 -T STM32F103RETx_FLASH.ld -specs=nosys.specs -static -Wl,-cref,-u,Reset_Handler -Wl,-Map=test.map -Wl,--gc-sections -Wl,--defsym=malloc_getpagesize_P=0x80 -Wl,--start-group -lc -lm -Wl,--end-group
接下来我们得到了.elf文件, 可以直接烧写到机器中执行了, 但是有建议说最好转换成bin文件, 所以我们还是在转换一波
arm-none-eabi-objcopy -O binary test.elf test.bin
当然, 代码肯定不太容易在第一时间就正常运行, 如果每个程序都是这样那就太好了, 不过这不太可能, 所以我们还需要调试一下, vscode里面有更加人性化的调试, 但是这次没有考虑使用, 我们用GDB调试即可
为了调试, 我们先将STM32板子和linux系统进行连接
-
首先, 我们将USB共享给linux系统, 其操作在之前的usbipd的部分里的手册里有所说明, 这里就不细说了
-
然后我们需要将gdb, stm设备, 和elf文件绑定, 这个操作下面这篇blog说的很好, 可以跟着操作
-
最后在修改了代码之后, 这灯也是点上了哈哈哈哈
庆祝✌️
下面是对我帮助很大的文章:
linux工具链安装 + vscode配置(本文没有vscode配置环节, 可以参考下文blog):
posted on 2024-10-15 18:49 Fg0_MURAMASA 阅读(8) 评论(0) 编辑 收藏 举报