为 STM32 移植 Berry 脚本语言
Berry 是我为单片机设计的一款脚本语言,该语言具有资源占用小、平台无关、执行速度快和易于掌握等优点。在单片机上使用脚本语言可以提高单片机的二次开发能力以及调试效率,同时也是一种比较新颖的玩法。本教程将简要介绍在 STM32F103RBT6 单片机上移植 Berry 脚本语言的方法。教程的末尾给出了移植完成的示例工程,读者可以根据本教程的内容和示例工程完成自己的移植工作。
我使用 ST 推出的 CubeMX 软件进行单片机固件库的配置,选择用 CubeMX 生成 HAL 库工程而不用标准库是考虑到以下因素:
- 不必编写底层外设的驱动代码,减少工作量
- 方便后续支持更多的 STM32 型号
- 方便生成各种开发环境的工程
这只是一个简单的例子,只需要使用 CubeMX 建立一个基础的工程并进行少量的配置。开始本教程之前,读者需要先安装 CubeMX 软件和 STM32CubeF1 支持包,然后我们就可以开始建立工程了。
基础配置
打开 CubeMX 后,点击菜单栏中的 New -> NewProjext 来启动工程配置向导。按下图进行配置:
建立工程后将进入类似下图的界面(这是最终配置好的工程)
打开 Project Manager选项卡,我们进行以下配置:
这个界面用于进行工程的配置,除了工程名和路径等基本信息,我们需要注意 Toolchain/IDE 和 Linker Settings 中堆栈大小的设置。这里我选择了比较常用的 MDK-ARM V5 作为目标 IDE。对于堆栈大小,建议最小堆容量(Minimum Heap Size)不低于 4KB(0x1000),而最小栈容量不低于 2KB(0x800)。
读者可根据实际情况进行时钟配置,即使不进行任何配置也可以正常使用(将使用内部的 HSI 时钟源,且主频只有 8MHz)。
最后我们需要配置一个串口以方便运行脚本的交互模式。串口外设在 Pinout & Configuration 选项卡下的 Connectivity 目录中,我需要使用 USART1 进行通信,这里就只对它进行配置:
到此,基本的配置工作就完成了,点击 GENERATE CODE 按钮就可以生成 Keil MDK 的工程,接下来的移植工作将在 MDK 工程中进行。
移植 Berry
准备文件
目前项目的目录结构如下所示:
首先到 GitHub 中下载 Berry 的源代码并进行编译(这需要电脑上安装 GCC 工具链并执行 make prebuild
命令,如果没有的话读者可以直接使用文末我已经移植好的工程),该过程是为了生成需要自动生成的代码。完成之后我们需要进行以下操作:
- 将整个 berry 文件夹移动到 stm32f103rb_berry 文件夹下,该文件夹中包含了 Berry 解释器的核心代码
- 将 berry/generate 文件夹移动到 stm32f103rb_berry 文件夹下,这是在使用
make prebuild
命令时由 berry/tools/map_build/map_build.exe 工具自动生成的代码,文末的参考工程也给出了这些代码 - 将 berry/default 文件夹中的源文件和头文件分别移动到 Src 文件夹和 Inc 文件夹下,该文件夹包含了一个 Berry 交互式解释器的默认实现,后面我们将通过调用它的主函数来运行该解释器
Berry 解释器需要从一种输入设备中读取字符流输入,在 PC 上可以使用 C 标准库中的 fgets()
函数或者 GNU/Readline 库中的 readline()
函数。本教程中使用 STM32 的 USART1 进行字符流传输,因此要实现基于串口的 readline()
函数。参考工程中的 stm32f103rb_berry/Src/readline.c 和 stm32f103rb_berry/Inc/readline.h 文件即用于实现该功能。从 readline.h 中我们可以看到一些公共的函数:
// 向输入队列中放入一个字符
// 该函数在串口接收中断服务函数中调用,实参为串口收到的字符
int queue_putchar(int ch);
// 从输入设备(串口)中读取一个字符串
// 参数 prompt 为导言字符串,导言会在开始接收字符流之前输出
// 返回值为接收到的一行字符串,如果没有接收到 '\r' 或 '\n' 则该函数会一直等待
const char* readline(const char *prompt);
// 从输入设备中(串口)读取一个字符,如果没有接收到字符则该函数会一直等待
int readchar(void);
现在我们将得到以下文件结构:
打开 MDK-ARM 目录下的 Keil 工程进行下一部的移植。在 Project 窗口下工程的根目录的右键菜单中打开 Manage Project Items 对话框,新建一个名为 berry 的 Group,然后将 stm32f103rb_berry/berry/src 中的所有源文件加入该分组。同时将刚才新加入 stm32f103rb_berry/Src 文件夹中的源文件加入 Application/User 分组。
MDK 工程配置
到此为止,源文件的配置就完成了。我们还要到工程的 Options 中将 ../berry/src 路径加入到 Include Paths 并勾选 C99 Mode:
注意,除非需要调试代码,建议将优化选项(Optimize)开到 O2 或更高以减少代码体积并提高运行速度。
源码修改
Applicatio/User 下的 berry.c 包含了一个 Berry 的交互式解释器的入口,不过其主函数名确是 main
,这里需要将其改掉以避免和 STM32 工程的 main
函数冲突:
int berry_main(int argc, char *argv[])
{
// ...
}
将 REPL 的字符串输入函数修改为 readline
:
// ...
#include "readline.h"
// ...
static int analysis_args(bvm *vm)
{
// ...
if (args & arg_i) { /* enter the REPL mode */
return be_repl(vm, readline);
}
return 0;
}
在 main.c 中修改 main()
函数以启动解释器:
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init();
berry_main(0, NULL); /* ADD: start berry interpreter */
while (1);
}
串口通信支持
为了让 readline()
函数能接收到字符,我们需要对串口中断服务函数进行修改,该函数在 stm32f1xx_it.c 文件中,以下是修改后的中断服务函数:
// ...
#include "readline.h"
// ...
void USART1_IRQHandler(void)
{
/* USER CODE BEGIN USART1_IRQn 0 */
if ((USART1->SR & USART_SR_RXNE) != RESET) {
USART1->SR &= ~USART_SR_RXNE;
queue_putchar(USART1->DR);
return;
}
/* USER CODE END USART1_IRQn 0 */
HAL_UART_IRQHandler(&huart1);
/* USER CODE BEGIN USART1_IRQn 1 */
/* USER CODE END USART1_IRQn 1 */
}
// ...
为了让 Berry 解释器的输入输出重定向到 USART1,需要修改 be_port.c 文件中的 be_writebuffer
函数和 be_readstring
函数:
// ...
#include "stm32f1xx_hal.h"
/* USART 字符输出函数 */
static void usart_putchar(USART_TypeDef *USARTx, int ch)
{
USARTx->DR = (uint16_t)(ch & 0x01FF);
while (!(USARTx->SR & UART_FLAG_TXE));
}
static int usart_putc(int ch)
{
if (ch == '\n') { /* 将 '\n' 转换为 "\r\n" */
usart_putchar(USART1, '\r');
}
usart_putchar(USART1, ch);
return ch;
}
void be_writebuffer(const char *buffer, size_t length)
{
while (length--) {
usart_putc(*buffer++);
}
}
char* be_readstring(char* buffer, size_t size)
{
return be_fgets(stdin, buffer, (int)size);
}
Berry 解释器配置
berry_conf.h 是 Berry 解释器的配置文件,默认的配置文件是为运行在 PC 上的解释器设计的,它并不适合于资源受限的嵌入式系统,为此我们需要对该文件中定义的宏定义进行修改:
#define BE_SINGLE_FLOAT 1 // 使用单精度浮点数
#define BE_INTGER_TYPE 0 // 使用 int 类型作为整数类型
#define BE_DEBUG_RUNTIME_INFO 2 // 使用内存占用较少的调试信息
#define BE_STACK_TOTAL_MAX 100 // 最大堆栈数量无需太大
// 关闭所有模块,如果读者需要可以根据需要打开部分模块
#define BE_USE_STRING_MODULE 0
#define BE_USE_JSON_MODULE 0
#define BE_USE_MATH_MODULE 0
#define BE_USE_TIME_MODULE 0
#define BE_USE_OS_MODULE 0
// 部分系统相关的标准库函数定义,注意单片机中实际上一般没有 abort 和 exit 函数的实现
#define BE_EXPLICIT_ABORT abort
#define BE_EXPLICIT_EXIT (void)
#define BE_EXPLICIT_MALLOC malloc
#define BE_EXPLICIT_FREE free
#define BE_EXPLICIT_REALLOC realloc
编译及运行
到此,对工程进行编译并下载到一块开发板将可以正常运行。将开发板使用串口(波特率为 115200bps)连接到 PC 后就可以使用 Putty、SecureCRT 等终端模拟工具来使用运行在单片机中的 Berry 解释器了。
直接使用键盘来输入脚本代码,按下回车后将会执行并输出结果:
Berry 0.1.1 (build in Jul 29 2019, 21:38:36)
[ARMCC] on STM32 (default)
> print('Hello World!')
Hello World!
> 100 + 4 * 10
140
>
示例工程
示例工程的百度网盘链接: https://pan.baidu.com/s/1vfndyNaHJLsNvPeMlOFPQw,提取码: hxri 。
后续
本篇教程只涉及简单的移植,并没有包含单片机外设的支持,这些内容会在后续的教程中给出。另外,以后的示例将迁移到 STM32F407VET6 上,而底层外设的驱动依然由 CubeMX 来生成。
我还会提供更多关于 Berry 的资料并完善语言文档。如果有任何需求或者问题,读者可以通过留言、邮箱或者 GitHub 进行反馈。
为 STM32 移植 Berry 脚本语言的更多相关文章
- 单片机脚本语言-移植lua到stm32-MDK
Lua简单介绍 Lua[1] 是一个小巧的脚本语言.作者是巴西人.该语言的设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能. Lua脚本能够非常easy的被C/C++ 代码调用, ...
- 适合学习C语言开源项目——嵌入式脚本语言 Berry
嵌入式脚本语言 Berry github网址 :https://github.com/Skiars/berry Berry 是一款面向小型嵌入式系统的脚本语言,目前发布了 0.1.0 版本.相比于其他 ...
- 为 32 位单片机设计的脚本语言 Berry
Berry是一款一款为32位单片机设计的脚本语言.Berry解释器使用C89标准实现,该语言可以在RAM或ROM很小的设备上运行. 尽管Berry的体积很小,但是它也支持class以及闭包等功能,使得 ...
- [改善Java代码]易变业务使用脚本语言编写
建议16: 易变业务使用脚本语言编写 Java世界一直在遭受着异种语言的入侵,比如PHP.Ruby.Groovy.JavaScript等,这些“入侵者”都有一个共同特征:全是同一类语言—脚本语言,它们 ...
- SiKuli 图形脚本语言【转载】
Sikuli 是一种新颖的图形脚本语言,或者说是一种另类的自动化测试技术.它与我们常用的自动化测试技术(工具)有很大的区别. 当你看到上图sikuli的脚本时,一定会惊呼,这样都可以~!脚本加截图~~ ...
- Hello China操作系统STM32移植指南(一)
Hello China操作系统移植指南 首先说明一下,为了适应更多的文化背景,对Hello China操作系统的名字做了修改,修改为"Hello X",或者连接在一起,写为&quo ...
- Lua脚本语言快速入门手册
学了两天Lua语言,感叹其短小精悍,上手极快,语法还很舒服,不错!整理下学习过程中经常用到的基础知识,共勉! Lua用法简述 Lua语言是在1993年由巴西一个大学研究小组发明,其设计目标是作为嵌入式 ...
- Lua 是一个小巧的脚本语言
Redis进阶实践之七Redis和Lua初步整合使用 一.引言 Redis学了一段时间了,基本的东西都没问题了.从今天开始讲写一些redis和lua脚本的相关的东西,lua这个脚本是一个好东西,可以运 ...
- ESP8266 LUA脚本语言开发: 准备工作-LUA开发是怎么来的
前言 当前8266有各种开发 1.在官方已经封装好的C库上开发(SDK开发) 2.官方在SDK的基础上封装的AT指令程序(AT指令开发) 3.在SDK的基础上嵌入脚本语言(Lua,Python等开发方 ...
随机推荐
- mac OS 安装 Homebrew及常用命令
Homebrew 是由国外大神 Max Howell 开发的一款包管理工具,类似Debian的apt,他可以安装任何你想安装的东西. 安装方法 命令行输入 /usr/bin/ruby -e &quo ...
- mysql数据库的水平分表与垂直分表实例讲解
mysql语句的优化有局限性,mysql语句的优化都是围绕着索引去优化的,那么如果mysql中的索引也解决不了海量数据查询慢的状况,那么有了水平分表与垂直分表的出现(我就是记录一下自己的理解) 水平分 ...
- sleep - 延迟指定数量的时间
总览 (SYNOPSIS) sleep [OPTION]... NUMBER[SUFFIX] 描述 (DESCRIPTION) 暂停 NUMBER 秒. SUFFIX 如果 是 s, 指 暂停 的 秒 ...
- 【leetcode】994. Rotting Oranges
题目如下: In a given grid, each cell can have one of three values: the value 0 representing an empty cel ...
- 【Flutter学习】页面跳转之SliverAppBar,CustomScrollView,NestedScrollView的使用
一,flutter SliverAppbar 控件介绍 SliverAppBar “应用栏” 相当于升级版的 appbar 于 AppBar 位置的固定的应用最上面的; 而 SliverAppBar ...
- mybatis generator工具集成(一)
第一步,pom中加入 <build> <plugins> <plugin> <groupId>org.springframework.boot</ ...
- Delphi直接读取XmL
有时,只需要用XML作一些小的应用,比如只是简单地保存日志或者一些配置,这时我们只需要直接读写XML就好,效率第一. Delphi盒子有一个直接读写XML文件 (例子和代码),其核心函数为下面两个函数 ...
- 1.zabbix编译安装(环境lnmp)
zabbix服务端安装 1.使用脚本安装.脚本内容如下.安装完用http://192.168.159.20/zabbix #!/bin/bash #使用说明,此版本是针对程序安装路径不在/opt/下的 ...
- 51nod1340地铁环线
经典题. 经典差分约束模型. 但是 显然这个总长是有上下界的. 直接二分总长,判断有没有负环 如果没有负环好办,有负环就不知道怎么偏了. 因为没有单调性! (如果所有没有单调性的函数图像,都知道往哪里 ...
- [NOIP模拟测试37]反思+题解
一定要分析清楚复杂度再打!!!窝再也不要花2h20min用暴力对拍暴力啦!!! 雨露均沾(滑稽),尽量避免孤注一掷.先把暴力分拿全再回来刚正解. 即使剩下的时间不多了也优先考虑认真读题+打暴力而非乱搞 ...