Skip to content

Commit

Permalink
Added ML and neopixel examples
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexFWulff committed Jun 27, 2022
1 parent 1ebee8e commit 318cb25
Show file tree
Hide file tree
Showing 14 changed files with 299 additions and 63 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
build/
build/
edge-impulse-sdk/
tflite-model/
model-parameters/
Binary file added adc_fft/.pico_sdk_import.cmake.icloud
Binary file not shown.
Binary file added adc_time/.pico_sdk_import.cmake.icloud
Binary file not shown.
62 changes: 0 additions & 62 deletions adc_time/pico_sdk_import.cmake

This file was deleted.

Binary file added pico-daq/.DS_Store
Binary file not shown.
23 changes: 23 additions & 0 deletions pico-daq/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
cmake_minimum_required(VERSION 3.12)

include(pico_sdk_import.cmake)

project(pico_daq)

pico_sdk_init()

add_executable(pico_daq
pico_daq.cpp
base64.cpp
)

pico_enable_stdio_usb(pico_daq 1)
pico_enable_stdio_uart(pico_daq 1)

pico_add_extra_outputs(pico_daq)

target_link_libraries(pico_daq
pico_stdlib
hardware_adc
hardware_dma
)
19 changes: 19 additions & 0 deletions pico-daq/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# pico-daq

This program samples from the ADC, converts the sampled values to base64, and then dumps it out over Serial. You can then read in the base 64 values into a program to convert them to whatever you'd like.

## A note on the sampling

At 5 kHz w/ 16-bit samples (the default in the program) the Pico cannot write out data as fast as it reads it. Therefore, at the end of each sampling window, the program will pause ADC sampling as it finishes writing out the rest of the samples. This will lead to some jumps in the data. For my purposes of collecting audio for training a ML model this is fine. If this is not ok, try using 8-bit samples or turn down the sample rate until the LED on the Pico starts to flash.

## Logging Base64 Values

On macOS and Linux you can use the `screen` tool to save off the base64 values:

screen -L /dev/tty.usbmodem21301 115200

This command opens a serial console for the Pico and then will save the resulting text to the file `screenlog.0`, which you can then convert back to whatever format you'd like. Obviously you'd need to change `/dev/....` to the address of your Pico.

## Converting to WAV

I included a sample program to read in the base64 text values and convert it to WAV files that can be played on your computer. See `py/b64_16_to_wav.py`.
121 changes: 121 additions & 0 deletions pico-daq/base64.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/*
base64.cpp and base64.h
Copyright (C) 2004-2008 René Nyffenegger
This source code is provided 'as-is', without any express or implied
warranty. In no event will the author be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this source code must not be misrepresented; you must not
claim that you wrote the original source code. If you use this source code
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original source code.
3. This notice may not be removed or altered from any source distribution.
René Nyffenegger [email protected]
*/

#include "base64.h"

static const std::string base64_chars =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";


static inline bool is_base64(unsigned char c) {
return (isalnum(c) || (c == '+') || (c == '/'));
}

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len) {
std::string ret;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];

while (in_len--) {
char_array_3[i++] = *(bytes_to_encode++);
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;

for(i = 0; (i <4) ; i++)
ret += base64_chars[char_array_4[i]];
i = 0;
}
}

if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';

char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;

for (j = 0; (j < i + 1); j++)
ret += base64_chars[char_array_4[j]];

while((i++ < 3))
ret += '=';

}

return ret;

}
std::string base64_decode(std::string const& encoded_string) {
int in_len = encoded_string.size();
int i = 0;
int j = 0;
int in_ = 0;
unsigned char char_array_4[4], char_array_3[3];
std::string ret;

while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) {
char_array_4[i++] = encoded_string[in_]; in_++;
if (i ==4) {
for (i = 0; i <4; i++)
char_array_4[i] = base64_chars.find(char_array_4[i]);

char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

for (i = 0; (i < 3); i++)
ret += char_array_3[i];
i = 0;
}
}

if (i) {
for (j = i; j <4; j++)
char_array_4[j] = 0;

for (j = 0; j <4; j++)
char_array_4[j] = base64_chars.find(char_array_4[j]);

char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4);
char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2);
char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3];

for (j = 0; (j < i - 1); j++) ret += char_array_3[j];
}

return ret;
}
3 changes: 3 additions & 0 deletions pico-daq/base64.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#include <string>

std::string base64_encode(unsigned char const* bytes_to_encode, unsigned int in_len);
92 changes: 92 additions & 0 deletions pico-daq/pico_daq.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Sample from the ADC continuously at a particular sample rate
// and then outputs base64 bytes via Serial
// much of this code is from pico-examples/adc/dma_capture/dma_capture.c
// the rest is written by Alex Wulff (www.AlexWulff.com)

#include <stdio.h>
#include <cstring>
#include "pico/stdlib.h"
#include "hardware/adc.h"
#include "hardware/dma.h"
#include "base64.h"

// set this to determine sample rate
// 0 = 500,000 Hz
// 960 = 50,000 Hz
// 9600 = 5,000 Hz
#define CLOCK_DIV 9600

// Channel 0 is GPIO26
#define CAPTURE_CHANNEL 0
#define LED_PIN 25
#define NSAMP 20000

uint16_t capture_buf[NSAMP];
uint16_t sending_buf[NSAMP];

int main() {
stdio_init_all();

gpio_init(LED_PIN);
gpio_set_dir(LED_PIN, GPIO_OUT);
adc_gpio_init(26 + CAPTURE_CHANNEL);

adc_init();
adc_select_input(CAPTURE_CHANNEL);
adc_fifo_setup(
true, // Write each completed conversion to the sample FIFO
true, // Enable DMA data request (DREQ)
1, // DREQ (and IRQ) asserted when at least 1 sample present
false, // We won't see the ERR bit because of 8 bit reads; disable.
false // Don't shift each sample to 8 bits when pushing to FIFO
);

// set sample rate
adc_set_clkdiv(CLOCK_DIV);

sleep_ms(1000);
// Set up the DMA to start transferring data as soon as it appears in FIFO
uint dma_chan = dma_claim_unused_channel(true);
dma_channel_config cfg = dma_channel_get_default_config(dma_chan);

// Reading from constant address, writing to incrementing byte addresses
channel_config_set_transfer_data_size(&cfg, DMA_SIZE_16);
channel_config_set_read_increment(&cfg, false);
channel_config_set_write_increment(&cfg, true);

// Pace transfers based on availability of ADC samples
channel_config_set_dreq(&cfg, DREQ_ADC);

while (1) {
adc_fifo_drain();
adc_run(false);

dma_channel_configure(dma_chan, &cfg,
capture_buf, // dst
&adc_hw->fifo, // src
NSAMP, // transfer count
true // start immediately
);

// if the light does not flash, then there is some data loss.
// pico can't print the data out fast enough, so the capturing
// finishes before the data is done printing. This will result
// in the ADC not collecting for portions of the sampling

// run sampling routine
gpio_put(LED_PIN, 1);
adc_run(true);

// first transmission will be garbage since we haven't filled the buffer yet
std::string encoded =
base64_encode((unsigned char const *)sending_buf, NSAMP*2);
printf("%s", encoded.c_str());

gpio_put(LED_PIN, 0);

dma_channel_wait_for_finish_blocking(dma_chan);


memcpy(sending_buf, capture_buf, NSAMP*2);
}
}
File renamed without changes.
35 changes: 35 additions & 0 deletions pico-daq/py/b64_16_to_wave.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
from pydub import AudioSegment
import numpy as np
import base64

if __name__=="__main__":
infile = "/Users/alex/Desktop/pico/other-3"
outfile = "/Users/alex/Desktop/pico/other.wav"

f = open(infile, "r")

byte_data = bytearray()
vals = f.read(6000)
# Reading all at once didn't work for some reason
while vals:
byte_data.extend(base64.b64decode(vals))
vals = f.read(6000)
f.close()

# Enforce little endian
dt = np.dtype(np.uint16)
dt = dt.newbyteorder('<')
data = np.frombuffer(byte_data, dtype=dt)

data = data.astype(np.int32)
data = data-np.mean(data)
data = data.astype(np.int16)

audio = AudioSegment(
data.tobytes(),
sample_width=2,
frame_rate=5000,
channels=1
)

audio.export(outfile, format="wav")
1 change: 1 addition & 0 deletions pico-light-voice
Submodule pico-light-voice added at 5a8258
1 change: 1 addition & 0 deletions pico-voice-v1
Submodule pico-voice-v1 added at 5a8258

0 comments on commit 318cb25

Please sign in to comment.