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/
硬件概览:
引脚图:
底部Grove接口图:
这里我将麦克风模块插入了右下角的Grove接口,所以就使用了13号引脚(A0),则心电检测与光感则分别使用了16号引脚(A2)和18号引脚(A3)。麦克风模块与光感传感器来自Grove生态系统(这两是我找同学借的),心电传感器来子Fastbond项目,感兴趣的可以去瞅一眼。
后面接线如图:
首先介绍按钮吧。共有三个按钮可用于 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上。
检查了SAM_D5XE5X_Family_Data_Sheet,
下面程序来测试使用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月,考试月,时间有点紧,所以做的有点简单,但是我在准备项目的过程中发现了一些很有意思的东西(打算复现一下),链接如下:
热成像仪 (有的需要科学上网)