# pikapython-bluepill **Repository Path**: Lyon1998/pikapython-bluepill ## Basic Information - **Project Name**: pikapython-bluepill - **Description**: pikapython-bluepill - **Primary Language**: Unknown - **License**: GPL-3.0 - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2024-01-26 - **Last Updated**: 2025-06-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # PikaPython On BluePill ![输入图片说明](assets/256f16c761a2c7c31eb242f86cf1be5.jpg) **How To Play Python** 章节介绍了如何在 PikaPython-BluePill 上使用 Python 进行编程,适合初学者。 **How To Make Python** 章节介绍了如何在 AT32F403A 上制作 Python 固件,适合进阶学习,需要有较好的 C 语言基础、RTOS、文件系统等中间件的基础。 ## How To Play Python (How To Make Python 章节结束后开始更新 How To Play Python) ## How To Make Python ### 准备基础工程 首先准备制作 Python 固件的基础工程,为了便于在后续开发中支持操作系统,这里选择了使用雅特力提供的带有 FreeRTOS 支持的 DEMO 工程。 首先下载 AT32F403A_407_Firmware_Library_V2.1.7 资料包,找到里面的 AT32F403A_407_Firmware_Library_V2.1.7\utilities\at32f403a_407_freertos_demo,然后将一些无关的配置项清除掉,只留下 at_start_at32f403a_ac6 的配置项。 ![image-20240129165115043](assets/image-20240129165115043.png) 芯片选择 AT32F403ACGT7 ![image-20240129165137300](assets/image-20240129165137300.png) 为了保持工程的简洁,将 SDK 里面的这三个文件夹移动到工程所在的目录,然后将工程的路径进行修改。 ![image-20240129165214186](assets/image-20240129165214186.png) 做完这一步后可以得到一个独立的、干净的 FreeRTOS DEMO 工程,做完这一步的 commit id: `581f835d174f22f0a5e2a8ed4d4f4069d31094b4` ### 移植 PikaPython 首先调整一下系统的堆栈。 因为这是带 FreeRTOS 的工程,所以堆是由 FreeRTOS 进行管理的,我们可以看到这个 Demo 使用的是 heap_4。 ![image-20240129170136894](assets/image-20240129170136894.png) 所以我们在 FreeRTOSConfig.h 里面修改 `configTOTAL_HEAP_SIZE` 就可以改变系统的堆栈大小。 ![image-20240129170029358](assets/image-20240129170029358.png) 我们把堆先调到 64K,比较常见的 Python 应用就足够运行了。 ```c #define configTOTAL_HEAP_SIZE ( ( size_t ) ( 64 * 1024 ) ) ``` 接下来添加 PikaPython 的源代码: ![image-20240129170348028](assets/image-20240129170348028.png) 在工程根目录新建一个 PikaPython 文件夹来放置 PikaPython 的源代码。 打开 PikaPython 的文档:https://pikapython.com/doc/ ![image-20240129170515824](assets/image-20240129170515824.png) 找到下载 PikaPython 包管理器的章节,下载包管理器,将包管理放到 PikaPython 的目录里面: ![image-20240129170615304](assets/image-20240129170615304.png) 然后双击运行就可以了,包管理器会自动创建一个 PikaPython 的最小项目的源码。 运行成功后就是这样样子: ![image-20240129170754053](assets/image-20240129170754053.png) 然后参考文档的说明,把源码和包含路径都添加好 ![image-20240129171049718](assets/image-20240129171049718.png) 在 main.c 里面加入 PikaPython 的启动代码: - 包含路径 ```C #include "pikaScript.h" ``` - PikaPython 的 Task ```C /* pikapython task function */ void pikapython_task(void *pvParameters) { pikaScriptInit(); while(1) { vTaskDelay(1000); } } ``` - 在 main.c 中增加启动 PikaPython 的 Task 任务 ```C /* create pikapython task */ if(xTaskCreate(pikapython_task, "pikapython_task", 512, NULL, 2, NULL) != pdPASS) { printf("pikapython_task could not be created as there was insufficient heap memory remaining.\r\n"); } else { printf("pikapython_task was created successfully.\r\n"); } ``` 直接编译运行会出现问题,这是因为 PikaPython 默认使用的是 libc 的 malloc 和 free,我们这里用的是 FreeRTOS 的 heap,所以要换成 FreeRTOS 的堆的接口。 ![image-20240129171719774](assets/image-20240129171719774.png) 我们可以在 PikaPlatform.c 里面看到这些平台接口的默认实现 ![image-20240129171819470](assets/image-20240129171819470.png) 这些平台接口都是 `weak` 函数,所以我们可以在自己的 .c 文件里面覆盖这些默认实现。 我们出于工程结构的考虑,将这些 .c 文件放到新建的 PikaPython-port 文件夹里面 ![image-20240129172056624](assets/image-20240129172056624.png) 把源文件和路径配置好: ![image-20240129172208042](assets/image-20240129172208042.png) ``` C // pika_platform_port.c #include "pikaScript.h" #include "FreeRTOS.h" #include "task.h" #include "pika_platform_port.h" void* pika_platform_malloc(size_t size) { return pvPortMalloc(size); } void pika_platform_free(void* ptr) { free(ptr); } ``` ```C // pika_platform_port.h #ifndef _PIKA_PLATFORM_PORT_H_ #define _PIKA_PLATFORM_PORT_H_ #endif ``` 现在 `hello pikapython!` 可以被打印出来,就说明 PikaPython 的内核可以运行成功了。 ![image-20240129173110053](assets/image-20240129173110053.png) 完成到这一步的 commit id: `b742e346e9d429963c89abe9cf92b694909179d3` ### 支持交互运行 为了支持交互运行,需要给 PikaPython 提供用户输入的接口,实现的方式参考文档: ![image-20240129233841725](assets/image-20240129233841725.png) 这个工程使用的是串口1作为 stdio 的打印口,我们也继续使用串口1作为 pikapython 的 `getchar()` 的接口。 为了实现串口的接收,我们先研究一下 AT32 给出的串口接收例程 AT32F403A_407_Firmware_Library_V2.1.7\project\at_start_f403a\examples\usart\polling ![image-20240129234930582](assets/image-20240129234930582.png) 可以看到有一个串口接收使能的设置,我们在串口初始化里面也加入这个配置 ![image-20240129235019043](assets/image-20240129235019043.png) 下一步是分析一下接收的代码: ![image-20240129235053758](assets/image-20240129235053758.png) 这里是通过不断查询接收标志位来判断是否有数据接收,然后将接收到的数据放到接收缓冲区里面。 所以 PikaPython 的交互运行的接收可以这样写: ```C // pika_platform_port.c #include "at32f403a_407_board.h" ... char pika_platform_getchar(void){ while(usart_flag_get(PRINT_UART, USART_RDBF_FLAG) == RESET); return usart_data_receive(PRINT_UART); } ``` 然后在 PikaPython 的线程里面调用 pikaScriptShell 来开启交互运行: ``` C // main.c /* pikapython task function */ void pikapython_task(void *pvParameters) { pikaScriptShell(pikaScriptInit()); while(1) { vTaskDelay(1000); } } ``` 然后我们下载运行,就可以通过交互来执行 `print('test')` 语句了: ![image-20240129235734732](assets/image-20240129235734732.png) 但是,我们在尝试其他语句的时候,又会发现报了一个heap不足的错误 ![image-20240129235812638](assets/image-20240129235812638.png) 这有些异常,但是我们还不能立刻确定真的是 heap 不足引起的,如果是栈溢出或者其他的 hardfault 也会引起类似的 heap 申请失败的错误。 为了更好的跟踪错误,我们尽可能地开启 FreeRTOS 的检测机制,让更多的错误可以被检测到。 我们通过下面的一系列修改来尽可能多地开启 FreeRTOS 的检测: - 定义 ASSERT 输出提示信息,开启栈溢出和 Heap 申请失败的检测: ```c // FreeRTOSConfig.h #define configCHECK_FOR_STACK_OVERFLOW 2 // 开启栈溢出检测 #define configUSE_MALLOC_FAILED_HOOK 1 // 开启Heap失败钩子 ... /* Define to trap errors during development and print debug info. */ #define configASSERT(x) \ if((x) == 0) { \ taskDISABLE_INTERRUPTS(); \ int printf(const char * __restrict /*format*/, ...); \ printf("Assertion Failed: %s, \nLine: %d, Function: %s()\n", __FILE__, __LINE__, __FUNCTION__); \ /* Additional debug information can be printed here. */ \ for (;;); \ } ``` - 在 main.c 中加入在 FreeRTOS heap 申请失败时的提示信息: ```C extern size_t xPortGetFreeHeapSize(void); extern size_t xPortGetMinimumEverFreeHeapSize(void); void vApplicationMallocFailedHook(void) { /* Called if a call to pvPortMalloc() fails because there is insufficient free memory available in the FreeRTOS heap. */ // Disable interrupts for a fail-safe state taskDISABLE_INTERRUPTS(); // Print error message printf("Error: Heap alloc failure...\n"); // Print the name of the current task if possible TaskHandle_t xTask = xTaskGetCurrentTaskHandle(); if (xTask != NULL) { char *pcTaskName = pcTaskGetName(xTask); printf(" -- Current Task: %s\n", pcTaskName); } else { printf(" -- Current Task: Unknown\n"); } // Print heap status size_t xFreeHeapSize = xPortGetFreeHeapSize(); size_t xMinEverFreeHeapSize = xPortGetMinimumEverFreeHeapSize(); size_t xTotalHeapSize = configTOTAL_HEAP_SIZE; size_t xUsedHeapSize = xTotalHeapSize - xFreeHeapSize; printf(" -- Total Heap Size: %u bytes\n", xTotalHeapSize); printf(" -- Used Heap Size: %u bytes\n", xUsedHeapSize); printf(" -- Free Heap Size: %u bytes\n", xFreeHeapSize); printf(" -- Minimum Ever Free Heap Size: %u bytes\n", xMinEverFreeHeapSize); // Endless loop to halt the system for (;;); } ``` - 在 main.c 中加入 FreeRTOS 检测到栈溢出的提示信息 ```C void vApplicationStackOverflowHook(TaskHandle_t xTask, char* pcTaskName) { // 禁用中断,防止进一步的栈损坏。 portDISABLE_INTERRUPTS(); // 构建一个信息字符串。注意这里我们尽量不使用栈空间。 static char pcMessage[128]; snprintf(pcMessage, sizeof(pcMessage), "Error: Stack overflow in task %s\r\n", pcTaskName); // 使用安全的方法发送信息。 printf("%s", pcMessage); // 在此处加入更多的错误处理代码,比如重置硬件、保存错误日志到持久化存储等。 // 循环,直到设备被重置或开发者介入。 for (;;) ; } ``` 再运行时,发现确实是 heap 使用满了,但这明显是异常的,这些简单的 python 语句不会占满 64K 的 Heap 空间: ![image-20240130001905243](assets/image-20240130001905243.png) 我们再检查对 heap 的对接函数: ![image-20240130001944594](assets/image-20240130001944594.png) 发现 pika_platform_free 没有使用 FreeRTOS 的接口,这是错误的,修改后: ```C void pika_platform_free(void* ptr) { vPortFree(ptr); } ``` 现在可以正常运行了 ![image-20240130002039352](assets/image-20240130002039352.png) 写道这里,我在考虑是否要修改上一章中的 **移植 PikaPython** 的 heap port 的编写错误。 最后的考虑是继续保留这个错误,还原最真实的 Debug 场景,因为愿意阅读文档的读者肯定不是希望拿来一个完整的固件就使用的,而是有学习移植适配流程的意图,而**真实的移植适配流程肯定是伴随这各种问题的**。 留下这些错误一方面可以展示排查思路,另一方面可以引出一些常用的错误诊断技巧,例如本章就引出了 FreeRTOS 的栈溢出和 Heap 错误钩子的技巧。 **我相信这些思路和技巧是比我通过篡改历史的方式来得到一个完美无瑕的操作流程是更有意义的。** 因此我没有修改上一章中出现的错误,在后续的文档编写中,我也会保持这个做法。 **有心的读者可以在遇到错误时,先尝试自己解决,然后再继续往下翻**。 做到这一步的 commit id: `846d5b48eaea0adffa50020c066b0f217de65dff` ### 支持多线程 PikaPython 提供了 `_thread` 标准模块来提供多线程的支持。 参考文档中的包管理器章节: ![image-20240130121852860](assets/image-20240130121852860.png) 我们在 PikaPython/requestment.txt 里面写入 `_thread` 模块的依赖 ``` python pikascript-core==v1.13.2 PikaStdLib==v1.13.2 _thread ``` 然后运行 pikaPackage.exe ![image-20240130122144353](assets/image-20240130122144353.png) 可以看到 _thread 模块已经被安装了 接下来我们将 _thread 模块的源码添加到工程里面 ![image-20240130122318596](assets/image-20240130122318596.png) 我们直接编译是可以通过的 我们可以对 `_thread` 模块进行一些测试,我们打开 PikaPython 的文档,可以找到一些 `_thread` 模块相关的例子: ![image-20240130122435613](assets/image-20240130122435613.png) 我们使用下面的例子进行测试,为了实现延时,我们按照同样的方法再安装 `time` 模块 ```python import _thread import time def test_task(): while True: print('test task') time.sleep(1) _thread.start_new_thread(test_task, ()) ``` 我们在测试的时候,会发现报了一个错误: ![image-20240130122849350](assets/image-20240130122849350.png) 这里提示了 `pika_platform_thread_mutex_init()` 这个函数被调用了,但是没有被实现。也就是说,`_thread` 模块是依赖于底层的 RTOS 的,我们先搜索这个函数看看: 我们在依然在 PikaPlatform.c 里面找到了这个函数 ![image-20240130123037481](assets/image-20240130123037481.png) 可以看到,这个 platform 函数也是 `weak` 定义的,我们可以像之前的 `pika_platform_getchar()` 一样,自己定义一个 `pika_platform_thread_mutex_init()` 来覆盖这个函数。 不过,我们继续观察可以看到,这个函数里面提供了在 `linux` 、`FreeRTOS` 和 `RT-Thread` 平台的默认实现,我们只要用宏来开启就可以使用了,不需要再自己编写。 我们当前使用的 RTOS 是 FreeRTOS,所以只要将 `PIKA_FREERTOS_ENABLE` 这个宏定义为 `1` 即可。 我们在 PikaPython-port 文件夹下创建一个 `pika_config.h` 文件,用来放置用户的 PikaPython 相关的宏配置。 ![image-20240130123442745](assets/image-20240130123442745.png) 然后在里面写入: ```c #define PIKA_FREERTOS_ENABLE 1 ``` 为了让这个宏能够被 PikaPython 内核识别到,我们要让 PikaPython 能够包含 `pika_config.h`。 我们打开 `pika_config_valid.h` 可以看到这一段说明: ![image-20240130123635016](assets/image-20240130123635016.png) 通过提示信息和源码,我们可以看到,我们应该在编译器的配置中定义 `PIKA_CONFIG_ENABLE = 1`,这样 `pika_config.h` 就会被 PikaPython 的内核包含了。 我们在编译器选项里面加入这个宏: ![image-20240130123749482](assets/image-20240130123749482.png) 编译后报了找不到 `pika_config.h` 这个文件的错误: ![image-20240130123838361](assets/image-20240130123838361.png) 检查一下包含路径,将 `PikaPython-port` 路径补充上: ![image-20240130123915027](assets/image-20240130123915027.png) 我们再编译,发现 `pika_config.h` 可以被包含了,但是出现了一个函数未找到的错误 ![image-20240130124211981](assets/image-20240130124211981.png) `xQueueCreateMutex()` 是 FreeRTOS 的内核功能,这里找不到,大概率是在 FreeRTOS 里面没有开启这个功能,我们搜索一下这个函数 ![image-20240130124521726](assets/image-20240130124521726.png) 可以看到,有 `configUSE_MUTEXES` 和 `configSUPPORT_DYNAMIC_ALLOCATION` 两个宏控制着 `xQueueCreateMutex()` 是否被开启。 搜索这两个宏,可以看到,`configUSE_MUTEXES` 没有被开启,`configSUPPORT_DYNAMIC_ALLOCATION` 是开启的。 ![image-20240130124707466](assets/image-20240130124707466.png) ![image-20240130124735317](assets/image-20240130124735317.png) 因此我们在 FreeRTOSconfig.h 里面加入配置宏: ```c #define configUSE_MUTEXES 1 // 开启 Mutex ``` 这下可以编译成功了 ![image-20240130124912484](assets/image-20240130124912484.png) 运行测试 ![image-20240130125032946](assets/image-20240130125032946.png) 现在可以看到,`task test` 按照每秒一次的频率打印,同时我们的交互终端依然可以使用,来执行其他命令。 这说明,test_task() 被作为除了交互终端所在线程之外的另一个线程开启运行了,到此可以验证多线程的支持是正常的。 完成到这一步的 commit id: `49ca8a1b8f091cd6a5dfa4d4eafd82178d0f85b3`