为 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等开发方 ...
随机推荐
- shell变量及相关命令
- 一 shell编程
好啦.从今天开始我们转入shell编程的行列.从鸟哥私房菜中,已经学到了一些shell编程的皮毛,这两个月打算系统的学习,学会,学熟练.加油吧 bash shell [root@localhost s ...
- windows server 进入组策略管理
方法: win+R 然后输入 gpmc.msc 即可在域服务器上进行组策略管理了.
- 内嵌iframe撑高父容器,底部有4px留白问题解决办法
由于iframe是特殊标签, 1,iframe默认是块元素,其display样式默认值是block2,frame应该说即不是块元素也不是行内元素,它虽然有display样式,但其默认值是none3,f ...
- JavaSE---Annotation
1.概述 1.1 JDK1.5开始,java提供了对Annotation的支持: 1.2 Annotation其实就是 代码中的特殊标记,这些标记 可以在编译.类加载.运行时被读取,并执行相应的处理: ...
- 【leetcode】983. Minimum Cost For Tickets
题目如下: In a country popular for train travel, you have planned some train travelling one year in adva ...
- Database基础(一):构建MySQL服务器、 数据库基本管理 、MySQL 数据类型、表结构的调整
一.构建MySQL服务器 目标: 本案例要求熟悉MySQL官方安装包的使用,快速构建一台数据库服务器: 安装MySQL-server.MySQl-client软件包 修改数据库用户root的密码 确认 ...
- echarts 柱状图 X(Y)轴数据过多时,滑动以及内置缩放的问题
前言:在开发中碰到的情况(菜鸟出门). 在使用echarts 图表的时候发现要展示的数据过多,但是系统留的展示框太小,造成数据都挤压在一块(不好看而且新感觉很不专业). ...
- \pset 、\x命令
\pset命令用于设置输出的格式 \pset border 0:表示输出内容无边框.示例如下: \pset border 1:表示边框只在内部.示例如下: \pset border 2:表示内外都有边 ...
- 建站手册-浏览器信息:Internet Explorer 浏览器
ylbtech-建站手册-浏览器信息:Internet Explorer 浏览器 1.返回顶部 1. http://www.w3school.com.cn/browsers/browsers_inte ...