Monthly Archives: December 2020

Implementing a Decay Curve Generator into Microprocessor

In the previous article, I considered how to calculate a decay curve without using a lookup table. I tried implementing it to a microprocessor (atmega168). But I hit a few obstacles.

First I planned to keep envelope value as a Q10.6 fixed point number (meaning 10 integer bits and 6 fractional bits), so that I can conveniently use a 16-bit integer. But I found it does not give enough resolution of the data. I needed to extend it to 32-bit (Q10.22). Integer part is enough with 10 bit because it’s the resolution of the PWM output.

Then I wrote a program like following (shortened from the actual program). It looks to work, but it did not. The value did not show up to g_value where I keep the current envelope amount.

volatile uint32_t g_value;  // value holder, Q10.22 fixed point number
volatile uint32_t g_decay_factor;  // amount of decay,  Q0.32 fixed point
volatile uint8_t g_update_ready;

ISR(Time1_OVF_vect) {
  OCR1A = g_value >> 22;  // reflect the value to PWM、Q10.22 to integer
  g_update_ready = 1;
}

void UpdateValue() {
  if (!update_ready) {
    return;
  }
  // reduce the value by multiplying the decay factor
  uint64_t temp = g_value;
  temp *= g_decay_factor;
  g_value = temp >> 32;
  g_update_ready = false;
}

int main() {
  g_value = 1023 << 22; // integer to Q10.22
  g_decay_factor = 0.99309249543703590153321021688807 * 0x100000000;
  g_update_ready = 0;

  while (true) {
    UpdateValue();
  }
}

I couldn’t find any document or article explaining this, but it seems that avr-gcc, the compiler for AVR, is not following C language standard strictly. I presume it’s because there is some necessity to control register usage tightly by way of writing code (but I don’t know whether I’m right. I, again, couldn’t find any doc yet). I modified the code as follows, then it started working.

volatile uint32_t g_value;  // value holder, Q10.22 fixed point number
volatile uint32_t g_decay_factor;  // amount of decay,  Q0.32 fixed point
volatile uint8_t g_update_ready;

ISR(Time1_OVF_vect) {
  register uint32_t temp = g_value;  // copy the Q10.22 value to a variable
  temp >>= 22;  // Q10.22 to integer
  OCR1A = temp;  // reflect the value to PWM
  g_update_ready = 1;
}

void UpdateValue() {
  if (!update_ready) {
    return;
  }
  register uint64_t temp = g_value;
  // copy the Q10.22 value to a 64-bit int
  temp *= g_decay_factor;
  // multiply by the Q0.32 decay factor
  temp >>= 32;  // shift 32 bit to adjust scale, now it's Q10.22 again
  g_value = temp;
  // copy back to the value holder
  g_update_ready = false;
}

int main() {
  register uint32_t temp = 1023 << 22;   // this is calculated by the compiler
  g_value = temp;
  g_decay_factor = 0.99309249543703590153321021688807 * 0x100000000;
  g_update_ready = 0;

  while (true) {
    UpdateValue();
  }
}

The program works even without the “register” keywords. The compiler seems to take care of it.

The key points in the second implementation are:

  • Clarify where to load the data (such as g_value to temp)
  • Clarify where a calculation happens (use compound assignment operators such as *=, <<=, and >>=, rather than binary operators (*, <<, >>, etc.)
  • Do not mix assignment (loading data) and calculation (modifying the data on the register) in a single line

I have no idea which point was effective. But I keep this note hoping it helps when I have similar issue with microprocessor programming in future.

Calculating a Decaying Exponential Curve Recursively by using Fixed Point Multiplication

I’ve been working on implementing Envelope Generator into microprocessors. Output of an Envelope Generator consists of rising and falling exponential curve. I’m currently doing table lookup to generate those curves.

https://github.com/naokiiwakami/vceg

This is a quick and straightforward approach, and you don’t have to be worried much about data resolution. But the program is a little complicated, and memory consuming. I don’t really like this approach. I don’t really like the approach, so I started to think about alternative approaches.

Mathematically, a exponentially decaying curve can be calculated recursively by following formula:

V(i+1) = V(i) * k

where 0 <= k < 1

This is very simple. You can program this such as:

double value = INITIAL_VALUE;
double decay_factor = 0.99309249543703590153321021688807;
while (true) {
  value *= decay_factor;
}

but implementing the algorithm into a microprocessor is not straightforward like this.

I’m planning to use an AVR (e.g. atmega168), it has limited number of registers, and floating point multiplication is generally slow in AVR. So I would like to avoid floating point data types.

So shall I go for simple integer calculation? It’s actually not feasible. For example, if I update the value in every 10ms (100 updates per second) and would like to make a decaying curve that reduces its value to 50% after 1 second, the decay factor k would be

k = 0.5 ^ (1 / 100) = 0.99309249543703590153321021688807

It’s less than 1 so cannot be expressed by an integer.

There’s a technique called fixed point multiplication. This fundamentally uses integers but can handle fractions. This is my first time to try this technique, so learned how you do it. I found following link a good instruction:

https://www.allaboutcircuits.com/technical-articles/multiplication-examples-using-the-fixed-point-representation/

Tried to write it in C.

#include <stdio.h>
#include <stdint.h>

int main(int argc, char *argv[]) {
  // fixed point integer 10bit.6bit
  uint16_t value = (1023) << 6;
  // fixed point integer .16bit
  uint16_t decay_factor = 0.99309249543703590153321021688807 * 65536;
  for (uint16_t i = 0; i < 1000; ++i) {
    // multiplication
    uint32_t temp = value * decay_factor;
    value = temp >> 16;
    // retrieve the integer part
    uint16_t output = value >> 6;
    printf("%d %d\n", i, output, 10);
  }
  return 0;
}

Ran it on my PC (not on the microprocessor yet). Plotted the result to a graph:

OK it looks good so far.