首页 > 评测 > 基于PikaScript在MM32平台上部署Python开发环境
基于PikaScript在MM32平台上部署Python开发环境
- [导读]
- 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后,即实现了程序代码的交互式运行;代码如下所示:
- 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)
- 本文系21ic原创,未经许可禁止转载!
网友评论
- 联系人:巧克力娃娃
- 邮箱:board@21ic.com
- 我要投稿
-
欢迎入驻,开放投稿
行业新闻
-
RAK831 Lite:基于树莓派3的LoRa网关... 2022-06-17
-
Imagination推出PowerVR AX2185和AX2145神经... 2022-06-17
-
ODROID-GO是一款兼容Arduino的基于ESP32的便... 2022-06-17
项目外包
more+
- PID温度控制器
预算:¥700003天前
- 摄像头的可视画面需要一直超上(linux系统,ARM)
预算:¥1000018小时前
- DICOM协议搭建(ARM架构,linux系统)
预算:¥1000017小时前
- 需MIPI摄像头隔离电路,实现3-4米能长距离
预算:¥1000017小时前
- 膨宫仪的控制板,控制流量和检测压力
预算:¥1000016分钟前
- 无创自动测量血压计 NIBP模块
预算:¥100007小时前