发新帖本帖赏金 80.00元(功能说明)我要提问
返回列表

[MM32生态] 基于PikaScript在MM32平台上部署Python开发环境

[复制链接]
864|6
手机看帖
扫描二维码
随时随地手机跟帖
xld0932|  楼主 | 2022-6-16 14:09 | 显示全部楼层 |阅读模式
本帖最后由 xld0932 于 2022-6-16 14:11 编辑

#申请原创#   @21小跑堂


痛点
MicroPython是Python3的精简实现,包括Python标准库的一小部分,经过优化可在微控制器和受限的环境中运行,在官方提供了相应的开发板,但最低的配置都需要32KB的SRAM空间和4KB的STACK空间,对MCU的性能也有绝对的要求,基于STM32F103的MicroPython开发板在某宝上几乎没有,最少也得STM32F4及以上系列的才玩得动,那我想在资源有限的MCU上运行Python程序,难道就不配吗?

想要在一个MCU上运行Python程序,一般步骤如下:首先得需要一个Linux环境(大多数人选择通过虚拟机来安装Linux)、然后需要更新相应的命令工具并下载交叉编译工具和编译器(交叉编译器选择gcc-arm-none-eabi、编译器选择gcc)、接着需要下载MicroPython源代码,生成固件程序(建议选择官方已经支持的开发板,否则需要自行实现和移植)、最后通过烧录工具或者使用USB的DFU模式烧录程序到MCU、至此才可以开始使用Python编程来实现应用功能;期间一步都不能错哦……但对于做MCU嵌入式开发的工程师来说,我们常用的都是KEIL、IAR这些IDE集成开发环境,难道一定得按照上面步骤一步步来吗,就不能使用KEIL来开发、调试了?


PiKaScript应运而生
PiKaScript可以为资源受限的MCU提供极易部署和拓展的Python脚本支持。PiKaScript不需要操作系统和文件系统,支持裸机运行,最低可运行在RAM≥4KB,FLASH≥32KB的MCU中,而且还支持KEIL、IAR等IDE集成开发环境。此外PiKaScript是完全开源的(https://github.com/pikasTech/pikascript),采样的是MIT协议,允许修改和商用,但是要注意保留原作者的署名即可。


部署PikaScript到MM32平台
PikaScript可以在所有支持libc的裸机和操作系统上运行,只需要编译器能够支持C99标准即可。当前PikaScript仅支持32位和64位内核的MCU,暂不支持8位内核的MCU;考虑到拓展模块的资源占用情况,如果是ARM内核的MCU推荐最低应该配备64KB FLASH和8KB SRAM,如果是RISC内核的MCU推荐最低应该配备128KB FLASH和8KB SRAM。

  • 准备模板工程
我当前使用的IDE集成开发环境是KEIL MDK,在部署PikaScript到MM32之前,我们需要新建一个基于MM32 MCU的模板工程,这个模板工程只需要实现printf功能的串口初始化即可,重载fputc和fgetc这两个函数,为后面实现功能做准备,至此我们就完成了部署PikaScript的第一步。

  • 获取PikaScript源码和工具集
我在模板工程中的Source文件夹中新建立一个PikaScript文件夹作为PikaScript部署路径;然后我们需要到GIT上去下载PikaScript包管理器:pikaPackage.exe,将这个包管理器存放在PikaScript文件夹下,通过这个包管理器我们可以轻松地拉取指定版本的源码和模块;接下来我们在PikaScript文件夹下新建一个requestment.txt文件,然后写入如下内容:
pikascript-core==v1.8.6
PikaStdLib==v1.8.6

如上内容表示使用1.8.6版本的pikascript解释器内核和1.8.6版本的标准库,解释器内核和标准库是必选项,且这两个版本号需要保持一致,而其它的模块则是可以有选择性的添加;在初始部署时,尽可能的只添加解释器内核和标准库即可,这样可以遇到兼容性的问题;版本号可以通过http://pikascript.com/这个网址来查看,当然你也可以通过这个网址来自动生成工程……

现在PikaScript文件夹下就有了pikaPackage.exe和requestment.txt这两个文件,双击运行pikaPackage.exe就可以拉取requestment.txt文件中指定版本的源码和模块了。拉取过程如下所示:
1.png

在源码和模块拉取结果后,PikaScript文件夹就多了不少文件,如下图所示:
2.png

其中pikascript-api文件夹下存放的是模块API相关文件,在预编译前这个文件夹是空的,pikascript-core文件夹下存放的是内核相关文件,pikascript-lib文件夹下存放的是模块库,rust-msc-latest-win10.exe是预编译器。然后我们在PikaScript文件夹新建一个main.py文件,然后写入:
import PikaStdLib
print('Hello PikaScript!')
其中import PikaStdLib表示导入标准库,而且标准库是必需要导入的;而print('Hello PikaScript!')则是用来测试pikascript是否正常启动。

  • 预编译模块
pikascript预编译器可以把python模块预编译为.c和.h文件;接下来我们运行PikaScript文件夹下的rust-msc-latest-win10.exe预编译器,它会将main.py和导入的模块预编译为pikascript的API文件,预编译后的文件存放在pikascript-api文件夹下;我们打开pikascript-api文件夹会发现多了很多.c和.h的文件,这就说明预编译成功运行了。

  • 添加源码
我们使用KEIL软件模板工程,在模板工程中添加3个Group,分别命名为:pikascript-api、pikascript-core、pikascript-lib,这也是PikaScript文件夹下的3个文件夹名,如下图所示:
3.png

然后将这3个文件夹下的所有.c源码文件分别添加到上面的3个Group当中,如下图所示:
6.png 7.png 8.png

然后设置KEIL编译器的Include Path,如下图所示:
4.png

  • 调整堆栈大小
我们可以在启动文件中修改堆和栈的大小,也可以通过修改建议SCF文件来修改堆和栈的大小;对于PikaScript的部署建议分配4KB的栈空间和16KB的堆空间;如下图所示:
11.png

  • 启动PikaScript
在main.c中添加PikaScript的头文件和启动代码,在代码中通过重载fgetc函数结合libc实现了getchar函数功能,再通过覆用pikascript中读取用户输入字节的底层接口函数__platform_getchar(),加上启动PickScript Shell后,即实现了程序代码的交互式运行;代码如下所示:
void MCU_InitClock(void)
{
    /* 使能内部高速时钟HSI */
    RCC->CR |= RCC_CR_HSION_MASK;
    /* 等待内部高速时钟HSI稳定 */
    while(RCC_CR_HSIRDY_MASK != (RCC->CR & RCC_CR_HSIRDY_MASK));


    /* 选择HSI输出用作系统时钟 */
    RCC->CFGR = RCC_CFGR_SW(0u);
    /* 等待系统时钟选择状态稳定 */
    while(RCC_CFGR_SWS(0u) != (RCC->CFGR & RCC_CFGR_SWS_MASK));


    /* 复位除HSI之外的所有时钟 */
    RCC->CR = RCC_CR_HSION_MASK;

    RCC->CIR = RCC->CIR;    /* 清除中断标志位 */
    RCC->CIR = 0u;          /* 禁卡相应的中断 */


    /* PWR/DBG时钟使能 */
    RCC->APB1ENR |= (1u << 28u);

    /* 如果系统时钟需要达到最大频率120MHz时, 需要将VOS设置为1.7V */
    PWR->CR1 = (PWR->CR1 & ~PWR_CR1_VOS_MASK) | PWR_CR1_VOS(3u);


    /* 使能外部高速时钟HSE */
    RCC->CR |= RCC_CR_HSEON_MASK;
    /* 等待外部高速时钟HSE稳定 */
    while(RCC_CR_HSERDY_MASK != (RCC->CR & RCC_CR_HSERDY_MASK));


    /* PLL1 = HSE * (MUL + 1) / (DIV + 1)
            = 12MHz * 20 / 2
            = 120MHz
    */
    RCC->PLL1CFGR = RCC_PLL1CFGR_PLL1SRC(1) |   /* 0:HSI作为PLL1时钟源, 1:HSE作为PLL1时钟源 */
                    RCC_PLL1CFGR_PLL1MUL(19)|   /* PLL1倍频系数 */
                    RCC_PLL1CFGR_PLL1DIV(1) |   /* PLL1分频系数 */
                    RCC_PLL1CFGR_PLL1LDS(1) |   /* PLL1锁定检测器精度选择: 高精度 */
                    RCC_PLL1CFGR_PLL1ICTRL(3);  /* PLL1输入时钟源大于等于8MHz时,推荐设置值为2'b11
                                                   PLL1输入时钟源小于    8MHz时,推荐设置值为2'b01 */


    /* 使能PLL1 */
    RCC->CR |= RCC_CR_PLL1ON_MASK;
    /* 等待PLL1稳定 */
    while((RCC->CR & RCC_CR_PLL1RDY_MASK) == 0);


    /* FLASH时钟使能 */
    RCC->AHB1ENR |= (1u << 13u);
    FLASH->ACR    = FLASH_ACR_LATENCY(4u) |     /* 0 : 零个等待状态, 当 0MHz < SYSCLK <= 24MHz
                                                   1 : 一个等待状态, 当24MHz < SYSCLK <= 48MHz
                                                   2 : 二个等待状态, 当48MHz < SYSCLK <= 72MHz
                                                   3 : 三个等待状态, 当72MHz < SYSCLK <= 96MHz
                                                   4 : 四个等待状态, 当96MHz < SYSCLK <= 120MHz */
                    FLASH_ACR_PRFTBE_MASK;      /* 预取缓冲区开启 */


    /* 时钟配置 */
    RCC->CFGR = RCC_CFGR_HPRE(0)    |           /* AHB 预分频系数, HCLK
                                                   0xxx : SYSCLK  不分频
                                                   1000 : SYSCLK   2分频
                                                   1001 : SYSCLK   4分频
                                                   1010 : SYSCLK   8分频
                                                   1011 : SYSCLK  16分频
                                                   1100 : SYSCLK  64分频
                                                   1101 : SYSCLK 128分频
                                                   1110 : SYSCLK 256分频
                                                   1111 : SYSCLK 512分频 */
                RCC_CFGR_PPRE1(0x4) |           /* APB1预分频系数, PCLK1
                                                   0xx : HCLK 不分频
                                                   100 : HCLK  2分频
                                                   101 : HCLK  4分频
                                                   110 : HCLK  8分频
                                                   111 : HCLK 16分频 */
                RCC_CFGR_PPRE2(0x4) |           /* APB2预分频系数, PCLK2
                                                   0xx : HCLK 不分频
                                                   100 : HCLK  2分频
                                                   101 : HCLK  4分频
                                                   110 : HCLK  8分频
                                                   111 : HCLK 16分频 */
                RCC_CFGR_MCO(7);                /* MCO输出时钟源选择
                                                   000x : 没有时钟输出
                                                   0010 : LSI时钟输出
                                                   0011 : LSE时钟输出
                                                   0100 : SYSCLK时钟输出
                                                   0101 : HSI时钟输出
                                                   0110 : HSE时钟输出
                                                   0111 : PLL1时钟输出
                                                   1000 : PLL2时钟输出 */


    /* ADC1预分频(频率范围15MHz - 48MHz)
                 = PCLK2 / (PRE + 2),要求PRE为偶数,使占空比为50%
                 = 60MHz / ( 2  + 2)
                 = 15MHz */
    RCC_SetADCClockDiv(ADC1, 2);

    /* ADC1 calibration时钟分频(频率范围187.5kHz - 1.5MHz)
                               = PCLK2 / (PRECAL + 2),要求PRECAL为偶数,使占空比为50%
                               = 60MHz / (58     + 2)
                               = 1MHz */
    RCC_SetADCClockDiv(ADC1, 58);


    /* 选择PLL输出用作系统时钟 */
    RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW_MASK) | RCC_CFGR_SW(2);
    /* 等待系统时钟选择状态稳定 */
    while((RCC->CFGR & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS(2));
}

void MCU_InitUART1(void)
{
    GPIO_Init_Type GPIO_InitStructure;
    UART_Init_Type UART_InitStructure;

    /* 先配置GPIO, 再配置UART参数, 否则UART ENABLE后会有一个0xFF的异常字节 */
    RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true);

    GPIO_PinAFConf(GPIOB, GPIO_PIN_6, GPIO_AF_7);   /* PB6 <-> UART1_TX */
    GPIO_PinAFConf(GPIOB, GPIO_PIN_7, GPIO_AF_7);   /* PB7 <-> UART1_RX */

    GPIO_InitStructure.Pins     = GPIO_PIN_6;
    GPIO_InitStructure.PinMode  = GPIO_PinMode_AF_PushPull;
    GPIO_InitStructure.Speed    = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    GPIO_InitStructure.Pins     = GPIO_PIN_7;
    GPIO_InitStructure.PinMode  = GPIO_PinMode_In_Floating;
    GPIO_InitStructure.Speed    = GPIO_Speed_50MHz;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_UART1, true);

    UART_InitStructure.ClockFreqHz   = CLOCK_APB2_FREQ;
    UART_InitStructure.BaudRate      = 115200;
    UART_InitStructure.WordLength    = UART_WordLength_8b;
    UART_InitStructure.StopBits      = UART_StopBits_1;
    UART_InitStructure.Parity        = UART_Parity_None;
    UART_InitStructure.XferMode      = UART_XferMode_RxTx;
    UART_InitStructure.HwFlowControl = UART_HwFlowControl_None;
    UART_Init(UART1, &UART_InitStructure);

    UART_Enable(UART1,   true);
}

int fputc(int ch, FILE *f)
{
    UART_PutData(UART1, (uint8_t)ch);
    while((UART_GetStatus(UART1) & UART_STATUS_TX_DONE) == 0);

    return ch;
}

int fgetc(FILE *f)
{
    while((UART_GetStatus(UART1) & UART_STATUS_RX_DONE) == 0u);
    return UART_GetData(UART1);
}

void InitSystem(void)
{
    MCU_InitClock();

    MCU_InitUART1();
}

int main(void)
{
    InitSystem();

    printf("\r\n");
    printf("\r\nPikaScript PLUS-F5270(MM32F5277E9P) %s %s", __DATE__, __TIME__);
    printf("\r\n");
    printf("\r\n------------------------------------------------------------------");
    printf("\r\n|                                                                |");
    printf("\r\n|     ____   _   __            _____              _          __  |");
    printf("\r\n|    / __ \\ (_) / /__ ____ _  / ___/ _____ _____ (_) ____   / /_ |");
    printf("\r\n|   / /_/ // / / //_// __ `/  \\__ \\ / ___// ___// / / __ \\ / __/ |");
    printf("\r\n|  / ____// / / ,<  / /_/ /  ___/ // /__ / /   / / / /_/ // /_   |");
    printf("\r\n| /_/    /_/ /_/|_| \\__,_/  /____/ \\___//_/   /_/ / .___/ \\__/   |");
    printf("\r\n|                                                /_/             |");
    printf("\r\n|          PikaScript - An Ultra Lightweight Python Engine       |");
    printf("\r\n|                                                                |");
    printf("\r\n|           [ https://github.com/pikastech/pikascript ]          |");
    printf("\r\n|           [  https://gitee.com/lyon1998/pikascript  ]          |");
    printf("\r\n|                                                                |");
    printf("\r\n------------------------------------------------------------------");
    printf("\r\n");

    PikaObj *pikaMain = pikaScriptInit();
    goto main_loop;

main_loop:
    pikaScriptShell(pikaMain);

    /* after exit() from pika shell */
    NVIC_SystemReset();
}

char __platform_getchar(void)
{
    return getchar();
}

编译程序无误后,我们将代码下载到MM32芯片,将MM32的UART通过USB转TTL工具连接到电脑,打开MobaXterm终端软件进行调试,芯片上电启动后如下图所示:
9.png

  • 在线运行Python脚本程序
在MobaXterm终端软件中我们输入如下图所示代码后,敲入回车键后Python代码就自动解析执行了,并输出相对应的结果:
10.png

如果出现如下图所示的error提示,请检查一下MM32对于堆栈大小的配置情况,适当的调整一下就可以了:
5.png


后续
后续将继续来实现和分享通过串口来下载Python脚本并运行Python程序的功能、以及基于一块开发板来实现对模块的开发、调用、应用的全方位实现;通过对Python的支持,让更多的精力投入到应用功能的开发中去,同时也让资源相对不富裕的MCU有了施展的平台。


附件
模板工程: PikaScript.zip (665.18 KB)

使用特权

评论回复

打赏榜单

21小跑堂 打赏了 80.00 元 2022-06-16
理由:恭喜通过原创文章审核!请多多加油哦!

评论
21小跑堂 2022-6-16 17:59 回复TA
花更少的钱,买更便宜的单片机,还想用最好用的脚本语言。PikaScript 满足全都要人群的需求。资源占用小,开发方便快捷。作者将PikaScript 成功部署到MM32平台,移植过程详细,值得鼓励。 
gouguoccc| | 2022-6-17 07:59 | 显示全部楼层
谢谢分享,空了也玩玩。

使用特权

评论回复
xld0932|  楼主 | 2022-6-17 08:28 | 显示全部楼层
gouguoccc 发表于 2022-6-17 07:59
谢谢分享,空了也玩玩。

使用特权

评论回复
www5911839| | 2022-6-17 10:18 | 显示全部楼层
感谢大佬分享,相当Nice。
大佬,我看你闲鱼上出好多开发板啊,你这思路太强了。学会用完,物尽其用,得空要在你闲鱼上淘淘宝


使用特权

评论回复
xld0932|  楼主 | 2022-6-17 11:16 | 显示全部楼层
www5911839 发表于 2022-6-17 10:18
感谢大佬分享,相当Nice。
大佬,我看你闲鱼上出好多开发板啊,你这思路太强了。学会用完,物尽其用,得空 ...

使用特权

评论回复
tail066| | 2022-6-18 21:38 | 显示全部楼层
嗯,可以可以,好东西

使用特权

评论回复
发新帖 本帖赏金 80.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则