为 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 命令,如果没有的话读者可以直接使用文末我已经移植好的工程),该过程是为了生成需要自动生成的代码。完成之后我们需要进行以下操作:

  1. 将整个 berry 文件夹移动到 stm32f103rb_berry 文件夹下,该文件夹中包含了 Berry 解释器的核心代码
  2. berry/generate 文件夹移动到 stm32f103rb_berry 文件夹下,这是在使用 make prebuild 命令时由 berry/tools/map_build/map_build.exe 工具自动生成的代码,文末的参考工程也给出了这些代码
  3. berry/default 文件夹中的源文件和头文件分别移动到 Src 文件夹和 Inc 文件夹下,该文件夹包含了一个 Berry 交互式解释器的默认实现,后面我们将通过调用它的主函数来运行该解释器

Berry 解释器需要从一种输入设备中读取字符流输入,在 PC 上可以使用 C 标准库中的 fgets() 函数或者 GNU/Readline 库中的 readline() 函数。本教程中使用 STM32 的 USART1 进行字符流传输,因此要实现基于串口的 readline() 函数。参考工程中的 stm32f103rb_berry/Src/readline.cstm32f103rb_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 进行反馈。

原文地址:https://www.cnblogs.com/skiars/p/11268726.html

时间: 2024-10-08 00:51:49

为 STM32 移植 Berry 脚本语言的相关文章

单片机脚本语言-移植lua到stm32-MDK

Lua简介 Lua[1]  是一个小巧的脚本语言.作者是巴西人.该语言的设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能. Lua脚本可以很容易的被C/C++ 代码调用,也可以反过来调用C/C++的函数,这使得Lua在应用程序中可以被广泛应用.不仅仅作为扩展脚本,也可以作为普通的配置文件,代替XML,Ini等文件格式,并且更容易理解和维护. Lua的目标是成为一个很容易嵌入其它语言中使用的语言.大多数程序员也认为它的确做到了这一点. 很多应用程序使用LUA作为自己的嵌入式脚本

动态网页脚本语言

在ASP .PHP.JSP环境下,HTML代码主要负责描述信息的显示样式,而程序代码则用来描述处理逻辑.普通的 HTML页面只依赖于Web服务器,而ASP .PHP.JSP页面需要附加的语言引擎分析和执行程序代码.程序代码的执行结果被重新嵌入到HTML代码中,然后一起发送给浏览器.ASP .PHP.JSP三者都是面向Web服务器的技术,客户端浏览器不需要任何附加的软件支持. 二.技术特点: (一)ASP的技术特点: 1. 使用VBScript .JScript等简单易懂的脚本语言,结合HTML代

Feekood脚本语言介绍(转)

转 http://blog.csdn.net/wooyoogame/article/details/43940511 想想现在的编程语言,可谓是五花八门,各有所长.技术的发展日新月异,可怜的程序猿不得不去阅读那些眼花缭乱的技术文档,了解那些层出不穷的开发框架,熟练使用各种各样的开发工具,搜索无法应对的开发问题. 试问有没有一门编程语言能够让程序猿在完全不用搭建任何开发环境,完全没有任何语言基础的情况下快速开发出简单实用.效果炫酷,而且跨平台的游戏或者应用呢. This is where amaz

SiKuli 图形脚本语言【转载】

Sikuli 是一种新颖的图形脚本语言,或者说是一种另类的自动化测试技术.它与我们常用的自动化测试技术(工具)有很大的区别. 当你看到上图sikuli的脚本时,一定会惊呼,这样都可以~!脚本加截图~~~ OK ,在惊讶过后,我们一起来大体的了解一下这个技术. 什么是Sikuli? Sikuli脚本自动化,你在屏幕上看到的任何东西.它使用图像识别,识别和控制GUI组件.这是有用的,当有一个GUI的内部或源代码的访问是不容易的. Sikuli(在墨西哥维乔印第安人的语言里是”上帝之眼”的意思)是由美

php、python、ruby——web脚本语言的比较

原文地址:http://klau.si/php-vs-python-vs-ruby 摘要 在过去几年里,在web应用程序编程中,脚本语言变得越来越受欢迎.本论文试图在如今三种最受欢迎的语言:PHP.Python和Ruby中找到其中的差异.优点.缺点.很明显,他们有他们自己的观点和支持,所以要陈述客观事实并满足一种科学的方法是一项困难的任务.这三种语言将在web应用程序环境进行有关历史.进化.流行程度.语法.语义.功能.安全性和性能方面做评估.最后,一个最终的结论将建议最有前途的一种语言. 1.介

java脚本语言学习心得

第一篇技术博客,一定要认真! 第一篇技术博客,一定要认真! 第一篇技术博客,一定要认真! 好了,进入正题: 一 什么是脚本语言? 程序的运行方式有两种:编译运行和解释运行 1.1 前者的典型代表是java, 从文件角度看分为三步: write[编写]: a.java文件(拿个记事本就能写,扩展名是.java), compile[编译]: 编译(cmd命令是java a.java,ide集成了编译器运行之前自动编译)之后产生了a.class文件(是一堆二进制码,人看不懂,是给虚拟机看的) 运行[r

关于JS脚本语言的基础语法

JS脚本语言的基础语法:输出语法  alert("警告!");  confirm("确定吗?");   prompt("请输入密码");为弱类型语言: 开始时要嵌入JS代码:<script type="text/javascript"></script>: 关于写程序是需注意的基本语法:1.所有的字符全都是英文半角的:2.大部分情况下每条语句结束后要加分号:3.每一块代码结束后加换行:4.程序前呼后应:

shell、cmd、dos和脚本语言区别和联系

问题一:DOS与windows中cmd区别   在windows系统中,"开始-运行-cmd"可以打开"cmd.exe",进行命令行操作. 操作系统可以分成核心(kernel)和Shell(外壳)两部分,其中,Shell是操作系统与外部的主要接口,位于操作系统的外层,为用户提供与操作系统核心沟通的途径.在windows系统中见到的桌面即explorer.exe(资源管理器)是图形shell,而cmd就是命令行shell.这算是cmd与dos的最大区别,一个只是接口.

自动化运维脚本语言之expect实践学习(1)

一.expect简介 expect是一种简单的基于Tcl的脚本语言工具,一个可实现自动交互功能的软件套件,其功能就是进行自动化的人机交互:也能够按照脚本内容里面设定的方式与交互式程序进行"会话"的程序,根据脚本内容expect可以知道程序会提示或反馈什么内容以及什么是正确的应答:它是一种可以提供"分支和嵌套结构"来引导程序流程的解释型脚本语言. shell功能虽然强大,但是不能实现有交互功能的多机器之间的操作例如ssh和ftp,而expect可以帮助我们来实现. 主