首先按照我之前发的《如何通过PlatformIO编译ESP32的MicroPython固件》尝试用PlatformIO烧一遍固件。成功了再往下看。
接下来进入正题,如何把你在PlatformIO中写的C++代码集成进MicroPython。
这里以ArduSensorPlatform为例。ArduSensorPlatform的代码这里就不提供了,会看这篇文章的大概率能看到相关代码。我们的目标是直接在Python中对Host的通道进行Read和Write。
我写了一个很简陋的TestPort作为示例Sensor,有两个成员变量sample1和sample2,Read读取这两个变量,write修改这两个变量,代码如下:
#include "TestPort.h"
TestPort::TestPort(float _sample1, float _sample2) {
sample1 = _sample1;
sample2 = _sample2;
}
TestPort::~TestPort() {
}
int TestPort::GetChannelNum() {
return 2;
}
int TestPort::Update() {
return 0;
}
float TestPort::Read(int channelNo) {
if (channelNo == 0) {
return sample1;
} else if (channelNo == 1) {
return sample2;
}
return -1.0;
}
int TestPort::Write(int channelNo, float value, bool isAsync)
{
if (channelNo == 0) {
sample1 = value;
} else if (channelNo == 1) {
sample2 = value;
} else {
return 1;
}
return 0;
}
然后在PlatformIO中新建HostWrapper.h和HostWrapper.cpp,cpp文件的代码等会儿再放出来:
// HostWrapper.h
#ifndef _HOSTWRAPPER_H
#define _HOSTWRAPPER_H
#ifdef __cplusplus
extern "C" {
#endif
struct tagHost {
void *host;
};
extern struct tagHost mp_ardu_host;
float ArduHostRead(int channelNo);
float ArduHostRead2(int sensorNo, int channelNo);
void ArduHostWrite(int channelNo, float value);
void ArduHostWrite2(int sensorNo, int channelNo, float value);
void ArduHostUpdate();
#ifdef __cplusplus
}
#endif
#endif
同时在MicroPython repo中,在port/esp32下新建一个host.c文件,然后在port上一级文件夹的extmod目录下新建host.c和HostWrapper.h文件,把上面这个头文件的代码复制进HostWrapper.h。两个host.c的代码相同,代码如下:
#include "py/obj.h"
#include "py/runtime.h"
#include "py/builtin.h"
#include "extmod/HostWrapper.h"
STATIC mp_obj_t host_read(mp_obj_t channelNo_obj) {
int channelNo = mp_obj_get_int(channelNo_obj);
float value = ArduHostRead(channelNo);
return mp_obj_new_float(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_1(host_read_obj, host_read);
STATIC mp_obj_t host_read_2(mp_obj_t sensorNo_obj, mp_obj_t channelNo_obj) {
int sensorNo = mp_obj_get_int(sensorNo_obj);
int channelNo = mp_obj_get_int(channelNo_obj);
float value = ArduHostRead2(sensorNo, channelNo);
return mp_obj_new_float(value);
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(host_read_2_obj, host_read_2);
STATIC mp_obj_t host_write(mp_obj_t channelNo_obj, mp_obj_t value_obj) {
int channelNo = mp_obj_get_int(channelNo_obj);
float value = mp_obj_get_float(value_obj);
ArduHostWrite(channelNo, value);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_2(host_write_obj, host_write);
STATIC mp_obj_t host_write_2(mp_obj_t sensorNo_obj, mp_obj_t channelNo_obj, mp_obj_t value_obj) {
int sensorNo = mp_obj_get_int(sensorNo_obj);
int channelNo = mp_obj_get_int(channelNo_obj);
float value = mp_obj_get_float(value_obj);
ArduHostWrite2(sensorNo, channelNo, value);
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_3(host_write_2_obj, host_write_2);
STATIC mp_obj_t host_update() {
ArduHostUpdate();
return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_0(host_update_obj, host_update);
STATIC const mp_rom_map_elem_t host_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_host) },
{ MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&host_read_obj) },
{ MP_ROM_QSTR(MP_QSTR_read2), MP_ROM_PTR(&host_read_2_obj) },
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&host_write_obj) },
{ MP_ROM_QSTR(MP_QSTR_write2), MP_ROM_PTR(&host_write_2_obj) },
{ MP_ROM_QSTR(MP_QSTR_update), MP_ROM_PTR(&host_update_obj) },
};
STATIC MP_DEFINE_CONST_DICT(host_module_globals, host_module_globals_table);
const mp_obj_module_t mp_module_host = {
.base = { &mp_type_module },
.globals = (mp_obj_dict_t*)&host_module_globals,
};
// MP_REGISTER_MODULE(MP_QSTR_host, host_user_cmodule, MODULE_HOST_ENABLED);
这里就不解释代码了,自行查阅py/obj.c和py/obj.h。
然后在port/esp32/mpconfigport.h中添加两行代码,这里要善用自己常用的文本编辑器的搜索功能:
extern const struct _mp_obj_module_t esp_module;
extern const struct _mp_obj_module_t esp32_module;
extern const struct _mp_obj_module_t utime_module;
extern const struct _mp_obj_module_t uos_module;
extern const struct _mp_obj_module_t mp_module_usocket;
extern const struct _mp_obj_module_t mp_module_machine;
extern const struct _mp_obj_module_t mp_module_network;
extern const struct _mp_obj_module_t mp_module_onewire;
// added line
extern const struct _mp_obj_module_t mp_module_host;
上面added line下是第一行
以及
// added line
extern const struct _mp_obj_module_t mp_module_host;
#define MICROPY_PORT_BUILTIN_MODULES \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp), (mp_obj_t)&esp_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_esp32), (mp_obj_t)&esp32_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_utime), (mp_obj_t)&utime_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_uos), (mp_obj_t)&uos_module }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_usocket), (mp_obj_t)&mp_module_usocket }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_machine), (mp_obj_t)&mp_module_machine }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_network), (mp_obj_t)&mp_module_network }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR__onewire), (mp_obj_t)&mp_module_onewire }, \
{ MP_OBJ_NEW_QSTR(MP_QSTR_host), (mp_obj_t)&mp_module_host }, \
上面这部分的最后一行。
这些改动也要在pio的mpconfigport.h中对应地进行改动。
然后再Makefile中添加我们的host.c
SRC_C = \
main.c \
uart.c \
…………
host.c\
$(SRC_MOD)
然后就是照着我开头提到的那篇帖子里的那样,执行make以及make staticlib这两个命令,app_main()的名字别忘了改。生成.a文件后把库文件复制到相应目录下。
接着回到pio project,main.c改成main.cpp,代码再做一些修改
#include <string.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "esp_system.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_log.h"
#include "nvs_flash.h"
// #include "lib/utils/pyexec.h"
#include "lwip/err.h"
#include "lwip/sys.h"
#include <stdio.h>
#include <stdlib.h>
#include "driver/gpio.h"
#include "Host.h"
#include "HostWrapper.h"
#include "SensorBase.h"
#include "TestPort.h"
// #include "mpHelper.h"
#define GPIO_OUTPUT_IO_0 2
#define GPIO_OUTPUT_PIN_SEL (1ULL << GPIO_OUTPUT_IO_0)
extern "C" {
void app_main();
#include "mpHelper.h"
#include "lib/utils/pyexec.h"
}
struct tagHost mp_ardu_host;
void app_main()
{
// Initialize obj
mp_ardu_host.host = new Host(2);
SensorBase *sensor = new TestPort(2.0, 1.5);
static_cast<Host *>(mp_ardu_host.host)->AddSensor(sensor);
sensor = new TestPort(3.5, 4.0);
static_cast<Host *>(mp_ardu_host.host)->AddSensor(sensor);
static_cast<Host *>(mp_ardu_host.host)->Start();
static_cast<Host *>(mp_ardu_host.host)->Update();
//Initialize NVS
esp_err_t ret = nvs_flash_init();
if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
{
ESP_ERROR_CHECK(nvs_flash_erase());
ret = nvs_flash_init();
}
ESP_ERROR_CHECK(ret);
mp_start();
pyexec_friendly_repl();
gc_sweep_all();
mp_deinit();
fflush(stdout);
}
然后是HostWrapper.c的代码
#include "HostWrapper.h"
#include "Host.h"
#ifdef __cplusplus
extern "C" {
#endif
float ArduHostRead(int channelNo)
{
return static_cast<Host *>(mp_ardu_host.host)->Read(channelNo);
}
float ArduHostRead2(int sensorNo, int channelNo)
{
return static_cast<Host *>(mp_ardu_host.host)->Read(sensorNo, channelNo);
}
void ArduHostWrite(int channelNo, float value)
{
static_cast<Host *>(mp_ardu_host.host)->Write(channelNo, value);
}
void ArduHostWrite2(int sensorNo, int channelNo, float value)
{
if (!static_cast<Host *>(mp_ardu_host.host)->IsChannelWrittable[sensorNo][channelNo])
{
static_cast<Host *>(mp_ardu_host.host)->IsChannelWrittable[sensorNo][channelNo] = 1;
}
static_cast<Host *>(mp_ardu_host.host)->Write(sensorNo, channelNo, value);
}
void ArduHostUpdate()
{
static_cast<Host *>(mp_ardu_host.host)->Update();
}
#ifdef __cplusplus
}
#endif
这里我做的工作是在HostWrapper.h中定义了一个结构体类型,成员为一个指针,然后在main.cpp中声明了一个结构体变量并把C++对象指针赋给这个成员指针,在HostWrapper.cpp中extern这个变量从而调用它,这样能够在符合C的规范的条件下调用这个对象的成员函数。代码比较丑陋,希望你能给出更优美的代码。
ArduHostWrite中我没有访问IsChannelWrittable是因为有点麻烦,等下就不用那个函数做演示了。
main.cpp中我添加了两个Sensor,第一个Sensor的sample1和sample2分别为2.0, 1.5,第二个Sensor的sample1和2分别为3.5和4.0。
然后就是Build和Upload
以下是MicroPython示例
>>> import host
>>> host.read(0)
2.0
>>> host.read2(1, 1)
4.0
>>> host.write2(0, 1, 6.3)
>>> host.write2(1, 0, 7.2)
>>> host.update()
>>> host.update()
>>> host.read(1)
6.3
>>> host.read(2)
7.2
大概就是这样。花几分钟写的,可能有纰漏,今天先摸了明天再改。
本文章使用limfx的vscode插件快速发布