
工程师笔记 | 使用RT-Thread的Arduino兼容层开发ES32应用程序
Arduino在2005年发布,致力于让非电子专业的学生、爱好者、设计师等可以快速实现一些设计和想法。经过多年发展,Arduino的社区生态已经十分丰富,大多数常用的外设都可以找到对应的库使用,避免了重复开发的繁琐。时至今日,在Arduino的官网上已经有了100多种硬件、5000多个库可供选择。

RT-Thread是一款国产的嵌入式实时操作系统,支持多线程任务切换、中断管理,支持丰富的功能和软件包。RTduino(https://github.com/RTduino/RTduino)表示为RT-Thread的Arduino生态兼容层,是RT-Thread的软件包。旨在兼容Arduino丰富的生态(如Arduino库,以及作品等),来丰富RT-Thread生态,降低RT-Thread操作系统以及与RT-Thread适配的芯片的学习门槛。可以让用户通过Arduino的函数和编程方法,轻松地将RT-Thread以及特定的芯片使用起来。不用再拘泥于Arduino的开发板,而是能够使用各种类型的板子,大幅降低开发难度和开发周期。
对于专业的开发人员来说,直接使用Arduino库可以极大的减少开发成本和各类不同芯片、bsp的学习成本,甚至能够直接应用成熟的Arduino项目,避免重复开发。
对于已经对Arduino开发有一定基础的人来说,RTduino能够减少学习芯片、操作系统的时间,仅需很少的时间就能做出成果,在这之后还可以借此逐步深入学习RT-Thread操作系统等其他嵌入式开发的领域。
而对于完全不了解嵌入式开发的爱好者来说,还可以搭配各类可视化编程界面使用,从简单的搭积木开始,逐渐学习编写代码,再到完成更加复杂的项目。

本文将介绍如何在ES-PDS-ES32F3696LX开发板上开启RTduino,并简要介绍keil和rt-thread studio下Arduino库的使用方法和注意事项。
1. 硬件配置
需求:ES-PDS-ES32F3696LX开发板,ES-Bridge,BMI160传感器模块(可选),AHT10温湿度传感器模块(可选)
开发板的背面,有两排与Arduino UNO的管脚分布相同的管脚,提供GPIO、PWM、I2C、SPI等功能,正面有LED灯、按键等功能。
按照实验需要,分别连接ES-Bridge的对应管脚和开发板的管脚,测试功能是否正常。

2. 系统配置
RTduino支持在keil或者RT-Thread Studio环境下编译运行,但由于Arduino的编译环境是gcc,因此如果使用了第三方库,keil环境编译可能会不通过,需要自行修改部分代码和编译参数。目前,如果想要使用RTduino,需要至少rt-thread 4.1.1版本才能支持。
注:使用ES32F3696LX开发板时,请设置控制台使用的串口为uart2
RT-Thread Kernel --->
Kernel Device Object --->
(uart2) the device name for console
2.1 使用Keil+Env
-
Env 工具下敲入 menuconfig 命令,进入菜单,开启Arduino:
Hardware Drivers Config --->
Onboard Peripheral Drivers --->
[*] Support Arduino

-
进入RTduino配置,确认配置:
RT-Thread online packages --->
Arduino libraries --->
RTduino: Arduino Ecological Compatibility Layer

开启Adafruit库支持(可选,建议开启)
返回到Arduino libraries,进入Sensors,选择使用Adafruit Unified Sensor和Adafruit BusIO。
Adafruit提供了更简洁易用的SPI/I2C接口和丰富的传感器框架,不使用则会影响后续的用例测试和传感器测试,但不影响其他功能。

-
使用 pkgs --update下载RTduino包:

-
使用scons --target=mdk5生成工程文件:

-
打开工程,开始编译即可。
2.2 使用RT-Thread Studio
-
选择 文件 -> 导入 -> BSP导入:

-
选择es32f369x的bsp路径,并填写工程名称、芯片(es32f3696lx):

-
打开RT-Thread Settings,配置好其他设置(console选择uart2)后,选择硬件,板载设备驱动,开启Arduino支持:

(注意:目前ES32的驱动程序不支持软件I2C,请进入组件->设备驱动程序->使用I2C驱动程序中,关闭"使用GPIO模拟I2C") -
开启Adafruit库支持(可选,建议开启)打开软件包->Arduino libraries->Sensors,开启Adafruit Unified Sensor和Adafruit BusIO,方便后续使用传感器

-
保存后稍等片刻,等待Studio下载库并完成配置,然后点击锤子图标编译即可。
-
可能遇到的问题与解决方案
-
Unknown flag "-T"
项目->属性->C/C++构建->GNU ARM Cross C++ Linker -> General在右侧添加lds连接脚本
"${workspace_loc:/${ProjName}/drivers/linker_scripts/link.lds}"(需要先删除原有的再添加)
-
无法正常启动,直接进入HardFault
选择 项目->属性->C/C++构建->GNU ARM Cross C++ Complier-> Miscellaneous ,在Other compiler flags 中添加
-mcpu=cortex-m3 -mthumb
-
gcc版本的startup入口点存在问题,打开"libraries\CMSIS\Device\EastSoft\ES32F36xx\Startup\gcc\startup_es32f36xx.S",找到
bl main,改为bl entry。
3. 实验说明
arduino_main.c文件中存放标准的setup-loop循环及简单的led灯闪烁程序。applications/arduino_pinout/examples/arduino_example.cpp文件中存放有使用arduino进行编程的示例,可以用该文件的内容覆盖arduino_main.cpp文件的内容进行实验测试。为了方便测试,arduino_example.cpp文件中除了arduino工程常见的setup-loop函数外,还提供了一系列宏定义控制的功能,用于测试Arduino兼容层的各项功能是否正常,并提供各个功能的使用实例。
详细使用说明请参见applications/arduino_pinout/README.MD,文档中包含了arduino管脚与rt-thread的管脚对应关系、使用说明、注意事项等。
3.0 Serial 串口通信
串口通信是默认开启的,无需通过宏定义切换,当进入Setup函数时会向rt-thread控制台发送一句话

3.1 GPIO: ARDU_TEST_GPIO
开启此宏定义,会将D4设为高电平,D7设为低电平,D2和D18每隔0.25秒切换一次高低电平(其中D18为LED灯,可观察到闪烁)。可以使用ES-Bridge连接对应的管脚,观察电压波形

3.2 PWM: ARDU_TEST_PWM
开启此宏定义,会在D3、D5、D6三个管脚上设置不同占空比的波形,可连接ES-Bridge查看
3.3 I2C: ARDU_TEST_I2C和ARDU_TEST_ADAFRUIT_I2C
默认I2C接口为I2C0,使用的管脚为D14(PB7)-SDA,D15(PB6)-SCL
这两个宏定义都是I2C功能,区别仅仅只是接口不同,ARDU_TEST_I2C使用Arduino原生接口,ARDU_TEST_ADAFRUIT_I2C使用Adafruit库的接口,比原生接口更方便一些。(如果使用Adafruit_I2C,前面需要使用Adafruit_BusIO软件包)
使用ES-Bridge的I2C管脚连接开发板的对应管脚,可观察到开发板发送过来的消息。

3.4 SPI: ARDU_TEST_SPI和ARDU_TEST_ADAFRUIT_SPI
默认SPI接口为SPI0,使用的管脚为D11(PB5)-MOSI,D12(PB4)-MISO,D13(PD3)-SCK D10(PB0)-NSS片选
同样都是SPI接口,区别仅仅只是使用的库不同。需要注意的是使用原生的SPI库需要自己控制片选引脚的电平,Adafruit库在初始化时指定片选引脚。(如果使用Adafruit_SPI,前面需要使用Adafruit_BusIO软件包)
用例功能说明:
SPI程序会先发送一次test,然后读取4个字节,在下一次循环的时候会发送上一次读到的数据并再次读取4个字节。
先打开ES-Bridge的SPI功能,并在发送并输入4个字节的数据,并点击同步数据,然后运行开发板上的程序或重新复位,可以看到ES-Bridge先收到了一个"test",然后不断收到"abcd"。

3.5 Interrupt 中断: ARDU_TEST_INT
使用方向键的中键,测试中断形式响应外界输入。按下方向键的中键,可以看到串口打印的消息。

3.6 GPIO输入: ARDU_TEST_DIGITAL_READ
测试读取GPIO输入的功能。在Loop循环的过程中,会读取方向键四向的状态,如果检测到按下,会通过串口打印消息。

3.7 ADC: ARDU_TEST_ADC_READ
依次读取A0-A5,共6个ADC模拟输入管脚的数据,并显示。
注:由于ES32的BSP暂时不支持调节ADC的分辨率,返回的数据均为真实的ADC采样值,需要自行计算出真实的电压。

3.8 Arduino第三方库使用(以BMI160传感器库为例)
注意,虽然RTduino的接口与Arduino的接口一致,但是由于编译环境等的不同,还是有可能无法直接使用,需要根据情况,进行一些修改(尤其是keil环境下)。
从Arduino库下载BMI160的驱动库DFRobot_BMI160 1.0.0,下载完毕后,解压放置到packages\RTduino-latest\libraries\user目录下。
连接好BMI160传感器模块与开发板上的I2C0接口。
需要修改一处:这个库有一处函数重载歧义
int8_t DFRobot_BMI160::I2cGetRegs(struct bmi160Dev *dev, uint8_t reg_addr, uint8_t *data, uint16_t len)
{
...
//将原代码Wire.requestFrom(dev->id,len); 改为下面一行
Wire.requestFrom(dev->id,(uint8_t)len);
...
}
-
用keil编译时还有以下问题:
-
缺少INT8_C和UINT8_C宏定义,手动添加
#ifndef INT8_C
#define INT8_C(x) ((int8_t)x)
#endif
#ifndef UINT8_C
#define UINT8_C(x) ((uint8_t)x)
#endif -
选择DFRobot_BMI160.cpp文件,右键点击并选择Options,在C/C++页中,在Misc Control的输入框中输入--gnu --cpp11

解决以上问题后,能成功调用传感器获取加速度和重力等信息
-
如果用RT-Thread Studio编译,则不存在这些问题,只需修改存在歧义的问题即可直接使用
完成配置后,打开宏定义ARDU_TEST_BMI160_PKG即可在rt-thread的控制台上看到传感器的读数

4. 示例程序
本节的所有示例以keil环境下为例,但同样在RT-Thread Studio环境下也可以编译。相比较于传统的以C语音为主的嵌入式开发,Arduino编程使用C++可以大幅度降低编程的复杂程度,加快开发进度。Arduino的库隐藏了所有与底层寄存器相关的信息,用户即使完全不了解所使用的芯片信息,只要能够调用Arduino的库函数,也能够完成诸如读取外部传感器数据、控制外部设备、与其他设备通信等功能。与RTthread结合后产生的RTduino可以充分运用这一优势,使用Arduino库简化开发。
1. LED灯闪烁
这是最基本的测试工程,也是默认的arduino_main.cpp文件的内容,用于测试配置是否正确,开发板功能是否正常。
打开工程后,直接编译运行即可,如果配置无误,可观察到板子上的LED4持续闪烁
2. 传感器数据读取并打印
本节简要介绍如何利用Arduino库,仅使用几行代码,读取传感器的数据并显示,展示Arduino强大的快速开发能力。无需特别配置,甚至不需要知道芯片的外设信息,只要导入了传感器库并正确连接传感器,就能够直接驱动传感器,极大地减少了调试时间。此用例需要使用Adafruit Unified Sensor和Adafruit BusIO软件包。
-
将板卡上的I2C0接口连接到AHT10和BMI160传感器
-
下载对应的arduino包,地址如下:
https://github.com/adafruit/Adafruit_AHTX0
https://github.com/DFRobot/DFRobot_BMI160
也可以从Arduino官方库下载
https://www.arduino.cc/reference/en/libraries/adafruit-ahtx0/
https://www.arduino.cc/reference/en/libraries/dfrobot_bmi160/
(注:从Arduino网站下载的bmi160库存在一些问题,建议使用github版本)
其中的AHTX0也可以通过RT的软件包管理下载,打开RT-Thread online packages -> Arduino libraries -> Sensors,选择 Adafruit AHTX0 (AHT10 & AHT20)即可 -
下载后,将解压出来的文件夹复制到 packages\RTduino-latest\libraries\user 路径

-
调用scons --target=mdk5重新生成keil工程文件
-
给添加的cpp文件添加'--cpp11 --gnu'(具体操作过程参见3.8节中的第2个问题) 包含arduino_main.cpp和传感器库文件DFRobot_BMI160.cpp、Adafruit_AHTX0.cpp
-
打开arduino_main.cpp文件,删除原有内容,并粘贴以下代码
#include <Arduino.h>
#include <Adafruit_AHTX0.h>
#include <DFRobot_BMI160.h>
DFRobot_BMI160 *bmi160;
Adafruit_AHTX0 *aht10;
void setup(void)
{
//初始化BMI160
bmi160=new DFRobot_BMI160();
int result= bmi160->I2cInit();
if (result)
{
Serial.println("BMI160 Init failed.");
}
bmi160->setStepPowerMode(bmi160->stepNormalPowerMode);
//初始化AHT10
aht10=new Adafruit_AHTX0();
if (!aht10->begin())
Serial.println("AHT10 begin failed");
Serial.println("AHT10 Sensor info:");
aht10->getHumiditySensor()->printSensorDetails();
aht10->getTemperatureSensor()->printSensorDetails();
//开启I2C总线(非必要,传感器初始化时会自动开启)
Wire.begin();
}
void loop(void)
{
int16_t data[3]={0,0,0};
//读取加速度计数据
bmi160->getAccelData(data);
Serial.print("Accel=");
Serial.print(data[0]);
Serial.print(",");
Serial.print(data[1]);
Serial.print(",");
Serial.println(data[2]);
//读取陀螺仪数据
bmi160->getGyroData(data);
Serial.print("Gyro =");
Serial.print(data[0]);
Serial.print(",");
Serial.print(data[1]);
Serial.print(",");
Serial.println(data[2]);
//读取温湿度计数据
sensors_event_t humi_event,temp_event;
aht10->getEvent(&humi_event,&temp_event);
Serial.print("Humi=");
Serial.print(humi_event.relative_humidity);
Serial.print(",Temp=");
Serial.println(temp_event.temperature);
Serial.println();
//延迟1s
delay(1000);
} -
运行结果


5. 可视化编程
Arduino的代码本身比较简单易懂,通过一些工具可以以拖曳方块的形式实现可视化的编程,这些工具生成的代码在RTduino中大部分情况下也是可以通用的。目前大部分的工具界面大同小异,左侧为用户可以通过搭积木的方式,进行程序流程的设计,右侧为工具根据搭建的结果输出的代码,包含setup和loop循环,只需要将右侧的代码复制到arduino_main.cpp文件中,替换原有的setup和loop函数,即可实现在RTduino上进行可视化编程,而无需自己编写代码。
下面以一个控制LED灯周期闪烁的程序为例,展示可视化编程的效果:

左侧为可视化编程界面,右侧为生成的Arduino代码,按照左侧搭建好程序流程,将代码复制到arduino_main.cpp文件并替换setup和loop函数,编译后下载运行,可以看到串口输出的"hello",并通过ES-Bridge观察到D2的电平信号。
演示使用的可视化编程软件为Mixly(地址:http://mixly.org/),其他软件如ArduBlockly(https://tftcanvas.blascarr.com/,可在线开发)等的使用基本,都有提供类似的功能和模块(包括简单的IO或是串口,一些还能够支持SPI等模块),只需要将软件生成的代码复制到arduino_main.cpp文件中即可。

△ArduBlockly的界面(实现同样的功能)
注1:尽管大多数情况下生成的代码都能够正常使用,但是由于底层驱动的不同,存在一些区别,如:RTduino框架的SPI需要先调用begin以获取spi device,才能使用其他功能。
注2:目前可视化编程仅支持以代码的形式使用,暂时只能使用标准Arduino库的功能,需要底层支持的如电机控制、TFT等暂时无法使用。
6. 其他说明
-
目前RTduino和ES32的RTduino适配还处于初步阶段,如有问题,可以尝试查看applications/arduino_pinout目录下的README.MD或查找RTduino官方文档。
-
如果需要调整I2C/SPI/UART的管脚等信息,可以使用ES-CodeMaker进行对应的调整。注意Arduino相关的GPIO/I2C/SPI等功能需要全部使能,否则调用对应的接口时会出错。
-
使用非默认的SPI/I2C时,只需要在初始化函数中传入对应的rt-thread设备名,如I2C : Wire.begin("i2c1") 或 SPI : SPI.begin("spi1")。

