Posted on Leave a comment

Arduino: Transmit 10-bit ADC Reading Through Serial

So the standard Serial.write() or Serial.print in Arduino land is 8-bit All this really means is we need to send one byte ( bits) at a time. If we try to send 10 bits, then bad things happen.

On top of that, it’s better to setup a secret word for start and another one for end. This is the same thing that naked couples do when they get out the power tools. They need safe words. Maybe we all do. Anyhow, by telling our receiver not to bother receiving until it hears the start word and not to finish until it hears the end word, we can be much more confident that the data we are receiving is legit.

A good chunk of this code was taken from various sources from this article. https://forum.arduino.cc/t/serial-input-basics-updated/382007/3

Sending

// Uses ATmega4808  (Thunder Magnum dev board)
#include <Arduino.h>

#define LEDPIN PIN_PA7
#define LEDPIN2 PIN_PA6

int sensorValue;
int newsensorValue;

#define MONITOR_SPEED 115200
void setup()
{
  // put your setup code here, to run once:
  pinMode(LEDPIN, OUTPUT);

  // Setup serial with 2nd ucontroller
  Serial.begin(MONITOR_SPEED); // PA0 is tx and PA1 is rx

  // Setup serial with computer
  Serial1.begin(MONITOR_SPEED); // PC0 is tx.   PC1 is rx.
}

void loop()
{

  // 10 bit ADC read
  newsensorValue = analogRead(PIN_PD0);

  // throw away the lowest 8 bytes to send just the biggest 2 bits
  int Bigbyte = (newsensorValue >> 8);

  // AND 0b1111111 with ADC read to keep only the lowest 8 bits
  int Littlebyte = newsensorValue & 0xFF;

  // talk to computer com port
  Serial1.print("sending: ");

  // combine back together and talk to computer ( sanity check )
  Serial1.println((Bigbyte << 8) + Littlebyte);

  byte startMarker = 0x3C;
  byte endMarker = 0x3E;

  // Talk to 2nd microcontroller
  Serial.write(startMarker);
  Serial.write(Bigbyte);
  Serial.write(Littlebyte);
  Serial.write(endMarker);

  delay(1000);
}

Receive

// Uses ATmega4808  (Thunder Magnum dev board)
#include <Arduino.h>

#define LEDPIN PIN_PA7
#define LEDPIN2 PIN_PA6

void showNewData();
void recvBytesWithStartEndMarkers();

const byte numBytes = 32;
byte receivedBytes[numBytes];
byte numReceived = 0;

boolean newData = false;

#define MONITOR_SPEED 115200
void setup()
{
  // put your setup code here, to run once:
  pinMode(LEDPIN, OUTPUT);

  Serial.begin(MONITOR_SPEED);  // PA0 is tx and PA1 is rx
  Serial1.begin(MONITOR_SPEED); // PC0 is tx.   PC1 is rx.


  digitalWriteFast(LEDPIN, LOW);
  delay(1000);
}

void loop()
{
  recvBytesWithStartEndMarkers();
  showNewData();
}

void recvBytesWithStartEndMarkers()
{
  static boolean recvInProgress = false;
  static byte ndx = 0;
  byte startMarker = 0x3C;
  byte endMarker = 0x3E;
  byte rb;

  while (Serial.available() > 0 && newData == false)
  {
    rb = Serial.read();
 
    if (recvInProgress == true)
    {
      if (rb != endMarker)
      {
        receivedBytes[ndx] = rb;
        ndx++;
        if (ndx >= numBytes)
        {
          ndx = numBytes - 1;
        }
      }
      else
      {
        receivedBytes[ndx] = '\0'; // terminate the string
        recvInProgress = false;
        numReceived = ndx; // save the number for use when printing
        ndx = 0;
        newData = true;
      }
    }

    else if (rb == startMarker)
    {
      recvInProgress = true;
    }
  }
}

void showNewData()
{
  if (newData == true)
  {
    Serial1.println("Received: ");
 
    int receivedandformatted = (receivedBytes[0] << 8) + receivedBytes[1];
    Serial1.println(receivedandformatted);

    Serial1.println();
    newData = false;
  }
Posted on Leave a comment

ESP32TimerInterrupt Simple Example

Here’s a stripped down example of Khoi Hoang’s ESP32TimerInterrupt library. The library claims to run up to 16 compares (or alarms) on one, single timer. It seems to work well although I’ve only tested 3 timers thus far. I plan to use it on my Idiot Christmas Light Controller controlling triacs.

include <Arduino.h>
 /
   TimerInterruptTest.ino
   For ESP32 boards
   Written by Khoi Hoang
 Built by Khoi Hoang https://github.com/khoih-prog/ESP32TimerInterrupt
   Licensed under MIT license
 This is the most basic example using Khoi Hoang's ESP32TimerInterrupt library.  
   The library works well, but I found myself wanting an ultra basic version to understand the functionality
 */
 define TIMER_INTERRUPT_DEBUG 0
 define TIMERINTERRUPT_LOGLEVEL 0
 include "ESP32TimerInterrupt.h"
 // Change these to whatever GPIO you'd like to toggle
 define LED0 25 // GPIO 25
 define LED1 27 // GPIO 27
 define LED2 33 // GPIO 33
 // The lenght of each timer in milliseconds
 define TIMER0_INTERVAL_MS 100
 define TIMER1_INTERVAL_MS 3000
 define TIMER2_INTERVAL_MS 400
 void IRAM_ATTR TimerHandler0(void)
 {
   static bool toggle0 = false;
 //timer interrupt toggles pin LED0
   digitalWrite(LED0, toggle0);
   toggle0 = !toggle0;
 }
 void IRAM_ATTR TimerHandler1(void)
 {
   static bool toggle1 = false;
 //timer interrupt toggles LED1
   digitalWrite(LED1, toggle1);
   toggle1 = !toggle1;
 }
 void IRAM_ATTR TimerHandler2(void)
 {
   static bool toggle2 = false;
 //timer interrupt toggles LED2
   digitalWrite(LED2, toggle2);
   toggle2 = !toggle2;
 }
 // Init ESP32 timer 0,1, and 2
 ESP32Timer ITimer0(0);
 ESP32Timer ITimer1(1);
 ESP32Timer ITimer2(2);
 void setup()
 {
 pinMode(LED0, OUTPUT);
   pinMode(LED1, OUTPUT);
   pinMode(LED2, OUTPUT);
 Serial.begin(115200);
   while (!Serial)
     ;
 delay(100);
 Serial.print(F("\nStarting TimerInterruptTest on "));
   Serial.println(ARDUINO_BOARD);
 Serial.println(ESP32_TIMER_INTERRUPT_VERSION);
 Serial.print(F("CPU Frequency = "));
   Serial.print(F_CPU / 1000000);
   Serial.println(F(" MHz"));
 // Using ESP32  => 80 / 160 / 240MHz CPU clock ,
   // For 64-bit timer counter
   // For 16-bit timer prescaler up to 1024
 // Setup Timer0
   // Interval in microsecs
   if (ITimer0.attachInterruptInterval(TIMER0_INTERVAL_MS * 1000, TimerHandler0))
   {
     Serial.print(F("Starting  ITimer0 OK, millis() = "));
     Serial.println(millis());
   }
   else
     Serial.println(F("Can't set ITimer0. Select another freq. or timer"));
 // Setup Timer1
   // Interval in microsecs
   if (ITimer1.attachInterruptInterval(TIMER1_INTERVAL_MS * 1000, TimerHandler1))
   {
     Serial.print(F("Starting  ITimer1 OK, millis() = "));
     Serial.println(millis());
   }
   else
     Serial.println(F("Can't set ITimer1. Select another freq. or timer"));
 // Setup Timer2
   // Interval in microsecs
   if (ITimer2.attachInterruptInterval(TIMER2_INTERVAL_MS * 1000, TimerHandler2))
   {
     Serial.print(F("Starting  ITimer2 OK, millis() = "));
     Serial.println(millis());
   }
   else
     Serial.println(F("Can't set ITimer1. Select another freq. or timer"));
 Serial.flush();
 }
 void loop()
 {
 }
Posted on Leave a comment

MIDI Test Code on Arduino and STM32 Blue Pill

This code turns the onboard PC13 LED of the STM32 Blue Pill on when a MIDI NoteOn message is received and turns the LED off when a MIDI Noteoff message is received. It’s my preferred way to confirm that a MIDI circuit is working. It relies on the Arduino MIDI Library.

#include <MIDI.h>

#define LED PC13 // LED pin on Arduino Uno

MIDI_CREATE_DEFAULT_INSTANCE();

void doSomeStuffWithNoteOn(byte channel, byte pitch, byte velocity);
void NoteOff(byte channel, byte note, byte velocity);

void setup()
{
  pinMode(LED, OUTPUT);
  MIDI.begin();
  MIDI.setHandleNoteOn(doSomeStuffWithNoteOn);
  MIDI.setHandleNoteOff(NoteOff);
}

void loop()
{

  MIDI.read();
}

void doSomeStuffWithNoteOn(byte channel, byte pitch, byte velocity)
{
  // note on code goes here
  digitalWrite(PC13, LOW);
}

void NoteOff(byte channel, byte note, byte velocity)
{
  // note off code goes here
  digitalWrite(PC13, HIGH);
}
Posted on Leave a comment

Smooth Fading LEDs With STM32 Blue Pill on Arduino Platform

The smooth fading of LEDs is possible, but it’s best to have 16 bits to work with and ditch the linear world.

// My code
dutyCycle = pow(1.03, time_increment);

// Math equivalent
y = 1.03^x

Above is the most interesting piece of code today. dutyCycle for this code is a number from 0 – 65535. (16-bit resolution is (2^16 – 1 = 65535).

I have the time_increment set to 10ms. So, basically, we are going to increase the dutycyle by 3% every 10ms. You’ll see below that I settled on 375 steps with each time taking 10ms. By using a time_increment that is set to increase every X milliseconds, it’s easy to control the speed of the fading.

WHY 1.03?

To get the 1.03 value, I worked backward and shot for a ballpark rating of 400 individual steps of brightness. Why? No idea.
After some playing, I figured out that 1.03^375 = 65156. 375 steps works for me. The big rule is we can’t exceed 65535 or the microcontroller will rollover.
To put it another way, 65535 + 1 = 0 in digital land. It’s no different than any other cyclical process. In military time, if you add another minute to 23:59, you get 00:00.
For our purposes, we didn’t want rollover. We want to smoothly fade the LED up to it’s maximum brightness and then back down.

WHY NOT LINEAR?

Much like our ears, eyes have evolved to handle an atrocious level of dynamic range. Such systems play in the world of logs and exponentials (same thing after you flip the graph axis around). It’s one of those scientific marvels that we can make sense of a duty cycle of 65535 and still tell the difference between 500 and 600. In a linear system, we’d have to pick one extreme or the other.

MAKE IT BETTER

It turned out that the dutyCycle code above spent a ton of time in the bottom region. After 50 increments of time, for example, the duty cycle would be 4  (1.03^50 = approx 4). That’s “off” in terms of LED brightness to my eyes. There’s a solution.

Remember that 1.03^375 = 65156. Technically, this wastes a bit of our headroom. We have 65535 to work with and our problem is we are spending too much time down around 0. The lucky fix is to use an offset much like you may have learned (and forgotten) in Alegebra class.

y = mx + b

This is a linear function, but the idea of “b” is the same.  “b” lets us shift our function up our down. We want to shift the whole thing up.   To get our “b” I took 65535 – 65156 to get 379. Let’s just say 375 to be conservative.

// New Code
dutyCycle = pow(1.03, time_increment) + 375;

// Math equivalent
y = 1.03^x + 375.

Now the LEDs do what they are supposed to.  There is not excessive time spent with the LEDs “off”.    So, at time = 0, we end up with a duty cycle of 376. (1 + 375). 376/65535 * 100 = 0.5% actual duty cycle. In reality, this is a brightness of zero. Solved!

 

 

 

 

 

#include <Arduino.h>

#define PWM1_pin PA8
#define PWM2_pin PA9
#define PWM3_pin PA10

#define INCREMENTER 100

void CycleA(int pin, int pwm_limit)
{
	int run = 1;
    long timeA = 0;
    int countUp = 1;
    int dutyCycle = 1;
    int time_increment = 0;
    

  while (run == 1)
  {
     
    long currentTime = millis();


    if (pin == 1)
    {
      analogWrite(PWM1_pin, dutyCycle);
    }
    if (pin == 2)
    {
      analogWrite(PWM2_pin, dutyCycle);
    }
        if (pin == 3)
    {
      analogWrite(PWM3_pin, dutyCycle);
    }

    if (currentTime - timeA > 10)
    {
      dutyCycle = pow(1.03, time_increment);
      timeA = currentTime;

      if (dutyCycle > pwm_limit)
      {
        delay(2750);
        //dutyCycle = 1;
        //time_increment = 0;
        countUp = 0;
      }

      if (dutyCycle < 10 && countUp == 0)
      {
        countUp = 1;
        run = 0;
      }

      if (countUp == 1)
      {
        time_increment++;
      }
      else
      {
        time_increment--;
      }
    }
  }
}

void setup()
{

// All PWM pins need to be set as output
// Note:  I've seen multiple examples in which the PWM pins were set to "PWM" instead of "OUTPUT".  I have no explanation for that other than maybe they are using 
// the other guy's STM32-to-Arduino library.
  pinMode(PWM1_pin, OUTPUT);
  pinMode(PWM2_pin, OUTPUT);
  pinMode(PWM3_pin, OUTPUT);
  
  // Change the analogWrite function to operate at 16-bit.  It maxes out at 65535 instead of the 255 of 8-bit.
  analogWriteResolution(16);

}

void loop()
{
 // CycleA(  pin_number,  duty_cycle_limit).
 // This function allows selecting a pin number and sets the duty cycle limit on a scale of 0-65535.
 //  You'll see below that the 10000 for LED Color #1 is there because this was an LED strip that was exceedingly bright.
 //  Basically, the duty_cycle_limit is a brightness limiter.
 
 // I didn't take the time to pass the pin define (PA8, PA9, and PA10) through a function.
 
 // Run LED Sequence for LED Color #1
  CycleA(1, 10000);
  
  // Run LED Sequence for LED Color #2
  CycleA(2, 65000);
  
  // Red LED Sequence for LED Color #3.
  CycleA(3, 65000);
}


Posted on Leave a comment

Microchip 25LC256 EEPROM on STM32 Blue Pill and Arduino

For the big, bad synth project, I needed an EEPROM.  I selected the Microchip 25LC256 from Digikey simply because it had excess capacity (256k) and was thru-hole.   (I always recommend this approach when jumping into a new arena of unpolished prototyping).  I had never worked with external EEPROM before.   It turns out that EEPROM is fairly straight forward.  You pick an address and you write to it.  It’s not all that different from how a file cabinet works.  When you run out of room in one drawer, you move on to the next drawer.  Sometimes you want to put stuff in separate drawers even if they aren’t full.  These”drawers” are known as pages.

The page size in the Microchip 25LC256 is 64 bytes.   At the moment, each preset for my synth requires about 40 bytes, so this means I’m gonna waste 24 bytes per page in the interest of staying organized.  There are many other chips in the 25LCxxx series of varying sizes.  The smaller capacity EEPROM chips use smaller page sizes, which means I’ll need to dedicate multiple pages to each preset.   You’ll see a function to write a 64 byte array to the EEPROM all at once below.  This is significantly faster than writing one byte at a time as there is a 5ms penalty after finishing up the write process no matter if you are writing 1 byte or 64 bytes.

Of course, it took longer than I hoped to effectively use the Microchip 25LC256 EEPROM.  What else is new!    You’ll certainly want to adapt these functions below to suit your needs, but this code does work on an STM32 Blue Pill in PlatformIO using the Arduino framework.  I suspect the critical piece I was missing was slowing down the SPI clock. You’ll see this code uses a clock divider of 16.  According to the datasheet, the larger the Vcc, the faster clock you can get away with.

I’m moving these functions into a dedicated Microchip 25LCxx library which I’ll give away when its ready.  For now, here’s the working code in a rough format.



#include "Arduino.h"

#include <SPI.h>

byte pin_SS2 = PB12;

HardwareSerial Serial3(USART3); // PB11 (RX3)  PB10   (TX3)

void EEPROMsetup()
{
  //SPI_2.begin();
  pinMode(pin_SS2, OUTPUT);
  digitalWrite(pin_SS2, HIGH);
}

byte EEPROMread(uint16_t address)
{
  byte read_buffer = 0;
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000011);         // Set EEPROM to read mode.
  SPI.transfer16(address);          // Send the 16-bit address
  read_buffer = SPI.transfer(0xFF); // A dummy byte is sent to read the buffer.
  digitalWrite(pin_SS2, HIGH);

  Serial3.print("Reading:  ");
  Serial3.println(read_buffer);
  Serial3.print("Address:  ");
  Serial3.println(address);
  Serial3.println("");

  return read_buffer;
}

void EEPROMwriteByte(uint16_t address, byte value)
{
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000110); // WREN write enable latch.
  digitalWrite(pin_SS2, HIGH);

  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000010); // WRITE INSTRUCTION
  SPI.transfer16(address);
  SPI.transfer(value);
  digitalWrite(pin_SS2, HIGH);
  delay(5); // Taken from datasheet.  This slows down EEPROMWriteByte a bit, but if a second write is started before the
  // first can be completed, it will ignore the second one.  That's bad.   While the write is in progress, the STATUS register
  // may be read to check the status of the Write-in-process (WIP) bit (Figure 2-6).  I haven't tried that one yet.

  Serial3.println("Write complete");
}

byte EEPROMreadStatus()
{
  byte read_buffer;
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000101); // Red STATUS register
  read_buffer = SPI.transfer(0xFF); // A dummy byte to read the buffer.
  digitalWrite(pin_SS2, HIGH);
  return read_buffer;
  // Serial3.print("Status:  ");
  // Serial3.println(read_buffer);
  // Serial3.println("");
}

void EEPROMWritePage(uint16_t page_num, byte *save_this_array)
{
  // Pages are 64 bytes.  I'll give each preset a page for simplicity.

  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000110); // WREN write enable latch.
  digitalWrite(pin_SS2, HIGH);
  digitalWrite(pin_SS2, LOW);
  SPI.transfer(0b00000010); // WRITE INSTRUCTION

  SPI.transfer16(page_num * 64); // Send address of page number.  Must start at n * 64.

  for (int i = 0; i < 64; i++)
  {
    SPI.transfer(save_this_array[i]); // WRONG WRONG WRONG WRONG WRONG
  }

  digitalWrite(pin_SS2, HIGH); // close up the write process
  delay(5);                    // See TWC in datasheet https://ww1.microchip.com/downloads/en/DeviceDoc/25AA256-25LC256-256K-SPI-Bus-Serial-EEPROM-20001822H.pdf

  Serial3.println("Page Write complete");

}

void setup()
{
  SPI.begin();
  SPI.setClockDivider(SPI_CLOCK_DIV16); // 72Mhz / 16 = 4.5Mhz
  SPI.setBitOrder(MSBFIRST);

  Serial3.begin(19200); // PB11 (RX)  PB10   (TX)
  Serial3.println("Serial3: 3");
  EEPROMsetup();

  byte preset_arr[64];

  for (int i = 0; i < 64; i++)
  {
    preset_arr[i] = i;
  }

  EEPROMWritePage(0, preset_arr);

  EEPROMWritePage(2, preset_arr);

  // Read Page Two   Page 2 starts at 64*2 and ends at (64*3 - 1)
  for (int i = 128; i < 192; i++)
  {
    EEPROMread(i);
  }

  EEPROMwriteByte(50, 21);
  EEPROMwriteByte(52, 22);
  EEPROMwriteByte(30, 23);

} // end of setup

void loop()
{
  EEPROMread(50);
  EEPROMread(52);
  EEPROMread(30);
  delay(500);
}