- 2024-09-25
-
加入了学习《【DigiKey“智造万物,快乐不停”】基于LLM大语言模型和VITS文本转语音的全息AI助手演示》,观看 基于LLM大语言模型和VITS文本转语音的全息AI助手演示
-
加入了学习《【Follow me第二季第1期】全部任务演示》,观看 全部任务演示2.0
- 2024-08-30
-
发表了主题帖:
《拥抱AIGC 应用ChatGPT和OpenAI API》 ChatGPT的应用
本帖最后由 MioChan 于 2024-8-30 17:06 编辑
最近抽时间把本书的第二部分读完了,这一部分主要就是关于ChatGPT的应用。
第三章 熟悉 ChatGPT
这一章主要是介绍如何使用 ChatGPT,包括如何注册账户、用户界面介绍以及组织对话等,主要是面向新用户而言。
第一节详细介绍了如何注册和设置ChatGPT 账户,确保新用户能够快速开始使用,通过实际步骤,比如如何选择账号类型(免费或付费)、如何进行身份验证,以及设置个人偏好,读者能够更清楚地了解如何进入和使用 ChatGPT。另外,书中没有提到的是,目前最新的ChatGPT相比之前已经大大放宽了注册流程,也不需要验证外区手机号,即使不注册也能进行会话,就是每次刷新浏览器后都会被重置。
下一节则讲解了 ChatGPT 的用户界面,包括聊天窗口、工具栏、历史记录以及个性化设置等功能区域。通过对界面的各个部分进行详细介绍,读者可以更快地找到所需的功能。例如,用户可以在左侧的对话栏中找到历史对话记录,方便回顾和继续先前的对话。同时,通过个性化设置,用户可以调整界面显示风格、对话语言、自定义GPT等,打造更符合个人习惯的使用体验。
最后本章还介绍了如何通过良好的对话组织来提高与 ChatGPT 互动的效率和效果。例如,在进行复杂任务时,用户可以将问题分解成多个小问题,逐步与 ChatGPT 讨论,最终得到更加精准和全面的答案。此外,使用明确和简洁的语言来提出问题,可以帮助 ChatGPT 更好地理解用户意图,生成更有用的回复。举个例子,用户在请求某个产品的评测时,可以首先说明产品的基本信息,然后逐步询问产品的性能、价格和用户评价等细节。
第四章 了解提示设计
本章重点介绍了如何通过设计有效的提示(Prompt)来引导 ChatGPT 生成更精准和符合预期的内容。提示设计在使用大型语言模型时至关重要,因为它能够直接影响模型输出的质量和相关性。本章共分为三个主要部分,深入探讨了提示的概念、重要性,以及如何利用零样本学习、一次样本学习和少样本学习来提升生成效果,并特别强调了 Transformer 模型在这些任务中的作用。
什么是提示,提示为什么重要
这一节首先定义了“提示”的概念。在人工智能领域,提示是向语言模型输入的文本片段或问题,它起到了“指示牌”的作用,引导模型生成期望的答案或内容。提示的设计直接影响生成的效果。有效的提示能够帮助模型更好地理解上下文,过滤出相关信息,进而生成符合需求的内容。
提示的重要性在于它不仅仅是一个问题的表达方式,更是影响模型输出的一个关键因素。例如,当用户希望生成一份关于气候变化的报告时,一个简单的提示如“写一篇关于气候变化的文章”可能会得到一个过于宽泛的答案,而如果提示被精细化为“写一篇关于2024年气候变化对沿海城市的经济影响的分析”,则生成结果会更加具体和有针对性。这体现了提示设计在优化内容生成中的重要作用。
零样本学习、一次样本学习和少样本学习——Transformer 模型的典型功能
零样本学习指模型在没有任何示例的情况下,直接依赖提示生成答案的能力。例如,用户可能会给出一个完全新领域的问题,模型需通过理解问题的语境进行推理,从而给出一个合理的答案。此能力对于处理不常见或未知领域的信息非常重要。一次样本学习则是给出一个示例来指导模型学习。例如,用户想要写一封商业邮件,可以先提供一个样例邮件,模型将根据这个单一示例理解格式、语调和内容结构,并生成类似的文本。少样本学习提供少量示例来帮助模型更准确地理解任务。例如,用户提供了几条客户评论,希望模型根据这些评论生成产品描述。少样本学习通过这些示例让模型学习到更加复杂的模式和语境,从而生成更符合需求的输出。
定义明确的提示规则,以获得相关和一致结果
在提示中清楚说明你想要的结果和目标。例如,想要获取某个特定领域的技术报告,应在提示中明确指定领域和技术要求。
为模型提供足够的背景信息,以帮助它更好地理解问题。例如,用户希望生成一段关于某个历史事件的描述,可以先简单概述事件的时间和地点。
在提示中添加限制条件,例如字数、格式或风格,以确保输出结果符合要求。明确的提示能够引导模型更精准地生成内容,并符合用户的特定需求。
通过上述方法可以获得更具体的回复。
除此之外,PLUS订阅用户现在可以直接自定义GPT,直接让其能面向不同的领域。例如,下面这个是我之前尝试的一个猫娘版GPT,能产生一些比较有趣的回复。
另外,就算不是Plus用户也可以在探索GPTs中找到别人已经做好的GPT使用
第五章到第八章 助力各领域应用:提升生产力与效率
后面这几章主要展示了 ChatGPT 在多个领域中的多种应用,涵盖了日常工作、软件开发、营销策略和科学研究等不同场景。
比如ChatGPT 可以作为日常助手,通过文本生成、写作技能提升、优化翻译效果和加速信息检索等功能,为用户提供实用的支持。例如,它可以帮助用户撰写商业邮件、生成创意文案,或为复杂主题撰写分析报告。ChatGPT 还可以作为个人学习助手,优化学习计划,提高工作和生活中的整体效率。
在软件开发领域,ChatGPT 可用来生成、优化和调试代码,同时也能生成技术文档和代码注释,帮助开发者更高效地进行编程工作。ChatGPT 还可以帮助理解复杂的编程语言模型,甚至在不同编程语言之间进行转换。对于开发者来说,利用 ChatGPT 可以显著提升开发效率,减少出错率,并确保代码质量更高。例如下面就是用GPT帮助写一个快排算法。
在营销领域,ChatGPT 可以支持 A/B 测试、SEO 优化、情绪分析,以及新品研发等活动,帮助企业更好地了解客户需求和市场趋势。通过 ChatGPT 生成的内容,可以更好地进行市场宣传、提升客户满意度,最终带来更高的品牌影响力和商业收益。
对于科学研究人员来说,ChatGPT 也提供了多种实用功能,可以显著简化研究过程。例如,它可以收集和总结文献资料,设计实验框架,生成和格式化参考文献,对现有结果进行辅助分析,甚至帮助撰写研究论文。利用 ChatGPT,科研人员可以更专注于核心研究内容,同时减少在琐碎任务上的时间消耗,使研究工作更加高效和精准。
总体来看,这部分内容的最大亮点在于其全面性和适用性。从理论到实际,深入到各个专业领域,帮助不同背景的读者在工作中发挥 AI 的最大效用。但阅读过程中我也发现很多问题,书中很多图片分辨率太低,看不清文字,而且书中与 GPT 的对话示例大多使用英文,但作为一本面向中文用户的书籍,这样的设计对于那些不熟悉英文的读者显得不太友好,起码应该提供这些英文对话的中文翻译或对话中包含的主要要点翻译。
- 2024-08-14
-
发表了主题帖:
《拥抱AIGC 应用ChatGPT和OpenAI API》 整书速览及其第一部分基础知识阅读
本帖最后由 MioChan 于 2024-8-14 16:19 编辑
前几天终于收到了《拥抱AIGC 应用ChatGPT和OpenAI API》这本书,简单翻了一下,总的来看是一本探讨生成式人工智能及其应用的书籍,像是比较难懂的理论知识其实很少,主要都是在介绍怎么应用,甚至我感觉就是一本ChatGPT和OpenAI API的使用说明书。自从ChatGPT推出之后我就其实一直在用了,让GPT帮忙写一下废话或者逻辑比较清晰的代码还是很方便。
第一部分差不多就是绪论吧,主要介绍了生成式人工智能的概念、发展历程以及相关技术背后的数学原理。AIGC简单来说就是一类通过学习大量数据来生成新内容的人工智能模型,像是第一章也列举了常见的几种AIGC应用,比如ChatGPT这种大语言模型、图片生成模型以及音乐生成模型,事实上现在的AIGC应用太多了,除了这些最常用的还有语音方向的例如TTS这种语音生成模型,而且除了二维图片现在3D模型以及视频方面的AIGC也在迅速发展。
至于生成式AI的研究历史,书中主要列举了生成对抗网络(GAN)、变分自动编码器(VAE)、TransFormer等等,其实生成式AI再往早一点说,还有基于统计方法,如朴素贝叶斯模型和隐马尔可夫模型(HMM)这些。Transformer提出应该算是一个里程碑的事件,可以说是彻底取代了RNN,模型结构图如上所示,因为采用了自注意力机制,使得模型能够高效地捕捉序列数据中的长程依赖关系,在自然语言处理任务中表现出色,成为许多生成模型的基础。随后OpenAI提出了GPT模型,GPT也是基于Transformer架构的大型语言模型实现的,核心思想是预训练和微调。GPT-1是首个GPT模型,展示了预训练与微调结合的有效性。GPT-2的模型规模显著增加,能够生成高质量、连贯的长文本,展现了更好的生成能力。GPT-3的模型参数达到1750亿,进一步提升了生成文本的质量和多样性,能够在多个任务上实现零样本和少样本学习能力。GPT-4继续在模型规模和生成能力上取得突破,支持多模态输入(如图像和文本),在跨领域任务中展现了更广泛的应用。后续的具体原理因为OpenAI逐渐变成了CloseAI,也没有开源所以详细的细节也不得而知。
我记得当时注册Oppo AI账号还要验证手机号之类的,步骤还比较麻烦,但现在似乎不验证手机号了,注册还是很容易,而且现在国内的LLM发展的也算可以,如果没玩过还是值得体验一下。最后附上一张LLM的发展历史图供大家学习参考。
- 2024-08-10
-
发表了主题帖:
【2024 DigiKey 创意大赛】物料开箱帖
很高兴能入围今年的创意大赛,这次我想做的是一个AI全功能气象站,所以选择的物料基本都是各种气体传感器,还有一块Adafruit的esp32点阵屏驱动版,用于驱动点阵屏,用于显示气象站各个传感器的数值。
下面是开箱照:
不得不说,得捷每次的包装是真的好
Adafruit的板子还是一如即往的精致
顺便简单介绍一下这些传感器的功能,涵盖了各种气体和挥发物
BME680:温度、湿度、气压、VOC
SEN0440:C2H5OH,C3H8,C4H10,CH4,CO,H2,H2S,NH3
SGP30:H2、TVOC、eCO2
- 2024-08-05
-
回复了主题帖:
共读入围名单(第二轮):《拥抱AIGC 应用ChatGPT和OpenAI API》
个人信息无误,确认可以完成评测计划。
- 2024-08-01
-
发表了主题帖:
【Follow me第二季第1期】汇总提交帖:全部任务
本帖最后由 MioChan 于 2024-8-8 13:52 编辑
从收到板子到今天终于完成了所有任务,Follow Me也参加了好几期,真的是很好的活动。本期的Adafruit Circuit Playground Express这块板子,总的来说真的很不错,集成度非常高,各种传感器应有尽有,确实配称之为Playground,最后感谢社区和得捷提供的机会,期待后续活动再能出现一些有意思的板子~
项目视频演示
本项目完成了本期活动的全部内容,所有任务的展示如下:
视频快捷索引:
入门任务:00:54
基础任务一:01:06
基础任务二:01:18
基础任务三:01:40
进阶任务:01:54
创意任务一:02:03
创意任务二:03:33
创意任务三:03:48
物料展示
我购买物料为活动必备物料开发板、推荐物料舵机、一条MicroUSB的线缆用于连接开发板和计算机
物料全家福
任务成果展示
入门任务:开发环境搭建,板载LED点亮
对应论坛帖:【Follow me第二季第1期】物料开箱、开发环境搭建以及板载LED点亮 https://bbs.eeworld.com.cn/thread-1288882-1-1.html
开发环境搭建:
本次我使用Arduino来完成全部任务,Arduino环境搭建比较简单
1.官网下载IDE,https://www.arduino.cc/en/software
2.安装完成后,打开IDE,在左侧栏的Boards Manager,安装Arduino SAMD
3.连接开发板,就能看见上面正确显示了板子的型号Adafruit Circuit Playground
板载LED点亮:
所使用的板载元件:红色LED
只要知道红色LED对应13号引脚,点灯任务非常简单,下面给出代码和演示
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
基础任务一:控制板载炫彩LED,跑马灯点亮和颜色变换
对应论坛帖:【Follow me第二季第1期】基础任务1-3与进阶任务https://bbs.eeworld.com.cn/thread-1288902-1-1.html
所使用的板载元件:彩色LED、蜂鸣器、按钮
用Adafruit_NeoPixel.h这个库就能实现,还加入了一些额外的功能,左边的按钮可以切换亮度,右边的按钮可以切换灯效,一共设置了五种灯效,分别是三种常亮,追逐,彩虹。除此之外,按下按钮蜂鸣器也会依次发出do, re, mi, fa, sol, la, si的音调,通过调节占空比我们还能控制蜂鸣器音量。
流程图:
代码:
#include <Adafruit_NeoPixel.h>
#define PIN 8
// 定义按钮引脚
#define BUTTON_BRIGHTNESS 4 // 调节亮度
#define BUTTON_EFFECT 5 // 切换效果
// 定义音调
const int tones[] = {262, 294, 330, 349, 392, 440, 494}; // do, re, mi, fa, sol, la, si
int toneIndex = 0; // 当前音调
int volume = 30; // 音量
Adafruit_NeoPixel strip = Adafruit_NeoPixel(10, PIN, NEO_GRB + NEO_KHZ400);
int currentEffect = 0;
int brightness = 50;
int lastButtonBrightnessState = LOW;
int lastButtonEffectState = LOW;
void setup() {
pinMode(BUTTON_BRIGHTNESS, INPUT); // 设置按钮
pinMode(BUTTON_EFFECT, INPUT);
pinMode(A0, OUTPUT); // 设置扬声器
strip.begin();
strip.setBrightness(brightness);
strip.show();
Serial.begin(9600);
}
void loop() {
// 检查按钮状态
checkButtons();
// 执行当前效果
switch (currentEffect) {
case 0:
colorWipe(strip.Color(255, 0, 0), 50);
break;
case 1:
colorWipe(strip.Color(0, 255, 0), 50);
break;
case 2:
colorWipe(strip.Color(0, 0, 255), 50);
break;
case 3:
theaterChase(strip.Color(127, 127, 127), 50);
break;
case 4:
rainbowCycle(20);
break;
}
}
bool checkButtons() {
bool buttonPressed = false;
// 检查D4按钮是否被按下以更改亮度
int buttonBrightnessState = digitalRead(BUTTON_BRIGHTNESS);
if (buttonBrightnessState == HIGH && lastButtonBrightnessState == LOW) {
brightness = (brightness + 30) % 128; // 增加亮度
strip.setBrightness(brightness);
strip.show();
playTone(); // 播放音调
buttonPressed = true;
delay(200);
}
lastButtonBrightnessState = buttonBrightnessState;
// 检查D5按钮是否被按下以更改效果
int buttonEffectState = digitalRead(BUTTON_EFFECT);
if (buttonEffectState == HIGH && lastButtonEffectState == LOW) {
currentEffect = (currentEffect + 1) % 5; // 效果循环
playTone(); // 播放音调
buttonPressed = true;
delay(200);
}
lastButtonEffectState = buttonEffectState;
return buttonPressed;
}
// 用一种颜色逐个填充像素点
void colorWipe(uint32_t c, uint8_t wait) {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
if (checkButtons()) return; // 如果按钮被按下,则中断循环
}
}
// 使彩虹色均匀分布
void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for (j = 0; j < 256 * 5; j++) {
for (i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
delay(wait);
if (checkButtons()) return; // 如果按钮被按下,则中断循环
}
}
// 剧院风格的爬行灯
void theaterChase(uint32_t c, uint8_t wait) {
for (int j = 0; j < 10; j++) {
for (int q = 0; q < 3; q++) {
for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, c); // 每三个像素点亮一个
}
strip.show();
delay(wait);
if (checkButtons()) return; // 如果按钮被按下,则中断循环
for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, 0);
}
}
}
}
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
// 播放音调
void playTone() {
int frequency = tones[toneIndex];
int period = 1000000L / frequency;
int pulse = (period * volume) / 256;
for (long i = 0; i < 200L * frequency / 1000L; i++) {
digitalWrite(A0, HIGH);
delayMicroseconds(pulse);
digitalWrite(A0, LOW);
delayMicroseconds(period - pulse);
}
toneIndex = (toneIndex + 1) % 7; // 循环切换音调
}
演示:
基础任务二:监测环境温度和光线,通过板载LED展示舒适程度
对应论坛帖:【Follow me第二季第1期】基础任务1-3与进阶任务https://bbs.eeworld.com.cn/thread-1288902-1-1.html
所使用的板载元件:彩色LED、温度传感器、光线传感器
这里直接用到了#include <Adafruit_CircuitPlayground.h>这个库,传感器已经封装好了,直接获取数值就行。这里对灯珠做了一个划分,左边五颗用来显示温度,亮三颗处于绿色表示在人体舒服的20-24度,红色表示过热,蓝色表示过冷(因为MCU多少会发热,这个温度传感器放的太靠近了导致测得不准,我对比了一下温度计会比我环境高个五度所以代码里简单粗暴减了5)。右边五颗白色灯珠对应当前环境亮度,板子上所有led也会跟着亮度进行变化(环境亮度比较低的时候,LED也会变暗)
流程图:
代码:
#include <Adafruit_CircuitPlayground.h>
int prevTempLedCount = -1; // 初始化
int prevBrightnessLedCount = -1; // 初始化
void setup() {
CircuitPlayground.begin();
}
void loop() {
displayTemperature();
displayBrightness();
delay(1000); // 每秒更新
}
void displayTemperature() {
float temperature = CircuitPlayground.temperature() - 6;
int ledCount = 3; // 20-24度时亮三颗
uint32_t color = CircuitPlayground.colorWheel(85); // 绿色
if (temperature < 20) {
ledCount = max(0, 3 - (20 - temperature) / 5);
color = CircuitPlayground.colorWheel(170); // 蓝色
} else if (temperature > 24) {
ledCount = min(5, 3 + (temperature - 24) / 5);
color = CircuitPlayground.colorWheel(0); // 红色
}
// 如果LED数量没有变化,则不更新
if (ledCount != prevTempLedCount) {
for (int i = 0; i < 5; i++) {
if (i < ledCount) {
CircuitPlayground.setPixelColor(4 - i, color);
} else {
CircuitPlayground.setPixelColor(4 - i, 0); // 关闭LED
}
}
prevTempLedCount = ledCount; // 更新上一次的LED数量
}
}
void displayBrightness() {
int brightness = CircuitPlayground.lightSensor();
int ledCount = min(5, brightness / 50); // 亮度每增加50 LED增加一颗
uint8_t overallBrightness = map(brightness, 0, 500, 0, 100);
// 如果LED数量没有变化,则不更新
if (ledCount != prevBrightnessLedCount) {
CircuitPlayground.setBrightness(overallBrightness); //调整所有灯珠亮度
for (int i = 0; i < 5; i++) {
if (i < ledCount) {
CircuitPlayground.setPixelColor(5 + i, 255, 255, 255); // 白色
} else {
CircuitPlayground.setPixelColor(5 + i, 0); // 关闭LED
}
}
prevBrightnessLedCount = ledCount; // 更新上一次的LED数量
}
}
演示:
基础任务三:接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警
对应论坛帖:【Follow me第二季第1期】基础任务1-3与进阶任务https://bbs.eeworld.com.cn/thread-1288902-1-1.html
所使用的板载元件:彩色LED、蜂鸣器、IR LED、IR接收器
通过IR LED发射38KHZ的IR脉冲,再读取接收器测得的模拟值,当模拟值增大即有物体靠近。没有物体接近时灯珠全灭或显示一个,随着物体接近灯珠会亮的越来越多,最后全亮蜂鸣器报警
流程图:
代码:
#include <Adafruit_CircuitPlayground.h>
#define SAFE_DISTANCE 450 // 定义安全距离
const int alertTone = 1000; // 警报音调
const int irTransmitterPin = 25; //引脚定义
const int irReceiverPin = A10;
void setup() {
CircuitPlayground.begin();
Serial.begin(9600); //
pinMode(irReceiverPin, INPUT); // 红外传感器输入
pinMode(irTransmitterPin, OUTPUT);// 红外led输出
delay(100);
}
void loop() {
sendIRPulse();
int distance = analogRead(irReceiverPin); // 读取红外传感器的值
displayDistance(distance);
checkForIntrusion(distance);
delay(300);
}
void displayDistance(int distance) {
int ledCount = map(distance, 290, SAFE_DISTANCE, 1, 10); // 将距离值映射到0-10的LED数量
Serial.print("Distance: ");
Serial.print(distance);
Serial.print(", LED Count: ");
Serial.println(ledCount);
for (int i = 0; i < 10; i++) {
if (i < ledCount) {
CircuitPlayground.setPixelColor(i, 0, 255, 0);
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
}
void checkForIntrusion(int distance) {
if (distance > SAFE_DISTANCE) {
Serial.println("Intrusion detected!");
playAlertTone();
}
}
void sendIRPulse() {
for (int i = 0; i < 32; i++) {
digitalWrite(irTransmitterPin, HIGH);
delayMicroseconds(13);
digitalWrite(irTransmitterPin, LOW);
delayMicroseconds(13);
}
}
void playAlertTone() {
CircuitPlayground.playTone(alertTone, 500); // 播放警报音500ms
}
演示:
进阶任务:制作不倒翁——展示不倒翁运动过程中的不同灯光效果
对应论坛帖:【Follow me第二季第1期】基础任务1-3与进阶任务https://bbs.eeworld.com.cn/thread-1288902-1-1.html
所使用的板载元件:彩色LED、蜂鸣器、IR LED、IR接收器
流程图:
代码:
#include <Adafruit_CircuitPlayground.h>
#include <math.h>
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
}
void loop() {
float x = CircuitPlayground.motionX();
float y = CircuitPlayground.motionY();
float z = CircuitPlayground.motionZ();
// 计算与水平面夹角
float angleX = atan2(x, z) * 180 / PI;
float angleY = atan2(y, z) * 180 / PI;
int ledToLight = -1;
//映射到彩色灯珠
if(angleX>11){
if(angleY<-11) ledToLight = 1;
if(angleY<11 && angleY>-11) ledToLight = 2;
if(angleY>11) ledToLight =3;
}
if(angleX>-11&& angleX<11){
if(angleY>11)
{
ledToLight =4;
// CircuitPlayground.setPixelColor(5, CircuitPlayground.colorWheel(85));
}
if(angleY<-11)
{
ledToLight = 0; // X: 0, Y: -9, Z: 0
// CircuitPlayground.setPixelColor(9, CircuitPlayground.colorWheel(85));
}
}
if(angleX<-11){
if(angleY<-11) ledToLight = 8;
if(angleY<11 && angleY>-11) ledToLight = 7;
if(angleY>11) ledToLight =6;
}
uint8_t color= map( ledToLight, 0, 9, 0, 255);
if (ledToLight == -1) {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, CircuitPlayground.colorWheel(85)); //水平时都点亮
}
} else {
for (int i = 0; i < 10; i++) {
if (i == ledToLight || (ledToLight == 4 && i == 5) || (ledToLight == 0 && i == 9)) {
CircuitPlayground.setPixelColor(i, CircuitPlayground.colorWheel(color));//点亮对应位置的
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
}
Serial.print("X: ");
Serial.print(x);
Serial.print(" - Y: ");
Serial.print(y);
Serial.print(" - Z: ");
Serial.print(z);
Serial.print(" - angleX: ");
Serial.print(angleX);
Serial.print(" - angleY: ");
Serial.print(angleY);
Serial.print(" - LED To Light: ");
Serial.println(ledToLight);
delay(100);
}
演示:
创意任务一:有创意的可穿戴装饰——可结合多种传感器和灯光效果展示
对应论坛帖:【Follow me第二季第1期】创意任务一:可以跟随MIDI音符律动的音乐摆件 https://bbs.eeworld.com.cn/thread-1288904-1-1.html
所使用的板载元件:彩色LED、滑动开关、按钮
该任务实现了一个可以跟着MIDI音符律动或显示电平的指示器,开关在左边为电平指示,在右为律动,可以在运行中实时切换,板子上那两个按钮左边的可以减小灯珠的亮度,右边则是增加。
流程图:
代码:
#include <Adafruit_CircuitPlayground.h>
#include <MIDIUSB.h>
bool slideSwitch;
int brightness = 100;
// 定义颜色数组
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
randomSeed(analogRead(0)); // 随机数生成器
}
void loop() {
slideSwitch = CircuitPlayground.slideSwitch();
bool noteHandled = false; // 标记是否处理过音符
midiEventPacket_t rx;
if (CircuitPlayground.leftButton()) {
brightness = max(0, brightness - 10); // 减少亮度
CircuitPlayground.setBrightness(brightness);
delay(100);
}
if (CircuitPlayground.rightButton()) {
brightness = min(255, brightness + 10); // 增加亮度
CircuitPlayground.setBrightness(brightness);
delay(100);
}
if (slideSwitch) {
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
// 检查是否是 NOTE ON 消息
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int velocity = rx.byte3; // 获取音符力度
updateLEDs(velocity);
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
// 处理 NOTE OFF 消息或者 NOTE ON 力度为 0
turnOffAllLEDs();
}
}
} while (rx.header != 0);
delay(5);
} else {
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
if (!noteHandled) {
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int velocity = rx.byte3;
lightRandomLED(velocity);
noteHandled = true;
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
turnOffAllLEDs();
}
}
}
} while (rx.header != 0 && !noteHandled);
delay(5);
}
}
void updateLEDs(int velocity) {
// 计算亮灯的数量
int ledCountToLight = map(velocity, 0, 127, 0, 10);
Serial.print("Velocity: ");
Serial.print(velocity);
Serial.print(", LED Count: ");
Serial.println(ledCountToLight);
// 更新 LED
for (int i = 0; i < 10; i++) {
if (MidiUSB.read().header != 0) {
turnOffAllLEDs();
return; // 检查是否有新的 MIDI 输入
}
delay(20);
if (i < ledCountToLight) {
CircuitPlayground.setPixelColor(i, gradientColors[i]);
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
for (int i = ledCountToLight; i >= 0; i--) {
if (MidiUSB.read().header != 0) {
turnOffAllLEDs();
return; // 检查是否有新的 MIDI 输入
}
delay(20);
CircuitPlayground.setPixelColor(i, 0);
}
}
void lightRandomLED(int velocity) {
// 计算亮度
int brightness = map(velocity, 0, 127, 0, 150);
// 生成随机 LED 和颜色
int randomLED = random(0, 10);
uint32_t randomColor = random(0, 254);
CircuitPlayground.setBrightness(brightness);
// 点亮随机 LED
CircuitPlayground.setPixelColor(randomLED, CircuitPlayground.colorWheel( randomColor));
Serial.print("Random LED: ");
Serial.print(randomLED);
Serial.print(", Color: ");
Serial.print((randomColor >> 16) & 0xFF);
Serial.print(",");
Serial.print((randomColor >> 8) & 0xFF);
Serial.print(",");
Serial.println(randomColor & 0xFF);
}
void turnOffAllLEDs() {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0); // 关闭每个 LED
}
}
演示:
创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩
对应论坛帖:【Follow me第二季第1期】创意任务二:害怕声音的纸章鱼~https://bbs.eeworld.com.cn/thread-1289007-1-1.html
所使用的元件:板载麦克风以及舵机
这个任务就是要用开发板识别环境中的声音大小,然后控制舵机作出相应的动作。我是自己折了个纸章鱼,将其头部贴到快递纸板上,背面用回形针+风筝线拉到两个触角上,给纸板打个洞挂到舵机上,就能实现触角收缩的效果了。
流程图:
代码:
#include <Adafruit_CircuitPlayground.h>
#include <Servo.h>
// 滤波系数
const float alpha = 0.3;
float filteredSoundLevel = 0;
// 舵机定义
const int servoPin = A3; // 舵机连接的引脚
Servo myServo;
const int neutralSpeed = 90; // 中立位置速度
const int leftSpeed = 84; // 左转速度
const int rightSpeed = 94; // 右转速度
const int turnDuration = 700; // 转动持续时间
const int delayAfterTurn = 5000; // 等待时间
void setup() {
CircuitPlayground.begin();
Serial.begin(115200); // 用于调试
myServo.attach(servoPin); // 附加舵机到指定引脚
myServo.write(neutralSpeed); // 设置舵机初始速度为停止
}
void loop() {
checkButtons();
float soundLevel = CircuitPlayground.mic.soundPressureLevel(10);
// 滤波
filteredSoundLevel = alpha * soundLevel + (1 - alpha) * filteredSoundLevel;
Serial.print("Raw Sound Level: ");
Serial.print(soundLevel);
Serial.print(", Filtered Sound Level: ");
Serial.println(filteredSoundLevel);
updateLEDs(filteredSoundLevel);
myServo.write(neutralSpeed);
delay(200);
}
void updateLEDs(float soundLevel) {
// 计算亮灯的数量
int ledCountToLight = map(soundLevel, 55, 70, 0, 10);
Serial.print("LED Count: ");
Serial.println(ledCountToLight);
if (ledCountToLight >= 9) {
turnLeft180();
delay(delayAfterTurn);
turnRight180();
}
}
void turnLeft180() {
myServo.write(leftSpeed); // 设置舵机左转速度
delay(1900); // 转动持续时间
myServo.write(neutralSpeed); // 停止舵机
}
void turnRight180() {
myServo.write(rightSpeed); // 设置舵机右转速度
delay(turnDuration); // 转动持续时间
myServo.write(neutralSpeed); // 停止舵机
}
void checkButtons() {//按钮控制
if (CircuitPlayground.leftButton()) {
turnLeft180();
delay(500);
}
if (CircuitPlayground.rightButton()) {
turnRight180();
delay(500);
}
}
演示:
创意任务三:水果钢琴——通过触摸水果弹奏音乐,并配合灯光效果
对应论坛帖:【Follow me第二季第1期】创意任务三: 触控&MIDI钢琴https://bbs.eeworld.com.cn/thread-1289066-1-1.html
所使用的板载元件:彩色LED、蜂鸣器、按钮、TouchPad
水果钢琴原理就是当手触摸水果时,检测其对应引脚的电容发生的变化,当其大于某个阈值时,执行if内语句,可以说是把水果当成一个大号“按钮”。除了A0其余引脚都支持这个触摸检测的功能,用CircuitPlayground.readCap(Pin)就可以读取引脚的电容值。
代码:
#include <Adafruit_CircuitPlayground.h>
// 触控引脚及其音符频率
int touchPins[] = {A1, A2, A3, A4, A5, A6, A7};
int touchFrequencies[] = {262, 294, 330, 349, 392, 440, 494};
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
void loop() {
checkTouch();
delay(5);
}
void checkTouch() {
for (int i = 0; i < 7; i++) {
int pin = touchPins[i];
int touchValue = CircuitPlayground.readCap(pin);
if (touchValue > 1000) {
Serial.print("Touched pad A");
Serial.println(pin - A0);
playTouchNoteAndLight(i);
return;
}
}
}
void playTouchNoteAndLight(int pad) {
int frequency = touchFrequencies[pad];
uint32_t randomColor = CircuitPlayground.colorWheel(random(0, 255));
tone(A0, frequency);//播放
for (int i = 0; i < 10; i++) { //亮灯
CircuitPlayground.setPixelColor(i, randomColor);
}
delay(600); // 播放600毫秒
noTone(A0); //停止播放
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0);
}
}
演示:
还实现了一个进阶版,可以根据MIDI音符信号,使用板载蜂鸣器进行播放。程序会自动识别传入MIDI音符的音调,时值以及力度,会像创意任务一中的一样用彩色LED给出力度的电平指示,可以作为外部MIDI乐器用DAW或MIDI键盘直接控制。此外,还手动实现了一个PWM发生器,可以通过板子上的两个按钮实时的调整蜂鸣器音量大小。
流程图:
代码:
#include <Adafruit_CircuitPlayground.h>
#include <MIDIUSB.h>
// MIDI音符到频率的映射,A0到C8
const int noteFrequencies[] = {
27, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186
};
// 定义渐变颜色数组
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
int volume = 100; // 初始音量
int currentNote = -1;
unsigned long noteStartTime = 0;
unsigned long noteDuration = 0;
int currentFrequency = 0;
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
}
void loop() {
checkButtons(); // 检查按钮状态
midiEventPacket_t rx;
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
// 检查NOTE ON 消息
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int note = rx.byte2; // 获取音符音高
int velocity = rx.byte3; // 获取音符力度
playNoteAndLight(note, velocity);
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
stopNoteAndLight();
}
}
} while (rx.header != 0);
if (currentNote != -1 && millis() - noteStartTime >= noteDuration) {
stopNoteAndLight();
}
delay(5);
}
void playNoteAndLight(int note, int velocity) {
if (note >= 21 && note <= 108) { // 确保音符在有效范围内
int frequency = noteFrequencies[note - 21];
currentFrequency = frequency;
currentNote = note;
noteStartTime = millis();
noteDuration = 1000;
// 调用PWM函数来播放音符
startPWM(A0, frequency, volume);
// 计算亮灯的数量
int ledCountToLight = map(velocity, 0, 127, 0, 10);
Serial.print("Velocity: ");
Serial.print(velocity);
Serial.print(", LED Count: ");
Serial.println(ledCountToLight);
// 更新 LED 灯的亮度
for (int i = 0; i < 10; i++) {
if (i < ledCountToLight) {
// 点亮 LED
CircuitPlayground.setPixelColor(i, gradientColors[i]);
} else {
// 关闭 LED
CircuitPlayground.setPixelColor(i, 0);
}
}
}
}
void stopNoteAndLight() {
stopPWM(A0); // 停止自PWM
turnOffAllLEDs(); // 关闭所有 LED 灯
currentNote = -1;
}
void turnOffAllLEDs() {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0); // 关闭 LED
}
}
void checkButtons() {
if (CircuitPlayground.leftButton()) {
volume = max(volume - 5, 0); // 减小音量
Serial.print("Volume Down: ");
Serial.println(volume);
delay(200);
}
if (CircuitPlayground.rightButton()) {
volume = min(volume + 5, 127); // 增大音量
Serial.print("Volume Up: ");
Serial.println(volume);
delay(200);
}
}
void startPWM(int pin, int frequency, int volume) {
int dutyCycle = map(volume, 0, 127, 0, 255);
analogWrite(pin, dutyCycle);
tone(pin, frequency);
}
void stopPWM(int pin) {
noTone(pin);
}
演示:
项目总结
总的来说,在这次活动中,我尝试了 Adafruit Circuit Playground 板上的各种功能,并将其应用于不同的任务。通过实现不同的功能模块,包括音符播放、LED 视觉反馈和各种传感器的使用,我深入了解了这块板子的多样性和强大功能。通过这些尝试,我对硬件编程有了更全面的理解,对如何利用现有资源进行创意性的实现有了更深的认识。这次项目不仅提升了我在实际操作中的技能,也让我认识到了系统设计中的细节和优化的重要性。每一次的测试和调试都让我获得了宝贵的经验,尤其是在调节和优化过程中学到的实际问题解决技巧。对于未来的活动,我希望能够看到更多有趣和创新的硬件开发板。这将激发硬件爱好者的创意和兴趣,让我们能够在更广泛的应用场景中进行尝试和实现,使得每次活动都充满新鲜感和探索的乐趣。最后再次感谢EEWorld和得捷电子提供的机会,让我们能够深入体验和探索 Adafruit Circuit Playground 板的各种功能,期待未来能够继续参与类似的活动,并探索更多有趣的硬件和技术。
项目源码
https://download.eeworld.com.cn/detail/eew_nkXjf8/633913
-
上传了资料:
【Follow me第二季第1期】全部任务代码
- 2024-07-30
-
发表了主题帖:
【Follow me第二季第1期】创意任务三: 触控&MIDI钢琴
本帖最后由 MioChan 于 2024-7-31 00:24 编辑
水果钢琴原理就是当手触摸水果时,检测其对应引脚的电容发生的变化,当其大于某个阈值时,执行if内语句,可以说是把水果当成一个大号“按钮”。除了A0其余引脚都支持这个触摸检测的功能,用CircuitPlayground.readCap(Pin)就可以读取引脚的电容值。
手边没有完整的水果,所以就实现一个最简单的,直接用手触摸A1-A7焊盘就能发出do ra mi .. 对应的音,用水果的话应该稍微改一下判断阈值即可,原理大同小异。
下面是演示:
[localvideo]dd2fec7fe71745d7da8d8a2c2897a3f4[/localvideo]
代码:
#include <Adafruit_CircuitPlayground.h>
// 触控引脚及其音符频率
int touchPins[] = {A1, A2, A3, A4, A5, A6, A7};
int touchFrequencies[] = {262, 294, 330, 349, 392, 440, 494};
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
void loop() {
checkTouch();
delay(5);
}
void checkTouch() {
for (int i = 0; i < 7; i++) {
int pin = touchPins[i];
int touchValue = CircuitPlayground.readCap(pin);
if (touchValue > 1000) {
Serial.print("Touched pad A");
Serial.println(pin - A0);
playTouchNoteAndLight(i);
return;
}
}
}
void playTouchNoteAndLight(int pad) {
int frequency = touchFrequencies[pad];
uint32_t randomColor = CircuitPlayground.colorWheel(random(0, 255));
tone(A0, frequency);//播放
for (int i = 0; i < 10; i++) { //亮灯
CircuitPlayground.setPixelColor(i, randomColor);
}
delay(600); // 播放600毫秒
noTone(A0); //停止播放
for (int i = 0; i < 10; i++) { //关灯
CircuitPlayground.setPixelColor(i, 0);
}
}
下面再实现一个进阶版,可以根据MIDI音符信号,使用板载蜂鸣器进行播放。程序会自动识别传入MIDI音符的音调,时值以及力度,会像创意任务一中的一样用彩色LED给出力度的电平指示,可以作为外部MIDI乐器用DAW或MIDI键盘直接控制。此外,还手动实现了一个PWM发生器,可以通过板子上的两个按钮实时的调整蜂鸣器音量大小。
下面是视频演示:
[localvideo]24e54d6e290018f3c47c372b473665e9[/localvideo]
对应代码:
#include <Adafruit_CircuitPlayground.h>
#include <MIDIUSB.h>
// MIDI音符到频率的映射,A0到C8
const int noteFrequencies[] = {
27, 29, 31, 33, 35, 37, 39, 41, 44, 46, 49, 52, 55, 58, 62, 65, 69, 73, 78, 82, 87, 93, 98, 104, 110, 117, 123, 131, 139, 147, 156, 165, 175, 185, 196, 208, 220, 233, 247, 262, 277, 294, 311, 330, 349, 370, 392, 415, 440, 466, 494, 523, 554, 587, 622, 659, 698, 740, 784, 831, 880, 932, 988, 1047, 1109, 1175, 1245, 1319, 1397, 1480, 1568, 1661, 1760, 1865, 1976, 2093, 2217, 2349, 2489, 2637, 2794, 2960, 3136, 3322, 3520, 3729, 3951, 4186
};
// 定义渐变颜色数组
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
int volume = 100; // 初始音量
int currentNote = -1;
unsigned long noteStartTime = 0;
unsigned long noteDuration = 0;
int currentFrequency = 0;
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
}
void loop() {
checkButtons(); // 检查按钮状态
midiEventPacket_t rx;
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
// 检查NOTE ON 消息
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int note = rx.byte2; // 获取音符音高
int velocity = rx.byte3; // 获取音符力度
playNoteAndLight(note, velocity);
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
stopNoteAndLight();
}
}
} while (rx.header != 0);
if (currentNote != -1 && millis() - noteStartTime >= noteDuration) {
stopNoteAndLight();
}
delay(5);
}
void playNoteAndLight(int note, int velocity) {
if (note >= 21 && note <= 108) { // 确保音符在有效范围内
int frequency = noteFrequencies[note - 21];
currentFrequency = frequency;
currentNote = note;
noteStartTime = millis();
noteDuration = 1000;
// 调用PWM函数来播放音符
startPWM(A0, frequency, volume);
// 计算亮灯的数量
int ledCountToLight = map(velocity, 0, 127, 0, 10);
Serial.print("Velocity: ");
Serial.print(velocity);
Serial.print(", LED Count: ");
Serial.println(ledCountToLight);
// 更新 LED 灯的亮度
for (int i = 0; i < 10; i++) {
if (i < ledCountToLight) {
// 点亮 LED
CircuitPlayground.setPixelColor(i, gradientColors[i]);
} else {
// 关闭 LED
CircuitPlayground.setPixelColor(i, 0);
}
}
}
}
void stopNoteAndLight() {
stopPWM(A0); // 停止自PWM
turnOffAllLEDs(); // 关闭所有 LED 灯
currentNote = -1;
}
void turnOffAllLEDs() {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0); // 关闭 LED
}
}
void checkButtons() {
if (CircuitPlayground.leftButton()) {
volume = max(volume - 5, 0); // 减小音量
Serial.print("Volume Down: ");
Serial.println(volume);
delay(200);
}
if (CircuitPlayground.rightButton()) {
volume = min(volume + 5, 127); // 增大音量
Serial.print("Volume Up: ");
Serial.println(volume);
delay(200);
}
}
void startPWM(int pin, int frequency, int volume) {
int dutyCycle = map(volume, 0, 127, 0, 255);
analogWrite(pin, dutyCycle);
tone(pin, frequency);
}
void stopPWM(int pin) {
noTone(pin);
}
到此所有任务就都做完了,板子的所有功能也都玩了一遍~
- 2024-07-29
-
发表了主题帖:
【Follow me第二季第1期】创意任务二:害怕声音的纸章鱼~
这个任务就是要用开发板识别环境中的声音大小,然后控制舵机作出相应的动作。至于章鱼哥其实用3D打印实现是一个不错的选择,网上有很多这样的模型,直接切片软件里找个立方体布尔一下,挖一个能放下舵机的位置就可以了,然后用绳子之类的很好改装,都不需要自己建模。奈何暑期我不在实验室用不了,所以就自己用传单的纸折了一个🤦。
第一步,纸章鱼哥的制作以及控制结构制作
基本就是折个章鱼(这个是Splatoon3里的章鱼角色~),头部贴到快递纸板上,背面用回形针+风筝线拉到两个触角上,给纸板打个洞挂到舵机上
第二步,识别环境中的音量
这里先制作了一个音量指示器,开发板会根据环境音量大小进行亮灯,#include <Adafruit_CircuitPlayground.h>中已经封装了相应功能,调用CircuitPlayground.mic.soundPressureLevel(10)即可获取声音大小,音量计的颜色还是参考上一个创意任务一里面的。顺便加了一个滑动平均滤波器,让音量的识别平滑一点。
效果如下:
代码:
#include <Adafruit_CircuitPlayground.h>
// 定义渐变颜色
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
// 滤波系数
const float alpha = 0.3;
float filteredSoundLevel = 0;
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
}
void loop() {
float soundLevel = CircuitPlayground.mic.soundPressureLevel(10);
// 移动平均滤波
filteredSoundLevel = alpha * soundLevel + (1 - alpha) * filteredSoundLevel;
Serial.print("Raw Sound Level: ");
Serial.print(soundLevel);
Serial.print(", Filtered Sound Level: ");
Serial.println(filteredSoundLevel);
updateLEDs(filteredSoundLevel);
delay(200); // 更新频率
}
void updateLEDs(float soundLevel) {
// 计算亮灯的数量
int ledCountToLight = map(soundLevel, 55, 70, 0, 10);
for (int i = 0; i < 10; i++) {
if (i < ledCountToLight) {
CircuitPlayground.setPixelColor(i, gradientColors[i]);
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
}
void turnOffAllLEDs() {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0); // 关闭每个 LED
}
}
第三步,加入舵机控制
官方推荐的那个舵机是个连续舵机,没法控制具体的角度,只能让它以某个速度向什么方向转动多少时间来控制,我们使用#include <Servo.h>这个库来实现,用Servo.write来设置转速,当设置为90时舵机停止,具体来说连续旋转舵机在接收到中立脉宽时停止旋转,在接收到不同偏离中立脉宽的信号时分别以不同速度和方向旋转,两个方向离90越远转的越快。这个速度和时间,每个舵机都不太一样需要自己试一试。板子上的按钮也做了设置,可以控制舵机左转右转。
在代码实现中还遇到一个问题,#include <Servo.h>和彩色led的库有冲突,如果同时使用舵机和彩色led灯话,舵机会时不时朝一个方向抽搐,所以就简单粗暴的把亮灯部分灯功能删掉了,但判断逻辑还是沿用上面的代码,9颗灯珠都亮起时,转动舵机180度(触角收缩),等几秒再转回去。
以下是效果,视频中就不放声音了,可以对这板子的mic吹一口气基本声音就够大了:
[localvideo]ae15383c147a4a6958debe9d4e2ab98d[/localvideo]
#include <Adafruit_CircuitPlayground.h>
#include <Servo.h>
// 定义渐变颜色
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
// 滤波系数
const float alpha = 0.3;
float filteredSoundLevel = 0;
// 舵机定义
const int servoPin = A3; // 舵机连接的引脚
Servo myServo;
const int neutralSpeed = 90; // 中立位置速度
const int leftSpeed = 84; // 左转速度
const int rightSpeed = 94; // 右转速度
const int turnDuration = 700; // 转动持续时间
const int delayAfterTurn = 5000; // 等待时间
void setup() {
CircuitPlayground.begin();
Serial.begin(115200); // 用于调试
myServo.attach(servoPin); // 附加舵机到指定引脚
myServo.write(neutralSpeed); // 设置舵机初始速度为停止
}
void loop() {
checkButtons();
float soundLevel = CircuitPlayground.mic.soundPressureLevel(10);
// 滤波
filteredSoundLevel = alpha * soundLevel + (1 - alpha) * filteredSoundLevel;
Serial.print("Raw Sound Level: ");
Serial.print(soundLevel);
Serial.print(", Filtered Sound Level: ");
Serial.println(filteredSoundLevel);
updateLEDs(filteredSoundLevel);
myServo.write(neutralSpeed);
delay(200);
}
void updateLEDs(float soundLevel) {
// 计算亮灯的数量
int ledCountToLight = map(soundLevel, 55, 70, 0, 10);
Serial.print("LED Count: ");
Serial.println(ledCountToLight);
if (ledCountToLight >= 9) {
turnLeft180();
delay(delayAfterTurn);
turnRight180();
}
}
void turnLeft180() {
myServo.write(leftSpeed); // 设置舵机左转速度
delay(1900); // 转动持续时间
myServo.write(neutralSpeed); // 停止舵机
}
void turnRight180() {
myServo.write(rightSpeed); // 设置舵机右转速度
delay(turnDuration); // 转动持续时间
myServo.write(neutralSpeed); // 停止舵机
}
void checkButtons() {//按钮控制
if (CircuitPlayground.leftButton()) {
turnLeft180();
delay(500);
}
if (CircuitPlayground.rightButton()) {
turnRight180();
delay(500);
}
}
-
回复了主题帖:
【Follow me第二季第1期】基础任务1-3与进阶任务
sipower 发表于 2024-7-29 09:57
你这个板A10检测效果如何,我这个板,A10用ADC采集的数不对,我用的是CircuitPython做的
挺灵敏的,没有东西阻挡时读出来285,有物体阻挡的话最大能到670左右,他这个IR接收器只能接受脉冲信号,官方文档里有说是38KHZ,arduino可以这么实现
void sendIRPulse() {
for (int i = 0; i < 32; i++) {
digitalWrite(irTransmitterPin, HIGH);
delayMicroseconds(13);
digitalWrite(irTransmitterPin, LOW);
delayMicroseconds(13);
}
-
回复了主题帖:
【Follow me第二季第1期】基础任务1-3与进阶任务
sipower 发表于 2024-7-29 00:41 int distance = analogRead(A6); // 读取红外传感器的值 红外传感器应该是A10吧?
是的,没仔细看文档还以为板子上RX就是对应IR接收器上面的RX,A6 A7是串口的,已经修改了,之前那个相当于是靠检测电容实现的接近检测,感谢纠正😂
- 2024-07-28
-
发表了主题帖:
【Follow me第二季第1期】创意任务一:可以跟随MIDI音符律动的音乐摆件
本帖最后由 MioChan 于 2024-7-29 08:48 编辑
创意任务一:有创意的可穿戴装饰——可结合多种传感器和灯光效果展示
假期不在实验室,焊台、3D打印、电池啥都没有,想了想就做一个可以跟着MIDI音符律动或显示电平的指示器吧,用个绳子就可以挂到显示器上,算是个给桌面的装饰吧。主要用到的就是MIDI USB这个库。
要实现这个,最主要的是能读取到USB的 MIDI音符信息,通过以下几个代码就能做到。首先通过rx = MidiUSB.read() 接受USB MIDI信息,rx.header != 0 表示有收到信息,if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) rx.byte1 是接收到的 MIDI 消息的第一个字节(状态字节),MIDI 状态字节的前四位表示消息类型,后四位表示通道号,通过位掩码操作,将 rx.byte1 的后四位清零,只保留前四位消息类型部分。如果前四位等于 0x90,表示这是一个 “NOTE ON”消息。rx.byte3 是接收到的 MIDI 消息的第三个字节音符力度velocity,判断其不为0即可。后面只是针对这个消息做处理就好了,并不复杂。
第一个功能是针对单音符旋律的电平指示,当开发版接受到音符信号后根据力度大小用一个从绿到红的颜色环指示出力度大小,力度大小被映射为灯珠编号,灯珠会从0-对应位置逐渐增加,仿照lv表一样。有新音符进来时会及时打断上一个音符的电平显示,以保证同步。颜色就用自带的色环来定义,0就是红色。
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
第二个功能是跟随音符律动,简单来说当开发板接受到midi音符信号后随机亮起板子上0-9号彩色灯珠,并显示随机的颜色。亮度则由音符力度控制,除了旋律也可以处理和弦(毕竟一个音符只占用一个灯珠,最多可以同时显示10个)
本来是为这两个功能写了两个程序,但突然想到板子上的滑动开关还没用过,刚好可以用这个来切换两个程序。最后直接写了一个二合一程序,开关在左边为电平指示,在右为律动,可以在运行中实时切换。顺便把板子上那两个按钮也用到了,左边的可以减小灯珠的亮度,右边则是增加。
再简单介绍一下怎么用,其实只要能输出midi信号的软件都行,这里以Logic DAW为例,创建一个工程,然后在图中空白处右键创建外部MIDI轨
选中这个轨道,在侧栏的MIDI Output中选择我们的开发板
接下来,写好音符就可以在板子上看见效果了。顺便一提,如果在上面的MIDI Input中选择了MDI 键盘也可以实现键盘对开发板的直接控制,键盘的按压力度也能直观的展现。
下面是演示视频:
[localvideo]29a08de3ff659976b74acdd544aeac64[/localvideo]
第一段是电平计演示,使用的是人声轨旋律的音符
第二段是律动演示,使用的是电吉他的音符
代码如下:
#include <Adafruit_CircuitPlayground.h>
#include <MIDIUSB.h>
bool slideSwitch;
int brightness = 100;
// 定义颜色数组
uint32_t gradientColors[10] = {
CircuitPlayground.colorWheel(90),
CircuitPlayground.colorWheel(80),
CircuitPlayground.colorWheel(70),
CircuitPlayground.colorWheel(60),
CircuitPlayground.colorWheel(50),
CircuitPlayground.colorWheel(40),
CircuitPlayground.colorWheel(30),
CircuitPlayground.colorWheel(20),
CircuitPlayground.colorWheel(10),
CircuitPlayground.colorWheel(0)
};
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
randomSeed(analogRead(0)); // 随机数生成器
}
void loop() {
slideSwitch = CircuitPlayground.slideSwitch();
bool noteHandled = false; // 标记是否处理过音符
midiEventPacket_t rx;
if (CircuitPlayground.leftButton()) {
brightness = max(0, brightness - 10); // 减少亮度
CircuitPlayground.setBrightness(brightness);
delay(100);
}
if (CircuitPlayground.rightButton()) {
brightness = min(255, brightness + 10); // 增加亮度
CircuitPlayground.setBrightness(brightness);
delay(100);
}
if (slideSwitch) {
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
// 检查是否是 NOTE ON 消息
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int velocity = rx.byte3; // 获取音符力度
updateLEDs(velocity);
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
// 处理 NOTE OFF 消息或者 NOTE ON 力度为 0
turnOffAllLEDs();
}
}
} while (rx.header != 0);
delay(5);
} else {
do {
rx = MidiUSB.read();
if (rx.header != 0) {
Serial.print("Received: ");
Serial.print(rx.header);
Serial.print("-");
Serial.print(rx.byte1);
Serial.print("-");
Serial.print(rx.byte2);
Serial.print("-");
Serial.println(rx.byte3);
if (!noteHandled) {
if ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 != 0) {
int velocity = rx.byte3;
lightRandomLED(velocity);
noteHandled = true;
} else if ((rx.byte1 & 0xF0) == 0x80 || ((rx.byte1 & 0xF0) == 0x90 && rx.byte3 == 0)) {
turnOffAllLEDs();
}
}
}
} while (rx.header != 0 && !noteHandled);
delay(5);
}
}
void updateLEDs(int velocity) {
// 计算亮灯的数量
int ledCountToLight = map(velocity, 0, 127, 0, 10);
Serial.print("Velocity: ");
Serial.print(velocity);
Serial.print(", LED Count: ");
Serial.println(ledCountToLight);
// 更新 LED
for (int i = 0; i < 10; i++) {
if (MidiUSB.read().header != 0) {
turnOffAllLEDs();
return; // 检查是否有新的 MIDI 输入
}
delay(20);
if (i < ledCountToLight) {
CircuitPlayground.setPixelColor(i, gradientColors[i]);
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
for (int i = ledCountToLight; i >= 0; i--) {
if (MidiUSB.read().header != 0) {
turnOffAllLEDs();
return; // 检查是否有新的 MIDI 输入
}
delay(20);
CircuitPlayground.setPixelColor(i, 0);
}
}
void lightRandomLED(int velocity) {
// 计算亮度
int brightness = map(velocity, 0, 127, 0, 150);
// 生成随机 LED 和颜色
int randomLED = random(0, 10);
uint32_t randomColor = random(0, 254);
CircuitPlayground.setBrightness(brightness);
// 点亮随机 LED
CircuitPlayground.setPixelColor(randomLED, CircuitPlayground.colorWheel( randomColor));
Serial.print("Random LED: ");
Serial.print(randomLED);
Serial.print(", Color: ");
Serial.print((randomColor >> 16) & 0xFF);
Serial.print(",");
Serial.print((randomColor >> 8) & 0xFF);
Serial.print(",");
Serial.println(randomColor & 0xFF);
}
void turnOffAllLEDs() {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, 0); // 关闭每个 LED
}
}
-
发表了主题帖:
【Follow me第二季第1期】基础任务1-3与进阶任务
本帖最后由 MioChan 于 2024-7-31 00:09 编辑
基础任务一(必做):控制板载炫彩LED,跑马灯点亮和颜色变换
这个用Adafruit_NeoPixel.h这个库就能实现,还加入了一些额外的功能,左边的按钮可以切换亮度,右边的按钮可以切换灯效,一共设置了五种灯效,分别是三种常亮,追逐,彩虹。除此之外,按下按钮蜂鸣器也会依次发出do, re, mi, fa, sol, la, si的音调,通过调节占空比我们还能控制蜂鸣器音量。
#include <Adafruit_NeoPixel.h>
#define PIN 8
// 定义按钮引脚
#define BUTTON_BRIGHTNESS 4 // 调节亮度
#define BUTTON_EFFECT 5 // 切换效果
// 定义音调
const int tones[] = {262, 294, 330, 349, 392, 440, 494}; // do, re, mi, fa, sol, la, si
int toneIndex = 0; // 当前音调
int volume = 30; // 音量
Adafruit_NeoPixel strip = Adafruit_NeoPixel(10, PIN, NEO_GRB + NEO_KHZ400);
int currentEffect = 0;
int brightness = 50;
int lastButtonBrightnessState = LOW;
int lastButtonEffectState = LOW;
void setup() {
pinMode(BUTTON_BRIGHTNESS, INPUT); // 设置按钮
pinMode(BUTTON_EFFECT, INPUT);
pinMode(A0, OUTPUT); // 设置扬声器
strip.begin();
strip.setBrightness(brightness);
strip.show();
Serial.begin(9600);
}
void loop() {
// 检查按钮状态
checkButtons();
// 执行当前效果
switch (currentEffect) {
case 0:
colorWipe(strip.Color(255, 0, 0), 50);
break;
case 1:
colorWipe(strip.Color(0, 255, 0), 50);
break;
case 2:
colorWipe(strip.Color(0, 0, 255), 50);
break;
case 3:
theaterChase(strip.Color(127, 127, 127), 50);
break;
case 4:
rainbowCycle(20);
break;
}
}
bool checkButtons() {
bool buttonPressed = false;
// 检查D4按钮是否被按下以更改亮度
int buttonBrightnessState = digitalRead(BUTTON_BRIGHTNESS);
if (buttonBrightnessState == HIGH && lastButtonBrightnessState == LOW) {
brightness = (brightness + 30) % 128; // 增加亮度
strip.setBrightness(brightness);
strip.show();
playTone(); // 播放音调
buttonPressed = true;
delay(200);
}
lastButtonBrightnessState = buttonBrightnessState;
// 检查D5按钮是否被按下以更改效果
int buttonEffectState = digitalRead(BUTTON_EFFECT);
if (buttonEffectState == HIGH && lastButtonEffectState == LOW) {
currentEffect = (currentEffect + 1) % 5; // 效果循环
playTone(); // 播放音调
buttonPressed = true;
delay(200);
}
lastButtonEffectState = buttonEffectState;
return buttonPressed;
}
// 用一种颜色逐个填充像素点
void colorWipe(uint32_t c, uint8_t wait) {
for (uint16_t i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, c);
strip.show();
delay(wait);
if (checkButtons()) return; // 如果按钮被按下,则中断循环
}
}
// 使彩虹色均匀分布
void rainbowCycle(uint8_t wait) {
uint16_t i, j;
for (j = 0; j < 256 * 5; j++) {
for (i = 0; i < strip.numPixels(); i++) {
strip.setPixelColor(i, Wheel(((i * 256 / strip.numPixels()) + j) & 255));
}
strip.show();
delay(wait);
if (checkButtons()) return; // 如果按钮被按下,则中断循环
}
}
// 剧院风格的爬行灯
void theaterChase(uint32_t c, uint8_t wait) {
for (int j = 0; j < 10; j++) {
for (int q = 0; q < 3; q++) {
for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, c); // 每三个像素点亮一个
}
strip.show();
delay(wait);
if (checkButtons()) return; // 如果按钮被按下,则中断循环
for (uint16_t i = 0; i < strip.numPixels(); i = i + 3) {
strip.setPixelColor(i + q, 0);
}
}
}
}
uint32_t Wheel(byte WheelPos) {
WheelPos = 255 - WheelPos;
if (WheelPos < 85) {
return strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);
}
if (WheelPos < 170) {
WheelPos -= 85;
return strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);
}
WheelPos -= 170;
return strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);
}
// 播放音调
void playTone() {
int frequency = tones[toneIndex];
int period = 1000000L / frequency;
int pulse = (period * volume) / 256;
for (long i = 0; i < 200L * frequency / 1000L; i++) {
digitalWrite(A0, HIGH);
delayMicroseconds(pulse);
digitalWrite(A0, LOW);
delayMicroseconds(period - pulse);
}
toneIndex = (toneIndex + 1) % 7; // 循环切换音调
}
基础任务二(必做):监测环境温度和光线,通过板载LED展示舒适程度
这里直接用到了#include <Adafruit_CircuitPlayground.h>这个库,传感器已经封装好了,直接获取数值就行。这里对灯珠做了一个划分,左边五颗用来显示温度,亮三颗处于绿色表示在人体舒服的20-24度,红色表示过热,蓝色表示过冷(因为MCU多少会发热,这个温度传感器放的太靠近了导致测得不准,我对比了一下温度计会比我环境高个五度所以代码里简单粗暴减了5)。右边五颗白色灯珠对应当前环境亮度,板子上所有led也会跟着亮度进行变化(环境亮度比较低的时候,LED也会变暗)
目前环境灯光温度都很适宜
把台灯关了右边亮度就剩一格了
压了一个冰盒到开发版上,可以看见温度显示为蓝色
#include <Adafruit_CircuitPlayground.h>
int prevTempLedCount = -1; // 初始化
int prevBrightnessLedCount = -1; // 初始化
void setup() {
CircuitPlayground.begin();
}
void loop() {
displayTemperature();
displayBrightness();
delay(1000); // 每秒更新
}
void displayTemperature() {
float temperature = CircuitPlayground.temperature() - 6;
int ledCount = 3; // 20-24度时亮三颗
uint32_t color = CircuitPlayground.colorWheel(85); // 绿色
if (temperature < 20) {
ledCount = max(0, 3 - (20 - temperature) / 5);
color = CircuitPlayground.colorWheel(170); // 蓝色
} else if (temperature > 24) {
ledCount = min(5, 3 + (temperature - 24) / 5);
color = CircuitPlayground.colorWheel(0); // 红色
}
// 如果LED数量没有变化,则不更新
if (ledCount != prevTempLedCount) {
for (int i = 0; i < 5; i++) {
if (i < ledCount) {
CircuitPlayground.setPixelColor(4 - i, color);
} else {
CircuitPlayground.setPixelColor(4 - i, 0); // 关闭LED
}
}
prevTempLedCount = ledCount; // 更新上一次的LED数量
}
}
void displayBrightness() {
int brightness = CircuitPlayground.lightSensor();
int ledCount = min(5, brightness / 50); // 亮度每增加50 LED增加一颗
uint8_t overallBrightness = map(brightness, 0, 500, 0, 100);
// 如果LED数量没有变化,则不更新
if (ledCount != prevBrightnessLedCount) {
CircuitPlayground.setBrightness(overallBrightness); //调整所有灯珠亮度
for (int i = 0; i < 5; i++) {
if (i < ledCount) {
CircuitPlayground.setPixelColor(5 + i, 255, 255, 255); // 白色
} else {
CircuitPlayground.setPixelColor(5 + i, 0); // 关闭LED
}
}
prevBrightnessLedCount = ledCount; // 更新上一次的LED数量
}
}
基础任务三(必做):接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警
前面已经用过光照传感器了,这里发现红外还没玩,所以这里我用红外传感器来做,通过IR LED发射38KHZ的IR脉冲,再读取接收器测得的模拟值,当模拟值增大即有物体靠近。没有物体接近时灯珠全灭或显示一个,随着物体接近灯珠会亮的越来越多,最后全亮蜂鸣器报警。
下面是文档中的IR介绍:
没有物体靠近时
用手靠近时,灯珠全亮以及蜂鸣器报警
#include <Adafruit_CircuitPlayground.h>
#define SAFE_DISTANCE 450 // 定义安全距离
const int alertTone = 1000; // 警报音调
const int irTransmitterPin = 25; //引脚定义
const int irReceiverPin = A10;
void setup() {
CircuitPlayground.begin();
Serial.begin(9600); //
pinMode(irReceiverPin, INPUT); // 红外传感器输入
pinMode(irTransmitterPin, OUTPUT);// 红外led输出
delay(100);
}
void loop() {
sendIRPulse();
int distance = analogRead(irReceiverPin); // 读取红外传感器的值
displayDistance(distance);
checkForIntrusion(distance);
delay(300);
}
void displayDistance(int distance) {
int ledCount = map(distance, 290, SAFE_DISTANCE, 1, 10); // 将距离值映射到0-10的LED数量
Serial.print("Distance: ");
Serial.print(distance);
Serial.print(", LED Count: ");
Serial.println(ledCount);
for (int i = 0; i < 10; i++) {
if (i < ledCount) {
CircuitPlayground.setPixelColor(i, 0, 255, 0);
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
}
void checkForIntrusion(int distance) {
if (distance > SAFE_DISTANCE) {
Serial.println("Intrusion detected!");
playAlertTone();
}
}
void sendIRPulse() {
for (int i = 0; i < 32; i++) {
digitalWrite(irTransmitterPin, HIGH);
delayMicroseconds(13);
digitalWrite(irTransmitterPin, LOW);
delayMicroseconds(13);
}
}
void playAlertTone() {
CircuitPlayground.playTone(alertTone, 500); // 播放警报音500ms
}
进阶任务(必做):制作不倒翁——展示不倒翁运动过程中的不同灯光效果
这个任务本质上就是用板载陀螺仪检测板子的姿态,然后给出对应的灯光效果。我实现的是根据板子的倾斜方向亮起对应的灯,当板子水平时所有灯全亮。唯一有点麻烦的是,要搞清楚不同x y z值对应的真实方向,为了方便处理我直接计算了x y两个方向对于x y水平面的夹角。
angleX = atan2(x, z) * 180 / PI; angleY = atan2(y, z) * 180 / PI;
水平时所有灯都亮,并为绿色(这里是对着桌面俯拍)
朝不同方向倾斜的效果,可以看见都能很好的识别,灯珠的颜色是根据灯珠编号定义的 CircuitPlayground.colorWheel(LED Number),这样刚好构成了一个色环
直接把板子垂直也能正确识别
拆了个家里的不倒翁,把开发版安在了底座上
下面是代码
#include <Adafruit_CircuitPlayground.h>
#include <math.h>
void setup() {
CircuitPlayground.begin();
Serial.begin(115200);
}
void loop() {
float x = CircuitPlayground.motionX();
float y = CircuitPlayground.motionY();
float z = CircuitPlayground.motionZ();
// 计算与水平面夹角
float angleX = atan2(x, z) * 180 / PI;
float angleY = atan2(y, z) * 180 / PI;
int ledToLight = -1;
//映射到彩色灯珠
if(angleX>11){
if(angleY<-11) ledToLight = 1;
if(angleY<11 && angleY>-11) ledToLight = 2;
if(angleY>11) ledToLight =3;
}
if(angleX>-11&& angleX<11){
if(angleY>11)
{
ledToLight =4;
// CircuitPlayground.setPixelColor(5, CircuitPlayground.colorWheel(85));
}
if(angleY<-11)
{
ledToLight = 0; // X: 0, Y: -9, Z: 0
// CircuitPlayground.setPixelColor(9, CircuitPlayground.colorWheel(85));
}
}
if(angleX<-11){
if(angleY<-11) ledToLight = 8;
if(angleY<11 && angleY>-11) ledToLight = 7;
if(angleY>11) ledToLight =6;
}
uint8_t color= map( ledToLight, 0, 9, 0, 255);
if (ledToLight == -1) {
for (int i = 0; i < 10; i++) {
CircuitPlayground.setPixelColor(i, CircuitPlayground.colorWheel(85)); //水平时都点亮
}
} else {
for (int i = 0; i < 10; i++) {
if (i == ledToLight || (ledToLight == 4 && i == 5) || (ledToLight == 0 && i == 9)) {
CircuitPlayground.setPixelColor(i, CircuitPlayground.colorWheel(color));//点亮对应位置的
} else {
CircuitPlayground.setPixelColor(i, 0);
}
}
}
Serial.print("X: ");
Serial.print(x);
Serial.print(" - Y: ");
Serial.print(y);
Serial.print(" - Z: ");
Serial.print(z);
Serial.print(" - angleX: ");
Serial.print(angleX);
Serial.print(" - angleY: ");
Serial.print(angleY);
Serial.print(" - LED To Light: ");
Serial.println(ledToLight);
delay(100);
}
到这里基本任务就做完了,开始折腾创意任务啦,三个打算尽量都做做看,视频到时候会放到汇总贴里
- 2024-07-27
-
回复了主题帖:
【Follow me第二季第1期】物料开箱、开发环境搭建以及板载LED点亮
秦天qintian0303 发表于 2024-7-27 19:40
这个板子的灯相当丰富,CircuitPython一般用什么IDE比较普遍啊?
之前用的vs code,里面有个circuitpython插件,装了就可以了,而且自带包管理工具也能直接连串口,很方便
-
发表了主题帖:
【Follow me第二季第1期】物料开箱、开发环境搭建以及板载LED点亮
本帖最后由 MioChan 于 2024-7-27 17:35 编辑
Follow me第二季第一期的板子是Adafruit Circuit Playground Express,这个板子应该是我玩过功能最多的,各种传感器元件都安排上了,圆形的设计也很好看,唯一缺点可能就是不好连接外围设备,官方有一个稍微有点小贵面包版配件,不过感觉给杜邦线剥开直接绕上去也不是不行。
我买的东西是这些,舵机是官方推荐的,因为这个板子是micro接口,官方也不带线,顺手买了一根。(暑假不在学校实验室,手边没焊台,要不绝对给这个拆了改个c口)。
这次发货很快,一周不到就收到了,下面是拆箱图
上电测试,板子出厂自带了一个跑马灯的程序,还是挺好看的
下面顺便把入门任务做了,首先是开发环境搭建。之前参加FM活动用过几次CircuitPython了,这次就用Arduino来实现全部任务吧。
Arduino的环境配置比较简单,首先去官网下载IDE,https://www.arduino.cc/en/software,按自己实际设备的系统选就好了。
安装完成后,打开IDE,点击左边的Boards Manager,输入Arduino SAMD回车,然后点下面的Install即可
安装完成后,连接开发板,就能看见上面正确显示了板子的型号。
下面开始点灯,官方文档可以在https://learn.adafruit.com/adafruit-circuit-playground-express 这里查看,板载LED对应的是13引脚,点灯程序如下
void setup() {
pinMode(13, OUTPUT);
}
void loop() {
digitalWrite(13, HIGH);
delay(1000);
digitalWrite(13, LOW);
delay(1000);
}
选择板子对应的串口点击上传,出现下面的内容表示成功
可以观察到板子的红色led开始闪烁
入门任务完成,已经等不及去玩其他功能了😍
- 2024-06-04
-
回复了主题帖:
当给出了一个电路,有什么软件可以给出其对应的方程,并给出相应的解,附图
图发给chatgpt给他分析
- 2024-06-02
-
回复了主题帖:
【Beetle ESP32 C6迷你开发板】 网络弹幕点阵时钟的实现
Jacktang 发表于 2024-6-2 08:52
总结了一下,大概这么几步
硬件连接,将点阵显示屏连接到ESP32开发板的相应引脚上。
网络配置,配置ESP ...
- 2024-05-31
-
发表了主题帖:
【Beetle ESP32 C6迷你开发板】 网络弹幕点阵时钟的实现
本帖最后由 MioChan 于 2024-5-31 12:53 编辑
本帖主要介绍基于Beetle ESP32-C6 的网络弹幕点阵时钟的代码实现
TCP接受字符串请求的API服务器以及NTP服务授时的实现代码也可以参考上一帖
本来想使用64x32的LED点阵屏幕,但因为这个板子的引脚比较少,试了就算全用上也不太够,但我想到可以不做RGB屏只使用其中一个颜色实现单色不就好了,但写完代码开始编译的时候发现常用的那几个驱动点阵屏的库在这个目标开发板上编译都会各种报错,实在没找到啥好办法,国内外论坛也没有用这种迷你开发版驱动这个屏幕的例子,奈何能力不够,所以退而求其次,我们使用MAX7219 四合一模块,大概17元。然后使用下面两个库来驱动。
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
代码中点阵字体部分参考了M5stack悬浮点阵时钟这个项目,原项目给的是镜像后的字体,本项目将其变成了正像,代码直接在Arduino IDE中安装好必要的库后即可编译烧录。
屏幕可以直接安装下面的定义连到这5个引脚
#define MAX_DEVICES 4
#define CS_PIN 5
#define CLK_PIN 23
#define Din_PIN 4
代码整体结构比较简单,里面也有注释,有问题可以直接帖子里交流
#include <WiFi.h>
#include <NTPClient.h>
#include <ArduinoJson.h>
#include <NetworkUdp.h>
#include <MD_Parola.h>
#include <MD_MAX72xx.h>
#include <SPI.h>
//配网及其引脚定义
const int led = 15;
const char* ssid = "XXXX";
const char* password = "XXXX";
int status = WL_IDLE_STATUS;
String receivedContent = "";
const int timeZone = 8;
const char* ntpServer = "pool.ntp.org";
NetworkUDP udp;
WiFiServer server(80);
NTPClient timeClient(udp,ntpServer, timeZone * 3600, 60000);
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
#define MAX_DEVICES 4
#define CS_PIN 5
#define CLK_PIN 23
#define Din_PIN 4
//用于驱动点阵屏显示
MD_Parola Display = MD_Parola(HARDWARE_TYPE,Din_PIN,CLK_PIN, CS_PIN, MAX_DEVICES);
MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE,Din_PIN,CLK_PIN, CS_PIN, MAX_DEVICES);
void setup() {
Display.begin();
mx.begin();
Display.setIntensity(3);//设置亮度,0为最亮
Display.displayClear();
Display.setTextAlignment(PA_CENTER);//字体居中显示
Serial.begin(9600);
pinMode(led, OUTPUT);
digitalWrite(led, 0);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
Display.print("Connected");
timeClient.begin();
timeClient.update();
server.begin();
Display.displayClear();
}
//定义点阵字符
//Small_font_0-10 分别为0-10数字以及:
//uint8_t clock_0-7 表示秒针位置指示
//sun moon为太阳和月亮的图形
uint8_t Small_font_0[] = {0x3E, 0x22, 0x3E};
uint8_t Small_font_1[] = {0x24, 0x3E, 0x20};
uint8_t Small_font_2[] = {0x3A, 0x2A, 0x2E};
uint8_t Small_font_3[] = {0x2A, 0x2A, 0x3E};
uint8_t Small_font_4[] = {0x0E, 0x8, 0x3E};
uint8_t Small_font_5[] = {0x2E, 0x2A, 0x3A};
uint8_t Small_font_6[] = {0x3A, 0x2A, 0x3E};
uint8_t Small_font_7[] = {0x02, 0x02, 0x3E};
uint8_t Small_font_8[] = {0x3E, 0x2A, 0x3E};
uint8_t Small_font_9[] = {0x2E, 0x2A, 0x3E};
uint8_t Small_font_10[] = {0x14};
uint8_t clock_0[] = {0x1C, 0x22, 0x2E, 0x22, 0x1C};
uint8_t clock_1[] = {0x1C, 0x22, 0x2A, 0x26, 0x1C};
uint8_t clock_2[] = {0x1C, 0x22, 0x2A, 0x2A, 0x1C};
uint8_t clock_3[] = {0x1C, 0x22, 0x2A, 0x32, 0x1C};
uint8_t clock_4[] = {0x1C, 0x22, 0x3A, 0x22, 0x1C};
uint8_t clock_5[] = {0x1C, 0x32, 0x2A, 0x22, 0x1C};
uint8_t clock_6[] = {0x1C, 0x2A, 0x2A, 0x22, 0x1C};
uint8_t clock_7[] = {0x1C, 0x26, 0x2A, 0x22, 0x1C};
uint8_t sun[] = {0x24, 0x00, 0xbd, 0x3c, 0x3c, 0xbd, 0x00, 0x24};
uint8_t moon[] = {0x1c, 0x3e, 0x47, 0x03, 0x23, 0x72, 0x24, 0x00};
uint8_t * bitmap_data[] = {
Small_font_0,
Small_font_1,
Small_font_2,
Small_font_3,
Small_font_4,
Small_font_5,
Small_font_6,
Small_font_7,
Small_font_8,
Small_font_9,
Small_font_10,
clock_0,
clock_1,
clock_2,
clock_3,
clock_4,
clock_5,
clock_6,
clock_7,
sun,
moon
};
//用于在点阵上绘制bitmap
void display_bitmap(int abscissa, int width, int bitmap_number) {
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
mx.setBuffer(abscissa, width, bitmap_data[bitmap_number]);
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
//用于绘制水平横线
void drawHorizontalLine(int y, int x_start, int x_end,bool pixel) {
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::OFF);
for (int x = x_start; x <= x_end; x++) {
mx.setPoint(y, x, pixel);
delay(10);
}
mx.control(MD_MAX72XX::UPDATE, MD_MAX72XX::ON);
}
//检测是否有设备Post字符串
void checkForNewClient() {
WiFiClient client = server.available();
if (client) {
Serial.println("New client");
digitalWrite(led, 1);
// Read the first line of the request
String firstLine = client.readStringUntil('\n');
// Check if this is a POST request
if (firstLine.startsWith("POST")) {
// Read the headers and find the Content-Length
int contentLength = -1;
while (client.connected()) {
String line = client.readStringUntil('\n');
if (line.startsWith("Content-Length:")) {
contentLength = line.substring(15).toInt();
}
// Check if the end of headers is reached (empty line)
if (line == "\r") {
break;
}
}
// Read the request body
if (contentLength > 0) {
String requestBody = client.readStringUntil('\n');
// Parse JSON from the request body
DynamicJsonDocument doc(1024);
deserializeJson(doc, requestBody);
String content = doc["content"];
if (content != "") {
Serial.println("Received content: " + content);
receivedContent =content; // Update the received string
} else {
Serial.println("No content received");
}
}
}
digitalWrite(led,0);
// Send response to the client
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Connection: close");
client.println();
client.println("Response sent");
client.stop();
Serial.println("Client disconnected");
}
}
int hours;
int minutes ;
int seconds;
String times;
int Clock_variable = 11;
unsigned long startTime;
const unsigned long runDuration = 10000;
void loop() {
times=timeClient.getFormattedTime();
hours = times.substring(0, 2).toInt();
minutes = times.substring(3, 5).toInt();
seconds = times.substring(6, 8).toInt();
if (minutes%30 ==0 ) timeClient.update();
checkForNewClient();
if (receivedContent=="")
{
display_bitmap(22, 3, hours/ 10);
display_bitmap(18, 3, hours % 10);
display_bitmap(14, 1, 10);
display_bitmap(12, 3, minutes / 10);
display_bitmap(8, 3, minutes % 10);
display_bitmap(4, 5, seconds/8 +11);
if (seconds==0) drawHorizontalLine(7, 0,23,false);
if ((hours >= 6) && (hours <= 18)) {
display_bitmap(31, 8, 19);
} else {
display_bitmap(31, 8, 20);
}
// drawHorizontalLine(7, (23 - seconds / 10),23,true);
// drawHorizontalLine(7, (15 - seconds % 10),15,true);
startTime = millis();
delay(2000);
}
else
{
if (Display.displayAnimate()) {
Display.displayScroll(receivedContent.c_str(), PA_LEFT, PA_SCROLL_LEFT, 50);//滚动显示文字
if (millis() - startTime >= runDuration)
{
Display.displayClear(); // 清除显示
receivedContent="";
}
}
}
}
下面是实际的效果,不过这样的话感觉比较难看,我们还是打印个壳子顺便给屏幕加上柔光板。
测量好尺寸,画个外壳
直接用Bambu Studio切片并打印,3mf文件放在附件了有需要可以自取
最终实物的效果,因为我板子已经焊了排针,放不进去只能外挂了,当时设计的时候已经考虑了板子、电池和typec口的位置,没有排针的话板子直接放屏幕后面,焊条线连上就可以了
当时钟接受到其他设备的文本Post请求后,会自动开始播放弹幕,下面是文字弹幕的效果(我这里投的是Hello EEWorld),文字会从左向右滚动显示,播放几秒后会再次进入时钟模式。
弹幕投送直接用ios的快捷指令就能实现,详细解释之前那一帖以及说明。
到此,我们这个小时钟就制作完毕啦
- 2024-05-27
-
回复了主题帖:
【AI挑战营终点站】应用落地:部署手写数字识别应用到幸狐RV1106开发板
完成打卡 #AI挑战营终点站# 使用RV1106通过摄像头实时识别手写数字 https://bbs.eeworld.com.cn/thread-1282873-1-1.html