首页 > 评测 > 基于PikaScript在MM32平台上部署Python开发环境

基于PikaScript在MM32平台上部署Python开发环境

  
  • 作者:xld0932
  • 来源:21ic BBS
  • [导读]
  • MicroPython是Python3的精简实现,包括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协议,允许修改和商用,但是要注意保留原作者的署名即可。


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

在源码和模块拉取结果后,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后,即实现了程序代码的交互式运行;代码如下所示:
  1. void MCU_InitClock(void)
  2. {
  3.     /* 使能内部高速时钟HSI */
  4.     RCC->CR |= RCC_CR_HSION_MASK;
  5.     /* 等待内部高速时钟HSI稳定 */
  6.     while(RCC_CR_HSIRDY_MASK != (RCC->CR & RCC_CR_HSIRDY_MASK));
  7.  
  8.  
  9.     /* 选择HSI输出用作系统时钟 */
  10.     RCC->CFGR = RCC_CFGR_SW(0u);
  11.     /* 等待系统时钟选择状态稳定 */
  12.     while(RCC_CFGR_SWS(0u) != (RCC->CFGR & RCC_CFGR_SWS_MASK));
  13.  
  14.  
  15.     /* 复位除HSI之外的所有时钟 */
  16.     RCC->CR = RCC_CR_HSION_MASK;
  17.  
  18.     RCC->CIR = RCC->CIR;    /* 清除中断标志位 */
  19.     RCC->CIR = 0u;          /* 禁卡相应的中断 */
  20.  
  21.  
  22.     /* PWR/DBG时钟使能 */
  23.     RCC->APB1ENR |= (1u << 28u);
  24.  
  25.     /* 如果系统时钟需要达到最大频率120MHz时, 需要将VOS设置为1.7V */
  26.     PWR->CR1 = (PWR->CR1 & ~PWR_CR1_VOS_MASK) | PWR_CR1_VOS(3u);
  27.  
  28.  
  29.     /* 使能外部高速时钟HSE */
  30.     RCC->CR |= RCC_CR_HSEON_MASK;
  31.     /* 等待外部高速时钟HSE稳定 */
  32.     while(RCC_CR_HSERDY_MASK != (RCC->CR & RCC_CR_HSERDY_MASK));
  33.  
  34.  
  35.     /* PLL1 = HSE * (MUL + 1) / (DIV + 1)
  36.             = 12MHz * 20 / 2
  37.             = 120MHz
  38.     */
  39.     RCC->PLL1CFGR = RCC_PLL1CFGR_PLL1SRC(1) |   /* 0:HSI作为PLL1时钟源, 1:HSE作为PLL1时钟源 */
  40.                     RCC_PLL1CFGR_PLL1MUL(19)|   /* PLL1倍频系数 */
  41.                     RCC_PLL1CFGR_PLL1DIV(1) |   /* PLL1分频系数 */
  42.                     RCC_PLL1CFGR_PLL1LDS(1) |   /* PLL1锁定检测器精度选择: 高精度 */
  43.                     RCC_PLL1CFGR_PLL1ICTRL(3);  /* PLL1输入时钟源大于等于8MHz时,推荐设置值为2'b11
  44.                                                    PLL1输入时钟源小于    8MHz时,推荐设置值为2'b01 */
  45.  
  46.  
  47.     /* 使能PLL1 */
  48.     RCC->CR |= RCC_CR_PLL1ON_MASK;
  49.     /* 等待PLL1稳定 */
  50.     while((RCC->CR & RCC_CR_PLL1RDY_MASK) == 0);
  51.  
  52.  
  53.     /* FLASH时钟使能 */
  54.     RCC->AHB1ENR |= (1u << 13u);
  55.     FLASH->ACR    = FLASH_ACR_LATENCY(4u) |     /* 0 : 零个等待状态, 当 0MHz < SYSCLK <= 24MHz
  56.                                                    1 : 一个等待状态, 当24MHz < SYSCLK <= 48MHz
  57.                                                    2 : 二个等待状态, 当48MHz < SYSCLK <= 72MHz
  58.                                                    3 : 三个等待状态, 当72MHz < SYSCLK <= 96MHz
  59.                                                    4 : 四个等待状态, 当96MHz < SYSCLK <= 120MHz */
  60.                     FLASH_ACR_PRFTBE_MASK;      /* 预取缓冲区开启 */
  61.  
  62.  
  63.     /* 时钟配置 */
  64.     RCC->CFGR = RCC_CFGR_HPRE(0)    |           /* AHB 预分频系数, HCLK
  65.                                                    0xxx : SYSCLK  不分频
  66.                                                    1000 : SYSCLK   2分频
  67.                                                    1001 : SYSCLK   4分频
  68.                                                    1010 : SYSCLK   8分频
  69.                                                    1011 : SYSCLK  16分频
  70.                                                    1100 : SYSCLK  64分频
  71.                                                    1101 : SYSCLK 128分频
  72.                                                    1110 : SYSCLK 256分频
  73.                                                    1111 : SYSCLK 512分频 */
  74.                 RCC_CFGR_PPRE1(0x4) |           /* APB1预分频系数, PCLK1
  75.                                                    0xx : HCLK 不分频
  76.                                                    100 : HCLK  2分频
  77.                                                    101 : HCLK  4分频
  78.                                                    110 : HCLK  8分频
  79.                                                    111 : HCLK 16分频 */
  80.                 RCC_CFGR_PPRE2(0x4) |           /* APB2预分频系数, PCLK2
  81.                                                    0xx : HCLK 不分频
  82.                                                    100 : HCLK  2分频
  83.                                                    101 : HCLK  4分频
  84.                                                    110 : HCLK  8分频
  85.                                                    111 : HCLK 16分频 */
  86.                 RCC_CFGR_MCO(7);                /* MCO输出时钟源选择
  87.                                                    000x : 没有时钟输出
  88.                                                    0010 : LSI时钟输出
  89.                                                    0011 : LSE时钟输出
  90.                                                    0100 : SYSCLK时钟输出
  91.                                                    0101 : HSI时钟输出
  92.                                                    0110 : HSE时钟输出
  93.                                                    0111 : PLL1时钟输出
  94.                                                    1000 : PLL2时钟输出 */
  95.  
  96.  
  97.     /* ADC1预分频(频率范围15MHz - 48MHz)
  98.                  = PCLK2 / (PRE + 2),要求PRE为偶数,使占空比为50%
  99.                  = 60MHz / ( 2  + 2)
  100.                  = 15MHz */
  101.     RCC_SetADCClockDiv(ADC1, 2);
  102.  
  103.     /* ADC1 calibration时钟分频(频率范围187.5kHz - 1.5MHz)
  104.                                = PCLK2 / (PRECAL + 2),要求PRECAL为偶数,使占空比为50%
  105.                                = 60MHz / (58     + 2)
  106.                                = 1MHz */
  107.     RCC_SetADCClockDiv(ADC1, 58);
  108.  
  109.  
  110.     /* 选择PLL输出用作系统时钟 */
  111.     RCC->CFGR = (RCC->CFGR & ~RCC_CFGR_SW_MASK) | RCC_CFGR_SW(2);
  112.     /* 等待系统时钟选择状态稳定 */
  113.     while((RCC->CFGR & RCC_CFGR_SWS_MASK) != RCC_CFGR_SWS(2));
  114. }
  115.  
  116. void MCU_InitUART1(void)
  117. {
  118.     GPIO_Init_Type GPIO_InitStructure;
  119.     UART_Init_Type UART_InitStructure;
  120.  
  121.     /* 先配置GPIO, 再配置UART参数, 否则UART ENABLE后会有一个0xFF的异常字节 */
  122.     RCC_EnableAHB1Periphs(RCC_AHB1_PERIPH_GPIOB, true);
  123.  
  124.     GPIO_PinAFConf(GPIOB, GPIO_PIN_6, GPIO_AF_7);   /* PB6 <-> UART1_TX */
  125.     GPIO_PinAFConf(GPIOB, GPIO_PIN_7, GPIO_AF_7);   /* PB7 <-> UART1_RX */
  126.  
  127.     GPIO_InitStructure.Pins     = GPIO_PIN_6;
  128.     GPIO_InitStructure.PinMode  = GPIO_PinMode_AF_PushPull;
  129.     GPIO_InitStructure.Speed    = GPIO_Speed_50MHz;
  130.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  131.  
  132.     GPIO_InitStructure.Pins     = GPIO_PIN_7;
  133.     GPIO_InitStructure.PinMode  = GPIO_PinMode_In_Floating;
  134.     GPIO_InitStructure.Speed    = GPIO_Speed_50MHz;
  135.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  136.  
  137.     RCC_EnableAPB2Periphs(RCC_APB2_PERIPH_UART1, true);
  138.  
  139.     UART_InitStructure.ClockFreqHz   = CLOCK_APB2_FREQ;
  140.     UART_InitStructure.BaudRate      = 115200;
  141.     UART_InitStructure.WordLength    = UART_WordLength_8b;
  142.     UART_InitStructure.StopBits      = UART_StopBits_1;
  143.     UART_InitStructure.Parity        = UART_Parity_None;
  144.     UART_InitStructure.XferMode      = UART_XferMode_RxTx;
  145.     UART_InitStructure.HwFlowControl = UART_HwFlowControl_None;
  146.     UART_Init(UART1, &UART_InitStructure);
  147.  
  148.     UART_Enable(UART1,   true);
  149. }
  150.  
  151. int fputc(int ch, FILE *f)
  152. {
  153.     UART_PutData(UART1, (uint8_t)ch);
  154.     while((UART_GetStatus(UART1) & UART_STATUS_TX_DONE) == 0);
  155.  
  156.     return ch;
  157. }
  158.  
  159. int fgetc(FILE *f)
  160. {
  161.     while((UART_GetStatus(UART1) & UART_STATUS_RX_DONE) == 0u);
  162.     return UART_GetData(UART1);
  163. }
  164.  
  165. void InitSystem(void)
  166. {
  167.     MCU_InitClock();
  168.  
  169.     MCU_InitUART1();
  170. }
  171.  
  172. int main(void)
  173. {
  174.     InitSystem();
  175.  
  176.     printf("\r\n");
  177.     printf("\r\nPikaScript PLUS-F5270(MM32F5277E9P) %s %s", __DATE__, __TIME__);
  178.     printf("\r\n");
  179.     printf("\r\n------------------------------------------------------------------");
  180.     printf("\r\n|                                                                |");
  181.     printf("\r\n|     ____   _   __            _____              _          __  |");
  182.     printf("\r\n|    / __ \\ (_) / /__ ____ _  / ___/ _____ _____ (_) ____   / /_ |");
  183.     printf("\r\n|   / /_/ // / / //_// __ `/  \\__ \\ / ___// ___// / / __ \\ / __/ |");
  184.     printf("\r\n|  / ____// / / ,<  / /_/ /  ___/ // /__ / /   / / / /_/ // /_   |");
  185.     printf("\r\n| /_/    /_/ /_/|_| \\__,_/  /____/ \\___//_/   /_/ / .___/ \\__/   |");
  186.     printf("\r\n|                                                /_/             |");
  187.     printf("\r\n|          PikaScript - An Ultra Lightweight Python Engine       |");
  188.     printf("\r\n|                                                                |");
  189.     printf("\r\n|           [ https://github.com/pikastech/pikascript ]          |");
  190.     printf("\r\n|           [  https://gitee.com/lyon1998/pikascript  ]          |");
  191.     printf("\r\n|                                                                |");
  192.     printf("\r\n------------------------------------------------------------------");
  193.     printf("\r\n");
  194.  
  195.     PikaObj *pikaMain = pikaScriptInit();
  196.     goto main_loop;
  197.  
  198. main_loop:
  199.     pikaScriptShell(pikaMain);
  200.  
  201.     /* after exit() from pika shell */
  202.     NVIC_SystemReset();
  203. }
  204.  
  205. char __platform_getchar(void)
  206. {
  207.     return getchar();
  208. }
复制代码

 

编译程序无误后,我们将代码下载到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)


 

 

  • 本文系21ic原创,未经许可禁止转载!

网友评论