本帖最后由 Alohaq 于 2024-11-4 13:04 编辑
一、前言
本人这期任务所用器件如下:
- Adafruit Circuit Playground Express,为本期任务主板,任务必购硬件;
- Round Display for Xiao(Seeed Studio),在我看来,没有显示的主板是不完整的,加上本期任务主板为圆形主板,遂配备圆形显示器,其驱动芯片为GC9A01,任务选购硬件;
- 自制转接板,用于将任务主板与圆形显示器组合,自制PCB打印;
- SG90S,180°舵机,为创意任务《章鱼哥》配备,并配备机械手模拟触角,个人自备;
- 鲸鱼不倒翁一只,为进阶任务配备,个人自备;
- 机械手一个,模拟章鱼哥的触角,为创意任务《章鱼哥》配备,个人自备。
本人这期任务所用环境为:VScode+PlatformIO,软件开发框架为Arduino,主要依赖的驱动库为Adafruit Circuit Playground、GFX Library for Arduino,其中Adafruit Circuit Playground为官方Arduino驱动库,可以控制 Adafruit Circuit Playground Express上的所有外设;GFX Library for Arduino是一个 Arduino 图形库,支持具有各种数据总线接口的各种显示器,本次用于驱动Round Display for Xiao显示板。
二、任务实现详情
2.1 入门任务
任务介绍:开发环境搭建,板载LED点亮(个人增加显示屏点亮)。
涉及库:Adafruit Circuit Playground、GFX Library for Arduino。
具体内容:
开发环境采用VSCode+PlatformIO,其中PlatformIO为VSCode的插件,VSCode的安装不再赘述,安装完成后在扩展中搜索PaltformIO进行安装,随后新建项目,以本项目为例:Board选择Adafruit Circuit Playground Express,Framework选择Arduino,Name我命名为01_Primer_Led,可以个人选择保存路径,详情可见图2-1,随后创建即可,开发环境初步搭建完成。
图2-1 首次创建项目
接着我们再添加官方为准备的驱动库Adafruit Circuit Playground,在VSCode的标签页中选择PIO Home,然后点击侧边工具栏中的Libraries,搜索Adafruit Circuit Playground,找到后选择打开,并在进入后选择添加至工程,详情如图2-2所示。
图2-2 添加官方驱动库
添加完官方驱动库后,在源文件中包含头文件<Adafruit_CircuitPlayground.h>即可,随后进行编译,此时会发现编译不通过,如图2-3所示,有errors产出:#error TinyUsB is not selected, please select it in "Tools->Menu->USB Stack“,同时也能看出是源文件Adafruit_TinyUSB_API.cpp的29行报出。
图2-3 errors
追根溯源,打开Adafruit_TinyUSB_API.cpp,找到29行,如图2-4所示,可发现是在没有USE_TINYUSB宏定义的情况下,又宏定义了ARDUINO_ARCH_SAMD(Board选择 Adafruit Circuit Playground Express时会定义该宏定义)的时候,会报出上述错误。我们可以在预编译中增加USE_TINYUSB来解决报错,也可以选择忽略Tiny USB的驱动库,两者均可在platformio.ini中实现,分别为build_flags = -D USE TINYUSB和lib_ignore = Adafruit TinyUSB Library,具体如图2-4所示,具体使用哪一个,取决于您是否需要TinyUSB,二者选其一即可,可用‘;‘进行注释。随后便可正常编译烧录。
图2-4 platformio.ini添加内容
由于该任务本人还需驱动圆形显示器,需要额外添加库,我选择的驱动库为GFX Library for Arduino,操作步骤同库Adafruit Circuit Playground,详情如图2-2所示。
图2-3 添加显示驱动库
驱动库添加完成后,在需要使用的源文件中加上头文件<Arduino_GFX_Library.h>,然后选择合适的驱动函数即可,具体见代码:
#include <Adafruit_CircuitPlayground.h>
#include <Arduino_GFX_Library.h>
// #define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin
Arduino_DataBus *bus = new Arduino_SWSPI(3 /* DC */, 2 /*CS*/, 10 /* SCK*/, 9 /* MOSI*/, 6 /*MISO*/); //CS A5(2) SCK A3(10) MOSI A2(9) MISO A1(6) DC A4(3)
Arduino_GFX *gfx = new Arduino_GC9A01(bus, DF_GFX_RST, 0 /* rotation */, true /* IPS */);
// the setup function runs once when you press reset or power the board
void setup() {
// initialize digital pin 13 as an output.
pinMode(LED_BUILTIN, OUTPUT);
gfx -> begin();
gfx->fillScreen(BLACK);
gfx->setTextSize(3);
gfx->setTextColor(RED);
gfx->setCursor(15, 90);
gfx->println("Hello World!");
gfx->println("Follow me 2-1");
delay(1000); // 5 seconds
}
// the loop function runs over and over again forever
void loop() {
digitalWrite(LED_BUILTIN, HIGH); // turn the LED on (HIGH is the voltage level)
delay(1000); // wait for a second
digitalWrite(LED_BUILTIN, LOW); // turn the LED off by making the voltage LOW
delay(1000); // wait for a second
}
效果图:
图2-4 LED闪烁
图2-5 显示屏显示内容
2.2 基础任务一
任务介绍:控制板载炫彩LED,跑马灯点亮和颜色变换。
涉及库:Adafruit Circuit Playground。
具体内容:代码先是跑马灯,RGB逐一点亮,这个很好实现,for函数即可;随后是颜色变化,这里用上SIN函数加上时间,对r、g、b分别加上相位差赋值,进而实现炫彩呼吸渐变,详情见代码,代码如下:
#include <Adafruit_CircuitPlayground.h>
#include <math.h>
// do NOT include the standard NeoPixel library
void setup() {
// initialize the Circuit Playground as usual
// this will initialize the onboard NeoPixels as well
CircuitPlayground.begin();
delay(100);
// 跑马灯
for (uint8_t i=0; i<10; i++) {
CircuitPlayground.setPixelColor(i, 255, 255, 255);
delay(200);
}
}
void loop() {
static int time = 0; // 用于控制颜色渐变的时间变量
CircuitPlayground.clearPixels();
// 呼吸灯
for(int i=0; i<10; i++) {
float angle = (float)(i + time) / 5.0 * M_PI;
// 使用 sin 函数计算 RGB 颜色值
uint8_t r = (uint8_t)((sin(angle) + 1) * 127.5);
uint8_t g = (uint8_t)((sin(angle + 2.0 * M_PI / 3.0) + 1) * 127.5);
uint8_t b = (uint8_t)((sin(angle + 4.0 * M_PI / 3.0) + 1) * 127.5);
CircuitPlayground.setPixelColor(i, r, g, b); // 设置第 i 个 LED 的颜色
}
delay(50); // 延迟,用于控制动画的速度
time++; // 递增时间变量,以生成渐变效果
}
效果图:
图2-6 炫彩RGB
2.3 基础任务二
任务介绍:监测环境温度和光线,通过板载LED展示舒适程度。
涉及库:Adafruit Circuit Playground。
具体内容:主要利用光传感器,毕竟温度的升降较难控制且周期很长,简易利用读数,对应上0~1,该部分用于红光显示,1剩下部分分给蓝光,进而展示舒适度,这里红光表示环境很不舒适,反之越蓝越舒适。详情见代码,代码如下:
#include <Adafruit_CircuitPlayground.h>
void setup() {
CircuitPlayground.begin();
// Serial.begin(9600);
// while (!Serial); // Wait until serial console is opened
// Serial.println("Ready to detection environment");
}
float temp_range = 42-30;
float r, b = 0;
void loop() {
// Serial.print("Light sensor: ");
// Serial.println(CircuitPlayground.lightSensor());
// Serial.print("Temperature: ");
// Serial.println(CircuitPlayground.temperature());
r = (CircuitPlayground.temperature() - 30) / temp_range;
// Serial.println(r);
r += (300 - CircuitPlayground.lightSensor() ) / 300.0;
// Serial.println(r);
if (r > 1) r = 1;
b = 1 - r;
CircuitPlayground.clearPixels();
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 255*r, 0, 255*b);
}
delay(500);
}
效果图:
图2-7 黑暗环境下,红灯表明不舒适
2.4 基础任务三
任务介绍:接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警。
涉及库:Adafruit Circuit Playground、GFX Library for Arduino。
具体内容:
图2-8 RX对应引脚
利用红外的TX发出38KHz的信号,模拟超声波测距,然后直接读取RX处的ADC值,码值越大,说明接收的信号越强,即物体越接近,注意不能通过RX接收处的解码芯片,否则无法完成,遂涉及引脚的寄存器操作,对应工程图如图2-8所示,同时距离会在显示屏上显示,越近数字越小,当数字为1时,发出“danger”声报警,同时rgb颜色随距离越近,由绿变红,详情见代码,代码如下:
#include <Adafruit_CircuitPlayground.h>
#include <Arduino_GFX_Library.h>
// #define GFX_BL DF_GFX_BL // default backlight pin, you may replace DF_GFX_BL to actual backlight pin
Arduino_DataBus *bus = new Arduino_SWSPI(3 /* DC */, 2 /*CS*/, 10 /* SCK*/, 9 /* MOSI*/, 6 /*MISO*/); //CS A5(2) SCK A3(10) MOSI A2(9) MISO A1(6) DC A4(3)
Arduino_GFX *gfx = new Arduino_GC9A01(bus, DF_GFX_RST, 0 /* rotation */, true /* IPS */);
void setup() {
CircuitPlayground.begin();
// PA4做ADC输入
PORT->Group[PORTA].DIRCLR.reg = (1 << 4); // Set pin A4 as input
PORT->Group[PORTA].PINCFG[4].bit.PMUXEN = 1; // Enable peripheral multiplexer on pin A4
PORT->Group[PORTA].PMUX[4 >> 1].reg |= PORT_PMUX_PMUXE_B; // Set pin A4 to peripheral function B (ADC)
// 配置 ADC
ADC->CTRLA.bit.ENABLE = 0; // 先禁用 ADC 以进行配置
ADC->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_PIN4; // 选择 PA4 作为 ADC 输入通道
ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV64 | ADC_CTRLB_RESSEL_12BIT; // 设置预分频器和分辨率
ADC->SAMPCTRL.reg = 0x3F; // 设置采样时间
ADC->CTRLA.bit.ENABLE = 1; // 启用 ADC
// PORT->Group[PORTA].DIRSET.reg = (1 << 17); // 将 PA17 配置为输出
// PORT->Group[PORTA].OUTSET.reg = (1 << 17); // 将 PA17 设置为高电平
pinMode(CPLAY_IR_EMITTER, OUTPUT);
pinMode(CPLAY_BUZZER, OUTPUT);
// pinMode(CPLAY_SPEAKER_SHUTDOWN, OUTPUT);
// digitalWrite(CPLAY_SPEAKER_SHUTDOWN, HIGH);
gfx -> begin();
gfx->fillScreen(BLACK);
gfx->setTextSize(4);
gfx->setTextColor(RED);
// Serial.begin(9600);
// while (!Serial); // Wait until serial console is opened
// Serial.println("Ready to receive IR signals");
}
const uint8_t spDANGER[] PROGMEM = {0x2D,0xBF,0x21,0x92,0x59,0xB4,0x9F,0xA2,0x87,0x10,0x8E,0xDC,0x72,0xAB,0x5B,0x9D,0x62,0xA6,0x42,0x9E,0x9C,0xB8,0xB3,0x95,0x0D,0xAF,0x14,0x15,0xA5,0x47,0xDE,0x1D,0x7A,0x78,0x3A,0x49,0x65,0x55,0xD0,0x5E,0xAE,0x3A,0xB5,0x53,0x93,0x88,0x65,0xE2,0x00,0xEC,0x9A,0xEA,0x80,0x65,0x82,0xC7,0xD8,0x63,0x0A,0x9A,0x65,0x5D,0x53,0xC9,0x49,0x5C,0xE1,0x7D,0x2F,0x73,0x2F,0x47,0x59,0xC2,0xDE,0x9A,0x27,0x5F,0xF1,0x8B,0xDF,0xFF,0x03};
void loop() {
// CircuitPlayground.irSend.send(7, 0x5555, 16);
// delay(1);
for (int i = 0; i < 200; i++) { // 38kHz PWM信号
digitalWrite(CPLAY_IR_EMITTER, HIGH);
delayMicroseconds(13);
digitalWrite(CPLAY_IR_EMITTER, LOW);
delayMicroseconds(13);
}
// digitalWrite(CPLAY_BUZZER, LOW);
//Continue looping until you get a complete signal received
// 开始转换
ADC->SWTRIG.bit.START = 1; // 启动 ADC 转换
// 等待转换完成
while (ADC->INTFLAG.bit.RESRDY == 0);
// digitalWrite(CPLAY_IR_EMITTER, LOW);
// 读取结果
uint16_t result = ADC->RESULT.reg;
uint8_t dis = (result - 2300) / 60;
// if (result > 1500)
// Serial.println(result);
gfx->setCursor(110, 110);
gfx->fillScreen(BLACK);
gfx->println(7-dis);
if (dis >= 6) {
CircuitPlayground.speaker.say(spDANGER);
CircuitPlayground.speaker.end();
}
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 255 * dis / 7.0, 255 * (7 - dis) / 7.0, 0);
}
delay(300);
// delay(100);
}
效果图:
图2-9 物体距离过近,红光警示
2.5进阶任务
任务介绍:制作不倒翁——展示不倒翁运动过程中的不同灯光效果。
涉及库:Adafruit Circuit Playground。
具体内容:首先利用LIS3DH得到X、Y、Z,其中LIS3DH设置加速度为±2g,采样为50Hz,故需对其处理(乘上4),处理完后,进而算出横滚角和俯仰角,计算如下:
再通过俯仰角和横滚角计算出对应方位及深度,利用方向角索引至对应RGB,再利用深度,决定点亮几个RGB,达到RGB随不倒翁运动而变化的效果。详情见代码,代码如下:
#include <Adafruit_CircuitPlayground.h>
void setup() {
CircuitPlayground.begin();
CircuitPlayground.lis.setDataRate(LIS3DH_DATARATE_50_HZ);
CircuitPlayground.lis.setRange(LIS3DH_RANGE_2_G);
// Serial.begin(9600);
// while (!Serial); // Wait until serial console is opened
// Serial.println("Ready to read LIS3DH");
}
float X, Y, Z;
short pitch ,roll;
void loop() {
CircuitPlayground.lis.read();
X = (float)CircuitPlayground.lis.x*4/65536*1000; // 还可以试试看motionX
Y = (float)CircuitPlayground.lis.y*4/65536*1000;
Z = (float)CircuitPlayground.lis.z*4/65536*1000;
pitch = (short)(atan2((float)(0-Y), Z) * 180 / 3.14159); //转换为度数
roll = (short)(atan2((float)(X), Z) * 180 / 3.14159); //转换为度数
float direction = atan2(roll, pitch); // 计算方向角
int ledIndex;
// Serial.println(direction); // direction范围是[-PI, PI]
if (direction >= 0) {
ledIndex = round((direction / PI) * 6); // 映射到LED索引
}
else {
ledIndex = 11 + round((direction / PI) * 6); // 映射到LED索引
}
// int ledIndex = round((direction / PI) * 6) % 12; // 映射到LED索引
// Serial.println(ledIndex);
float tiltMagnitude = sqrt(pitch * pitch + roll * roll); // 计算倾斜幅度
int ledCount = round((tiltMagnitude / 90.0) * 6) * 2; // 映射点亮数量
static int time = 0; // 用于控制颜色渐变的时间变量
CircuitPlayground.clearPixels();
// float angle = (float)(time) / 10 * 2.0 * M_PI;
float angle = (float)(time) / 5.0 * M_PI;
// 使用 sin 函数计算 RGB 颜色值
uint8_t r = (uint8_t)((sin(angle) + 1) * 127.5);
uint8_t g = (uint8_t)((sin(angle + 2.0 * M_PI / 3.0) + 1) * 127.5);
uint8_t b = (uint8_t)((sin(angle + 4.0 * M_PI / 3.0) + 1) * 127.5);
for (int i = -ledCount/2; i < ledCount/2; i++) {
int indexToLight = ledIndex + i;
// CircuitPlayground.setPixelColor(indexToLight, 0, 64, 64);
CircuitPlayground.setPixelColor(indexToLight, r, g, b);
}
// Serial.println(ledIndex);
time++; // 递增时间变量,以生成渐变效果
delay(100);
}
效果图:
图2-10 不倒翁倾斜,rgb随倾斜方向亮灯
2.6 创意任务
任务介绍:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩。
涉及库:Adafruit Circuit Playground。
具体内容:章鱼哥听到环境噪音(利用板载的麦克风噪音检测实现),认为敌人来临,遂伸张触角游泳离开,即环境音变大则伸张触角,环境正常则收缩触角。代码如下:
#include <Adafruit_CircuitPlayground.h>
#include <Servo.h>
Servo myservo; // create Servo object to control a servo
void setup() {
CircuitPlayground.begin();
myservo.attach(A5); // attaches the servo on pin 9 to the Servo object
// Serial.begin(9600);
// myservo.writeMicroseconds(1450); // 舵机顺时针旋转
}
void loop() {
// for (int pos = 0; pos <= 180; pos += 1) { // goes from 0 degrees to 180 degrees
// // in steps of 1 degree
// myservo.write(pos); // tell servo to go to position in variable 'pos'
// delay(15); // waits 15ms for the servo to reach the position
// }
// CircuitPlayground.soundSensor();
// Serial.println(CircuitPlayground.soundSensor());
// Serial.println( CircuitPlayground.mic.soundPressureLevel(500) );
// myservo.writeMicroseconds(1300); // 舵机顺时针旋转 收缩
// delay(1000);
// myservo.writeMicroseconds(1600);// 舵机逆时针旋转 伸张
// myservo.writeMicroseconds(2000);// 舵机逆时针旋转
// delay(2000);
if ( CircuitPlayground.mic.soundPressureLevel(300) > 75)// 有噪声则伸张
{
myservo.writeMicroseconds(1800);// 舵机逆时针旋转 伸张
}
else
{
myservo.writeMicroseconds(1200); // 舵机顺时针旋转 收缩
}
}
效果图:
图2-11 章鱼听到噪音,准备伸张触角
三、活动心得
收获满满!动手能力进一步得到加强,思路进一步得以展开!祝越办越好!
|