2019-05-18

STM32F407G-DISC1 board and MEMS microphone MP45DT02

This is a repost of a couple of submissions I made on the ST Community site. It seems they are not searchable on the site, nor on Google, so I will republish them here in the hope to make them more visible.
I wanted to configure the I2S peripheral for the microphone MP45DT02 mounted on the STM32F4DISCOVERY board using STM32CubeMX and HAL. When searching for information regarding this I came across a couple of errors in the ST documentation I would like to point out, and at the same time give some hints on how to correctly capture audio data from the microphone. If I have made any mistakes I hope to be corrected.

References:

In [1] the "Data and Frame Format" for the I2S peripheral is set to "16 Bits Data on 32 Bits Frame" which I think is wrong. With "16 Bits Data on 16 Bits Frame" and an audio frequency of 16KHz, the data capturing rate in the peripheral will be 2*16 bits @ 16KHz and the I2S clock will oscillate with 2*16*16000 Hz. When the frame is extended to 32 bits the I2S clock will double its frequency to 2*32*16000 Hz, but the data capturing rate in the peripheral will not change. It is still 2*16 bits @ 16KHz. This means every other 16 bits of data coming from the microphone will be dropped as the pheripheral assumes they are zero. This is not what you want. I decided to use "32 Bits Data on 32 Bits Frame" to get a data aquisition rate of 2*32 bits @ 16KHz, i.e. 64 times oversampling.

This leads us to the next documentation error in [2]. When capturing 16 bits of data per frame, regardless of frame size, the size parameter is the number of 16 bit halfwords that will be captured. So far so good. When capturing 24 or 32 bits of data per frame the size parameter is the number of 32 bit words that will be captured, which results in 2*size number of 16 bit halfwords. This is not clearly expressed in the documentation of the function.

Setting the DMA channel to Circular will automatically restart the capture at the end of the buffer. To be able to work on stable data at the same time as the capturing is ongoing I use both the HAL_I2S_RxHalfCpltCallback() and HAL_I2S_RxCpltCallback(), each working on the part of the capturing buffer that is not currently being filled.
~ ~ ~
I thought I needed DMA double buffering as well and investigated how to get it to work. The HAL DMA module actually supports double buffering, but the HAL I2S module has no function to start a double buffer DMA transfer. I modified HAL_I2S_Receive_DMA() to work with double buffers and got it to work, but then I realized that HAL_I2S_RxHalfCpltCallback() can be used to accomplish the same thing and reverted my changes.

Yes, it is a PDM mono microphone which means you can't use the data as PCM samples directly. The data have to be filtered and decimated 64 times to become 16kHz PCM.
~ ~ ~ 

2019-05-12

Version Numbers

A version number is used to identify different versions, revisions or editions of an artifact. It is common to start to count from 0 and then continue with 1, 2, 3 and so on. No implied meaning should be assigned to any specific number. They are all equally good at identifying a version of the artifact.

A universal version number is open for branching at any existing version. There is always at least one free version number to pick. There are many ways to achieve this, but a simple variant only needs one extra rule; trailing zeros are free. You can always add ".0" to any version and it will still be the same version, e.g. 2, 2.0, 2.0.0, 2.0.0.0 are all the same version. Now when we want to branch, we add one or more zeros to designate the head of the branch. Then we increase the last zero with one for the first version on the branch.

   0 - 1 - 2 - 3 - 4 - 5 - (main line)
            \
      (2.0)  \- 2.1 - 2.2 - 2.3 - (1:st branch of v. 2)
              \
      (2.0.0)  \- 2.0.1 - 2.0.2 - 2.0.3 - (2:nd branch of v. 2)
                   \
      (2.0.1.0)     \- 2.0.1.1 - (1:st branch of v. 2.0.1)

It is easy to see that if you limit the number of dots in your version numbers, they are not universal.

In software it is common to track the version of different aspects of the artifact using dot notation and branches, e.g. 2.4, where the first number tracks the version of the interface and the second tracks the version of the implementation of that interface. Semantic Versioning[1] is one example of a versioning scheme that does this. Another case is when development takes place in multiple branches in parallel, for example when an old release is maintained at the same time as new functionality is added to a future release. Then you need a number to separate the branches.

   0.0           1.0 ---------> 2.0     3.0
      \           ^ \              \     ^
       \          |  \              \    |
        \- 0.1 - 0.2  \- 1.1 - 1.2   \- 2.1 - 2.2


Sometimes it is necessary to depart from the main line and branch off a separate version of the artifact. Semantic versioning lets you add an extra number to indicate that it is a patched version, e.g. 2.4.1. This has its limitations though. It is for example not possible to make changes to the interface as the interface version number is fixed. The insight that leads to a solution is that semantic version numbers always comes in pairs. Add a pair of numbers instead of just one, e.g. 2.4.0.1. The first is for interface changes and the second for implementation changes, just like the main line.

A few rules I try to follow for version numbers are...
  • All artifacts, insofar it is possible, should be able to tell you which version they are.
  • Change happens all the time. Don't assume you know beforehand when an artifact is free of errors and no longer will change.
  • Let the version numbers map to the branching strategy.
  • Use version numbers that are universal (see above).
  • Only change the version number when the artifact changes.
  • Don't use the version number for other things like review status, approval status or quality status.

The first version with a new major number, e.g. 1.0, 2.0 and so on, is not the end goal but the start of something new. If you treat it as the end goal you create a lot of practical problems for yourself. It is likely that 1.0 is less than perfect and you need to release a bugfix. You could call it 2.0 to replace 1.0 as the new perfect version with the high quality moniker .0, but it is better to reserve the first number to interface or other major changes where it serves a better purpose. You settle for 1.1. Now what do you call the pre-release version leading up to 2.0? Not 1.2 because that is the second bugfix release of 1.0. And you do want to give those internal pre-release artifacts a version number to be able to relate test results, reviews and other feedback to something traceable. Therefore it is better to start off the development with version 2.0 and tag the first few versions in the 2.x series as experimental pre-releases and then later decide which version of the 2.x series that happens to be good enough to qualify as the first usable version.

Release Candidate is also a useful concept, where you build an artifact that you hope will be good enough to later be released, but you don't know for sure until you have tested it. I am against rebuilding a good release candidate just to change the version number, therefore the RC designator will always be part of the version number, e.g. 2.0.12 which is the 12:th release candidate of version 2.0.

It is usually a mistake to use development versions and unstable pre-releases a la Semantic Versioning. Say you have decided to use something like Semantic Versioning to communicate with your users, but then say you won't follow it for 0.x versions. Then what's the point? It is likely that you will be stuck on 0.x for many years and your users will struggle to understand what will break in the next release. Use your versioning scheme right from the start, if only for the purpose of learning about its consequences. If you are too lazy to figure out if the interface has changed in the last iteration, just increase the major number to not give your users any unpleasant surprises. The urge not to waste numbers is a delusion. You will not run out of numbers and used numbers don't pollute.

To reiterate, a specific version number, say 2.0, is never a goal in itself. The purpose of the version number is to communicate what it supersedes and how extensive the changes are. The version number you eventually end up with is dictated by the changes you make and how often you release.

Another common misconception is when you insist that reviewed and approved documents should have the version number 1.0, 2.0, 3.0 and so on. The version number gets overloaded with meaning when its main purpose should be to uniquely identify the document and simplify traceability. Also, formal review is just one method among others to get feedback for improvement and if you link it to the version number it will suppress other types of feedback. One of the problems that this x.0 rule causes is that documents never get to be reviewed because the author thinks there still are too many loose ends for the document to be worthy a 1.0. Reviews are good for finding errors and spreading knowledge in the organization and a process that adds obstacles for reviews to take place is sub optimal. Another problem with x.0 is that you create artificial barriers to improve x.0 documents when you find errors because it costs too much effort to get it to the next y.0 version. There are very few organizations that can afford such rigor in the process. Most organizations I have worked with would fare much better with a process that prioritizes small and cheap iterative corrections to documents and only in rare cases require that documents should be reviewed and approved before the development can proceed. What I see in practice is that a 0.6 or a 2.1 never prevents the development to proceed, so why pretend to have a rigorous process when it only causes problems and discomfort? Track the quality status of a document with their own properties, e.g. as additional notes in the document changelog, so that both 0.7 and 2.3 can be reviewed as well as approved.

It is better to let the version number of documents follow the branching of the artifact they describe. This means you don't need to maintain a cross reference matrix for traceability. Documents with version 2.x contain changes that are valid for artifact version 2.y. If you need separate documentation for each minor version, then documents with version 2.1.x belong to artifact 2.1. You choose the granularity that is suitable for your circumstances. With documents in source control the most significant numbers in the version can be tracked globally by the branch. The least significant number can be assigned automatically, e.g. using the global Subversion repo revision or by counting the number of commits of the document in the branch.

Another reason to prefer the branch centric versioning scheme instead of the review centric is the way it changes how you think about your work. The artifact is never finished, only continuously improved.

A third superficial concern regarding version numbers is that users somehow are entitled to get all consecutive versions and that they will be confused if there is a jump in minor or major version between releases. If the user has version 2.3, they expect to get 2.4 next. To get 2.7 instead would cause severe psycological trauma. This is simply not true. Don't let this concern prevent you from tracking changes, may they be internal or external, with a universal version number. The persons that insist on this anti-pattern are typically also the ones that want to haphasardly jump to 10.0 just to catch up with competitors.

For practical advice on how to assign versions to software builds, see [2].

[1] https://semver.org/
[2] https://embeddedartistry.com/blog/2016/10/27/giving-you-build-a-version


2019-02-20

Wifi Remote Control

I have started to automate my home and thought that a dedicated remote control would be useful as an alternative to web pages and phone apps. Pushing a button on the remote performs an HTTP request that in turn controls some equipment at home.

An ESP8266 acts as wifi modem. Out of the box, it implements a serial protocol that is used to control its networking functions. An Arduino Pro Mini is master in the system and is responsible for monitoring the buttons and communicating with the ESP8266 to perform the desired action.

When the system is idle the ESP2866 is turned off and the Arduino is put to sleep. It is woken when a button push generates an interrupt on pin 2 or 3 (interrupt 0 and 1). An internal pull up resistor prevents the interrupt from being triggered when the button is inactive.

#include <avr/sleep.h>

#define GREEN_COLUMN 2
#define RED_COLUMN   3

#define FIRST_ROW   A3
#define SECOND_ROW  A2
#define THIRD_ROW   A1


int activeButton = 0;

void green_isr() { ... }

void red_isr() { ... }

void setup()
{
  pinMode(FIRST_ROW, OUTPUT); 
  pinMode(SECOND_ROW, OUTPUT);
  pinMode(THIRD_ROW, OUTPUT);

  digitalWrite(FIRST_ROW, 0);
  digitalWrite(SECOND_ROW, 0);
  digitalWrite(THIRD_ROW, 0);

  pinMode(GREEN_COLUMN, INPUT_PULLUP);
  pinMode(RED_COLUMN, INPUT_PULLUP);
 

  // Low power mode, no ADC
  ADCSRA &= ~(1<<ADEN);
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
 

}

void loop()
{
  if (activeButton == 0) {
    attachInterrupt(0, green_isr, LOW);
    attachInterrupt(1, red_isr, LOW);

    ...
    sleep_cpu();
    ...

  } 
  else {
    detachInterrupt(0);
    detachInterrupt(1);

    ...
  }
}

You may notice that I don't use CLI and SEI before SLEEP which is necessary to make sure I don't miss an interrupt. This would have been required with for example interrupt mode FALLING but with interrupt mode LOW new interrupts keep getting generated and it doesn't matter if one is missed. We need the button to be pushed for longer than one interrupt anyway as we need some time to figure out which button is pressed, which leads us to the next step.

The 6 LED buttons are connected to the Arduino in a 3x2 matrix to share the two interrupt enabled pins, one for each column. By setting the row outputs to 0 one by one, it is possible to figure out which button is currently active.

#define GREEN1 5
#define GREEN2 7
#define GREEN3 9
#define RED1   4
#define RED2   6
#define RED3   8
 


int greens[4] = {0, GREEN1, GREEN2, GREEN3};
int reds[4] = {0, RED1, RED2, RED3};
 

int scanRow = 0;

void green_isr() 
{
  if (scanRow != 0) {
    activeButton = greens[scanRow];
  }
}

void red_isr()
{
  if (scanRow != 0) {
    activeButton = reds[scanRow];
  }
}
 

void loop()
{
    ...
    sleep_cpu();

    digitalWrite(FIRST_ROW, 1);
    digitalWrite(SECOND_ROW, 1);
    digitalWrite(THIRD_ROW, 1);

    scanRow = 1;
    digitalWrite(FIRST_ROW, 0);
    digitalWrite(FIRST_ROW, 1);

    scanRow = 2;
    digitalWrite(SECOND_ROW, 0);
    digitalWrite(SECOND_ROW, 1);

    scanRow = 3;
    digitalWrite(THIRD_ROW, 0);
    digitalWrite(THIRD_ROW, 1);

    scanRow = 0;
    digitalWrite(FIRST_ROW, 0);
    digitalWrite(SECOND_ROW, 0);
    digitalWrite(THIRD_ROW, 0);
    ...
}

The interrupt will be triggered when the correct row output is toggled. This results in an assignment of activeButton and the program moves to the second phase where the ESP8266 is started and instructed to do an HTTP request.

The serial data received from ESP8266 is written to a small circular buffer. Whenever a carriage return ("\r") is detected the buffer is inspected to see if we have received a message that we are interested in. The following messages will trigger an action in the code.

ready
OK
GOT IP
CONNECT
CLOSED
FAIL
ERROR

Whenever "ready" or "OK" is received, a command will be sent to the ESP8266. A normal exchange looks something like this.

< ready\r
> AT+CIPMUX=1\r\n
< OK\r
> AT+CWJAP="SSID","password"\r\n
< GOT IP\r
< OK\r
> AT+CIPMUX=1\r\n
< OK\r
> AT+CIPSTART=0,"TCP","192.168.0.102",8081\r\n
< CONNECT\r
< OK\r
> AT+CIPSEND=0,31\r\n
< OK\r
> GET /on?group=Nere HTTP/1.1\r\n\r\n
< OK\r
< OK\r
> AT+CIPCLOSE\r\n
< CLOSED\r

Note the HTTP GET and the dual CRLF which executes the command on the web server. The second "OK" thereafter is part of the response from the web server.

The messages "CLOSED", "FAIL" and "ERROR" terminates the communication, turns off the ESP8266 and puts the Arduino back in sleep mode. There is also a timeout condition that does the same.

#define ESP32_CS 10
#define ESP32_RST 11
 

unsigned long start = 0;

byte next = 0;
#define BUFSZ 16
byte buffer[BUFSZ] = { 0 };

byte   ready[5] = { 'r', 'e', 'a', 'd', 'y' };
byte   gotip[6] = { 'G', 'O', 'T', ' ', 'I', 'P' };
byte      ok[2] = { 'O', 'K' };
byte connect[7] = { 'C', 'O', 'N', 'N', 'E', 'C', 'T' };
byte  closed[6] = { 'C', 'L', 'O', 'S', 'E', 'D' };
byte    fail[4] = { 'F', 'A', 'I', 'L' };
byte   error[5] = { 'E', 'R', 'R', 'O', 'R' };

byte cmd = 0;


void setup()
{
  Serial.begin(115200);


  pinMode(ESP32_CS, OUTPUT);
  pinMode(ESP32_RST, OUTPUT);
  ...

}

void allOff()
{
  digitalWrite(GREEN1, 1);
  digitalWrite(GREEN2, 1);
  digitalWrite(GREEN3, 1);
  digitalWrite(RED1, 1);
  digitalWrite(RED2, 1);
  digitalWrite(RED3, 1);
}

bool received(byte* msg, byte n)
{
  int i;
  for (i = 0; i < n; i++) {
    if (buffer[ ( BUFSZ + next - (n+1) + i ) % BUFSZ ] != msg[i]) {
      return 0;
    }
  }
  return 1;
}

bool activeIsGreen()
{
  return activeButton == GREEN1 || activeButton == GREEN2 || activeButton == GREEN3;
}


void loop()
{
  if (activeButton == 0) {
    ...
    digitalWrite(ESP32_CS, 0);
    digitalWrite(ESP32_RST, 0);

    ...
    start = millis();
  }
  else {

    ...
    digitalWrite(ESP32_CS, 1);
    digitalWrite(ESP32_RST, 1);

    digitalWrite(activeButton, 0);

    if (Serial.available())
    {
      buffer[ ( next++ ) % BUFSZ ] = Serial.read();
      if (buffer[ ( BUFSZ + next-1 ) % BUFSZ ] == '\r') {
        if (received(ok, sizeof(ok))) {
          if (cmd == 1) {
            Serial.print("AT+CWJAP=\"SSID\",\"password\"\r\n");
          }
          else if (cmd == 2) {
            cmd = 3;
            Serial.print("AT+CIPMUX=1\r\n");
          }
          else if (cmd == 3) {
            Serial.print("AT+CIPSTART=0,\"TCP\",\"192.168.0.102\",8081\r\n");
          }
          else if (cmd == 4) {
            cmd = 5;
            if (activeIsGreen()) {
              Serial.print("AT+CIPSEND=0,31\r\n");
            }
            else {
              Serial.print("AT+CIPSEND=0,32\r\n");
            }
          }
          else if (cmd == 5) {
            cmd = 6;
            if (activeIsGreen()) {
              Serial.print("GET /on?group=Nere HTTP/1.1\r\n\r\n");
            }
            else {
              Serial.print("GET /off?group=Nere HTTP/1.1\r\n\r\n");
            }
          }
          else if (cmd == 6) {
            cmd = 7;
            // Wait for HTTP GET 200 OK
          }
          else if (cmd == 7) {
            cmd = 8;
            Serial.print("AT+CIPCLOSE\r\n");
          }
        }
        else if (received(ready, sizeof(ready))) {
          cmd = 1;
          digitalWrite(activeIsGreen() ? RED1 : GREEN1, 0);
          Serial.print("AT+CIPMUX=1\r\n");
        }
        else if (received(gotip, sizeof(gotip))) {
          cmd = 2;
          digitalWrite(activeIsGreen() ? RED2 : GREEN2, 0);
          start = millis();
        }
        else if (received(connect, sizeof(connect))) {
          cmd = 4;
          digitalWrite(activeIsGreen() ? RED3 : GREEN3, 0);
          start = millis();
        }
        else if (received(closed, sizeof(closed)) ||
                 received(fail, sizeof(fail)) ||
                 received(error, sizeof(error))) {
          cmd = 0;
          allOff();
          activeButton = 0;

        }
      }
    }
    else if (millis() - start > 8000UL) {
      cmd = 0;
      allOff();
      activeButton = 0;

    }     
  }

}

Worth noting here is the timeout expression "millis() - start > 8000UL" which always works, especially when the number of millseconds rolls over to zero. A slightly different expression like "millis() > start + 8000UL" does not work as expected close to the rollover point.

As you can see, I have the same action on all green and red buttons, but this can easily be extended with a couple of if statements where the command is sent.

The three LED buttons of the opposite color of the one being pushed are used as a tiny progress bar.

To reduce the power consumption in idle mode I removed the power indicator LEDs from the Arduino and the ESP8266.

When debugging the serial communication I used my PC and the program socat to print a trace of the communication between ESP8266 and Arduino. I used two FTDI USB to Serial cables connected to the Arduino and an adapter board for the ESP8266 which besides the connector for FTDI also contains a 3.3V regulator and voltage dividers on RST, CS and RXT to allow it to be driven from the 5V Arduino. It is visible in the image, here connected directly to the Arduino.

$ socat -v /dev/ttyUSB0 /dev/ttyUSB1

It is highly recommended as a debugging tool.

Felfri produktutveckling

Skojade bara. När vi konstruerar och tillverkar produkter är det alltid möjligt att göra misstag. Konstruktionen fungerar inte som det var t...