Raspberry Pi Pico

it's probably possible, that is, there is sufficient computing power available. But how much Library development are you willing to do, to avoid using the Teensy 4?

Why not just use a Teensy? The libraries are powerful and mature, there's a competent & friendly support community here, and you might find that you need to do more audio processing (ie DSP filtering) on that audio that will never be possible on the Pico.

If Pico is a hard requirement (power, for instance) there are other lower quality audio solutions available. Pimoroni released an audio board for the Pico that includes a C/C++ library. No micropython yet, and about US$20.
https://shop.pimoroni.com/products/pico-audio-pack

I will most likely use the Teensy. :)
As far as the Pimoroni module, I believe that the module is only i2s out. I want to use the input as well.
Thanks.
 
RP2040 ADC to SD logger

I started playing with the Pico using Python but recently decided to see how much performance I could get with an adc to SD card data logger. I did a test of SD speed and got a write speed of just over 2 MB/sec. The ARM PrimeCell SSP (PL022) SPI is far better than SPI on other M0+ chips.

Next I tried the RP2040 SDK adc functions. The adc_read() function takes just over 2 μs so it really is a 500,000 sps ADC only about 8.5 ENOB at full speed.

Next I tried dual core tests. You can do non-blocking queues and synchronization since there is enough support for C++11 atomic types in the <atomic> include file.

I did a dedicated ADC acquire loop in core1 that queues sectors to the SdFat writer in core0. The RP2040 is great since all memory is RAM and I can have a huge amount of buffering compared to SAMD21.

So first cut of a simple logger with no optimization does over 100,000 sps. Amazing no DMA just simple stuff and even the multi-core could be done by beginners given the non-blocking queues for free and filled sectors. Just two simple loops with no tests for SD busy just a try_pop followed by a write to the SD if the try_pop succeeds.

I am just waiting for the '2' in RP2040 to increase I love having multiple cores. I worked on the BKY OS in the 1970s for the first Cray. The OS ran in 20 small processors. I worked on experiments at CERN that use soft CPUs on SOC chips. Running each process in a dedicated processor is great.
 
Pico SD logger source.

Bill, can you provide access to your pico source code for your ADC/SD logger?

Here is the code. It is rough since I am exploring the Pico. It runs at 100,000 sps. I may try to hit 200,000 sps or better by using simple chained DMA in core1.

The Pico has some fancy fast features to synchronize cores but I am not using them. It has two 8x32-bit hardware fifios and 32 hardware spinlocks.

Here is a simple non-blocking queue I wrote for sending data pointers between the cores.

AromicQueue.h:
Code:
#ifndef AtomicQueue_h
#define AtomicQueue_h
#include <atomic>
template<class T, size_t Size>
class AtomicQueue {
 private:
  T m_data[Size + 1]; 
  std::atomic<size_t> m_head {0};  
  std::atomic<size_t> m_tail {0};
  size_t next(size_t index) {
    return index < Size ? index + 1 : 0;
  }
 public:
  // Not thread/core safe.
  void clear() {
    m_head.store(0);
    m_tail.store(0);
  }
  bool empty() {
    return m_tail.load() == m_head.load();
  }
  bool full() {
    return next(m_tail.load()) == m_head.load();
  }
  bool try_pop(T& dst) {
    size_t head = m_head.load();   
    if (head == m_tail.load()) {
      return false;
    }
    dst = m_data[head];
    m_head.store(next(head));
    return true;
  }
  bool try_push(const T& src) {
    size_t tail = m_tail.load();
    size_t nextTail = next(tail);
    if (nextTail == m_head.load()) {
      return false;
    }
    m_data[tail] = src;
    m_tail.store(nextTail);
    return true;
  }
};
#endif  // AtomicQueue_h

Here is the test program. Not well commented. Certain to have bugs.

RP2040Logger.ino
Code:
#include "pico/multicore.h"
#include "AtomicQueue.h"
#include "SdFat.h"

#if !USE_SPI_ARRAY_TRANSFER
#error "Set USE_SPI_ARRAY_TRANSFER in SdFatConfig.h"
#endif  // USE_SPI_ARRAY_TRANSFER

// Max rate until DMA is used for the ADC.
const uint32_t LOG_INTERVAL_USEC = 10;  // 100,000 sps.

// Buffer for 200 SD sectors.
const size_t BUF_COUNT = 200;

const uint8_t SD_CS_PIN = SS;
#define SD_CONFIG SdSpiConfig(SD_CS_PIN, SHARED_SPI)

// Preallocate 1GiB file.
const uint32_t PREALLOCATE_SIZE_MiB = 1024UL;
const uint64_t PREALLOCATE_SIZE  =  (uint64_t)PREALLOCATE_SIZE_MiB << 20;

// Use exFat
SdFs sd;
FsFile file;

uint16_t bufArray[BUF_COUNT][256];
AtomicQueue<uint16_t*, BUF_COUNT> freeQueue;
AtomicQueue<uint16_t*, BUF_COUNT> fullQueue;
std::atomic<uint32_t> adcErr {0};
std::atomic<bool> adcRun {false};
//==============================================================================
// Core1 ADC process
// Don't use core0 functions in core1.
#define adcError() adcErr.store(__LINE__)

void core1_entry() {
  uint16_t* buf = nullptr;
  adc_init();
  // Make sure GPIO is high-impedance, no pullups etc
  adc_gpio_init(26);
  // Select ADC input 0 (GPIO26)
  adc_select_input(0);
  if (!freeQueue.try_pop(buf)) {
    adcError();
    return;
  }
  adcRun.store(true);
  size_t n = 0;
  uint32_t logTime = time_us_32();
  while (adcRun.load()) {
    logTime += LOG_INTERVAL_USEC;
    // Wait until time to log data.
    int32_t delta = time_us_32() - logTime;
    if (delta > 0) {
       adcError();
       break;
    }
    while (delta < 0) {
      delta = time_us_32() - logTime;
    }    
    buf[n++] = adc_read();
    if (n == 256) {
      if (!fullQueue.try_push(buf) || !freeQueue.try_pop(buf)) {
        adcError();
        break;
      }
      n = 0;
    }
  }
  // could tell core0 we exited
}
//==============================================================================
// Core0 SD write process
#define error(s) sd.errorHalt(&Serial, s)

void logData() {
  uint16_t* buf;
   // Initialize SD.
  if (!sd.begin(SD_CONFIG)) {
    sd.initErrorHalt(&Serial);
  }
  if (!file.open("PicoLog.bin", O_RDWR | O_CREAT | O_TRUNC)) {
    error("file.open failed");
  }
  if (!file.preAllocate(PREALLOCATE_SIZE)) {
    error("preAllocate failed");
  }
  freeQueue.clear();
  fullQueue.clear();
  // Put buffers in free queue
  for (size_t i = 0; i < BUF_COUNT; i++) {
    freeQueue.try_push(bufArray[i]);
  }
  // clear Serial.
  do {
    delay(10);
  } while (Serial.read() >= 0);
  Serial.println("type any character to stop");
  multicore_launch_core1(core1_entry); 
  while (!Serial.available()) {
    if (fullQueue.try_pop(buf)) {
      if (file.write(buf, 512) != 512) {
        error("file.write");
      }
      if (!freeQueue.try_push(buf)) {
        // Bug in queue if this happens.
        error("try_push");   
      }
    }
    if (adcErr.load()) {
      Serial.print("adc error line: ");
      Serial.println(adcErr.load());
      break;
    }
  }
  adcRun.store(false);
  file.truncate();
  file.rewind();
  // Print first part of file.
  for (size_t i = 0; i < 20; i++) {
    uint16_t adc;
    if (file.read(&adc, 2) != 2) {
      error("file.read");
    }
    Serial.println(adc);
  }
  Serial.print("FileSize ");
  // Warning cast used for print since fileSize is uint64_t.
  Serial.println((uint32_t)file.fileSize());
  file.close();
  Serial.println("done");
}

void setup() {
  Serial.begin(9600);
  while (!Serial) {}
  Serial.println(F("Type any character to begin"));
  while (!Serial.available()) {
    yield();
  }
  logData();
}

void loop() {
}

Edit: The above program is limited to about 100,000 sps because of APB bus saturation by adc_read(). adc_read() starts the ADC then loops on conversion done. This slows the SPI transfer rate and SD writes can't keep up. Looks like chained DMA for the ADC is the only way to to go faster.
 
Last edited:
Pico ADC Errata

the good news: I managed to implement DMA ADC with chained buffers at 500 ksps. The interval between samples can be set in steps 1/48 of a microsecond.

The bad news: The ADC has a design flaw. Like many SAR ADCs it uses a a capacitive DAC. One of the capacitors is not scaled correctly so at 512, 1,536, 2,560, and 3,584 — the ADC’s DNL error peaks. The 12-bit ADC only has an ENOB of about 9 bits.

Too bad since the SPI controller is great and SD write rates are much higher than other Arduino M0+ boards.

SdFat bench performance:

Pico
write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
1929.01,1761,259,260
1929.76,278,259,260

read speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
1933.49,263,259,260
1932.74,264,259,260
Arduino MKR ZERO SAMD21
write speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
429.27,1213,1187,1189
429.23,1213,1186,1189

read speed and latency
speed,max,min,avg
KB/Sec,usec,usec,usec
438.80,1172,1161,1163
438.76,1172,1161,1163
 
> ADC has a design flaw

How much of this can be fixed in software?



> Arduino support based on mbed

Might be something to consider for reducing the software work needed for future teensies.
 
> ADC has a design flaw
How much of this can be fixed in software?

It's really bad.
RP2040ADC.png

I really like the RP2040 for users that are using Arduino SDMD21 or AVR boards and want more SdFat performance. There are now at least ten boards. There is great documentation and help. You can be in the Arduino world, the Raspberry Pi world or the mbed world.

I know Teensy fans point out 600 MHz and hardware floating point but many users don't need Teensy 4.1.

Much about the RP2040 reminds me of how hard-real-time is done on the huge physics experiments I worked on like the CERN LHC. The LHC uses fairly slow processors based on FPGAs and soft CPU cores.

This is for reliability and software simplicity.
● Up to 8 LM32 cores, with private code/data memory
● Programed in bare metal C,using standard GCC tool chain
● Each core runs a single task in a tight loop.
● No caches, no interrupts
● Inter-core communication and task synchronization
● Small, but atomically accessed
Add/subtract
Bit operations
Test-and-set
● Task synchronization primitives
Mutexes
Semaphores
Queues
Flags
Events...
 
Hi....I watched a YouTuber telling the best way to interface some stuff dependent upon one and thought I gotta learn more equipment. I haven't utilized a fastening iron since I was an adolescent... what's more, I didn't actually have a clue what I was doing in those days... simply adding a reset change to a Vic-20 and stuff like that. I took a stab at patching RAM venture into one, yet I think my strategies were terrible, and I likely cooked my chips.
 
The Pico makes it easy to attach sensors directly to your laptop/PC, something that I have wanted to do for a long time. The Pico acts as an inexpensive and simple bridge between the PC and the hardware/sensor world. It supports ADC, I2C and SPI and the full complement of GPIO. There are times when this is really useful. There is a small trade-off because speeds are lower, limited by the USB connection and the Python interpreter. But it is not bad at all. The great merit of this is its extreme simplicity.

This Adafruit article describes how to do it: https://learn.adafruit.com/circuitpython-libraries-on-any-computer-with-raspberry-pi-pico/overview
 
Unlike Arduino, I felt right at home with the BSD Unix development environment from the 1980s

I smiled when I read that and can relate to it.

Bill, have you had a look at the recently announced Raspberry Pi Zero 2W?
To me at least, this seems to be a really exciting development for some classes of problems. Though nothing can diminish my love affair with the Teensy 4.0!
 
Paul will be pleased that I switched back to Teensy 3.0 for my Room Thermostat project after I started with the Raspberry Pi Pico.

Maybe I'll get back to using it on some other project.
 
Maybe i overread something, but is the Teensy Audio Library compatible with the Pico Pi? Would like to build some basic stuff :) TIA
 
Maybe i overread something, but is the Teensy Audio Library compatible with the Pico Pi? Would like to build some basic stuff :) TIA

I believe the official PJRC version is only compatible with Teensies. You might get a better answer asking in forums where people deal with the pico pi more often. I've heard that people had adapted the audio library to other boards, but don't remember the details.
 
Thank you for your Answer. As Adafruit ported a version for their neotrellis M4 board, i hoped there was also a fork or something for the pico pi, or even better use it straight from the box. From my small experience poeple tend to program the pico with circuit/ micro Python. Which is not suitable for me as i would like to have the midi over usb. That is why asked here ;)
 
pjrc.com says: "Update, July 30, 2022: We expect to have Teensy 4.0 in stock by August 8."

and: "Update: August 1, 2022: We have only Teensy 4.1 without ethernet right now. Many Teensy 4.1 with ethernet recently shipped to distributors, look for them at distributors if you need ethernet now. We expect to have more Teensy 4.1 with ethernet by August 22."
 
Back
Top