Wio Terminal—心电检测、麦克风、光感
利用扩展接口,连接了心电检测模块(来自fastbone)、麦克风模块(Grove)、光感(Grove),将采集的数据显示在LCD屏幕上,通过上方按钮切换。
标签
嵌入式系统
MPU
lxb
更新2022-01-05
哈尔滨工业大学
1491

      Wio Terminal微型控制器可以控制大量数据采集和数据处理、预判,是一个足够强大、且足够低功耗的主控。另外,这个主控同时配备一个LCD 屏幕、支持microSD存储音频文件,并且具备一些用于指引操作的按钮。

      该WIO终端是基于SAMD51的微控制器与无线连接搭载瑞昱RTL8720DN这是与Arduino的和MicroPython兼容。目前,只有 Arduino 支持无线连接。它运行在120MHz(提升至 200MHz)、4MB外部闪存和192KB RAM。它支持蓝牙和 Wi-Fi,为物联网项目提供骨干。Wio 终端本身配备2.4 英寸 LCD 屏幕、板载 IMU(LIS3DHTR)、麦克风、蜂鸣器、microSD 卡插槽、光传感器和红外发射器(IR 940nm)。最重要的是,它还有两个用于Grove 生态系统的多功能 Grove 端口 和 40 个 Raspberry pi 兼容引脚 GPIO,用于更多附加组件。

     具体介绍链接:https://wiki.seeedstudio.com/Wio-Terminal-Getting-Started/

硬件概览:

    FqzgQcaYD2ucsB4q6atKU2wyoXfV

引脚图:

FsTST9otEkGSr61xIUPPC_cZ8Idw

底部Grove接口图:

FkkH_k-ZgXzUjd8eVPVdMZjwPyze

      这里我将麦克风模块插入了右下角的Grove接口,所以就使用了13号引脚(A0),则心电检测与光感则分别使用了16号引脚(A2)和18号引脚(A3)。麦克风模块与光感传感器来自Grove生态系统(这两是我找同学借的),心电传感器来子Fastbond项目,感兴趣的可以去瞅一眼。

后面接线如图:

Fu_0rQzLXpRxN2PSiD_cqW-nRCEZ

     首先介绍按钮吧。共有三个按钮可用于 Wio 终端中的可配置按钮。

void setup() {
  Serial.begin(115200);
  pinMode(WIO_KEY_A, INPUT_PULLUP);
  pinMode(WIO_KEY_B, INPUT_PULLUP);
  pinMode(WIO_KEY_C, INPUT_PULLUP);
}
 
void loop() {
  // put your main code here, to run repeatedly:
   if (digitalRead(WIO_KEY_A) == LOW) {
    Serial.println("A Key pressed");
   }
   else if (digitalRead(WIO_KEY_B) == LOW) {
    Serial.println("B Key pressed");
   }
   else if (digitalRead(WIO_KEY_C) == LOW) {
    Serial.println("C Key pressed");
   }
   delay(200);
}

      LCD显示:

默认情况下,TFT LCD 库包含在 Wio 端子板库中。因此,无需再次下载 TFT LCD 库。

液晶显示基础教程链接:https://wiki.seeedstudio.com/Wio-Terminal-LCD-Basic/

字体设置:https://wiki.seeedstudio.com/Wio-Terminal-LCD-Fonts/

中文字体设置:https://luhuadong.blog.csdn.net/article/details/121939994

图片加载:https://wiki.seeedstudio.com/Wio-Terminal-LCD-Loading-Image/

折线图生成:https://wiki.seeedstudio.com/Wio-Terminal-LCD-Linecharts/

本项目主要用到折线图生成。

     Grove生态/传感器支持(博客):

Seeed链接:https://wiki.seeedstudio.com/Wio-Terminal-Grove/

elemnt14链接:https://community.element14.com/tags/wio%2bterminal     

                    (推荐这个)

 

三个传感器在数据处理上都是差不多的,这里就单以麦克风为例讲解好了。

       Wio 终端内置模拟麦克风。因此,我需要使用ADC对模拟信号进行数字化,并能够以所需的频率对其进行采样(边缘脉冲建议为16KHz)。

      第一步是验证基本功能。这很简单,因为可以使用 analogRead(WIO_MIC) 来获得麦克风输出的 ADC 转换,用于连续读取和绘制 LCD 显示屏上的麦克风信号。

基础代码:

#include"seeed_line_chart.h" //include the library
#include <math.h>

TFT_eSPI tft;

#define max_size 50 //maximum size of data
doubles data; //Initilising a doubles type to store data
TFT_eSprite spr = TFT_eSprite(&tft);  // Sprite 

void setup() {
    pinMode(WIO_MIC, INPUT);

    tft.begin();
    tft.setRotation(3);
    spr.createSprite(TFT_HEIGHT,TFT_WIDTH);
}

void loop() {
    spr.fillSprite(TFT_DARKGREY);

    int val = analogRead(WIO_MIC);

    if (data.size() == max_size) {
        data.pop();//this is used to remove the first read variable
    }
    data.push(val); //read variables and store in data

    //Settings for the line graph title
    auto header =  text(0, 0)
                .value("Microphone Reading")
                .align(center)
                .color(TFT_WHITE)
                .valign(vcenter)
                .width(tft.width())
                .thickness(2);

    header.height(header.font_height() * 2);
    header.draw(); //Header height is the twice the height of the font

  //Settings for the line graph
    auto content = line_chart(20, header.height()); //(x,y) where the line graph begins
         content
                .height(tft.height() - header.height() * 1.5) //actual height of the line chart
                .width(tft.width() - content.x() * 2) //actual width of the line chart
                .based_on(0.0) //Starting point of y-axis, must be a float
                .show_circle(true) //drawing a cirle at each point, default is on.
                .y_role_color(TFT_WHITE)
                .x_role_color(TFT_WHITE)
                .value(data) //passing through the data to line graph
                .color(TFT_RED) //Setting the color for the line
                .draw();

    spr.pushSprite(0, 0);
    delay(50);
}

进阶版:

在ADC上配置DMA

      使用ADC对模拟信号进行采样的问题在于能够以足够高的速率捕获数据。SAMD器件上的ADC可以配置为以所需的速率采样,但问题是能够有效地将数据移动到存储器。虽然使用ISR的中断可能有效,但首选方法是使用双缓冲DMA。因此,我需要弄清楚如何在 SAMD51 上配置 DMA。幸运的是,我找到了一个关于"为零上的ADC设置ISR"的帖子https://forum.arduino.cc/index.php?topic=685347.0帮助我。

找出正确的ADC输入

      SAMD51是一个高度可配置的部件,该器件具有2个ADC,这些ADC在模拟信号之间共享,每个ADC具有多达23个输入(16个外部通道),有些通道可以是差分的,有些输入具有专用功能。那么如何确定麦克风位于哪个ADC通道上呢?这很容易找到,因为Seeed在原理图上标记零件主体方面做得很好。

      如果您查看原理图中的这段摘录,则可以确定MIC_OUTPUT位于连接到ADC1通道12(AIN1.12)的Pad PC30上。

Fuxs76Ws8q4JO9fBa1c2-LFjxo_i

检查了SAM_D5XE5X_Family_Data_Sheet,

FuLC8ooOBrK7h7qcQ_sYuYrJnDG9

      下面程序来测试使用DMA将ADC1读取到两个数据缓冲区中。每个缓冲区保存 256 个值,DMA 交替填充每个缓冲区。

程序:

// Use SAMD51's DMAC to read ADC1 on the Microphone input and alternately store results in two memory arrays
// Use DMAC channel0
#define NO_RESULTS 256
#define LED_PIN D0                                                  // for debug

volatile boolean results0Ready = false;
volatile boolean results1Ready = false;
uint16_t adcResults0[NO_RESULTS];                                  // ADC results array 0
uint16_t adcResults1[NO_RESULTS];                                  // ADC results array 1

typedef struct           // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

void setup() {
  Serial.begin(115200);                                                       // Start the native USB port
  while(!Serial);                                                             // Wait for the console to open

  // initialize digital pin LED_BUILTIN as an output.
  pinMode(LED_PIN, OUTPUT);

  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                          // Specify the location of the descriptors
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                          // Specify the location of the write back descriptors
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                // Enable the DMAC peripheral
   
  DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(ADC1_DMAC_ID_RESRDY) |  // Set DMAC to trigger when ADC1 result is ready
                                 DMAC_CHCTRLA_TRIGACT_BURST;                  // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[1];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                           // Take the result from the ADC1 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults0 + sizeof(uint16_t) * NO_RESULTS; // Place it in the adcResults0 array
  descriptor.btcnt = NO_RESULTS;                                              // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                      DMAC_BTCTRL_VALID |                                     // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                           // Suspend DMAC channel 0 after block transfer
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[0];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC1->RESULT.reg;                           // Take the result from the ADC1 RESULT register
  descriptor.dstaddr = (uint32_t)adcResults1 + sizeof(uint16_t) * NO_RESULTS; // Place it in the adcResults1 array
  descriptor.btcnt = NO_RESULTS;                                              // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                      DMAC_BTCTRL_VALID |                                     // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                           // Suspend DMAC channel 0 after block transfer
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  NVIC_SetPriority(DMAC_0_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for TCC1 OVF to 0 (highest)
  NVIC_EnableIRQ(DMAC_0_IRQn);         // Connect TCC1 to Nested Vector Interrupt Controller (NVIC)

  DMAC->Channel[0].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;                     // Activate the suspend (SUSP) interrupt on DMAC channel 0

  ADC1->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN12_Val;                // Set the analog input to AIN12
  while(ADC1->SYNCBUSY.bit.INPUTCTRL);                                        // Wait for synchronization
  ADC1->SAMPCTRL.bit.SAMPLEN = 0x0a;                                          // Set max Sampling Time Length to half divided ADC clock pulse (2.66us) if set to 0x0
  while(ADC1->SYNCBUSY.bit.SAMPCTRL);                                         // Wait for synchronization 
  ADC1->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV256;                               // Divide Clock ADC GCLK by 256 (48MHz/256 = 187.5kHz)
  ADC1->CTRLB.reg = ADC_CTRLB_RESSEL_16BIT |                                  // Set ADC resolution to 12 bits
                    ADC_CTRLB_FREERUN;                                        // Set ADC to free run mode       
  while(ADC1->SYNCBUSY.bit.CTRLB);                                            // Wait for synchronization
  ADC1->CTRLA.bit.ENABLE = 1;                                                 // Enable the ADC
  while(ADC1->SYNCBUSY.bit.ENABLE);                                           // Wait for synchronization
  ADC1->SWTRIG.bit.START = 1;                                                 // Initiate a software trigger to start an ADC conversion
  while(ADC1->SYNCBUSY.bit.SWTRIG);                                           // Wait for synchronization
  DMAC->Channel[0].CHCTRLA.bit.ENABLE = 1;                                    // Enable DMAC ADC on channel 1
}

void loop()
{ 
  if (results0Ready)                                                          // Display the results in results0 array
  {
    Serial.println(F("Results0"));
    for (uint32_t i = 0; i < NO_RESULTS; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults0[i]);
    }
    Serial.println();
    results0Ready = false;                                                  // Clear the results0 ready flag
    digitalWrite(LED_PIN, HIGH);                                            // turn the LED on
  }
  if (results1Ready)                                                        // Display the results in results1 array
  {
    Serial.println(F("Results1"));
    for (uint32_t i = 0; i < NO_RESULTS; i++)
    {
      Serial.print(i);
      Serial.print(F(": "));
      Serial.println(adcResults0[i]);
    }
    Serial.println();
    results1Ready = false;                                                // Clear the results1 ready flag
    digitalWrite(LED_PIN, LOW);                                           // turn the LED off
  }
}

void DMAC_0_Handler()                                                     // Interrupt handler for DMAC channel 0
{
  static uint8_t count = 0;                                               // Initialise the count
  if (DMAC->Channel[0].CHINTFLAG.bit.SUSP)                                // Check if DMAC channel 0 has been suspended (SUSP)
  { 
    DMAC->Channel[0].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;               // Restart the DMAC on channel 0
    DMAC->Channel[0].CHINTFLAG.bit.SUSP = 1;                              // Clear the suspend (SUSP)interrupt flag
    if (count)                                                            // Test if the count is 1
    {
      results1Ready = true;                                               // Set the results 1 ready flag
    }
    else
    {
      results0Ready = true;                                               // Set the results 0 ready flag
    }
    count = (count + 1) % 2;                                              // Toggle the count between 0 and 1
  }
}

参考的博客链接:Wio Terminal Sensor Fusion - TinyML Keyword Spotting Part 2 - Blog - Sensors - element14 Community

 

个人感想体会:

首先是funpack12在12月,考试月,时间有点紧,所以做的有点简单,但是我在准备项目的过程中发现了一些很有意思的东西(打算复现一下),链接如下:

智能鼻子

智能听诊器

TinyML课程的代码    课程链接

语音识别发现关键字

热成像仪   (有的需要科学上网)

附件下载
sketch_jan01b.zip
程序
Seeed_Arduino_LCD-master.zip
Seeed_Arduino_Linechart-master.zip
画折线图的库
团队介绍
电信信抗大三
团队成员
lxb
哈尔滨工业大学电信学院信息对抗专业
评论
0 / 100
查看更多
目录
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号