使用Free Pascal开发STM32程序
使用Free Pascal开发STM32程序
前言
大部分人都知道嵌入式开发,一般用的都是C语言,但是实际上,除C语言之外还有许多语言都可以开发,本文将介绍使用Free Pascal(简称FPC)开发STM32程序的方法。
你可以进Free Pascal的官网看看,其第一段话就是说这个编译器支持多少处理器多少操作系统的,事实上其支持绝大多数你知道的和不知道的处理器和系统(甚至开发版本还支持龙芯架构的Linux平台),当然本文的主题肯定不是教你怎么用这个编译器给各种各样的平台和架构开发程序,而是讲述如何使用构建其有关STM32的开发环境。
STM32系列是ST公司推出的微控制器(MCU)产品,有诸多的型号,但是所有MCU的内核都是基于Arm公司的Cortex-M处理器内核。对应到Free Pascal来说,其编译目标处理器架构(Target CPU)是Arm,而目标操作系统(Target OS)是Embedded(嵌入式,可以理解为裸机系统)。当然由于各种Cortex-M内核,所以还要区分目标处理器(Target Processor)内核,比如ARMV7M、ARMV7EM等。
目录
开发环境搭建
由于Free Pascal官网并不提供arm-embedded目标的编译器,所以我们需要使用一款叫fpcupdeluxe的软件来安装,同时,我们一般使用Lazarus IDE作为开发环境,可以在官网下载,或者通过fpcupdeluxe安装,也可以自己构建。
个人建议:对于新手或者不想麻烦的,使用fpcupdeluxe安装所有环境就行了;对于已经安装了Lazarus的,可以只安装编译器。
我自己是用fpcupdeluxe安装了稳定版编译器和开发版编译器,同时使用git拉取代码构建了稳定版和开发版的Lazarus。
如果你是使用FPC的老手,估计已经安装了Lazarus用来开发,可以跳过部分内容,如果你熟悉fpcupdeluxe这款软件,也可以跳过部分内容。
注意:由于众所周知的原因,请在安装前确保网络通畅,可以正常访问github.com和gitlab.com这两个网站
其中gitlab主要是安装FPC和Lazarus的时候用到,而github主要是安装交叉编译器的时候用到。
本文会给出一些常见的问题及解决方法,主要是我自己遇到的。
下载使用fpcupdeluxe
fpcupdeluxe的GitHub链接是https://github.com/LongDirtyAnimAlf/fpcupdeluxe,在发布页面,可以看到许多不同的二进制文件,对于Windows系统,我们一般下载32位的(fpcupdeluxe-i386-win32.exe)比较好,当然64位的(fpcupdeluxe-x86_64-win64.exe)也是可以的,这里要注意的是,下载的版本会影响默认生成的编译器版本,当然可以改。对于Linux系统,基本上下64位的就行了,至于是qt5(fpcupdeluxe-x86_64-linux-qt5)、qt6(fpcupdeluxe-x86_64-linux-qt6)还是gtk(fpcupdeluxe-x86_64-linux)就自己看着选。
下载完成后放到一个单独的文件夹,然后运行软件。
注意第一步先改安装的文件夹,这里的建议是不要改安装的版本(如果你用经典的F103)。当然如果你用F407想用单精度浮点(目前有问题,可能需要手写汇编),或者你要开发版本的话就改trunk,根据需求点Only FPC
或者Install/update FPC+Lazarus
然后等着吧(如果你要同时安装这俩,建议要么都stable,要么都trunk),这里会有很多问题,我后面会列出来的。
点击左下角的Setup+
按钮进入设置界面,这里有很多细节可以说明。
如果你安装Lazarus,建议按照图里的加上-O3 -Xs -g-
以及去掉Debug的勾,然后勾上左边的两个,否则不用改别的。最后点右下角的OK就行了。
常见安装问题
下面说一说安装时候可能遇到的问题以及一些解决方法。
只要网可以搞定,大部分都不是问题(人家国外的开发者基本上不会考虑到网络问题的,毕竟他们很少懂我们的特色),咱们搞技术的,搞定这些应该都是基本能力。
界面卡死
这个基本出在第一次安装的时候,因为需要下载一些bootstrap的文件,如果你经常这样的话,建议按如下步骤来:
- 记住当前正在下载的文件,任务管理器关掉进程
- 找到你的安装目录,然后进入fpcbootstrap文件夹,删掉大小0KB的文件
- 重新打开软件,点击安装,接着卡死重复之前步骤,或者打开设置挂代理
- 或者可以去GitHub给作者提issue,让他想办法加点其他链接
注意,用任务管理器关闭进程,这个软件的代理设置是不会保存的,最好在修改完需要的设置后,关掉软件再重新打开。
只要你能成功一次,以后需要在其他文件夹安装这个,就可以把文件提前复制过去了,这些文件基本上可以用几年。
如果你还知道其他解决办法,欢迎补充。
git clone很久不动
基本上超过半小时一直卡git clone,可以确定是你的网访问gitlab有问题了,建议方法是挂代理,不过要在git里面设置。
HTTP使用:
git config --global http.proxy 127.0.0.1:7890
HTTPS使用:
git config --global https.proxy 127.0.0.1:7890
要取消代理设置,使用:
git config --global --unset http.proxy
git config --global --unset https.proxy
这些设置应该会保存到 C:\Users\用户名\.gitconfig
提示编译失败
这个只有在你选择安装trunk的时候有可能出现,因为开发人员有时候或提交无法编译的代码,这很正常,建议等一段时间再安装/更新,或者你可以去gitlab仓库(链接软件上就有)看代码的提交记录,看流水线的状态就可以判断能不能编译了。
安装交叉编译器
如图步骤所示,到底选armv7em还是armv7m,这个去st官网查你的芯片型号,一般F103的Cortex-M3要选armv7m,而F4系列的Cortex-M4要选armv7em,都有的话依次全都安装了就行(提醒一下如果要armv7em最好用trunk版的编译器)。
点击Install compiler
之后,第一次安装的话会弹出对话框提示没有需要的文件,然后提示你可以下载,这个要选是,然后它会从GitHub下载所需的文件,所以要确保GitHub网络畅通。
下载使用OpenOCD
OpenOCD全称Open On-Chip Debugger,是一个用来在嵌入式设备调试的工具,支持各种调试器,例如JLink,STLink,ULink,CMISIS-DAP等。
你可以在官方存储库的发布页面下载最新的Windows二进制文件,如果你需要Linux或者macOS的,则可以去这个页面下载。注意,这两个页面都是GitHub的。
解压到文件夹后,记得配置环境变量,然后在终端用命令openocd
就可以使用了,不过你还需要一个配置文件。
配置文件基本写法如下:
source [find interface/xxx.cfg]
source [find target/xxx.cfg]
find后面的路径可以在openocd文件夹下的share\scripts\
找到,自己翻一翻用的什么调试器和芯片。
比如我用的配置文件是这样的:
source [find interface/cmsis-dap.cfg]
source [find target/stm32f4x.cfg]
假如上述文件保存为stm32.cfg
,那么需要
openocd -f stm32.cfg
就可以连接了。
也可以直接将其保存为openocd.cfg
,这样就可以直接在终端执行openocd
而不用加-f
参数指定配置文件了(前提是这个配置文件在终端的工作目录,如果你想在任何地方运行openocd而且自动配置的话,就把配置保存到share\scripts\
文件夹下,如果要经常切换调试器的话,那只能多搞几个配置在这里,用-f
参数指定)。
启动效果如图:
记住端口3333,这个是给gdb调试用的,后面我们要用到。
如果你遇到绑定端口失败的情况,可以更换端口,比如说提示tcl、telnet或者gdb失败,就用如下命令:
openocd -f xxx.cfg -c "tcl_port 6667" -c "telnet_port 4445" -c "gdb_port 3334"
注意用几个-c看具体哪些端口绑定出错了,同时如果出错建议多试试其他端口,可以用数字大一点的端口,成功率高。
配置Lazarus
启动Lazarus,如果是第一次安装,可能会弹出一个对话框,这个应该没啥问题,特别简单的配置后面就不说了,包括可能启动的时候是英文怎么改中文什么的。
如果你已经安装了Lazarus,但是通过fpcupdeluxe安装了新的编译器,那么你需要在设置界面修改编译器路径。
点开菜单的工具,然后点选项,弹出的选项对话框默认应该会显示环境-文件,如果不是就在左边点一下就行了。
找到右边的编译器可执行文件选项,改成用fpcupdeluxe安装的fpc编译器位置,一般路径是安装路径\fpc\bin\处理器-操作系统\fpc.exe
,同理下面的FPC源代码目录就是安装路径\fpcsrc
,改完后点确定,然后回到主界面点菜单的工具,然后点重新扫码FPC源代码目录,这样之后再点菜单的文件-重启就行了。
接下来再配置一下调试器,同样打开工具-选项,然后找到左边的调试器-调试器后端,默认应该是这样的:
我们点这里的添加,然后依次填名称,选择调试器类型,选择调试器路径,修改选项,具体如图所示:
改完调试器以后,注意如果你要调试本机程序,记得点左上角的下拉框改回来。
示例
建立工程
在Lazarus菜单中选择工程-新建工程,然后在弹出来的对话框选择简单程序,你应该会看到只有没几行代码,然后按Ctrl+S保持到一个文件夹里。
接着点菜单的工程-工程选项,在弹出的对话框左边选择编译器选项-配置和目标,找到右边的目标平台,有3个下拉框,改法如图:
其中目标处理器看情况改,如果你的是F1系列就要改ARMV7M,而我用的F4就是ARMV7EM。
接着再看左边,选择调试,然后右边按照如图的改法:
注意,如果你用的是3.0之后的Lazarus,可能会多选项,注意,都保持默认不要动!图有问题,懒得换了,其实调试信息类型不用改的,自动会给你选好的,我们就只要去掉显示行号这个就行了。
还有,如果你在编译和链接那边改了优化,调试可能会有问题,最好调试的时候用O1就行了,发布二进制再用O2或O3,一般也不建议O4。
然后再回到左边选自定义选项,然后在右边输入-WpXXX
,注意大小写,比如-WpSTM32F407ZG
或者-WpSTM32F103X8
,注意F1系列的不要直接用具体型号,比如F103C8T6就是X8,C6T6就是X6,主要是区分Flash和SRAM大小用的。
具体可以改什么,可以在编译器文件夹找到ppcrossarm.exe并执行ppcrossarm -i
,我也在这里给出当前版本的所有目标:
LPC810M021FN8,LPC811M001FDH16,LPC812M101FDH16,LPC812M101FD20
LPC812M101FDH20,LPC1110FD20,LPC1111FDH20_002,LPC1111FHN33_101
LPC1111FHN33_102,LPC1111FHN33_103,LPC1111FHN33_201,LPC1111FHN33_202
LPC1111FHN33_203,LPC1112FD20_102,LPC1112FDH20_102,LPC1112FDH28_102
LPC1112FHN33_101,LPC1112FHN33_102,LPC1112FHN33_103,LPC1112FHN33_201
LPC1112FHN24_202,LPC1112FHN33_202,LPC1112FHN33_203,LPC1112FHI33_202
LPC1112FHI33_203,LPC1113FHN33_201,LPC1113FHN33_202,LPC1113FHN33_203
LPC1113FHN33_301,LPC1113FHN33_302,LPC1113FHN33_303,LPC1113FBD48_301
LPC1113FBD48_302,LPC1113FBD48_303,LPC1114FDH28_102,LPC1114FN28_102
LPC1114FHN33_201,LPC1114FHN33_202,LPC1114FHN33_203,LPC1114FHN33_301
LPC1114FHN33_302,LPC1114FHN33_303,LPC1114FHN33_333,LPC1114FHI33_302
LPC1114FHI33_303,LPC1114FBD48_301,LPC1114FBD48_302,LPC1114FBD48_303
LPC1114FBD48_323,LPC1114FBD48_333,LPC1115FBD48_303,LPC11C12FBD48_301
LPC11C14FBD48_301,LPC11C22FBD48_301,LPC11C24FBD48_301
LPC11D14FBD100_302,LPC1224FBD48_101,LPC1224FBD48_121,LPC1224FBD64_101
LPC1224FBD64_121,LPC1225FBD48_301,LPC1225FBD48_321,LPC1225FBD64_301
LPC1225FBD64_321,LPC1226FBD48_301,LPC1226FBD64_301,LPC1227FBD48_301
LPC1227FBD64_301,LPC12D27FBD100_301,LPC1311FHN33,LPC1311FHN33_01
LPC1313FHN33,LPC1313FHN33_01,LPC1313FBD48,LPC1313FBD48_01,LPC1315FHN33
LPC1315FBD48,LPC1316FHN33,LPC1316FBD48,LPC1317FHN33,LPC1317FBD48
LPC1317FBD64,LPC1342FHN33,LPC1342FBD48,LPC1343FHN33,LPC1343FBD48
LPC1345FHN33,LPC1345FBD48,LPC1346FHN33,LPC1346FBD48,LPC1347FHN33
LPC1347FBD48,LPC1347FBD64,LPC2114,LPC2124,LPC2194,LPC1754,LPC1756
LPC1758,LPC1764,LPC1766,LPC1768,AT91SAM7S256,AT91SAM7SE256,AT91SAM7X256
AT91SAM7XC256,STM32F030C6,STM32F030C8,STM32F030F4,STM32F030K6
STM32F030R8,STM32F050C4,STM32F050C6,STM32F050F4,STM32F050F6,STM32F050G4
STM32F050G6,STM32F050K4,STM32F050K6,STM32F051C4,STM32F051C6,STM32F051C8
STM32F051K4,STM32F051K6,STM32F051K8,STM32F051R4,STM32F051R6,STM32F051R8
STM32F091CC,STM32F091CB,STM32F091RC,STM32F091RB,STM32F091VC,STM32F091VB
STM32F100X4,STM32F100X6,STM32F100X8,STM32F100XB,STM32F100XC,STM32F100XD
STM32F100XE,STM32F101X4,STM32F101X6,STM32F101X8,STM32F101XB,STM32F101XC
STM32F101XD,STM32F101XE,STM32F101XF,STM32F101XG,STM32F102X4,STM32F102X6
STM32F102X8,STM32F102XB,STM32F103X4,STM32F103X6,STM32F103X8,STM32F103XB
STM32F103XC,STM32F103XD,STM32F103XE,STM32F103XF,STM32F103XG,STM32F107X8
STM32F107XB,STM32F107XC,STM32F105R8,STM32F105RB,STM32F105RC,STM32F105V8
STM32F105VB,STM32F105VC,STM32F107RB,STM32F107RC,STM32F107VB,STM32F107VC
STM32F401CB,STM32F401RB,STM32F401VB,STM32F401CC,STM32F401RC,STM32F401VC
DISCOVERYF401VC,STM32F401CD,STM32F401RD,STM32F401VD,STM32F401CE
STM32F401RE,NUCLEOF401RE,STM32F401VE,STM32F407VG,DISCOVERYF407VG
STM32F407IG,STM32F407ZG,STM32F407VE,STM32F407ZE,STM32F407IE,STM32F411CC
STM32F411RC,STM32F411VC,STM32F411CE,STM32F411RE,NUCLEOF411RE
STM32F411VE,DISCOVERYF411VE,STM32F429VG,STM32F429ZG,STM32F429IG
STM32F429VI,STM32F429ZI,DISCOVERYF429ZI,STM32F429II,STM32F429VE
STM32F429ZE,STM32F429IE,STM32F429BG,STM32F429BI,STM32F429BE,STM32F429NG
STM32F429NI,STM32F429NE,STM32F446MC,STM32F446RC,STM32F446VC,STM32F446ZC
STM32F446ME,STM32F446RE,NUCLEOF446RE,STM32F446VE,STM32F446ZE
STM32F745XE,STM32F745XG,STM32F746XE,STM32F746XG,STM32F756XE,STM32F756XG
LM3S1110,LM3S1133,LM3S1138,LM3S1150,LM3S1162,LM3S1165,LM3S1166,LM3S2110
LM3S2139,LM3S6100,LM3S6110,LM3S1601,LM3S1608,LM3S1620,LM3S1635,LM3S1636
LM3S1637,LM3S1651,LM3S2601,LM3S2608,LM3S2620,LM3S2637,LM3S2651,LM3S6610
LM3S6611,LM3S6618,LM3S6633,LM3S6637,LM3S8630,LM3S1911,LM3S1918,LM3S1937
LM3S1958,LM3S1960,LM3S1968,LM3S1969,LM3S2911,LM3S2918,LM3S2919,LM3S2939
LM3S2948,LM3S2950,LM3S2965,LM3S6911,LM3S6918,LM3S6938,LM3S6950,LM3S6952
LM3S6965,LM3S8930,LM3S8933,LM3S8938,LM3S8962,LM3S8970,LM3S8971,LM3S5951
LM3S5956,LM3S1B21,LM3S2B93,LM3S5B91,LM3S9B81,LM3S9B90,LM3S9B92,LM3S9B95
LM3S9B96,LM3S5D51,LM4F120H5,SC32442B,XMC4500X1024,XMC4500X768
XMC4502X768,XMC4504X512,ALLWINNER_A20,MK20DX128VFM5,MK20DX128VFT5
MK20DX128VLF5,MK20DX128VLH5,TEENSY30,MK20DX128VMP5,MK20DX32VFM5
MK20DX32VFT5,MK20DX32VLF5,MK20DX32VLH5,MK20DX32VMP5,MK20DX64VFM5
MK20DX64VFT5,MK20DX64VLF5,MK20DX64VLH5,MK20DX64VMP5,MK20DX128VLH7
MK20DX128VLK7,MK20DX128VLL7,MK20DX128VMC7,MK20DX256VLH7,MK20DX256VLK7
MK20DX256VLL7,MK20DX256VMC7,TEENSY31,TEENSY32,MK20DX64VLH7,MK20DX64VLK7
MK20DX64VMC7,MK22FN512CAP12,MK22FN512CBP12,MK22FN512VDC12
MK22FN512VLH12,MK22FN512VLL12,MK22FN512VMP12,FREEDOM_K22F
MK64FN1M0VDC12,MK64FN1M0VLL12,FREEDOM_K64F,MK64FN1M0VLQ12
MK64FN1M0VMD12,MK64FX512VDC12,MK64FX512VLL12,MK64FX512VLQ12
MK64FX512VMD12,ATSAM3X8E,ARDUINO_DUE,FLIP_N_CLICK,NRF51422_XXAA
NRF51422_XXAB,NRF51422_XXAC,NRF51822_XXAA,NRF51822_XXAB,NRF51822_XXAC
NRF52832_XXAA,NRF52840_XXAA,THUMB2_BARE
可以看到支持大量的平台,而不只是STM32!
编写运行
写一个经典的blink程序,来测试一下能不能正常使用。
这里我分别用经典的F103C8T6和F407ZGT6作为例子。
F103C8T6
我用的F103C8T6用的是非常经典的设计,拿来学习是相当便宜的,此开发板使用单LED,连接在PC13上。
program f103_blink;
// 小秀一手汇编,内联起来比C方便多了
procedure Delay(cnt:UInt32);assembler;nostackframe;
asm
.L1:
sub r0, #1
nop
cmp r0, #0
bne .L1
end;
begin
// 这里没有修改RCC,用的默认的HSI时钟
// 置位,表示使能PortC
RCC.APB2ENR:=RCC.APB2ENR or %10000;
// 设置CRH的第20~23位,配置PC13,选用推挽低速输出
PortC.CRH:=PortC.CRH and not ($F shl 20) or (%0010 shl 20);
// 主循环
while True do
begin
PortC.ODR:=PortC.ODR or (1 shl 13); // 置1,关灯
Delay(1000000); // 延迟一段时间
PortC.ODR:=PortC.ODR and not (1 shl 13); // 清零,开灯
Delay(1000000);
end;
F407ZGT6
F407用的是某原子的小系统版,两个LED接的的GPIOF的Pin9和Pin10,根据F4系列的文档看了寄存器的定义,于是写了这些代码:
program stm32_blink;
// 小秀一手汇编,内联起来比C方便多了
procedure Delay(cnt:UInt32);assembler;nostackframe;
asm
.L1:
sub r0, #1
nop
cmp r0, #0
bne .L1
end;
var
// 这里用到了位打包数组,这种数组可以简化位操作,对于GPIO这种寄存器内部都是连续单元的很有用
gpf_moder:bitpacked array[0..15] of 0..3 absolute GPIOF.MODER;
//gpf_otyper:bitpacked array[0..15] of 0..1 absolute GPIOF.OTYPER; // 用不着
//gpf_ospeedr:bitpacked array[0..15] of 0..3 absolute GPIOF.OSPEEDR; // 用不着
//gpf_pupdr:bitpacked array[0..15] of 0..3 absolute GPIOF.PUPDR; // 可选
gpf_odr:bitpacked array[0..15] of 0..1 absolute GPIOF.ODR;
begin
// 这里没有修改RCC,用的默认的HSI时钟
// 置位,表示使能GPIOF
RCC.AHB1ENR:=RCC.AHB1ENR or %100000;
// GPIOF Pin 9
gpf_moder[9]:=1; // 设置输出模式,这个必须有了
//gpf_ospeedr[9]:=0; // 不改默认就是0,表示低速,我们没必要改
//gpf_otyper[9]:=0; // 不改默认就是0,表示推挽,也没必要改
//gpf_pupdr[9]:=1; // 这里不改也是可以控制灯的
gpf_odr[9]:=1; // 关灯
// GPIOF Pin 10
gpf_moder[10]:=1;
//gpf_ospeedr[10]:=0;
//gpf_otyper[10]:=0;
//gpf_pupdr[10]:=1;
gpf_odr[10]:=1;
// 主循环
while True do
begin
gpf_odr[10]:=0; // 开灯10
Delay(2000000); // 延迟,具体多少看情况
gpf_odr[9]:=0; // 开灯9
gpf_odr[10]:=1; // 关灯10
Delay(1000000); // 延迟
gpf_odr[9]:=1; // 关灯9,此时全部关灯
Delay(1000000); // 延迟
end;
end.
代码部分基本的都有注释,有关语言机制以及其他什么的不在本文的范围,不能说和C比哪个好,但是编译速度那是真的快。
然后你就可以按照正常调试,下断点什么的,接着按F9一键运行了,注意按了以后可能要等几秒钟。
结束调试就按Ctrl+F2,或者左边的面板上应该也有按钮可以点,注意单步是F8和F7不是F10和F11,你还可以按Ctrl+Alt+D查看汇编情况。
这个时候看一下OpenOCD的那个控制台窗口,可以看到有关调试的信息打印出来哦。
结语
用Free Pascal开发STM32是完全可行的,而且开发调试非常方便,只不过没有库写起代码来比较麻烦,需要对寄存器进行直接的操作,当然也可以自己封装一个库来用。可能还是受限于语言的流行度,这方面资料非常稀少,就连官方Wiki也没有很详细的内容,我也希望有更多的人能了解到Free Pascal,了解Pascal语言及编译器的最新发展。
如果以后有机会,可以多讲一讲有关这方面的东西。
更新记录
- 2023-08-14:修正部分错误,增加更多内容,新增F103的代码。