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

痛点

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协议,允许修改和商用,但是要注意保留原作者的署名即可。

部署PikaScriptMM32平台

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文件中指定版本的源码和模块了。拉取过程如下所示:

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

其中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个文件夹下的所有.c源码文件分别添加到上面的3个Group当中,如下图所示:

  

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

  • 调整堆栈大小

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

  • 启动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终端软件进行调试,芯片上电启动后如下图所示:

  • 在线运行Python脚本程序

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

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

后续

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

附件

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

PikaScript模板工程:  PikaScript_Template.zip (8.2 MB)
---------------------
作者:xld0932
链接:https://bbs.21ic.com/icview-3232352-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值