65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIconstarIcon

5.00/5 (13投票s)

2023年9月17日

LGPL3

10分钟阅读

viewsIcon

37965

downloadIcon

374

使用此 Windows PC 适配器和模拟器,可在 PC 上运行和开发 Arduino 代码,包括显示屏。

Winduino

引言

更新:我现在已经在 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 日 - 添加了串行映射
© . All rights reserved.