Adalogger standby and wake-up now working

[ commit ]

This seems like quite a random fix, but calling rtc.standBy() only works from within setup() and loop(), and not from the RTC ISR. There is probably something happening behind the scenes, perhaps enabling/disabling of writes to protected registers. Its still weird that calling standBy() from the wrong routine just appears to stall the processor.

The revelation of the night was realizing that SAMD21 did successfully wake up after the first alarm, but it wasn’t going into loop() right after (indicated by missing LED flashes!) I was calling standBy() from the same alarmConfig() function: but it was first being called from setup(), and subsequently from within the RTC ISR. That’s why the first alarm always worked, but things messed up during its servicing (as soon as we hit standBy()).

Aside: delay() crashes inside the ISR too.

On the brightside (go Killers!), I am now more comfortable with writing SAM’s registers through the Arduino IDE. Still, using a debugger along with Atmel Studio would probably make development *much* faster. I now appreciate TI’s putting the XDS debuggers on all their eval boards. Well, Arduino Zero has that too, but Adalogger is too compact to allow that!

I just wish that re-enabling the Serial module again after wake-up was working. But since that’s not an end-user requirement, I’m going to put RTC on hold and get back to the SD. We could then come back to the RTC code so that the config. packet could be generated from a GUI.

Another interesting thing would be to store the alarm times in flash memory (SAMD21’s “EEPROM”) over just RAM – but I doubt that that would be useful unless I can guarantee that the RTC wouldn’t lose time during an accident. The idea is that if somehow the watchdog caused a reboot (is the watchdog setup anyway?), it should still be able to alarm – but we don’t want all the times to be skewed!

Hypothetically speaking, there could be a routine running every so often that writes the current time (and maybe even the next alarm’s index) to flash. Its just that this recorder would have to brave through weeks of work without human contact, and a little robustness might save us the frustration of looking at missing records on the SD later🙂

Lower hanging fruit: RTC on Adalogger

Code: [ firmware ] [ host PC ]

Well, SD cards are somewhat involved. So I’ve just been working on the easier problem of setting up the RTC: easier because there’s a library for it and all I need to do is setup a connection with Processing so I can configure the real time clock module on SAMD21!

Just so you know, I just learned about [ UNIX time ] and was quite excited to use it because of this [ post ]:

In the end, the only thing that really works for all situations,
is using a single epoch and then converting to timezone offsets
as needed. The unix guys really did get it right way back then.

Also, I should mention that it seems easier to store numbers in the logger’s memory, than to deal with strings like “12:00 AM 05/24/16”. And we could do a re-sync whenever the recorder is programmed so there’s aren’t any significant offsets. Besides, knowing an absolute number, we can always figure out what local times may have been!

But then I decided to not use epoch since the registers themselves are mapped to date/time:

alarm0.PNG

[ Source, pdf p. 294 ]

And converting the epoch to these register values is being done in software in the RTCZero library:

void RTCZero::setEpoch(uint32_t ts)
{
if (ts < EPOCH_TIME_OFF) {
ts = EPOCH_TIME_OFF;
}

time_t t = ts;
struct tm* tmp = gmtime(&t);

RTC->MODE2.CLOCK.bit.YEAR = tmp->tm_year - EPOCH_TIME_YEAR_OFF;
RTC->MODE2.CLOCK.bit.MONTH = tmp->tm_mon + 1;
RTC->MODE2.CLOCK.bit.DAY = tmp->tm_mday;
RTC->MODE2.CLOCK.bit.HOUR = tmp->tm_hour;
RTC->MODE2.CLOCK.bit.MINUTE = tmp->tm_min;
RTC->MODE2.CLOCK.bit.SECOND = tmp->tm_sec;

while (RTCisSyncing())
;
}

So regular date/time it is.

Although SAMD21 supports both an AM/PM and a 24 hour format, using an AM/PM format requires me to monitor an hour bit to determine the actual time (” HOUR[4] represents AM (0) or PM (1).” from the datasheet). Even though the end user may prefer AM/PM, writing out recording names by time would require using conditionals either way. So, I’ll just skip having to re-configure the RTC for AM/PM, and go with the 24 hour format for the RTC itself. We’ll bring back the AM/PM for filenames and such. And if RTC is to be reconfigured, its probably just one line to write CTRL.CLKREP anyway!

This week’s update is late…oh my!…and it consists of broken code! But here’s what I did:

  1. Processing code for sending current time and possible alarm times to SAMD21 from a host PC (over Serial)
    Status: mostly working. Checked by transmitting the received times back. Error-checking can be improved.
  2. Firmware for initializing the Real-Time Clock and Alarm0 using received information
    Status: just RTCZero library calls => working. Alarm0 information is transmitted back to double-check the registers.
  3. Firmware for going into sleep mode until Alarm0 hits.
    Status: working
  4. Firmware for configuring the next Alarm0 from information received in 1.
    Status: nope, fails.

Here’s a short video demoing an LED going off when the first alarm hits:

================== update ==================

It turns out that putting the processor to sleep is causing the problem! There was a comment in the library about it that I ignored!


void RTCZero::standbyMode()
{
// Entering standby mode when connected
// via the native USB port causes issues.
SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;
__WFI();
}

But then I found a multiple alarm example for the same library, which wasn’t included in the actual release (see this) but is forked here. Thanks agdl! And this implementation uses a linked list to hold alarm info – a much cleaner implementation of what I was trying to do.

Multiple alarms appear to work without going to sleep. Now its to see if they are still timed right!

Possible solutions for faster SPI -> SD writes on Adalogger (SAMD21)

As with any problem, one way out is to google the problem and see if the community has resolved it already. With writing SD cards, it appears that we have at least three lines of attack:

  • DMA
  • Clocking SPI faster
  • FIFOs (I don’t think we have these on SAMD21’s SPI)

Community wise, we have content on Arduino, Atmel, and ARM forums:

[ SdFat with DMA SPI ] which has been successful on Arduino Due. Since SAMD21’s SPI interface supports DMA as well [ Sec. 27 | pdf p.501 ], a similar solution could be developed.

SdFat’s documentation is [here].
[ ZeroDMA ] could be used to setup the DMA controller on SAMD21

[ SPI max speed ] along with using [ FatFs ]

[ GPIO ] would limit SPI to 12 MHz, but I’m not sure where the GPIO module is being used here.

[ Quickstart guide ] for setting up DMA is referenced here as well.

Now to dig into the libraries and see how SD card writes are configured!

 

[ echo-II ] Sparkfun’s SD library: timing writes

Today’s problem was simple. What’s the fastest rate at which we can write an SD card using the Adafruit Adalogger (based on Atmel SAMD21) and the standard SD library in the Arduino IDE? The answer that I have this far appears to be one write every ~70 micros (at best), but it gets weird.

Why does this matter? I want to write two 32-bit numbers (per stereo sample) to an SD card at 96 kHz. Coding this will be a breeze if I can just patch together some examples. I tried that today, and although things could work reliably at say 1-2 kHz (would change based on load on the processor), they definitely wouldn’t work at 96 kHz unless we tweak the library to exploit the SAMD21 a bit more. DMA would help.

But how good / bad can things be if we to just stitch together some calls to library functions? Well, good enough for a non-audio related project, but not when we need high quality audio recording (atleast not without re-configuring some things).

I ran a very simple test: I just wrote out the time elapsed on the microcontroller (in microseconds), and made plots. I think this is the best the SD library can do given that the processor is not doing anything else in the void loop (code is appended). Doing other things will probably slow the writes down more (unless we go for DMA, which we should!)

Check these timing plots out:

051516_results

On the top, I am showing you the total time elapsed as we keep writing micros()’s output to the SD card (note: micros() is used to time code execution in microseconds…just like millis() does it in milliseconds). Note the slope of the blue line – that’s the time period between consecutive samples – is *much* larger than what we need (see the red line for the expected slope for sampling @96 kHz). That is, writes to the SD card are too slow if we just use println() from the SD library in the void loop().

The second plot emphasizes another detail that’s easy to miss on the first plot, that is, how consistent is the timing of these writes anyway? Looks like most of the writes happen at intervals of ~70 micros, but there’s some rather consistent weirdness going on every 57th sample (no cursors shown), and then again every ~910th sample (cursors shown). For these “outliers”, the time elapsed between consecutive writes goes up from ~70 micros to about ~4200 micros in the middle of the plot, and ~8000 for the weirdest outliers towards the top. My first bet is that these have something to do with the actual data dump to the SD card, but that needs to be verified.

Summary this far: the simplest approach of stitching library examples together has failed. We need to go in and configure some registers to make things work faster, and in parallel.

Now, for the code, see [ github ].

Here’s the arduino sketch for quick reference:

 

// Project Echo | Timing writes to SD card using Sparkfun's SD library
// The goal here is to print the output of micros() to an SD card as quickly as possible
//  while using standard calls to the SD library.

// This sketch is adapted from:
//  https://learn.adafruit.com/adafruit-feather-m0-adalogger/using-the-sd-card
//   and Examples/SD/DumpFile

// Hardware: Adfruit Adalogger running Atmel SAMD21 ARM M0+ @48MHz

#include <SPI.h>
#include <SD.h>

// Set the pins used
#define cardSelect 4
#define redLED 13 // used for indicating button presses
#define grnLED 8 // lights up when logging
char filename[9]; // could reduce size...
File logfile;

// Some dummy data for more timing comparisons
// Replace with 32-bit left/right samples obtained from codec
float placeHolder1 = 1.0202020;
float placeHolder2 = 1.0202020;

void setup() {
  pinMode(redLED, INPUT);
  attachInterrupt(redLED, switchISR, FALLING); // for stopping logging.
  // Could use other interrupts; I happened to have a pushbutton already there.
  pinMode(grnLED, OUTPUT);

  Serial.begin(115200);
  while (!Serial) {} // wait until serial is available. Not neccessary?
  Serial.println("Welcome to the SD echo test!");

  // see if the card is present and can be initialized:
  if (!SD.begin(cardSelect)) {
    Serial.println("Card init. failed!");
  }

  strcpy(filename, "LOG00.CSV");
  for (uint8_t i = 0; i < 100; i++) {
    filename[3] = '0' + i / 10;
    filename[4] = '0' + i % 10;
    // create if does not exist, do not open existing, write, sync after write
    if (! SD.exists(filename)) break;
  }

  logfile = SD.open(filename, FILE_WRITE);
  if ( ! logfile ) {
    Serial.print("Couldn't create ");
    Serial.println(filename);
  }
  Serial.print("Writing to "); Serial.println(filename);
  Serial.println(F("\nSend any character to begin")); // from MPU-6050 teapot demo!
  while (Serial.available() && Serial.read()); // empty buffer
  while (!Serial.available());                 // wait for data
  while (Serial.available() && Serial.read()); // empty buffer again
  digitalWrite(grnLED, HIGH);
  Serial.println("Push button to stop logging!");
}

void loop() {
  logfile.println(micros());
  //logfile.print(micros()); //logfile.print("\t");
  //logfile.println(placeHolder1);// logfile.print("\t");
  //logfile.println(placeHolder2);
  //placeHolder1 += 1.0;
  //placeHolder2 *= 2.0;
  //placeHolder = analogRead(0); // apparently costs ~400 micros -> can be reduced
  //placeHolder = analogRead(1);
  //delayMicroseconds(75); // adds up
}

void fileDump() { // print contents of file for copy/paste/plotting
  Serial.println("Reading back what we wrote...");
  File dataFile = SD.open(String(filename));

  if (dataFile) { // read from the file if its available
    while (dataFile.available()) {
      Serial.write(dataFile.read());
    }
    Serial.println("FIN"); // we did it!
    dataFile.close();
  } else { // else: error!
    Serial.println("error opening datalog");
  }
}

void switchISR() {
  logfile.flush(); logfile.close();
  digitalWrite(grnLED, LOW); // not logging anymore
  fileDump();
  exit(0); // z Z z
}

Echo Part II – this one will actually work :D

[ spec ] [ block diagram ] [ github ]

Echo is back! We’re now calling it echo-II, and the goal of this instance is straightforward: make a sound recorder capable of supporting 96 kHz | 32-bit | stereo | PCM recordings over pre-allocated time slots for about 3 weeks, and do all this on a battery (without human intervention).

That’s a mouthful, so the spec is linked at the top. And we all love block diagrams, so here’s one!

That’s for the introduction. Some code coming in on the next one!

Finally on what’s with all these posts about Physionet and AFE4400

These slides explain it all! Basically, we have been trying to capture physiological changes associated with getting tired / falling asleep. The goal here is to have an embedded system sensing changes in heart rate over time, just to warn sleepy drivers about the potential of falling asleep while they are driving.

Progress this far: we are seeing some correlation between heart rate variability and sleepiness, but need more statistics. Device-wise, we have a test system using ECG to get RR intervals and changes in them. We have Physionet’s heart rate variability tools running on a Beaglebone black, but we still need exhaustive testing to confirm reliability.

And the current implementation is split over two Blacks because we were developing in parallel, but that can be fixed in a few days’ work (not happening until finals…)

Its very cool to see a physiological indication of potential drowsiness. If we can confirm reliability, we can warn people who might be pushing themselves to keep on the road!

Soldered most of an AFE4400 to ground…

[ AFE4400 ] [ SFH-7050 ] [ Design files on GitHub ]

So I finally took up the courage to paint solder balls onto our freshly cut PCB, but I think I placed too much solder on the thermal pad, and bam, about 5 pins are now shorted to ground. By shorted, I mean that they have about an ohm to 1k ohms to ground. That because our vias didn’t plate so the thermal pad itself isn’t really grounded. Not sure if we’ll still be able to use it to dissipate heat.

I can easily use jumper wires to wire the chip’s ground plane out (one tiny trace at pin 40 will get all the load), or we can try cutting again. I am just tempted to give this one a shot, because getting another board would take time and money.

img003

Front

img004

Back

By the way, the SFH-7050 is the coolest LED package I have come across (one notch above the reverse wingulls!) There are three tiny tiny emitters inside (I wish I had a camera to click the microscopic view…some day when my phone is fixed!)

 

 

Getting Physionet’s HRV tools (WFDB) running!

Our end goal is to do Heart Rate Variability analysis on the Beaglebone Black, but initially, just to understand how to run the toolkit, we are setting up everything on an Ubuntu laptop (with desktop and GUIs!)

Tutorial being followed: [ Physionet’s HRV howto ]

[ WFDB ]’s download was straightforward.

[ WAVE ] was slightly trickier. I wasn’t able to correctly update the sources.list file to include main universe (I think, because apt-get was complaining about duplicates) Things still worked out.

[ XView ] gave us some font problems, but using the flag -fn fixed worked.

To do our HRV analysis, here are the steps we followed:

  1. Sample ECG from AD8232 using an MSP432 (outputting both time of capture through millis(), and the actual analog value read)
  2. After conforming that the sample timings were consistent (3ms), generated a copy of the file with just the analog reads.
  3. Converted this text file to a Physionet compatible record:
    wrsamp -F 333.33 -G 1000 -i ecg_04_15_AM_3des_RibCollarAnkle_10min_2-3msSampling_fallingAsleep_headNods_ECG_only.txt -o ecg_04_15_AM 0
    I am not sure what exact gain value to use, but using 1000 allowed us to skip adjusting the thresholds on the the peak detection (next step)
  4. Used [ gqrs ] to get annotations for QRS complexes:
    gqrs -r ecg_04_15_AM -m 1
    m: sets the threshold
  5. The obtained peaks were viewed using rdann:
    rdann -a qrs -r ecg_04_15_AM
    (annotation type was set to qrs as gqrs outputs a .qrs file. This can be changed if another annotator was used. For example, -a wqrs can be used if a .wqrs file was generated from wqrs)
  6. wave was used to view the annotations too. There is some fonts issue with xview, and here’s physionet’s suggested workaround: use -fn fixed:
    WaveViewer
    (File>Annotator has to be specified to see the ‘N’s. View can be adjusted to change scales and add grids.)Everything except the first peak was correctly detected in this case.
  7. Getting the RR intervals:
    ann2rr -r ecg_04_15_AM -a qrs -w -i s -c >ecg_04_15_AM.rr1.txt
    -w: would specify that we are looking at ‘N’s
    -i s: would output times in seconds
    -c: would only exclude intervals that are not bounded on both sides (hopefully getting rid of the first problematic capture too)
  8. Next is to install [ plt ]. make install is not being able to find my X11 libraries…
    makeX11

 

FTPing ECG data to the Beaglebone Black

Before getting libpruio running, I am curious to see if I can process ECG to get HRV on the Black itself. I started with [ physionet’s HRV howto ], which included setting up [ wfdb software package ] on the Black. Working as root helped skip all the permission denied problems:

sudo -s

To get wfdb, I started with

apt-get install gcc libcurl4-openssl-dev libexpat1-dev

Running apt-get update helped.

The next was to wget the tarball and begin installation. Funnily enough, I had to apt-get make as well…

wget http://www.physionet.org/physiotools/wfdb.tar.gz

I had to remove the ‘s’ from https to skip this alert:

tlsAlarm

(Yes, I also need to setup NTP time updates on the Black…)

Next step was to run configure, make install and make check. That worked, except for some time sync problems (again, NTP)

Then, to follow along with the HRV files, I decided to send over some ECG data (collected on MSP432 with multitasking!) over to the Black. As I am on Windows, [ PSFTP ] is a good option.

I opened a connection to the board, and tried to write the files over, but it turned out that I didn’t have write permissions…

chmod777

And I didn’t have permissions to change these permissions. Thanks to [ dragonfly41] ‘s post, I used PuTTy again to change permissions for that folder.

chmod777

Then sending the files over worked.

Now to proceed with the HRV analysis!