Winduino:在 PC 上为 Arduino 项目进行原型设计的工具





5.00/5 (13投票s)
使用此 Windows PC 适配器和模拟器,可在 PC 上运行和开发 Arduino 代码,包括显示屏。
引言
更新:我现在已经在 PC 上实现了大部分 Arduino 框架,但我不得不将许可证从 MIT 更改为 Lesser GPL。
更新 2:我现在实现了 I2C、SPI 和设备仿真。文章和代码库已大量更新。
更新 3: 增加了将 HardwareSerial 实例映射到 PC COM 端口的功能
这一切都始于一时兴起。我只想看看我的图形和用户界面库是否能在 Windows PC 上运行。为此,我创建了一个通过 DirectX 绘制到窗口的机制,然后我将我的用户界面代码和图形代码与之连接。
它本身并没有太大用处,但我对自己说:“嘿,稍微加工一下,这可能是一个有用的原型工具”,然后我们就有了现在这个样子。
我所做的是围绕着 Arduino 功能的一个合理子集创建了一个包装器。这足以使用 LVGL** 或 UIX 运行用户界面,并将“串行”输出写入日志窗口,仿真和操作虚拟 GPIO、SPI 和 I2C 设备。
** 我应该注意的是,我没有用 LVGL 进行过测试,但理论上,它应该可以正常工作。对于外部虚拟 SPI 屏幕,以及像 htcw_ili9341 或 TFT_eSPI 这样的东西,肯定可以。
必备组件
您需要一台 Windows 10 或 Windows 11 PC。
您需要从 git-scm.org 安装 Git。
您需要安装 MinGW 以使用 GCC。我与 Winlibs 发行版合作良好。将其安装在系统驱动器的根目录下,以避免路径长度问题。
您需要安装带有 Microsoft 的 CMake 扩展的 VS Code。
理解这段乱码
Winduino 是一堆头文件和一个静态库。此外,我还包含了一个使用随附的CMake 文件即可构建的示例项目。我还包含了一个批处理文件,可用于获取依赖项。此外,我还包含了一些作为 DLL 的虚拟 SPI 和 I2C 设备。
下载后,创建一个“lib”文件夹(如果还没有)。然后运行fetch_deps.cmd 以拉取所有必要的依赖项。现在您可以通过 VS Code 构建它,方法是右键单击CMakeLists.txt 并选择“build all projects”。最后,您可以使用run.cmd 运行输出。
要使用它,您需要在主源文件中#include "Arduino.h"
,然后实现setup()
和loop()
。项目中已包含一个main.cpp 文件,并且脚本已针对我的 图形 和 用户界面 库进行了设置。
有一个特殊的函数,您可以实现它,称为void winduino()
,它在 Winduino 下运行时在setup()
之前运行。这可以用来调用 Winduino 特定的代码来设置虚拟硬件等,而不会弄乱setup()
,但否则它的功能与setup()
基本相同。无论如何,您都需要用#ifdef WINDUINO/#endif
包裹它,以避免在 Winduino 之外进行编译错误。
这是示例main.cpp 文件顶部的样子
#include <Arduino.h>
// include my graphics and ui
// iot libraries:
#include <gfx.hpp>
#include <uix.hpp>
using namespace gfx;
using namespace uix;
除了 Arduino 兼容性之外,还有一些其他的输入输出功能。
read_mouse()
获取鼠标信息flush_bitmap()
将位图发送到显示器winduino()
可以实现并在setup()
之前运行hardware_load()
从 DLL 加载虚拟硬件。hardware_set_pin()
将虚拟 GPIO 连接到虚拟硬件,例如虚拟显示器的 DC 线hardware_configure()
允许您设置特定于设备的配置属性hardware_transfer_bits_spi()
允许您与端口上的所有虚拟 SPI 设备进行双向位传输。但是,您应该使用SPI.h 中的SPIClass
实例。hardware_transfer_bytes_i2c()
与端口上的所有虚拟 I2C 设备进行双向字节传输。但是,您应该使用Wire.h 中的TwoWire
实例。hardware_attach_log()
将硬件的日志消息连接到 Winduino 中的输出窗口hardware_attach_spi()
将虚拟 SPI 设备连接到特定端口hardware_attach_i2c()
将虚拟 I2C 设备连接到特定端口hardware_attach_serial()
将 PC 串行 COM 端口连接到特定 Serial 实例hardware_get_attached_serial()
报告连接到特定 Serial 实例的 COM 端口-
hardware_set_screen_size()
设置集成显示屏的大小。默认为 320x240。
flush_bitmap()
接收位图的坐标和位图数据,并将其放置在指定坐标的显示器上。位图采用 BGRx8888 格式,如果定义了 USE_RGB
,则采用 RGBX8888 格式。这就是您的代码将与 Arduino 设备代码不同之处。特别是,颜色格式为 32 位而不是 16 位。您必须相应地进行调整。幸运的是,有了我的图形和用户界面库,这很容易。
read_mouse()
报告鼠标坐标以及左键是否按下。如果为 false,则坐标无效。这可用于模拟触摸。请注意,如果鼠标指针被拖出窗口,坐标可能会超出窗口边界。因此,read_mouse()
的工作方式与常规触摸略有不同,您必须自己进行值钳制。
注意: 以下函数只能在 winduino()
函数内部调用。如果在其他任何地方调用它们,结果将是未定义的。
hardware_load()
接收一个 DLL 名称,并返回一个 hw_handle_t
,可用于引用该硬件设备。此函数每次调用时都会创建一个新的设备实例,并返回一个新句柄。如果设备加载失败,则返回 false
。
hardware_set_pin()
在虚拟 MCU 和虚拟设备之间建立虚拟 GPIO 连接。它接收一个硬件句柄、一个 MCU 引脚 ID 和一个设备引脚 ID。您可以在设备附带的头文件中找到设备的引脚 ID。
hardware_configure()
设置特定于设备的配置属性。它接收一个硬件句柄、一个属性 ID、一些数据和数据大小。属性 ID 包含在设备 DLL 附带的头文件中。数据的格式是特定于属性的。
hardware_transfer_bits_spi()
和 hardware_transfer_bytes_i2c()
支持该框架,不应直接使用。
hardware_attach_log()
接收一个硬件句柄、一个字符串前缀和一个日志级别。前缀会附加到该设备发出的任何日志中。日志级别表示输出的详细程度,255 包括所有消息,包括调试信息。1 是错误,2 是警告,3 是信息。其余的是各种级别的信息或调试,具体取决于硬件实现。
hardware_attach_spi()
接收一个硬件句柄和一个端口号。虚拟硬件连接到指定的 SPI 端口。
hardware_attach_i2c()
接收一个硬件句柄和一个端口号。虚拟硬件连接到指定的 I2C 端口。
hardware_attach_serial()
接收一个 UART 号和一个端口号。COM 端口连接到指定的串行实例。
hardware_get_attached_serial()
接收一个 UART 号和一个端口号(作为输出参数)。检索与该 UART 关联的 COM 端口。
hardware_set_screen_size()
接收宽度和高度,并相应地设置集成屏幕的尺寸。
使用这个烂摊子
基本上,您可以导入您的 Arduino ino 或 PlatformIO 代码并声明屏幕尺寸。请记住将您的ino 文件“cpp-化”。将新的cpp 文件添加到CMakeLists.txt,或者直接使用提供的main.cpp。
实际上,您需要修改CMakeLists.txt 文件以包含您添加的任何新的 C 或 CPP 实现文件。
应注意的是,.ino 文件实际上无法与我的图形库一起使用,因为它需要比 Arduino IDE 工具链提供的更新版本的 C++ 标准。它们应该与 LVGL 一起使用。
如果您需要 RGBA8888 而不是 BGRA8888(DirectX 原生),请在包含Arduino.h 头文件之前使用此定义
#define USE_RGB
这决定了传递给 flush_bitmap()
的位图的预期格式。
之后,#include <Arduino.h>
。
#include <Arduino.h>
您可以使用 Serial
将内容打印到日志窗口。即使在当前代码库中不是必需的,您也应该在使用它们之前调用 begin()
。将来可能需要。
要使用 VS Code 进行构建,请右键单击 CMakeLists.txt 并选择“Build All Projects”。如果您没有该选项,则需要安装 Microsoft 的 CMake 扩展。首次构建时,系统会询问您要使用哪个编译器。您应该使用 GCC 之一,因为这是您为物联网编码时 Arduino 工具链使用的。
完成之后,您可以切换到 VS Code 中的终端并输入.\run 来运行程序。
main.cpp
此文件包含大量示例代码,用于在原生屏幕上运行演示以及加载和附加外部硬件。此文件是您的 Arduino 代码所在的位置。只需删除示例代码并实现您自己的 winduino()
/setup()
/loop()
例程。
实现虚拟设备
虚拟设备是公开一个或多个硬件设备实例的 DLL。硬件可能公开一个复合设备,例如带有触摸屏的屏幕。话虽如此,每个 DLL 只公开一个硬件设备(即使是复合的),以保持简单。否则,我会使用 COM。
您的设备应该是一个类,以便支持多个实例。每次调用 DLL 函数 CreateHardware()
时,都应该创建一个类的新实例,将其强制转换为硬件接口,然后返回。
该类必须实现硬件接口,其中包括与设备交互所需的所有核心函数。您的类可能不实现所有这些功能,但必须为没有其他方式提供的功能提供存根。
class hardware_interface {
public:
virtual int __cdecl CanConfigure() =0;
virtual int __cdecl Configure(int prop,
void* data,
size_t size) =0;
virtual int __cdecl CanConnect() =0;
virtual int __cdecl Connect(uint8_t pin,
gpio_get_callback getter,
gpio_set_callback setter,
void* state) =0;
virtual int __cdecl CanUpdate() =0;
virtual int __cdecl Update() =0;
virtual int __cdecl CanPinChange() =0;
virtual int __cdecl PinChange(uint8_t pin,
uint32_t value) =0;
virtual int __cdecl CanTransferBitsSPI() =0;
virtual int __cdecl TransferBitsSPI(uint8_t* data,
size_t size_bits) =0;
virtual int __cdecl CanTransferBytesI2C() =0;
virtual int __cdecl TransferBytesI2C(const uint8_t* in,
size_t in_size,
uint8_t* out,
size_t* in_out_out_size) =0;
virtual int __cdecl CanAttachLog() =0;
virtual int __cdecl AttachLog(log_callback logger,
const char* prefix,
uint8_t level) =0;
virtual int __cdecl Destroy() =0;
};
有四种方法可以驱动您的设备:通过 GPIO、通过 SPI、通过 I2C 和通过 Update()
函数。该函数与其他机制不太一样,因为它不需要总线或 GPIO 连接即可运行,但它本身无法与虚拟 MCU 通信。它只是作为应用程序循环的一部分定期运行。
您可以使用一种或多种这些机制来使设备工作。每个功能都有一个相应的 CanXXXX()
函数,用于指示基本功能是否受支持,例如 CanConfigure()
/Configure()
。
通常,您可以通过回调来响应 GPIO 的变化以及获取和设置 GPIO 的值。
对于 SPI 和 I2C,您可能需要状态机。由于协议差异,SPI 和 I2C 的工作方式略有不同。
对于 SPI,您传输一定数量的比特,并返回相同数量的比特。这可能在每次事务中被调用多次。当比特进入时,它们应该被处理,通常使用状态机,并且结果值应该覆盖传递给函数的读取值。如果函数是半双工的,那么非操作端应该是无损的,例如,对于半双工写入操作,数据参数应保持不变。确保在您的 CS
引脚为高电平时忽略传入数据。
对于 I2C,您可以在一次操作中处理整个事务。这是因为 I2C 会在整个事务期间保持线路,而不是像 SPI 那样允许您分割它们。您最有可能需要一个状态机,但它可以是局部的,仅限于处理例程,而不是像 SPI 那样可以在调用之间保留。
请参阅windiuno_hardware/spi_screen 文件,其中包含一个使用 GPIO、I2C 和 SPI 实现带有电容式触摸的虚拟触摸屏的示例。
历史
- 2023 年 9 月 17 日 - 首次提交
- 2023 年 9 月 20 日 - 大幅提高兼容性
- 2023 年 9 月 26 日 - 添加了虚拟硬件
- 2023 年 9 月 30 日 - 添加了串行映射