D语言在单片机上应用方法
使用D语言(dlang.org)编写单片机程序.
实现目标
尝试使用Dlang语言编写单片机程序.目标芯片为 STM32f401cc. cortex-m4
HOST主机为windows 系统,
优势列举
D语言是一种无"语言宗教"的编程语言,类似于C/C++的设计,函数书写无顺序限制,无不可调试的鸿定义,没有复杂的头文件.
使用LDC编译器时无需构建复杂的交叉编译环境
在单片机上编程主要是应用D语言的一个子集 “Better C”,这个子集包括大部分D语言的实现,如下的功能未实现,:
序号 | 功能 | 注释 |
---|---|---|
1 | 内存GC | 单片机编程来说,这部分还是需要程序员考虑取舍 |
2 | 结构信息 | TypeInfo和ModuleInfo,这部分主要应用是GC,还有就是一些共享库应用 |
3 | Class | |
4 | 内置线程 | |
5 | 动态数组 | |
6 | Exceptions | |
7 | 内置同步函数 | |
8 | 静态构造和析构函数 |
当然也可以应用完整的D语言,但付出的成本较高(FLASH/RAM),一般来说单片机开发的程序运行态都是可以推定的,从效率来说不推荐使用GC管理或动态加载
linux的嵌入式开发不在这份文章里讨论了.
环境准备
首先我们确定一下我们需要哪些东西:
序号 | 功能 | 当前可用 |
---|---|---|
1 | IDE | 推荐Vscode,外加 dlang所用的 webfreak.dlang-bundle插件 和 marus25.cortex-debug 调试插件 |
2 | 编译器 | LDC,通过LLVM实现,在无需RT库的情况下可以直接下载二进制版本, |
2.1 | 连接器 | lld,推荐直接使用LDC内置 |
2.2 | 杂项工具 | LLVM,包括不少工具用不惯的上套arm-none-eabi的也行啊~ |
3 | 构建工具 | 这个暂时没有,写个批处理搞定吧 😀 |
4 | 调试器 | GDB吧,理论上支持gdb协议都可以,暂时没发现更好的方案 |
5 | 仿真模拟 | qemu-arm,这模拟器可以仿真stm32多个系列,硬件浮点部分有问题,仿真时会出错 |
具体实现方法
源代码
module start;
/*
filename : start.d
*/
version(LDC)
{
import ldc.llvmasm;
}
alias void function() ISR;
extern(C) immutable ISR ResetHandler = &OnReset;
void SendCommand(int command, void* message)
{
__asm
(
"mov r0, $0;
mov r1, $1;
bkpt #0xAB",
"r,r,~{r0},~{r1}",
command, message
);
}
void OnReset()
{
while(true)
{
// Create semihosting message
uint[3] message =
[
2, //stderr
cast(uint)"hello\r\n".ptr, //ptr to string
7 //size of string
];
//Send semihosting command
SendCommand(0x05, &message);
}
}
说一下代码中的一些限制,这些限制主要是来源于TLS的实现,ldc自带的库中未实现这部分的函数造成.
D语言函数声明与C之间的关系
D语言函数声明与C++之间的关系
D定义 | SECTIONS region |
---|---|
__gshared int t | .bss |
__gshared int t=0x33 | .data |
shared int t | .data |
shared int t=0x33 | .data |
shared int t | .bss |
static int t | .bss |
static int t=0x33 | .data |
int t | .tbss |
int t=0x33 | .tdata |
这里遇到的麻烦主要是来至于TLS部分,这部分缺少实现函数以及LLVM的LLD部分的BUG(使用TLS后输出的binary 文件非常巨大),建议不要使用ABI的TLS,转而使用 emulated-tls
不过在"裸奔"的单片机上TLS什么的完全不需要,上嵌入式系统的话建议实现 emulated-tls方式
linker脚本
/*
filename:ldscript.ld
*/
MEMORY
{
FLASH (RX) : ORIGIN = 0x08000000, LENGTH = 256K
SRAM (WXAR) : ORIGIN = 0x20000000, LENGTH = 64K
}
_stackStart = ORIGIN(CCRAM) + LENGTH(CCRAM);
SECTIONS
{
/* We don't need exceptions, and discarding these sections
prevents linker errors with LDC */
/DISCARD/ :
{
*(.ARM.extab*)
*(.ARM.exidx*)
}
.text :
{
LONG(_stackStart); /* Initial stack pointer */
KEEP(start.o(*.ResetHandler)) /* Interrupt vector table (Entry point) */
/* the code */
*(.text)
*(.text*)
/* for "hello\r\n" string constant */
. = ALIGN(4);
*(.rodata)
*(.rodata*)
}>FLASH
/* Need .data, .bss, .ctors and probably more as program becomes
More complex */
}
这个连接脚本并未实现内存部分操作的 SECTIONS,仅仅作为这次的演示!!!
之后我会出一个注释说明的linker 脚本
编译指令
第一次心动时刻来了,
ldc2 --conf= --mtriple=thumb-none-eabi --mcpu=cortex-m4 --exception-model=sjlj --defaultlib= --platformlib= --betterC --nogc --static --link-internally -L=-Tldscript.ld -L=-nostdlib -L=--oformat -L=binary start.d --of start.bin
指令 | 功能 |
---|---|
--conf= | 禁止默认配置文件 |
--mtriple=thumb-none-eabi | 指定目标框架 |
--mcpu=cortex-m4 | 指定cpu系列 |
--exception-model=sjlj | 指定exception处理方式 |
--defaultlib= | 清空默认加载库 |
--platformlib= | 清空默认框架库 |
--betterC | 使用betterC子集 |
--static | 静态 |
--link-internally | 使用内置LLD |
-L=-Tldscript.ld | 使用的linker脚本 |
-L=-nostdlib | 传递给LLD的参数,禁止默认库 |
-L=--oformat -L=binary | 传递给LLD参数,设置输出格式,参阅LLD的oformat |
start.d | 目标文件,说明一下LDC支持通配符和文件列表 @<文件列表> |
--of start.bin | 指定输出文件 |
不出意外应该会得到一个 start.bin
连接仿真模拟
在仿真器内运行:
qemu-system-gnuarmeclipse -mcu STM32F405RG -no-reboot -nographic --image start.bin
如果打算通过GDB一类的调试启动命令应修改为
qemu-system-gnuarmeclipse -mcu STM32F405RG -no-reboot -nographic -s -S --image start.bin
-s
,gdb端口监听在 127.0.0.1:1234
-S
启动时等待gdb调试
gdb连接:
arm-none-eabi-gdb.exe start.bin
(gdb) target remote 127.0.0.1:1234
...
<其余的查阅相关gdb手册>
想得到更多调试信息,ldc编译参数加上--gc
输出格式采用elf,可以得到更多调试信息
其余说明
可以使用vscode的调试功能来调试,之后会写一个说明文档.
文档建立日期 : 2021年1月11日,01点23分