本帖最后由 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的第一步。
我在模板工程中的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的堆空间;如下图所示:
在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终端软件进行调试,芯片上电启动后如下图所示:
在MobaXterm终端软件中我们输入如下图所示代码后,敲入回车键后Python代码就自动解析执行了,并输出相对应的结果:
如果出现如下图所示的error提示,请检查一下MM32对于堆栈大小的配置情况,适当的调整一下就可以了:
后续 后续将继续来实现和分享通过串口来下载Python脚本并运行Python程序的功能、以及基于一块开发板来实现对模块的开发、调用、应用的全方位实现;通过对Python的支持,让更多的精力投入到应用功能的开发中去,同时也让资源相对不富裕的MCU有了施展的平台。
附件
|
花更少的钱,买更便宜的单片机,还想用最好用的脚本语言。PikaScript 满足全都要人群的需求。资源占用小,开发方便快捷。作者将PikaScript 成功部署到MM32平台,移植过程详细,值得鼓励。