eew_9XVJps

  • 2025-01-22
  • 回复了主题帖: 祝福2025!回帖即有奖!选取最有心的送5块国产开发板!

    祝大家新的一年里技术更牛,工作一流

  • 2024-12-23
  • 发表了主题帖: 【Follow me第二季第4期】全部任务提交

    本帖最后由 eew_9XVJps 于 2024-12-23 14:54 编辑 一、任务演示视频 【Follow me第二季第4期】全部任务演示视频-【Follow me第二季第4期】全部任务演示视频-EEWORLD大学堂 二、任务实现详情 (一)、物料介绍 此次活动的板子是Arduino Nano RP2040 Connect,主控是Raspberry Pi RP2040微控制器,板载了WiFi模块、MEMS麦克风、6轴惯性测量单元、RGB彩灯及加密协处理器,功能强大,外设丰富,具有很高的可玩性。为了配合任务我还采购了一块XIAO ESP32S3来配合完成相关的任务,利用两个板子的WIFI来实现无线数据传输功能,在这里主要是传输音频数据用于实现无线麦克风或者无线音频氛围灯的效果。除了以上两个板子之外还准备了SD卡模块,WS2812灯板以及MAX98357模块用于录音数据的存储和音乐氛围灯的展示。   活动购入的Arduino Nano RP2040 Connect和XIAO ESP32S3,非常的小巧精致 自备的SD卡模块   自备的WS2812灯板   自备MAX98357及喇叭 (二)、任务实现 必做任务一:搭建环境并开启第一步Blink三色LED / 串口打印Hello DigiKey & EEWorld!; 既然是Arduino官方板子,决定使用Arduino IDE进行开发,主要是环境安装简单,支持也肯定不会差。Arduino的安装较为简单,从官网上下载软件安装报后,直接在软件的开发板管理器中搜索Arduino Nano RP2040 Connect就可以看到Arduino Mbed OS Nano Boards的选项,点击安装即可。 第一个任务需要使用板载的RGB灯,从pinout资料中可以看到这个三色RGB灯由WiFi模块的三个引脚控制,因此需要下载WiFiNINA库方便对三个引脚进行单独控制。库的安装方法也较为简单,直接在Arduino IDE的库管理里面搜索WiFiNINA点击安装即可。 安装后引入相应的库头文件和SPI库头文件就可以对引脚进行操作了,这三个引脚已经在库中进行了定义,分别是LEDR、LEDG、LEDB,直接像使用其他Arduino板子上的引脚一样初始化、拉高、拉低就可以实现对RGB灯颜色和亮度的控制了。在这个控制上遇见个文件,那就是直接对灯的引脚拉高则灯全亮,拉低则灯灭,对应的使用analogWrite函数写的数值则相反,写0时灯完全亮,写255时灯几乎不亮,与digitalWrite控制相反。 程序先是对引脚和串口进行初始化,然后使用两个变量l、m分别控制rgb灯的亮度和切换,亮度l在循环过程中自加,加至最大亮度因uin8_t数据类型限制自动溢出至0,同时使灯珠控制变量自加1,如果灯珠变量超过2则重新置0,循环过程中打印相关信息输出至串口。 流程图: 代码如下: #include <SPI.h> #include <WiFiNINA.h> void setup() {   // put your setup code here, to run once:   Serial.begin(9600);   pinMode(LEDR, OUTPUT);   pinMode(LEDG, OUTPUT);   pinMode(LEDB, OUTPUT);   digitalWrite(LEDR, LOW);   digitalWrite(LEDG, LOW);   digitalWrite(LEDB, LOW);   } uint8_t l=0;//亮度控制 uint8_t m=0;//灯珠控制 void loop() {   // put your main code here, to run repeatedly:   if(m==0){     analogWrite(LEDR,l);     digitalWrite(LEDG, LOW);     digitalWrite(LEDB, LOW);   }   else if(m==1){     digitalWrite(LEDR, LOW);     analogWrite(LEDG,l);     digitalWrite(LEDB, LOW);   }   else if(m==2){     digitalWrite(LEDR, LOW);     digitalWrite(LEDG, LOW);     analogWrite(LEDB,l);   }   l++;   if(l==255){     m++;   }   if(m>2){     m=0;   }   Serial.println("Hello DigiKey & EEWorld!");   delay(5); }   效果图如下,动态效果见视频: 必做任务二:学习IMU基础知识,调试IMU传感器,通过串口打印六轴原始数据; 通过网络上查到的相关信息显示,MEMS加速度计基本原理为检测移动质量块与固定极板之间因位移而产生的电容变化和形变来检测加速度等信息变化的。 而陀螺仪则是通过外部施加周期电压产生周期性定向位移,如果有旋转发生则存在科里奥利使得移动方向发生偏移,从而从偏移中计算角速度的方法。 板载的这块IMU芯片能直接输出加速度、角速度数据,以及温度数据。为了方便获取相关数据,可下载Arduino_LSM6DSOX库来实现数据的读取,下载方法同上一个任务的库。 这个库提供了一个外部对象IMU供我们调用,使用begin函数启动后根据需要使用不同的read函数来读取加速度值、角度值和温度值。 程序流程图: 代码如下: #include <Arduino_LSM6DSOX.h> void setup() {   Serial.begin(9600);   while (!Serial);   if (!IMU.begin()) {     Serial.println("Failed to initialize IMU!");     while (1);   }   delay(1000); } void loop() {   float x, y, z;   float a, b, c,t;   if (IMU.accelerationAvailable()) {     IMU.readAcceleration(x, y, z);     Serial.print("Accelerometer");     Serial.print(x);     Serial.print('\t');     Serial.print(y);     Serial.print('\t');     Serial.print(z);     Serial.print('\t');   }   if (IMU.gyroscopeAvailable()) {     IMU.readGyroscope(a, b, c);     Serial.print("Gyroscope");     Serial.print(a);     Serial.print('\t');     Serial.print(b);     Serial.print('\t');     Serial.print(c);     Serial.print('\t');   }   if (IMU.temperatureAvailable()) {     IMU.readTemperatureFloat(t);     Serial.print("Temperature");     Serial.print(t);     Serial.println('\t');   }   else{     Serial.println();   } }   效果图如下,动态效果见视频: 必做任务三:学习PDM麦克风技术知识,调试PDM麦克风,通过串口打印收音数据和音频波形。 PDM麦克风内部集成了将声波转换为电信号的 MEMS 膜和数据处理芯片,MEMS膜负责将动能转换成电信号,而数据处理芯片则将电信号进一步处理。 PDM麦克风在数据处理时将模拟信号电压转换为经过单比特脉冲密度调制的数字流,PDM 信号更接近于纵波,而不是在音频中看到的典型横波。对于波峰部分则用高密度1低密度0表示,波谷则使用高密度0低密度1表示,平稳段则使用均匀的1和0表示。PDM麦克风的优势是使用资源少,只需要2个控制引脚。 在Arduino IDE中安装了Arduino Mbed OS Nano Boards之后就直接集成了PDM库用于PDM麦克风的使用,同时也提供了演示代码,这个任务直接使用演示代码即可实现。代码基本顺序是:配置PDM回调函数(用于处理麦克风数据)->开启麦克风(设置通道数和采样率)->判断并打印麦克风数据。需要注意PDM频率不能设置过高,否则无法启动和正常工作。 程序流程图: 代码如下: #include <PDM.h> static const char channels = 1; static const int frequency = 16000; short sampleBuffer[512]; volatile int samplesRead; void setup() {   Serial.begin(9600);   while (!Serial);   PDM.onReceive(onPDMdata);   if (!PDM.begin(channels, frequency)) {     Serial.println("Failed to start PDM!");     while (1);   } } void loop() {   if (samplesRead) {     for (int i = 0; i < samplesRead; i++) {       if(channels == 2) {         Serial.print("L:");         Serial.print(sampleBuffer[i]);         Serial.print(" R:");         i++;       }       Serial.println(sampleBuffer[i]);     }     samplesRead = 0;   } } void onPDMdata() {   int bytesAvailable = PDM.available();   PDM.read(sampleBuffer, bytesAvailable);   samplesRead = bytesAvailable / 2; }   效果图如下,动态效果见视频: 必做任务三的进阶: 任务三中使用PDM麦克风,获取到了音频数据并通过串口打印了音频数据和波形,在这里使用XIAO ESP32S3将PDM麦克风拾取的声音再次播放出来。 过程是利用Arduino Nano RP2040 Connect板载的PDM麦克风作为音乐拾取器拾取外部声音,通过UDP将数据打包后发送至XIAO ESP32S3开发板。XIAO ESP32S3支持I2S音频,将收到的声音通过I2S输出到MAX98357模块将声音放大外放。 与前面任务相比多了配置WIFI、启动WIFI和启动UDP的过程,并将收集的声音数据通过UDP打包发送。程序注意发射端和接收端的采样率要保持一致,避免声音变调。 程序流程图: Arduino Nano RP2040 Connect的WIFI配置代码如下: if (WiFi.status() == WL_NO_MODULE) {     Serial.println("Communication with WiFi module failed!");     // don't continue     while (true);   }   // attempt to connect to WiFi network:   while (status != WL_CONNECTED) {     Serial.print("Attempting to connect to SSID: ");     Serial.println(ssid);     // Connect to WPA/WPA2 network. Change this line if using open or WEP network:     WiFi.config(local_IP, gateway, subnet);     status = WiFi.begin(ssid, pass);     // wait 10 seconds for connection:     delay(10000);   }   Serial.println("Connected to WiFi");   printWifiStatus(); //如果收集到数据就打包发送: if (samplesRead) {     // send a reply, to the IP address and port that sent us the packet we received     Udp.beginPacket(udpAddress, localPort);     Udp.write((const uint8_t *)sampleBuffer, samplesRead*2);; Udp.endPacket(); samplesRead=0;   }   XIAO ESP32S3接收端的程序流程图: 代码如下: #include <WiFi.h> #include <WiFiUdp.h> #include "driver/i2s.h" #define I2S_SAMPLE_RATE 20000 #define I2S_DMA_BUF_LEN 256 const i2s_port_t SPK_I2S_PORT = I2S_NUM_0; const char * udpAddress = "192,168,1,101"; const int udpPort = 2390; //Are we currently connected? boolean connected = false; char *ssid = "";//网络名称 const char *password = "";//网络密码 IPAddress local_IP(192,168,1,100); IPAddress gateway(192,168,1,1); IPAddress subnet(255,255,255,0); //The udp library class WiFiUDP udp; char packetBuffer[256]; //buffer to hold incoming packet char  ReplyBuffer[] = "esp32s3";       // a string to send back void setup(){   // Initilize hardware serial:   Serial.begin(115200);   WiFi.disconnect();   WiFi.mode(WIFI_STA);   WiFi.config(local_IP, gateway, subnet);   WiFi.begin(ssid, password);   Serial.print("Connecting to WiFi ..");   while (WiFi.status() != WL_CONNECTED) {     Serial.print('.');     delay(1000);   }   Serial.println();   Serial.println(WiFi.localIP());   // while (WiFi.softAP(ssid,password,16) != 1){// 设置网络名称和密码   // }   udp.begin(WiFi.localIP(),udpPort);   I2SInit_SPK(); } void loop(){   int packetSize = udp.parsePacket();   if (packetSize) {     int len = udp.read(packetBuffer, 255);     if (len > 0) {       size_t sendSize;       //Serial.println(len);       i2s_write(SPK_I2S_PORT,(int16_t*)packetBuffer, len, &sendSize, portMAX_DELAY);     }   } } const i2s_config_t spk_i2s_config = {       .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX ),       .sample_rate = I2S_SAMPLE_RATE,       .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,  //I2S输出只支持16位       .channel_format = I2S_CHANNEL_FMT_ONLY_RIGHT,                           //2-channels       .communication_format =(i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),       .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,       .dma_buf_count = 8,       .dma_buf_len = I2S_DMA_BUF_LEN, }; const i2s_pin_config_t spk_pin_config = {       .bck_io_num = 9,       .ws_io_num = 8,       .data_out_num = 7,       .data_in_num = -1  //17Not used }; void I2SInit_SPK() { esp_err_t err;   err=i2s_driver_install(SPK_I2S_PORT, &spk_i2s_config, 0, NULL);   if(err!=0){     Serial.println("i2s driver install failed");   }   err=i2s_set_pin(SPK_I2S_PORT, &spk_pin_config);   if(err!=0){     Serial.println("i2s set pin failed");   } }   效果图如下,动态效果见视频: 选做任务一(非必做):通过RGB LED不同颜色、亮度显示PDM麦克风收到的声音大小; 从上一个任务程序可以看到,PDM可以直接输出声音的大小,大小范围为正负两位数值,在获取声音大小时只需要取数值的绝对值即可。另外可以根据实际环境音量的大小范围将其划分为3段后分别指定给R,G和B三个颜色,因为需要输出给LED等控制灯的亮度,可以使用map函数将划分后的音量大小转换为analogWrite可接受的范围,循环过程中需要记得将灯光重置为0,并在点亮后进行适当的延时。 程序流程图: 代码如下: #include <PDM.h> #include <SPI.h> #include <WiFiNINA.h> static const char channels = 1; static const int frequency = 16000; short sampleBuffer[512]; volatile int samplesRead; void setup() {   Serial.begin(9600);   while (!Serial);   pinMode(LEDR, OUTPUT);   pinMode(LEDG, OUTPUT);   pinMode(LEDB, OUTPUT);   digitalWrite(LEDR, LOW);   digitalWrite(LEDG, LOW);   digitalWrite(LEDB, LOW);   PDM.onReceive(onPDMdata);   if (!PDM.begin(channels, frequency)) {     Serial.println("Failed to start PDM!");     while (1);   } } void loop() {   if (samplesRead) {     for (int i = 0; i < 6; i++) {       uint16_t temp=0;       for(int j= 0; j < 10;j++){         temp+=abs(sampleBuffer[j+i*10]);       }       temp = temp/10;       //Serial.println(temp);       uint8_t lum;       analogWrite(LEDG,255);       analogWrite(LEDB,255);       analogWrite(LEDR,255);       if(1089>temp){         lum=map(temp,0,10892,255,0);//蓝色         analogWrite(LEDB,lum);       }       else if(2178>temp && temp>=1089){         lum=map(temp,10892,21784,255,0);//绿色         analogWrite(LEDG,lum);       }       else if(3267>=temp && temp>=2178){         lum=map(temp,21784,32676,255,0);//红色         analogWrite(LEDR,lum);       }       delay(20);       }     samplesRead = 0;   } } void onPDMdata() {   int bytesAvailable = PDM.available();   PDM.read(sampleBuffer, bytesAvailable);   samplesRead = bytesAvailable / 2; }   效果图如下,动态效果见视频: 上面这个任务使用板载的一个LED灯来显示声音,效果不够明显,于是想到了对声音数据进行FFT之后利用WS2812灯板进行显示,于是决定这么做。实际测试后发现PDM库和FASTELD库冲突,两个启动后板子就不能工作了,没有深入研究是为什么,改为利用XIAO ESP32S3开发板通过UDP接收数据后进行FFT并推送至WS2812灯板进行显示。 大体流程如下:   代码方面相较于前面的UDP发送音频和播放音频Arduino Nano RP2040 Connect可以直接使用原代码,XIAO ESP32S3开发板的接收端比前面多了FASTLED初始化及FF变换相关内容。代码如下: #include <arduinoFFT.h> #include <FastLED.h> #include <FastLED_NeoMatrix.h> #include <WiFi.h> #include <WiFiUdp.h> const int udpPort = 2390; boolean connected = false; char *ssid = "CMCC-sdtm"; const char *password = "mvkrxs76"; IPAddress local_IP(192,168,1,100); IPAddress gateway(192,168,1,1); IPAddress subnet(255,255,255,0); WiFiUDP udp; #define SAMPLES 64        // Must be a power of 2 #define LED_PIN     4     // Data pin to LEDS  D2 #define NUM_LEDS    256    // 灯珠数 #define BRIGHTNESS  150    // LED 亮度 #define LED_TYPE    WS2812B #define COLOR_ORDER GRB #define xres 16            // Total number of  columns in the display #define yres 16            // Total number of  rows in the display #define SAMPLING_FREQ 15000 double vReal[SAMPLES]; double vImag[SAMPLES]; //幅值算法************************************************************* //从group取出特征角标取出初始幅值→加上shift偏移→乘gain倍率→除以gain2倍率 //暂时只用了freq_gain2 int freq_group[8] = {2, 5, 12, 20, 30, 38, 50, 75}; int freq_shift[8] = {-50, -10, -10, -10, -5, -5, -5, -5}; int freq_gain[8] = {1, 1, 1, 1, 2, 2, 4, 4}; int freq_gain2[xres] = {40, 42, 45, 47, 51, 55, 57, 59, 62, 65, 69, 71, 73, 76, 80, 82}; //算出来的长度值 unsigned char freq_block[xres] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int Intensity[xres] = { }; // initialize Frequency Intensity to zero int Intensity_last[xres] = { }; // initialize Frequency Intensity to zero int Displacement = 1; CRGB leds[NUM_LEDS];            // Create LED Object ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, SAMPLES, SAMPLING_FREQ);  // Create FFT object int color = 0; //颜色变化 int color_rate = 1;  //单次变换间隔倍数 int color_interval = 1;  //颜色变换间隔 FastLED_NeoMatrix matrix = FastLED_NeoMatrix(leds, 8, 8,2,2,   NEO_MATRIX_TOP  + NEO_MATRIX_RIGHT +   NEO_MATRIX_COLUMNS       + NEO_MATRIX_PROGRESSIVE +   NEO_TILE_TOP + NEO_TILE_RIGHT + NEO_TILE_ROWS + NEO_TILE_PROGRESSIVE); void setup() {   Serial.begin(9600);         //Initialize Serial   delay(3000);                  // power-up safety delay   FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); //Initialize LED strips   FastLED.setBrightness(BRIGHTNESS);   WiFi.disconnect();   WiFi.mode(WIFI_STA);   WiFi.config(local_IP, gateway, subnet);   WiFi.begin(ssid, password);   Serial.print("Connecting to WiFi ..");   while (WiFi.status() != WL_CONNECTED) {     Serial.print('.');     delay(1000);   }   Serial.println();   Serial.println(WiFi.localIP());   udp.begin(WiFi.localIP(),udpPort); } void loop() {   for(int i = 0; i < 4; i++){     Visualizer();     delay(1);     flow(i);   } } void flow(int rate){   if(rate == 3){     for(int i = 0; i < xres; i++){       if(freq_block[i]>0){         freq_block[i] = freq_block[i]-1;        }     }   } } void Visualizer(){   //Collect Samples   getSamples();   FastLED.clear();   //Update Display   displayUpdate();   FastLED.show(); } char packetBuffer[256]; //buffer to hold incoming packet void getSamples(){   int packetSize = udp.parsePacket();   if (packetSize) {     int len = udp.read(packetBuffer, 255);     if (len > 0) {       for(int i = 0; i < SAMPLES; i++){         vReal[i] = packetBuffer[i]/2;         //Serial.println(vReal[i]);         vImag[i] = 0;       }       //FFT       FFT.windowing(vReal, 1, FFT_WIN_TYP_HAMMING, FFT_FORWARD);       FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD);       FFT.complexToMagnitude(vReal, vImag, SAMPLES);       //Update Intensity Array       for(int i = 2; i < (xres*Displacement)+2; i+=Displacement){         vReal[i] = constrain(vReal[i],freq_gain2[i-2] ,1800);            // set max value for input data         vReal[i] = map(vReal[i], freq_gain2[i-2], 1800, 0, yres);        // map data to fit our display         Intensity[(i/Displacement)-2] --;                      // Decrease displayed value         if (vReal[i] > Intensity[(i/Displacement)-2])          // Match displayed value to measured value           Intensity[(i/Displacement)-2] = vReal[i];       }     }   } } void displayUpdate(){   int color = 0; // 初始化颜色变量   int Intensity_ava[xres] = { };   for(int x=0; x< xres; x++){     Intensity_ava[x]=(Intensity_last[x]+Intensity[x])/2;     }   for(int i = 0; i < xres; i++){ // 遍历矩阵的每一行     for(int j = 0; j < yres; j++){ // 遍历每一行的每一个像素       if(j <= Intensity_ava[i]){ // Light everything within the intensity range         if(j > freq_block[i]){           matrix.drawPixel(i, j, CHSV(color, 255, BRIGHTNESS)); // 绘制亮像素         }       }     }     color += 255 / xres; // Increment the Hue to get the Rainbow effect     if(color > 255){       color = 0; // Wrap around the Hue value if it exceeds 255     }   }   for (int i = 0; i < xres; i++) {     Intensity_last[i] = Intensity_ava[i];   }   delay(5); }   效果图如下,动态效果见视频:   自定任务:Arduino Nano RP2040 Connect板载的PDM麦克风实现录音功能 看到板载有麦克风为什么不试一下录制声音呢,因此想通过板载的PDM麦克风实现声音拾取,并将收集到的数据以WAV格式形式存储在SD卡中。SD卡通过SPI接口实现与主控板通讯,SPI引脚直接使用了默认引脚。 我使用的SD卡外置模块为5V供电,需要将主控板的VBUS焊盘连接上来进行供电,否则3V3无法启动。 流程顺序大体如下:   在刚开始是按照这个程序进行测试发现播放录制的声音明显速度变快了,在群里咨询了各位大佬,提出了各种意见,后来测试发现这个PDM只有在固定频率下才能输出正常速度的声音,速率高了就快,速率低了就慢,只有14000左右时声音即清楚又与实际音频速度一致。按说这个时采样率应该不影响程序速度什么的,不知道底层是怎么写的,程序如下: #include <PDM.h> #include "wave.h" #include <SPI.h> #include "SdFat.h" const int chipSelect = 10; // default number of output channels static const char channels = 1; // default PCM output frequency static const int frequency = 14000; // Buffer to read samples into, each sample is 16-bits short sampleBuffer[128]; const int record_time = 30;  // second const char filename[] = "/record.wav"; const int waveDataSize = record_time * 14000; char partWavData[1024]; // Number of audio samples read volatile int samplesRead; FsFile file;  // 录音文件 SdFs sd; void setup() {   Serial.begin(9600);   delay(2000);   if(!sd.begin(SdSpiConfig(chipSelect, DEDICATED_SPI, SD_SCK_MHZ(16))))//初始化SD   {     Serial.println(F("sd init error"));     return;   }   sd.remove(filename);   file = sd.open(filename, O_WRITE|O_CREAT);   if(!file)   {     Serial.println("crate file error");     return;   }   // Configure the data receive callback   auto header = CreateWaveHeader(1, 14000, 16);   header.riffSize = waveDataSize + 44 - 8;   header.dataSize = waveDataSize;   file.write(&header,44);   PDM.onReceive(onPDMdata);   // Optionally set the gain   // Defaults to 20 on the BLE Sense and 24 on the Portenta Vision Shield   PDM.setGain(10);   // Initialize PDM with:   // - one channel (mono mode)   // - a 16 kHz sample rate for the Arduino Nano 33 BLE Sense   // - a 32 kHz or 64 kHz sample rate for the Arduino Portenta Vision Shield   if (!PDM.begin(channels, frequency)) {     Serial.println("Failed to start PDM!");     while (1);   }   else{     Serial.println("start");   } } int count = waveDataSize/128; void loop() {   // Wait for samples to be read   if(count==0){     samplesRead=0;     file.close();     Serial.println("finish");   }   if (samplesRead) {     file.write((const byte*)sampleBuffer, 128);     count--;     // Clear the read count     samplesRead = 0;   }   // if (samplesRead) {   //   Serial.println(samplesRead);   //   samplesRead=0;   // } } /**  * Callback function to process the data from the PDM microphone.  * NOTE: This callback is executed as part of an ISR.  * Therefore using `Serial` to print messages inside this function isn't supported.  * */ void onPDMdata() {   // Query the number of available bytes   int bytesAvailable = PDM.available();   // Read into the sample buffer   PDM.read(sampleBuffer, bytesAvailable);//每次64样本   // 16-bit, 2 bytes per sample   samplesRead = bytesAvailable / 2; }   效果图如下,动态效果见视频: 选做任务二(非必做):通过IMU数据结合机器学习算法,识别运动状态,并通过串口打印。 这个任务不是很懂,根据官方资料应该是可以将芯片训练数据发送给IMU,IMU能根据收集数据和训练数据进行对比做出响应?这是烧录后的实际效果。 Arduino官方关于这个的介绍页面Using the IMU Machine Learning Core Features | Arduino Documentation。 根据官网页面介绍完成开发吧安装,STM32duino X-NUCLEO-IKS01A3 库安装,然后打开示例并修改引脚信息,就可以进行烧录了。 烧录后可以看到在不同的运动状态下开发板可以识别出不同的信息并通过串口打印出来。 代码信息: #include "LSM6DSOXSensor.h" #include "lsm6dsox_activity_recognition_for_mobile.h" #ifdef ARDUINO_SAM_DUE #define DEV_I2C Wire1 #elif defined(ARDUINO_ARCH_STM32) #define DEV_I2C Wire #elif defined(ARDUINO_ARCH_AVR) #define DEV_I2C Wire #else #define DEV_I2C Wire #endif #define SerialPort Serial #define INT_1 INT_IMU //Interrupts. volatile int mems_event = 0; // Components LSM6DSOXSensor AccGyr(&DEV_I2C, LSM6DSOX_I2C_ADD_L); // MLC ucf_line_t *ProgramPointer; int32_t LineCounter; int32_t TotalNumberOfLine; void INT1Event_cb(); void printMLCStatus(uint8_t status); void setup() {   uint8_t mlc_out[8];   // Led.   pinMode(LED_BUILTIN, OUTPUT);   // Force INT1 of LSM6DSOX low in order to enable I2C   pinMode(INT_1, OUTPUT);   digitalWrite(INT_1, LOW);   delay(200);   // Initialize serial for output.   SerialPort.begin(115200);   // Initialize I2C bus.   DEV_I2C.begin();   AccGyr.begin();   AccGyr.Enable_X();   AccGyr.Enable_G();   /* Feed the program to Machine Learning Core */   /* Activity Recognition Default program */     ProgramPointer = (ucf_line_t *)lsm6dsox_activity_recognition_for_mobile;   TotalNumberOfLine = sizeof(lsm6dsox_activity_recognition_for_mobile) / sizeof(ucf_line_t);   SerialPort.println("Activity Recognition for LSM6DSOX MLC");   SerialPort.print("UCF Number Line=");   SerialPort.println(TotalNumberOfLine);   for (LineCounter=0; LineCounter<TotalNumberOfLine; LineCounter++) {     if(AccGyr.Write_Reg(ProgramPointer[LineCounter].address, ProgramPointer[LineCounter].data)) {       SerialPort.print("Error loading the Program to LSM6DSOX at line: ");       SerialPort.println(LineCounter);       while(1) {         // Led blinking.         digitalWrite(LED_BUILTIN, HIGH);         delay(250);         digitalWrite(LED_BUILTIN, LOW);         delay(250);       }     }   }   SerialPort.println("Program loaded inside the LSM6DSOX MLC");   //Interrupts.   pinMode(INT_1, INPUT);   attachInterrupt(INT_1, INT1Event_cb, RISING);   /* We need to wait for a time window before having the first MLC status */   delay(3000);   AccGyr.Get_MLC_Output(mlc_out);   printMLCStatus(mlc_out[0]); } void loop() {   if (mems_event) {     mems_event=0;     LSM6DSOX_MLC_Status_t status;     AccGyr.Get_MLC_Status(&status);     if (status.is_mlc1) {       uint8_t mlc_out[8];       AccGyr.Get_MLC_Output(mlc_out);       printMLCStatus(mlc_out[0]);     }   } } void INT1Event_cb() {   mems_event = 1; } void printMLCStatus(uint8_t status) {   switch(status) {     case 0:       SerialPort.println("Activity: Stationary");       break;     case 1:       SerialPort.println("Activity: Walking");       break;     case 4:       SerialPort.println("Activity: Jogging");       break;     case 8:       SerialPort.println("Activity: Biking");       break;     case 12:       SerialPort.println("Activity: Driving");       break;     default:       SerialPort.println("Activity: Unknown");       break;   }   }   效果图如下,动态效果见视频: (三)、总结 非常有幸能够参与此次活动,通过此次活动使我对IMU、UDP及PDM麦克风的相关知识有了更深入和具体的了解,通过直播活动及自己查找资料提高了自己的知识面和能力。此次活动的主控板选择非常合适,可以学习和研究的地方很多,比如除了上面任务要求的和自选的任务之外的还有语音识别功能,蓝牙功能以及RP2040的PIO等功能,活动后我也将继续去进一步学习。这种学习模式也非常有助于问题的解决和个人能力的提高,通过活动将大量不同地方的人聚集起来交流技术和知识。最后建议以后多组织这种活动,同时能否增加一些专门解答的老师之类的人员以提高活动效果。   三、可编译下载的代码 download.eeworld.com.cn/detail/eew_9XVJps/635331

  • 2024-12-18
  • 上传了资料: 【Follow me第二季第4期】全部任务通过Arduino进行实现

  • 加入了学习《【Follow me第二季第4期】全部任务演示视频》,观看 【Follow me第二季第4期】全部任务演示视频

  • 2024-12-17
  • 加入了学习《【Follow me第二季第4期】ARDUINO NANO RP2040 CONNECT》,观看 PDM功能演示-ARDUINO NANO RP2040 CONNECT

  • 发表了主题帖: 【Follow me第二季第4期】无线音乐氛围灯?

    本帖最后由 eew_9XVJps 于 2024-12-17 10:27 编辑 本期Follow me有个选作任务:通过RGB LED不同颜色、亮度显示PDM麦克风收到的声音大小。这里使用的是板载的LED灯,效果不够明显,于是想使用WS2812灯板来实现。之前有用过Arduino中的FASTLED库和ESP32来实现过类似的效果,但是实际测试发现PDM库和FASTELD库冲突,两个启动后板子就不能工作了,只能另想他法。如前面所说之前用ESP32实现过,所以决定改为由Arduino Nano RP2040 Connect通过UDP发送PDM数据到ESP32S3,然后由ESP32S3进行FFT变换并驱动WS2812灯板,流程上大体如下:   Arduino Nano RP2040 Connect代码如下: /* WiFi UDP Send and Receive String This sketch waits for a UDP packet on localPort using the WiFi module. When a packet is received an Acknowledge packet is sent to the client on port remotePort created 30 December 2012 by dlf (Metodo2 srl) */ #include <SPI.h> #include <WiFiNINA.h> #include <WiFiUdp.h> #include <PDM.h> int status = WL_IDLE_STATUS; ///////please enter your sensitive data in the Secret tab/arduino_secrets.h char ssid[] = ""; // your network SSID (name) char pass[] = ""; // your network password (use for WPA, or use as key for WEP) unsigned int localPort = 2390; // local port to listen on const char * udpAddress = "192.168.1.100"; char packetBuffer[256]; //buffer to hold incoming packet char ReplyBuffer[] = "arduinorp2040nano"; // a string to send back IPAddress local_IP(192,168,1,101); IPAddress gateway(192,168,1,1); IPAddress subnet(255,255,255,0); WiFiUDP Udp; volatile int samplesRead; short sampleBuffer[128]; static const char channels = 1; static const int frequency = 14000; void setup() { //Initialize serial and wait for port to open: Serial.begin(9600); // check for the WiFi module: if (WiFi.status() == WL_NO_MODULE) { Serial.println("Communication with WiFi module failed!"); // don't continue while (true); } // attempt to connect to WiFi network: while (status != WL_CONNECTED) { Serial.print("Attempting to connect to SSID: "); Serial.println(ssid); // Connect to WPA/WPA2 network. Change this line if using open or WEP network: WiFi.config(local_IP, gateway, subnet); status = WiFi.begin(ssid, pass); // wait 10 seconds for connection: delay(10000); } Serial.println("Connected to WiFi"); printWifiStatus(); Serial.println("\nStarting connection to server..."); // if you get a connection, report back via serial: Udp.begin(localPort); PDM.onReceive(onPDMdata); PDM.setGain(15); if (!PDM.begin(channels, frequency)) { Serial.println("Failed to start PDM!"); while (1); } else{ Serial.println("start"); } } void loop() { if (samplesRead) { // send a reply, to the IP address and port that sent us the packet we received Udp.beginPacket(udpAddress, localPort); Udp.write((const uint8_t *)sampleBuffer, samplesRead*2);; Udp.endPacket(); samplesRead=0; } } void onPDMdata() { // Query the number of available bytes int bytesAvailable = PDM.available(); // Read into the sample buffer PDM.read(sampleBuffer, bytesAvailable);//每次64样本 // 16-bit, 2 bytes per sample samplesRead = bytesAvailable / 2; } void printWifiStatus() { // print the SSID of the network you're attached to: Serial.print("SSID: "); Serial.println(WiFi.SSID()); // print your board's IP address: IPAddress ip = WiFi.localIP(); Serial.print("IP Address: "); Serial.println(ip); // print the received signal strength: long rssi = WiFi.RSSI(); Serial.print("signal strength (RSSI):"); Serial.print(rssi); Serial.println(" dBm"); } XIAO ESP32S3代码如下: #include <arduinoFFT.h> #include <FastLED.h> #include <FastLED_NeoMatrix.h> #include <WiFi.h> #include <WiFiUdp.h> const int udpPort = 2390; boolean connected = false; char *ssid = ""; const char *password = ""; IPAddress local_IP(192,168,1,100); IPAddress gateway(192,168,1,1); IPAddress subnet(255,255,255,0); WiFiUDP udp; #define SAMPLES 64 // Must be a power of 2 #define LED_PIN 4 // Data pin to LEDS D2 #define NUM_LEDS 256 // 灯珠数 #define BRIGHTNESS 150 // LED 亮度 #define LED_TYPE WS2812B #define COLOR_ORDER GRB #define xres 16 // Total number of columns in the display #define yres 16 // Total number of rows in the display #define SAMPLING_FREQ 15000 double vReal[SAMPLES]; double vImag[SAMPLES]; //幅值算法************************************************************* //从group取出特征角标取出初始幅值→加上shift偏移→乘gain倍率→除以gain2倍率 //暂时只用了freq_gain2 int freq_group[8] = {2, 5, 12, 20, 30, 38, 50, 75}; int freq_shift[8] = {-50, -10, -10, -10, -5, -5, -5, -5}; int freq_gain[8] = {1, 1, 1, 1, 2, 2, 4, 4}; int freq_gain2[xres] = {40, 42, 45, 47, 51, 55, 57, 59, 62, 65, 69, 71, 73, 76, 80, 82}; //算出来的长度值 unsigned char freq_block[xres] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0}; int Intensity[xres] = { }; // initialize Frequency Intensity to zero int Intensity_last[xres] = { }; // initialize Frequency Intensity to zero int Displacement = 1; CRGB leds[NUM_LEDS]; // Create LED Object ArduinoFFT<double> FFT = ArduinoFFT<double>(vReal, vImag, SAMPLES, SAMPLING_FREQ); // Create FFT object int color = 0; //颜色变化 int color_rate = 1; //单次变换间隔倍数 int color_interval = 1; //颜色变换间隔 FastLED_NeoMatrix matrix = FastLED_NeoMatrix(leds, 8, 8,2,2, NEO_MATRIX_TOP + NEO_MATRIX_RIGHT + NEO_MATRIX_COLUMNS + NEO_MATRIX_PROGRESSIVE + NEO_TILE_TOP + NEO_TILE_RIGHT + NEO_TILE_ROWS + NEO_TILE_PROGRESSIVE); void setup() { Serial.begin(9600); //Initialize Serial delay(3000); // power-up safety delay FastLED.addLeds<LED_TYPE, LED_PIN, COLOR_ORDER>(leds, NUM_LEDS).setCorrection( TypicalLEDStrip ); //Initialize LED strips FastLED.setBrightness(BRIGHTNESS); WiFi.disconnect(); WiFi.mode(WIFI_STA); WiFi.config(local_IP, gateway, subnet); WiFi.begin(ssid, password); Serial.print("Connecting to WiFi .."); while (WiFi.status() != WL_CONNECTED) { Serial.print('.'); delay(1000); } Serial.println(); Serial.println(WiFi.localIP()); udp.begin(WiFi.localIP(),udpPort); } void loop() { for(int i = 0; i < 4; i++){ Visualizer(); delay(1); flow(i); } } void flow(int rate){ if(rate == 3){ for(int i = 0; i < xres; i++){ if(freq_block[i]>0){ freq_block[i] = freq_block[i]-1; } } } } void Visualizer(){ //Collect Samples getSamples(); FastLED.clear(); //Update Display displayUpdate(); FastLED.show(); } char packetBuffer[256]; //buffer to hold incoming packet void getSamples(){ int packetSize = udp.parsePacket(); if (packetSize) { int len = udp.read(packetBuffer, 255); if (len > 0) { for(int i = 0; i < SAMPLES; i++){ vReal[i] = packetBuffer[i]/2; //Serial.println(vReal[i]); vImag[i] = 0; } //FFT FFT.windowing(vReal, 1, FFT_WIN_TYP_HAMMING, FFT_FORWARD); FFT.compute(vReal, vImag, SAMPLES, FFT_FORWARD); FFT.complexToMagnitude(vReal, vImag, SAMPLES); //Update Intensity Array for(int i = 2; i < (xres*Displacement)+2; i+=Displacement){ vReal[i] = constrain(vReal[i],freq_gain2[i-2] ,1800); // set max value for input data vReal[i] = map(vReal[i], freq_gain2[i-2], 1800, 0, yres); // map data to fit our display Intensity[(i/Displacement)-2] --; // Decrease displayed value if (vReal[i] > Intensity[(i/Displacement)-2]) // Match displayed value to measured value Intensity[(i/Displacement)-2] = vReal[i]; } } } } void displayUpdate(){ int color = 0; // 初始化颜色变量 int Intensity_ava[xres] = { }; for(int x=0; x< xres; x++){ Intensity_ava[x]=(Intensity_last[x]+Intensity[x])/2; } for(int i = 0; i < xres; i++){ // 遍历矩阵的每一行 for(int j = 0; j < yres; j++){ // 遍历每一行的每一个像素 if(j <= Intensity_ava[i]){ // Light everything within the intensity range if(j > freq_block[i]){ matrix.drawPixel(i, j, CHSV(color, 255, BRIGHTNESS)); // 绘制亮像素 } } } color += 255 / xres; // Increment the Hue to get the Rainbow effect if(color > 255){ color = 0; // Wrap around the Hue value if it exceeds 255 } } for (int i = 0; i < xres; i++) { Intensity_last[i] = Intensity_ava[i]; } delay(5); } 演示效果: [localvideo]ef56dfb15b82bcea9818462ab7fec15e[/localvideo]  

  • 2024-12-04
  • 加入了学习《FollowMe 第二季:3 - EK_RA6M5 开发板入门》,观看 EK-RA6M5 开发板入门

  • 回复了主题帖: 【Follow me第二季第4期】在Arduino下使用SD卡录制声音

    yilonglucky 发表于 2024-12-3 17:28 有更新了吗?换成14k就好了?   是的换成14000速率就好了,下面是新录制的视频[localvideo]5e77f76763898e5239c236209725ea1f[/localvideo]。 就是没搞明白明明麦克风和WAVE文件里面设置的声道数和速度都一致为啥只有14000采样率才正常。

  • 加入了学习《FM4演示视频》,观看 FM4演示视频配音

  • 2024-12-03
  • 发表了主题帖: 【Follow me第二季第4期】在Arduino下使用SD卡录制声音

    本帖最后由 eew_9XVJps 于 2024-12-3 09:02 编辑 自定任务:Arduino Nano RP2040 Connect板载的PDM麦克风实现录音功能        看到板载有麦克风为什么不试一下录制声音呢,因此想通过板载的PDM麦克风实现声音拾取,并将收集到的数据以WAV格式形式存储在SD卡中。SD卡通过SPI接口实现与主控板通讯,SPI引脚直接使用了默认引脚。          我使用的SD卡外置模块为5V供电,需要将主控板的VBUS焊盘连接上来进行供电,否则3V3无法启动。        流程顺序大体如下:   目前程序还是有点问题不知道是出在哪里,录音的长度和设定长度一致,但是播放录制的程序明显速度变快了,哪位大佬有空帮我看看问题出在哪里了,程序如下: #include <PDM.h> #include "wave.h" #include <SPI.h> #include "SdFat.h" const int chipSelect = 10; // default number of output channels static const char channels = 1; // default PCM output frequency static const int frequency = 20000; // Buffer to read samples into, each sample is 16-bits short sampleBuffer[128]; const int record_time = 30; // second const char filename[] = "/record.wav"; const int waveDataSize = record_time * 40000; char partWavData[1024]; // Number of audio samples read volatile int samplesRead; FsFile file; // 录音文件 SdFs sd; void setup() { Serial.begin(9600); delay(2000); if(!sd.begin(SdSpiConfig(chipSelect, DEDICATED_SPI, SD_SCK_MHZ(16))))//初始化SD { Serial.println(F("sd init error")); return; } sd.remove(filename); file = sd.open(filename, O_WRITE|O_CREAT); if(!file) { Serial.println("crate file error"); return; } // Configure the data receive callback auto header = CreateWaveHeader(1, 20000, 16); header.riffSize = waveDataSize + 44 - 8; header.dataSize = waveDataSize; file.write(&header,44); PDM.onReceive(onPDMdata); // Optionally set the gain // Defaults to 20 on the BLE Sense and 24 on the Portenta Vision Shield PDM.setGain(10); // Initialize PDM with: // - one channel (mono mode) // - a 16 kHz sample rate for the Arduino Nano 33 BLE Sense // - a 32 kHz or 64 kHz sample rate for the Arduino Portenta Vision Shield if (!PDM.begin(channels, frequency)) { Serial.println("Failed to start PDM!"); while (1); } else{ Serial.println("start"); } } int count = waveDataSize/128; void loop() { // Wait for samples to be read if(count==0){ samplesRead=0; file.close(); Serial.println("finish"); } if (samplesRead) { file.write((const byte*)sampleBuffer, 128); count--; // Clear the read count samplesRead = 0; } // if (samplesRead) { // Serial.println(samplesRead); // samplesRead=0; // } } /** * Callback function to process the data from the PDM microphone. * NOTE: This callback is executed as part of an ISR. * Therefore using `Serial` to print messages inside this function isn't supported. * */ void onPDMdata() { // Query the number of available bytes int bytesAvailable = PDM.available(); // Read into the sample buffer PDM.read(sampleBuffer, bytesAvailable);//每次64样本 // 16-bit, 2 bytes per sample samplesRead = bytesAvailable / 2; } 对应的WAVE文件内容 #include "wave.h" WAV_HEADER CreateWaveHeader(int numChannels, //声道数 unsigned int sampleRate, //采样率 unsigned short bitsPerSample) //采样位宽 { WAV_HEADER header; header.riffSize = 0; header.numChannels = numChannels; header.sampleRate = sampleRate; header.bitsPerSample = bitsPerSample; header.bytesPerSecond = sampleRate * numChannels * bitsPerSample /8 ; header.blockAlign = numChannels * bitsPerSample/8; header.dataSize = 0; return header; } 效果图如下,动态效果见视频:  [localvideo]d37d54d962efd3f80ff9f3471e1cf882[/localvideo]

  • 2024-08-15
  • 加入了学习《【Follow me第二季第1期】全部任务演示短视频》,观看 【Follow me第二季第1期】任务演示视频

  • 上传了资料: 【Follow me第二季第1期】全部任务arduino代码实现

  • 发表了主题帖: 【Follow me第二季第1期】第二季第1期全部任务作业提交

    本帖最后由 eew_9XVJps 于 2024-8-27 12:31 编辑 一、效果视频展示   https://training.eeworld.com.cn/video/40787 二、任务实现详情 物料介绍 本次Follow me活动使用的主控板是Adafruit Circuit Playground Express,这款主控板的特点是集成了红外、声、光电容等多种传感器,非常适合进行创意开发,具体参数不再单独罗列,可查询得捷网站https://www.digikey.cn/zh/products/detail/adafruit-industries-llc/3333/7310913 唯一缺点是没有蓝牙和wifi功能,因此我选购了M5STACK的ESP32STAMPS3模块用于扩展主控的无线通讯能力。得捷链接https://www.digikey.cn/zh/products/detail/m5stack-technology-co-ltd/S007-PIN127/21286965?s=N4IgTCBcDa5mBGAtAZQAxoOxIAoEkA5BMbAgERAF0BfIA 基础任务中有一个测量距离进行报警的任务,个人感觉使用红外效果太差、使用光线传感器受光照影响较大,因此还加购了一个超声波模块。得捷链接https://www.digikey.cn/zh/products/detail/rakwireless-technology-limited/RAK12007-0-WB-N/18682765?s=N4IgTCBcDaICwHYDMBWAtAJQIIGkCMYADIQmoWgOoBCaAcnQCIgC6AvkA 给他们相互连接简单画了1个连接板,用于将主控CPX和M5STAMPS3连接起来,同时固定住超声波模块。 M5STAMP转接板 组装后的样子 开发环境 此次使用的是arduinoIDE,安装arduino之后在开发板管理器中搜索arduinosamdboards即可找到,点击安装软件将自动安装所需安装包。安装完成后可在开发板-工具-arduinosamdboards-Adafruit Circuit Playground Express找到对应的主控板。之后我们在arduino中的文件-示例-Adafruit Circuit Playground Express中发现adafruit已经为这个板子写了大量的示例和库文件,可以帮我们快速的完成创意开发。 安装开发板核心包 选择开发板 查看示例程序 任务实现 入门任务(必做):开发环境搭建,板载LED点亮 在arduino中点亮或者熄灭LED只需两步,初始化引脚和拉高或拉低对应引脚。查询资料或者看板载标示可以看到LED是D13引脚,因此代码如下: void setup() {   // put your setup code here, to run once:   pinMode(13,OUTPUT); } void loop() {   // put your main code here, to run repeatedly:   digitalWrite(13,HIGH);   delay(1000);   digitalWrite(13,LOW);   delay(1000); } 效果图(动态效果见视频演示) 基础任务一(必做):控制板载炫彩LED,跑马灯点亮和颜色变换 查询adafruit提供的Adafruit Circuit Playground Express库中可以看到几个关于彩色LED灯的控制函数,void clearPixels(void)清楚所有灯的显示;void setPixelColor(uint8_t p, uint32_t c)某一个位置的灯显示指定颜色(十六进制颜色);void setPixelColor(uint8_t p, uint8_t r, uint8_t g, uint8_t b) 某一个位置的灯显示指定颜色(RGB颜色)。为实现跑马灯,我们需要循环对不同的灯的颜色进行变化,这里可以使用for语句来切换不同的灯,颜色变化可以使用random函数实现随机变化,代码如下: #include <Adafruit_CircuitPlayground.h> void setup() {   // initialize digital pin 13 as an output.   CircuitPlayground.begin();   randomSeed(millis()); } // the loop function runs over and over again forever void loop() {   for(int i=0;i<10;i++){     CircuitPlayground.setPixelColor(i,random(0,255),random(0,255),random(0,255));     delay(100);   }   for(int i=0;i<10;i++){     CircuitPlayground.setPixelColor(i,0);     delay(100);   } } 效果图(动态效果见视频演示)   基础任务二(必做):监测环境温度和光线,通过板载LED展示舒适程度 库中提供了1个光线检测函数uint16_t lightSensor(void)可直接读取光线强度数值,提供了2个温度检测函数float temperature(void); float temperatureF(void)一个是摄氏度一个是华氏度。为实现任务目标可进行轮训检查光线强度数值和温度数值,并依据温度数值和光线数值来调整灯光的颜色。我这里使用红色表示高温或者光线不足,用黄色显示温度稍高或者光线稍不足,用绿色表示温度事宜或者光线充足,用蓝色表示温度低,并使用左侧展示温度,右侧LED展示光线。代码如下: #include <Adafruit_CircuitPlayground.h> int16_t  old_temp=0; int16_t  old_light=0; void setup() {   // put your setup code here, to run once:   Serial.begin(115200);   CircuitPlayground.begin(); } void loop() {   // put your main code here, to run repeatedly:   int16_t  new_temp=CircuitPlayground.temperature();   if(new_temp  !=old_temp){     Serial.print("new_temp");     Serial.println(new_temp);     if(new_temp>28){       for(int i=0;i<5;i++){         CircuitPlayground.setPixelColor(i,0xFF0000);         delay(100);       }     }     else  if(new_temp<=28 &&  new_temp>=20){       for(int i=0;i<5;i++){         CircuitPlayground.setPixelColor(i,0x808000);         delay(100);       }     }     else  if(new_temp<20 &&  new_temp>0){       for(int i=0;i<5;i++){         CircuitPlayground.setPixelColor(i,0xADD8E6);         delay(100);       }     }     else  if(new_temp<0){       for(int i=0;i<5;i++){         CircuitPlayground.setPixelColor(i,0x00008B);         delay(100);       }     }     old_temp=new_temp;   }    int16_t  new_light=CircuitPlayground.lightSensor();   if(new_light  !=old_light){     Serial.print("new_light");     Serial.println(new_light);     if(new_light<100){       for(int i=5;i<10;i++){         CircuitPlayground.setPixelColor(i,0xFF0000);         delay(100);       }     }     else  if(new_light<=400 &&  new_light>=200){       for(int i=5;i<10;i++){         CircuitPlayground.setPixelColor(i,0xFFFF00);         delay(100);       }     }     else  if(new_light>300 ){       for(int i=5;i<10;i++){         CircuitPlayground.setPixelColor(i,0x008000);         delay(100);       }     }     old_light=new_light;   } } 效果图(动态效果见视频演示) 串口数据(由于没有加热或者制冷设备,温度变化范围过小,效果从串口查看)   光线严重不足   光线不足   光线充足 基础任务三(必做):接近检测——设定安全距离并通过板载LED展示,检测到入侵时,发起声音报警 研究了一下网上使用红外检测距离的代码,发现效果非常差,还不如光线检测,但是光线检测也收到光照的影响。使用光线检测其实和上个代码相似,其原理就是在靠近开发板时光照强度逐渐不足,从而达到检测物体靠近的效果。声音警报,板载的喇叭可以使用库函数中的void playTone(uint16_t freq, uint16_t time, bool wait = true);来进行控制发生,三个参数分别是频率、持续时间和是否等待,可在触发警报后调用该函数以一定频率响一段时间即可,我在这里使用的是蜂鸣器常用频率3KHZ,时间100ms。代码如下: #include <Adafruit_CircuitPlayground.h> int  old_light=0; void setup() {   // put your setup code here, to run once:   Serial.begin(115200);   CircuitPlayground.begin(); } void loop() {   // put your main code here, to run repeatedly:   int  new_light=CircuitPlayground.lightSensor();   if(new_light  !=old_light){     Serial.print("new_light");     Serial.println(new_light);     if(new_light<200){       for(int i=0;i<10;i++){         CircuitPlayground.setPixelColor(i,0xFF0000);       }     }     else  if(new_light<=400 &&  new_light>=200){       for(int i=0;i<10;i++){         CircuitPlayground.setPixelColor(i,0x008000);       }     }     old_light=new_light;   }   if(old_light<200){     CircuitPlayground.playTone(3000,100);   } } 效果图(动态效果见视频演示)   可以看出上面的效果非常不好,使用购买的超声波测距模块重新进行这个任务。任务采用M5STAMPS3控制超声波模块进行测距,并周期性向Adafruit Circuit Playground Express发送测量数据,Adafruit Circuit Playground Express使用串口读取距离数据,并根据数据大小进行声光警报。超声波测距代码就很简单了,TRIG引脚置高10us以上,然后计算和读取ECHO引脚高电平时间,之后就可以计算距离了。这里需要注意启动Serial1的串口方便和M5STAMPS3通讯,并且串口启动要在CircuitPlayground.begin();之后,否则串口不能正常工作。Adafruit Circuit Playground Express及M5STAMPS3代码如下: #include <Adafruit_CircuitPlayground.h> uint32_t  color[10]; void setup() {   // put your setup code here, to run once:   CircuitPlayground.begin();   Serial.begin(115200);   Serial1.begin(115200);   for(uint8_t i=40;i<140;i=i+10){     color[i/10-4]=CircuitPlayground.colorWheel(i);   } } int interval1=100,interval2=200; int distance=100; int limit=10; unsigned  long  lastAlarm; unsigned  long  readTime; void loop() {   // put your main code here, to run repeatedly:   if(Serial1.available()){     while(!Serial1.find("s")){     }     distance =Serial1.parseInt();     while(Serial1.read() !=  -1){     }     Serial.println(distance);     }   // uint8_t p=map(distance,2,200,1,10);  //以圆圈形式展示距离远近   // CircuitPlayground.clearPixels();   // for(int i=0;i<p;i++){   //     CircuitPlayground.setPixelColor(i,color[i]);   // }   if(distance<limit){     for(int i=0;i<10;i++){       CircuitPlayground.setPixelColor(i,0xFF0000);     }     if(millis()-lastAlarm>interval1){       CircuitPlayground.playTone(3000,50);       lastAlarm=millis();     }   }   else  if(distance>=limit  &&  distance<limit+10){     for(int i=0;i<10;i++){       CircuitPlayground.setPixelColor(i,0xFFFF00);     }     if(millis()-lastAlarm>interval2){       CircuitPlayground.playTone(3000,100);       lastAlarm=millis();     }   }   else  if(distance>=limit+20){     for(int i=0;i<10;i++){       CircuitPlayground.setPixelColor(i,0x00FF00);     }   } } #define PIN_TRIGGER 14          //超声波触发引脚 #define PIN_ECHO 15          //超声波接收引脚 #define SOUND_SPEED 0.034   //声速   void setup(){   Serial.begin(115200);   pinMode(PIN_TRIGGER, OUTPUT);//初始化引脚   pinMode(PIN_ECHO, INPUT); } void loop() {   int distance=distance_measure();   String  data='s'+(String)distance;   Serial.println(data);   delay(1000); } int distance_measure(){   digitalWrite(PIN_TRIGGER,LOW);   delayMicroseconds(2);   digitalWrite(PIN_TRIGGER,HIGH);   delayMicroseconds(10);   digitalWrite(PIN_TRIGGER,LOW);   long duration = pulseIn(PIN_ECHO,HIGH);   int distancecm = (duration * SOUND_SPEED) / 2;   return distancecm; } 效果图(动态效果见视频演示)   进阶任务(必做):制作不倒翁——展示不倒翁运动过程中的不同灯光效果 不倒翁需要用到加速度传感器,库中读取数值的函数为float motionX(void);float motionY(void);float motionZ(void)。利用三个函数可以直接读取开发板的XYZ状态,然后根据状态数值控制相应位置的彩色LED显示不同的光效从而展示不倒翁的状态。从读数可以看出在水平状态下时X、Y值基本在0左右,当发生倾斜时Z值减小,X、Y值变大,且靠近哪个轴,哪个轴大。因此,在设计灯光时还要比较XY的大小。实物将主控板连接一节14500锂电池放入透明塑料球内,效果很直观。代码如下: #include <Adafruit_CircuitPlayground.h> void setup() {   // put your setup code here, to run once:   Serial.begin(115200);   CircuitPlayground.begin();   CircuitPlayground.setAccelRange(LIS3DH_RANGE_2_G); } void loop() {   // put your main code here, to run repeatedly:   int8_t  x=CircuitPlayground.motionX();   int8_t  y=CircuitPlayground.motionY();   int8_t  z=CircuitPlayground.motionZ();   uint8_t X=abs(x);   uint8_t Y=abs(y);   uint8_t Z=abs(z);   if(X>0.5  ||  Y>0.5){     if(X>Y){    //偏前后       if(x>0){         CircuitPlayground.clearPixels();          for(int i=0;i<5;i++){           CircuitPlayground.setPixelColor(i,0x00008B);         }       }       else  if(x<0){         CircuitPlayground.clearPixels();         for(int i=5;i<10;i++){           CircuitPlayground.setPixelColor(i,0x00008B);         }       }     }     else  if(X<Y){    //偏左右       if(y>0){         CircuitPlayground.clearPixels();         for(int i=3;i<7;i++){           CircuitPlayground.setPixelColor(i,0xFF0000);         }       }       else  if(y<0){         CircuitPlayground.clearPixels();         CircuitPlayground.setPixelColor(0,0xFF0000);         CircuitPlayground.setPixelColor(1,0xFF0000);         CircuitPlayground.setPixelColor(8,0xFF0000);         CircuitPlayground.setPixelColor(9,0xFF0000);       }     }     else  if(X=Y){    //偏前后       if(x>0  &&  y<0){         CircuitPlayground.clearPixels();         for(int i=0;i<3;i++){           CircuitPlayground.setPixelColor(i,0xADD8E6);         }       }       else  if(x>0  &&  y>0){          CircuitPlayground.clearPixels();         for(int i=2;i<5;i++){           CircuitPlayground.setPixelColor(i,0xADD8E6);         }       }       else  if(x<0  &&  y>0){         CircuitPlayground.clearPixels();         for(int i=5;i<8;i++){           CircuitPlayground.setPixelColor(i,0xADD8E6);         }       }       else  if(x<0  &&  y<0){          CircuitPlayground.clearPixels();         for(int i=7;i<10;i++){           CircuitPlayground.setPixelColor(i,0xADD8E6);         }       }     }   }   else  if(z>9.5){     for(int i=0;i<10;i++){       CircuitPlayground.setPixelColor(i,0x008000);     }   } } 效果图(动态效果见视频演示)   创意任务一:有创意的可穿戴装饰——可结合多种传感器和灯光效果展示 这里利用Adafruit Circuit Playground Express上的彩色LED灯、M5STAMPS3以及超声波测距模块设计了一个网路时钟,利用M5STAMPS3的网络功能连接NTP服务器获取时间,和测量距离,并通过串口将时间和距离数据发送至Adafruit Circuit Playground Express,通过彩色LED和D13红色LED实现时钟显示。由于LED数量有限,因此分钟没五分钟刷新位置(不是很准的时钟),12点位置通过红色LED代替。另外就是测距数据如果超过限定值则全部LED发出红光和声音警报,声音警报可以通过点击开发板实现静音,这部分通过加速度模块实现点击识别。   代码如下: #include <Adafruit_CircuitPlayground.h> uint8_t  hours,minutes,seconds,distance; uint8_t  old_hours,old_minutes,old_seconds; bool warn=false;//是否触发了警告 bool silence=false;//是否静音 void setup() {   // put your setup code here, to run once:   CircuitPlayground.begin();   CircuitPlayground.setAccelRange(LIS3DH_RANGE_2_G);   CircuitPlayground.setAccelTap(1, 120);   attachInterrupt(digitalPinToInterrupt(CPLAY_LIS3DH_INTERRUPT), tapTime, FALLING);   pinMode(CPLAY_REDLED,OUTPUT);   delay(500);   Serial.begin(115200);   Serial1.begin(115200); } void tapTime(void) {   silence=!silence; } void loop() {   // put your main code here, to run repeatedly:   if(Serial1.available()){     //String  ReadT=Serial1.readString();     //readTime(ReadT);     if(Serial1.read()=='s'){       hours=Serial1.parseInt();       Serial1.read();       minutes=Serial1.parseInt();       Serial1.read();       seconds=Serial1.parseInt();       Serial1.read();       distance  =Serial1.parseInt();       // Serial.print(hours);       // Serial.print(":");       // Serial.print(minutes);       // Serial.print(":");       // Serial.println(seconds);       while(Serial1.read() !=  -1){       }     }     Serial.println(distance);     if(warn){       CircuitPlayground.clearPixels();       warn=false;     }     updateLight(hours,1);     updateLight(minutes,2);     updateLight(seconds,3);     if(distance<40){       warn=true;       for(int i=0;i<10;i++){         CircuitPlayground.setPixelColor(i,0xFFFF00);       }       if(!silence){         CircuitPlayground.playTone(3000,50);         delay(50);         CircuitPlayground.playTone(3000,50);       }     }   } } void  updateLight(int8_t num,uint8_t pin){   uint32_t  color;   if(pin==1  &&  num>12){     num=num-12;   }   if(pin==1){//1代表小时     color=0xFF0000;//红色   }   else if(pin==2){//2代表分钟     color=0x0000FF;//蓝色     num=num/5;     // if(num%5==0){     //   num=num/5;     // }     // else{     //   num=-1;     // }   }   else if(pin==3){//3代表秒钟     color=0x00FF00;//绿色     num=num/5;     // if(num%5==0){     //   num=num/5;     // }     // else{     //   num=-1;     // }   }   switch(num) {       case 1:         if(pin!=3){         digitalWrite(CPLAY_REDLED, LOW);       }       CircuitPlayground.setPixelColor(9,color);         break;       case 2:         CircuitPlayground.setPixelColor(9,0,0,0);       CircuitPlayground.setPixelColor(8,color);         break;     case 3:         CircuitPlayground.setPixelColor(8,0,0,0);       CircuitPlayground.setPixelColor(7,color);         break;     case 4:         CircuitPlayground.setPixelColor(7,0,0,0);       CircuitPlayground.setPixelColor(6,color);         break;     case 5:         CircuitPlayground.setPixelColor(6,0,0,0);       CircuitPlayground.setPixelColor(5,color);         break;     case 6:           break;     case 7:         CircuitPlayground.setPixelColor(5,0,0,0);       CircuitPlayground.setPixelColor(4,color);         break;     case 8:         CircuitPlayground.setPixelColor(4,0,0,0);       CircuitPlayground.setPixelColor(3,color);         break;     case 9:         CircuitPlayground.setPixelColor(3,0,0,0);       CircuitPlayground.setPixelColor(2,color);         break;     case 10:         CircuitPlayground.setPixelColor(2,0,0,0);       CircuitPlayground.setPixelColor(1,color);         break;     case 11:         CircuitPlayground.setPixelColor(1,0,0,0);       CircuitPlayground.setPixelColor(0,color);         break;     case 12:       CircuitPlayground.setPixelColor(0,0x00);       if (pin==1 || pin==2){         digitalWrite(CPLAY_REDLED, HIGH); //亮灯       }       else  if(pin==3){         bool  led_state=digitalRead(CPLAY_REDLED);         digitalWrite(CPLAY_REDLED, !led_state);         delay(200);         digitalWrite(CPLAY_REDLED, led_state);       }       break;     case 0:       CircuitPlayground.setPixelColor(0,0x00);       if (pin==1 || pin==2){         digitalWrite(CPLAY_REDLED, HIGH); //亮灯       }       else  if(pin==3){         bool  led_state=digitalRead(CPLAY_REDLED);         digitalWrite(CPLAY_REDLED, !led_state);         delay(200);         digitalWrite(CPLAY_REDLED, led_state);       }       break;     default:       break;   }       } void  readTime(String time){   char timeStr[10];   strcpy(timeStr,time.c_str());   char hour[3], minute[3], second[3];     int hourIdx = 0, minuteIdx = 0, secondIdx = 0;     bool foundColon = 0; // 用于标记是否找到了冒号     // 遍历字符串直到找到第一个冒号     for (int i = 0; timeStr[i] != '\0'; i++) {       if (timeStr[i] == ':') {         foundColon = 1;         break;       }       hour[hourIdx++] = timeStr[i];     }     hour[hourIdx] = '\0'; // 确保小时字符串正确终止     // 跳过第一个冒号,继续遍历直到找到第二个冒号     if (foundColon) {       for (int i = hourIdx + 1; timeStr[i] != '\0'; i++) {         if (timeStr[i] == ':') {           break;         }         minute[minuteIdx++] = timeStr[i];       }       minute[minuteIdx] = '\0'; // 确保分钟字符串正确终止       // 第二个冒号之后即为秒       int secStart = minuteIdx + hourIdx + 2; // 跳过两个冒号和前面的数字       for (int i = secStart; timeStr[i] != '\0'; i++) {         second[secondIdx++] = timeStr[i];       }       second[secondIdx] = '\0'; // 确保秒字符串正确终止     }   hours=atoi(hour);     minutes=atoi(minute);   seconds=atoi(second); } 效果图(动态效果见视频演示) 这个样子做个可穿戴也不算丑吧 单独展示 背面连接的连接板和M5STAMPS3 创意任务二:章鱼哥——章鱼哥的触角根据环境声音的大小,章鱼哥的触角可舒展或者收缩 任务实现可通过麦克风读取周围环境的升压实现环境声音大小的读取,之后根据声音大小进行重映射为舵机角度,通过PMW引脚输出PWM信号从而实现对章鱼哥触角舒展的控制。代码如下: #include <Adafruit_CircuitPlayground.h> #include <Wire.h> #include <SPI.h> // #include <Servo.h> // Servo myservo; #define SAMPLE_WINDOW   10  // Sample window for average level #define PEAK_HANG       24  // Time of pause before peak dot falls #define PEAK_FALL        4  // Rate of falling peak dot #define INPUT_FLOOR     56  // Lower range of mic sensitivity in dB SPL #define INPUT_CEILING  110  // Upper range of mic sensitivity in db SPL #define servo01         A1 byte peak = 16;        // Peak level of column; used for falling dots unsigned int sample; byte dotCount = 0;     //Frame counter for peak dot byte dotHangCount = 0; //Frame counter for holding peak dot float mapf(float x, float in_min, float in_max, float out_min, float out_max); void setup() {   CircuitPlayground.begin();   pinMode(servo01, OUTPUT);   //myservo.attach(A1);   Serial.begin(115200); } uint16_t  old_angle; uint16_t  old_peakToPeak; void loop() {   int numPixels = CircuitPlayground.strip.numPixels();   float peakToPeak = 0;   // peak-to-peak level   unsigned int c, y;   //get peak sound pressure level over the sample window   peakToPeak = CircuitPlayground.mic.soundPressureLevel(SAMPLE_WINDOW);   //limit to the floor value   peakToPeak = max(INPUT_FLOOR, peakToPeak);   //Serial.println(peakToPeak);   uint16_t angle=map((uint16_t)peakToPeak,56,110,0,180);   //Serial.println(angle);   int16_t div1=old_angle-angle;   int16_t div2=old_peakToPeak-peakToPeak;   Serial.println(div2);   if(abs(div2)>6){     for(uint8_t i=0;i<3;i++){       servoAction (servo01,angle);     }     //myservo.write(angle);     delay(50);     old_peakToPeak=peakToPeak;     old_angle=angle;   }   //Fill the strip with rainbow gradient   for (int i=0;i<=numPixels-1;i++){     CircuitPlayground.strip.setPixelColor(i,Wheel(map(i,0,numPixels-1,30,150)));   }   c = mapf(peakToPeak, INPUT_FLOOR, INPUT_CEILING, numPixels, 0);   // Turn off pixels that are below volume threshold.   if(c < peak) {     peak = c;        // Keep dot on top     dotHangCount = 0;    // make the dot hang before falling   }   if (c <= numPixels) { // Fill partial column with off pixels     drawLine(numPixels, numPixels-c, CircuitPlayground.strip.Color(0, 0, 0));   }   // Set the peak dot to match the rainbow gradient   y = numPixels - peak;   CircuitPlayground.strip.setPixelColor(y-1,Wheel(map(y,0,numPixels-1,30,150)));   CircuitPlayground.strip.show();   // Frame based peak dot animation   if(dotHangCount > PEAK_HANG) { //Peak pause length     if(++dotCount >= PEAK_FALL) { //Fall rate       peak++;       dotCount = 0;     }   }   else {     dotHangCount++;   } } //Used to draw a line between two points of a given color void drawLine(uint8_t from, uint8_t to, uint32_t c) {   uint8_t fromTemp;   if (from > to) {     fromTemp = from;     from = to;     to = fromTemp;   }   for(int i=from; i<=to; i++){     CircuitPlayground.strip.setPixelColor(i, c);   } } float mapf(float x, float in_min, float in_max, float out_min, float out_max) {     return (x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min; } // Input a value 0 to 255 to get a color value. // The colours are a transition r - g - b - back to r. uint32_t Wheel(byte WheelPos) {   if(WheelPos < 85) {     return CircuitPlayground.strip.Color(WheelPos * 3, 255 - WheelPos * 3, 0);   }   else if(WheelPos < 170) {     WheelPos -= 85;     return CircuitPlayground.strip.Color(255 - WheelPos * 3, 0, WheelPos * 3);   }   else {     WheelPos -= 170;     return CircuitPlayground.strip.Color(0, WheelPos * 3, 255 - WheelPos * 3);   } } void servoAction (int servopin, int angle) {   int pulsewidth= (angle * 11) +500;   digitalWrite(servopin,HIGH);   delayMicroseconds(pulsewidth);   digitalWrite(servopin,LOW);   delayMicroseconds(20000 - pulsewidth); } 实际效果   创意任务三:水果钢琴——通过触摸水果弹奏音乐,并配合灯光效果 主控板自带多个触摸引脚,可通过触摸引脚引出导线连接水果,检测不同的电容值配合板载喇叭发出不同频率和时长的声调实现水果钢琴效果。主控板库配套的读取函数uint16_t readCap(uint8_t p, uint8_t samples = 10);其中第一个是引脚编号(A1-A7分别对应15-21),第二个是取样数。需要注意的是任意两个触摸引脚不能同时接触,否则读数为0,作为触控引脚时最好不要外接其它设备。代码采取轮训模式,参考了示例代码。代码如下: #include <Adafruit_CircuitPlayground.h> #define CAP_SAMPLES 10 #define TONE_DURATION_MS 100 uint16_t CAP_THRESHOLD = 1000; void setup() {   // put your setup code here, to run once:   CircuitPlayground.begin();   Serial.begin(115200); } unsigned  long  clearTime; void loop() {   // put your main code here, to run repeatedly:   // Serial.println(pin);   // Serial.print(CircuitPlayground.readCap(15),CAP_SAMPLES);   // Serial.print(",");   // Serial.print(CircuitPlayground.readCap(16),CAP_SAMPLES);   // Serial.print(",");   // Serial.print(CircuitPlayground.readCap(17),CAP_SAMPLES);   // Serial.print(",");   // Serial.print(CircuitPlayground.readCap(18),CAP_SAMPLES);   // Serial.print(",");   // Serial.print(CircuitPlayground.readCap(19),CAP_SAMPLES);   // Serial.print(",");   // Serial.print(CircuitPlayground.readCap(20),CAP_SAMPLES);   // Serial.print(",");   // Serial.print(CircuitPlayground.readCap(21),CAP_SAMPLES);   // Serial.print(",");   if(millis()-clearTime>2000){     CircuitPlayground.clearPixels();   }    for (int i = 15; i < 22; i++) {     if(CircuitPlayground.readCap(i)>CAP_THRESHOLD){       CircuitPlayground.clearPixels();       clearTime=millis();       switch(i){         case 15:         CircuitPlayground.playTone(261, TONE_DURATION_MS);         for(int i=0;i<10;i++){           if(i !=5){             CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));           }         }         break;         case 16:         CircuitPlayground.playTone(293, TONE_DURATION_MS);         for(int i=0;i<10;i++){           if(i !=5  && i !=6 ){             CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));           }         }         break;         case 17:         CircuitPlayground.playTone(329, TONE_DURATION_MS);         for(int i=0;i<10;i++){           if(i !=5  && i !=6  && i !=7){             CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));           }         }         break;         case 18:         CircuitPlayground.playTone(349, TONE_DURATION_MS);         for(int i=0;i<5;i++){           CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));         }         break;         case 19:         CircuitPlayground.playTone(392, TONE_DURATION_MS);         for(int i=1;i<5;i++){           CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));         }         break;         case 20:         CircuitPlayground.playTone(440, TONE_DURATION_MS);         for(int i=2;i<5;i++){           CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));         }         break;         case 21:         CircuitPlayground.playTone(494, TONE_DURATION_MS);         for(int i=3;i<5;i++){           CircuitPlayground.strip.setPixelColor(i, CircuitPlayground.colorWheel(256/10*(i+1)));         }         break;       }       CircuitPlayground.strip.show();       }   } } 效果图(动态效果见视频演示)     心得体会 这次活动提供的开发板功能强大,非常适合创意的开发。在这个项目中学习使用到了电容、灯光、声效、红外及加速度等多种外设,对提高个人能力和激发创造力非常有帮助。同时,这个板子支持arduino、circuitpython、makecode等多种编程工具进行开发,适合不同阶段的人参与进来,后续可以将这个模块进行扩展和交给小孩来使用makecode做一些创意性的小玩意,非常感谢能有这样的机会来和大家一起学习新的东西。最后提个建议,每个任务有没有官方确定的最佳解决方案(主要是这次的接近警报,感觉使用板载的红外和光线传感器效果都不是很高),也让我们再进步一下。 三、可编译下载的代码 相关人物arduino代码均已上传,链接如下:https://download.eeworld.com.cn/detail/eew_9XVJps/634050

  • 2024-08-14
  • 加入了学习《【Follow me第二季第1期】+ 小白带你使用circuitpython开发CircuitPlaygroundExpress》,观看 【Follow me第二季第1期】+ 小白带你使用circuitpython开发CircuitPlaygroundExpress

  • 2024-04-24
  • 加入了学习《【DigiKey“智造万物,快乐不停”创意大赛】无线ToF室内定位小车》,观看 无线ToF室内定位小车

  • 2024-03-04
  • 回复了主题帖: 【得捷Follow me第4期】W5500-EVB-Pico PIO 挂载SD卡并实现文件读写访问

    学习学习,修改这个SPI还挺复杂,之前研究过,没改过来,楼主这个介绍的很详细呀

  • 2024-02-23
  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】综合实践之智能家居控制器

  • 2024-02-22
  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】简易FTP文件服务器

  • 加入了学习《 【得捷电子Follow me第4期】》,观看 【得捷Follow me第4期】进阶任务:从NTP服务器同步时间

  • 加入了学习《Follow me 第4期任务视频展示及介绍》,观看 Follow me 第4期任务完成效果视频

最近访客

< 1/1 >

统计信息

已有1人来访过

  • 芯积分:3
  • 好友:--
  • 主题:10
  • 回复:3

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言