STM32L011 serial, analog and low power examples

Here are some further examples for the STM32L011 Nucleo board. The first is a simple interrupt driven serial communications demonstration. It makes use of the built in serial interface in the Nucleo board and so no additional hardware is required for your host PC to exchange data with the MCU.
The second example builds on the first. It reads ADC channel 0 and outputs the result to the host PC over the serial port.
The third example is a little more elaborate and is aimed at those wishing to develop a very long life battery application. It uses the built-in real-time-clock (RTC) to wake the MCU up about once every second. While awake, the MCU reads the ADC, outputs data to the host PC over the serial port and blinks the LED on the Nucleo board. Current consumption is minimized during sleep by turning off
peripherals, reducing AHB clock speed and putting the Cortex M0+ into a deep sleep. I found it difficult to measure the current flow during this time but it seems to be around 3 to 4 microamps. One thing I did notice is that the CPU will not go to sleep with interrupts disabled – a good way of preventing you from shooting yourself in the foot.

Links to code.

Serial communications over the nucleo serial interface to the host PC
Read the ADC and output the result over the serial port to the host PC
Minimize power consumption and wake using the internal RTC every second

Blinky on the STM32L011 Nucleo

The STM32L011 Nucleo board is an mbed enabled development kit featuring the STM32Lo11K4 MCU. This is a Cortex M0+ device with some interesting features such as hardware accelerated AES encryption (Correction: AES not present on the STM32L011) and a “Firewall” which seems to prevent firmware and other data being offloaded from the device. The board is meant to be programmed with a large IDE or using the mbed online development environment. I was curious to see could it be done from the command line with all code contained in a single directory. Normally I would tackle this with OpenOCD and GDB but this is such a new board that OpenOCD/GDB can’t interface with it properly (yet). All is not lost however! The mbed feature of this board means that it appears as a removable storage device to the operating system. Programming the board is simply a matter of dropping a correctly formatted executable (binary) file into this removable storage device. The utility objcopy can be used to prepare the executable file.
Assuming you have compiled your code to an “elf” file called main.elf, you can generate a binary file as follows:
objcopy -O binary main.elf main.bin
The “bin” file can be dropped on the Nucleo and should run fine.
Code for blinky is available here

Analog pass-through using the MSP432 Launchpad and Energia

This example combines two previous posts regarding the TLV5618 DAC and timer interrupts with the MSP432 Launchpad.  The project reads the A0 analog input at 22050Hz (half CD quality) and outputs the reading  via the TLV5618 Channel A DAC.  Higher sample rates are possible but an error creeps in due to overhead handling the DAC.  This example also corrects an error in the previous post about timer interrupts which assumed that the timer base frequency was 16MHz – it seems to be 12MHz in fact.  The output for a 1kHz sinewave is shown below.

MSP432_PassThrough

The code is here.

// This program does an analog pass through between Analog input A0 (P5.5)
// on the MSP432 Launchpad and a TLV5618 SPI DAC.
// The green LED is used to indicate that the program is still alive :o)
// Further details at ioprog.com

#include <SPI.h>
#include <msp432.h>
#include <driverlib/timer_a.h>
#include <driverlib/interrupt.h>
#define SS_PIN 17
void setup()
{ 
  // put your setup code here, to run once:
  pinMode(SS_PIN,OUTPUT);
  pinMode(15,OUTPUT); // MOSI
  pinMode(7,OUTPUT); // SCK
  SPI.setModule(EUSCI_B0_MODULE); // Select correct SPI interface
  SPI.setDataMode(SPI_MODE2);
  SPI.setBitOrder(MSBFIRST);   
  SPI.setClockDivider(SPI_CLOCK_DIV2); // DIV2 = 8MHz, DIV4 = 4MHz, DIV8 = 2MHz etc. (from measurement)
  UCB0CTLW0 &= ~(BIT0); // Take SPI out of reset   
  pinMode(BLUE_LED,OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  setupTimer(1000000/22050); // Set sample frequency to 22050 : Period=(1000000 microseconds / desired sample rate)
  analogReadResolution(12); // set the ADC resolution to 12 bits (match the DAC)
}


void loop()
{ // Nothing happens here: it is all interrupt driven - see OnTimer for main processing code.
}
void OnTimer()
{
 
  writeDACA(analogRead(A0)); // write out what is coming in on A0
  // The following simply flashes the green LED to show the program is still running
  static int Count = 0;
  static int state = 0;
  Count++;
  if (Count > 22050)
  {
    Count = 0;
    digitalWrite(GREEN_LED,state);
    if (state)
      state = 0;
    else
      state = 1;
  }

}

void writeDACA(int Value)
{
  Value=Value & 0xfff;
  Value = Value | 0xc000; // Write DAC A
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (Value >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( Value  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High
}
void writeDACB(int Value)
{
  Value=Value & 0xfff;
  Value = Value | 0x4000; // Write DAC B value to buffer and update
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (Value >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( Value  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High
}
void writeDACs(int AValue, int BValue)
{
  
  // Write both DACs and update outputs simultaneously.
  BValue=BValue & 0xfff;
  BValue = BValue | 0x5000; // Write DAC B value to buffer 
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (BValue >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( BValue  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High
  digitalWrite(SS_PIN,LOW); // Drive CS low
  AValue=AValue & 0xfff;
  AValue = AValue | 0xc000; // Write DAC A and update B
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (AValue >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( AValue  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High

}
volatile uint32_t millisecondCounter=0;
int count = 0;
volatile int state = HIGH;
volatile int flag = HIGH;
void setupTimer(unsigned Period)
{
 
  // Configuration word
  // Bits 15-10: Unused
  // Bits 9-8: Clock source select: set to SMCLK (12MHz)
  // Bits 7-6: Input divider: set to 4
  // Bits 5-4: Mode control: Count up to TACCRO and reset
  // Bit 3: Unused
  // Bits 2: TACLR : set to initially clear timer system
  // Bit 1: Enable interrupts from TA0
  // Bit 0: Interrupt (pending) flag : set to zero (initially)
  TA3CTL=0b0000001010010110;
  TA3CCR0=Period*3; // Set TACCR0 = Period (3MHz clock)
  TA3CCTL0=BIT4; // Enable interrupts when TAR = TACCR0
// The following places the address of our interrupt service routine in the RAM based interrupt vector table
// The vector number is 14 + 16  = 30 which is represented by the symbol INT_TA3_0
  Interrupt_registerInterrupt(INT_TA3_0,timerA3ISR); 
 
  // according to the datasheet Table 6-12 timer A3 is on ISR 14
  NVIC_ISER0 = (1<<14); // enable this interrupt in the NVIC
 
}
 
void timerA3ISR(void)
{
  TA3CTL &= ~1;         // Acknowledge the interrupt
  TA3CCTL0 &= ~1;       // Acknowledge the interrupt
  NVIC_ICPR0 = (1<<14); // clear interrupt pending flag in NVIC
  millisecondCounter++;
  OnTimer();
}

Speedier DACs with the MSP432 Launchpad

In an earlier post I showed how a TLV5618 dual DAC could be interfaced to an MSP432 Launchpad.  This worked OK but it was a bit slow.  The code below drives the DAC’s much faster as it uses direct register writes to the SPI interface.  The Energia SPI library is used to configure the interface. A single DAC channel can be driven at just over 100kHz; while a pair of channels can be updated at approx 48kHz – good enough for decent audio.

 

// This program drives a TLV5618 dual DAC
// It outputs sawtooth waveforms on both channels
// Output update frequency (both channels) is about 48kHz which
// is sufficient for decent quality audio.
// A single channel can be driven at more than 100kHz.
#include <SPI.h>
#include <msp432.h>
#define SS_PIN 17
void setup()
{
  // put your setup code here, to run once:
  pinMode(SS_PIN,OUTPUT);
  pinMode(15,OUTPUT); // MOSI
  pinMode(7,OUTPUT); // SCK
  SPI.setModule(EUSCI_B0_MODULE); // Select correct SPI interface
  SPI.setDataMode(SPI_MODE2);
  SPI.setBitOrder(MSBFIRST);
  SPI.setClockDivider(SPI_CLOCK_DIV2); // DIV2 = 8MHz, DIV4 = 4MHz, DIV8 = 2MHz etc. (from measurement)
  UCB0CTLW0 &= ~(BIT0); // Take SPI out of reset
}
void writeDACA(int Value)
{
  Value=Value & 0xfff;
  Value = Value | 0xc000; // Write DAC A
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (Value >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( Value  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High
}
void writeDACB(int Value)
{
  Value=Value & 0xfff;
  Value = Value | 0x4000; // Write DAC B value to buffer and update
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (Value >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( Value  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High
}
void writeDACs(int AValue, int BValue)
{

  // Write both DACs and update outputs simultaneously.
  BValue=BValue & 0xfff;
  BValue = BValue | 0x5000; // Write DAC B value to buffer
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (BValue >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( BValue  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High
  digitalWrite(SS_PIN,LOW); // Drive CS low
  AValue=AValue & 0xfff;
  AValue = AValue | 0xc000; // Write DAC A and update B
  digitalWrite(SS_PIN,LOW); // Drive CS low
  UCB0TXBUF=( (AValue >> 8) & 0xff); // write high byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  UCB0TXBUF=  ( AValue  & 0xff); // write low byte
  while(UCB0STATW & BIT0); // wait while SPI busy
  digitalWrite(SS_PIN,HIGH); // Drive CS High

}
int i=0;
void loop()
{
  // put your main code here, to run repeatedly:
  while(1)
  {
    writeDACs(i,0xfff-i);
    i++;
    if (i > 0xfff)
      i = 0;
  }
}

The output is shown below

dualdacsawtooth

Periodic interrupts with Energia and the MSP432 Launchpad

Correction on 25th July 2016: Base clock for timer was wrong in original – now fixed.

In a previous post I showed how a periodic interrupt could be generated with the MSP430 Launchpad.  This post shows how it can be done using Energia and the MSP432 (ARM Cortex M4F) Launchpad.  The example uses Timer A3 to generate a periodic (1 kHz) interrupt.  There appears to be some interplay between this and the built-in delay routine however the delayMicroseconds function seems to work fine.  A global counter variable is updated at each interrupt so software can use this to perform additional time delays etc.

Some use is made of lower level library functions that are included with the Energia environment.  This example was compiled with Energia 0101E0017.

/* Periodic interrupt example for the MSP432 development board and the energia development environment */
/* The function OnTimer is called every millisecond and flashes the green LED every second
   The main loop flashes the blue LED every second
   Timer A3 is used to generate the interrupt
*/
 
#include <msp432.h>;
#include <driverlib/timer_a.h>
#include <driverlib/interrupt.h>
 
/*
General notes: serial comms uses eusci_a
PWM uses Timer A0 (at least I think so)
The built-in delay function appears not to work if it expires at the same time as a timer interrupt
The delayMicroseconds appears not to be so affected (so far)
*/
volatile uint32_t millisecondCounter=0;
int count = 0;
volatile int state = HIGH;
volatile int flag = HIGH;
 
void setup()
{
  Serial.begin(9600);
 
  pinMode(BLUE_LED,OUTPUT);
  pinMode(GREEN_LED, OUTPUT);
  setupTimer(1000); // set timer period to 1000 micro seconds
}
 
void OnTimer()
{
 
  static int Count = 0;
  static int state = 0;
  Count++;
  if (Count > 1000)
  {
    Count = 0;
    digitalWrite(GREEN_LED,state);
    if (state)
      state = 0;
    else
      state = 1;
  }
}
void loop()
{
 
  digitalWrite(BLUE_LED,HIGH);
  delayMicroseconds(500000);
  digitalWrite(BLUE_LED,LOW);
  delayMicroseconds(500000);
  Serial.println(millisecondCounter);
}
 
void setupTimer(unsigned Period)
{
 
  // Configuration word
  // Bits 15-10: Unused
  // Bits 9-8: Clock source select: set to SMCLK (12MHz)
  // Bits 7-6: Input divider: set to 4
  // Bits 5-4: Mode control: Count up to TACCRO and reset
  // Bit 3: Unused
  // Bits 2: TACLR : set to initially clear timer system
  // Bit 1: Enable interrupts from TA0
  // Bit 0: Interrupt (pending) flag : set to zero (initially)
  TA3CTL=0b0000001010010110;
  TA3CCR0=Period*3; // Set TACCR0 = Period (3MHz clock)
  TA3CCTL0=BIT4; // Enable interrupts when TAR = TACCR0
// The following places the address of our interrupt service routine in the RAM based interrupt vector table
// The vector number is 14 + 16  = 30 which is represented by the symbol INT_TA3_0
  Interrupt_registerInterrupt(INT_TA3_0,timerA3ISR); 
 
  // according to the datasheet Table 6-12 timer A3 is on ISR 14
  NVIC_ISER0 = (1<<14); // enable this interrupt in the NVIC
 
}
 
void timerA3ISR(void)
{
  TA3CTL &= ~1;         // Acknowledge the interrupt
  TA3CCTL0 &= ~1;       // Acknowledge the interrupt
  NVIC_ICPR0 = (1<<14); // clear interrupt pending flag in NVIC
  millisecondCounter++;
  OnTimer();
}

 

Adding a DAC to the MSP432 Launchpad

The MSP432 Launchpad features quite a powerful low energy processor (the MSP432P401R).  This should make it ideal for a little DSP but for one problem: there is no DAC.  I decided that I should remedy this by adding an SPI DAC in the form of a TLV5618A. This 12-bit IC has two DAC outputs and can be driven at a speed sufficient for some pretty decent audio.  Wiring is as shown below:

wiring

Code was developed in Energia – an experience that left me wondering about the completeness and documentation of the MSP432 port to this environment.

 

#include <SPI.h>
#include <msp432.h>
#define SS_PIN 17
void setup()
{
  // put your setup code here, to run once:
  pinMode(SS_PIN,OUTPUT);
  pinMode(15,OUTPUT); // MOSI
  pinMode(7,OUTPUT); // SCK
  SPI.setModule(EUSCI_B0_MODULE);
  SPI.setDataMode(SPI_MODE1);
  SPI.setBitOrder(MSBFIRST);   //
  SPI.setClockDivider(SPI_CLOCK_DIV2); // DIV2 = 8MHz, DIV4 = 4MHz, DIV8 = 2MHz etc. (from measurement)
}
void writeDACA(int Value)
{
  Value=Value & 0xfff;
  Value = Value | 0xc000; // Write DAC A
  digitalWrite(SS_PIN,LOW);
  SPI.transfer( (Value >> 8) & 0xff);
  SPI.transfer( Value  & 0xff);
  digitalWrite(SS_PIN,HIGH);
}
int i=0;
void loop()
{
  // put your main code here, to run repeatedly:
  while(1)
  {
    writeDACA(i);
    i++;
    if (i > 0xfff)
      i = 0;
  }

}

This produces the following output waveform:

waveform

This is a little noisy but more to the point : SLOW!  The pair of SPI writes are taking more than 70 microseconds which makes me wonder about the efficiency of the underlying libraries.  I hope to remedy this in a later post.

Disco Fever!

I have been working on a spectrum analyzer based on the STM32F303 Nucleo.  The analyzer uses a 16 LED WS2812B led ring to output spectrum data.  There were lots of interesting things on the way.

First of all, it seems that there are times when you should use gcc as your linker rather than ld.  It would appear that there are lots of options and switches built into gcc which it quietly passes on to the linker.  This tripped me up when specifying floating point options and specifying the FPU type.  After LOTS of fiddling and searching of mailing lists the problem was solved and the Makefile contains the necessary switches for the FPU and floating point libraries to all work together.

During the course of this project I tried to use the CMSIS fft functions – after all, they are optimized for ARM by ARM.  Sadly this didn’t work out because the 64k for flash memory in the STM32F303 isn’t big enough apparently.  I reverted to an fft function I’ve been using for years now on other MCU’s.  Its a lot smaller and executes quickly enough : the signal processing time for 512 samples is about 8.5ms.

Finally, the last hurdle to be overcome involved the WS2812B’s.  In a previous post I described the use of the STM32F042 with the WS2812 ring.  This MCU runs at 48MHz making it easy to generate a 3MHz SPI bus frequency : just divide by 16.  The STM32F303 however runs at 64MHz and only certain divisors are available (8/16/32 etc).  Initially I tried running the SPI at 2MHz and this worked for one or two LED’s however it did not work well for 16.  4MHz was then tried and its seems its ok although there is some flicker with certain combinations of LED colours.

The circuit.

Wiring

 

The video

The video below shows the STM32F303 performing a live FFT and controlling a WS2812 LED ring. The LED ring is driven as follows:
At the 6 o’clock position is low frequency. As you move anti-clockwise around the ring, each LED represents a higher frequency component.  Each LED represents a step of 128Hz so 2048Hz is the maximum frequency shown.  The system samples at 32768Hz however most of the music energy is at the lower end of the spectrum.  The LED ring is showing a “zoomed in” version of the spectrum

The LED colours change as follows (16 steps):
Blue : low magnitude
Yellow-Green : medium magnitude
Red : large magnitude

 

The music is from the Free Music Archive.  The track is Pianoman Sofa by Lobo Loco.

 

 

The code

The code is available on github at the link below.

https://github.com/fduignan/DiscoFever.git