Author Topic: <virtualwire> library optimization for DigiSpark ... and other arduino  (Read 6288 times)

RC Navy

  • Jr. Member
  • **
  • Posts: 54
  • When you like, even too much, it is not enough!
Hi,

For a new project using <virtualwire> and <DigiUSB> libraries , I was quickly facing a lack of program memory.

The source code of  <virtualwire> library shows the usage of float computation for timer prescaler and max tick in "_timer_calc()" function.
After having rewritten the "_timer_calc()" function, I get the following results:
- code size reduction: 858 bytes
- code speed: x3

See code below for the tests:
Code: [Select]
#include <DigiUSB.h>

/* virtualwire library optimization: float computation vs uint32_t computation for _timer_calc() function
RC Navy 2103
http://p.loussouarn.free.fr */

uint16_t RateTbl[]={1200, 2000, 2400, 3000, 4800, 5000, 7000, 9600}; /* some rate samples for test */

#define TBL_ITEM_NB(Tbl) (sizeof(Tbl)/sizeof(Tbl[0]))

#define NEW  /* Uncomment this line to use new _timer_calc() implementation */

void setup()
{
  DigiUSB.begin();
}

void loop()
{
uint8_t Prescaler;
uint16_t Nticks;
static boolean Go=0;
static uint32_t StartRefreshMs=millis(), StartUs, DurUs;
  if(DigiUSB.available())
  {
    DigiUSB.read();
    DigiUSB.print("Go");
    Go=1;
  }
  if(Go)
  {
    for(uint8_t Idx=0; Idx<TBL_ITEM_NB(RateTbl);Idx++)
    {
#ifdef NEW
      StartUs=micros();
      Prescaler=_Mytimer_calc(RateTbl[Idx], 255, &Nticks);
      DurUs=micros()-StartUs;
      DigiUSB.print("\r_p=");DigiUSB.print((int)Prescaler);DigiUSB.print(" _N=");DigiUSB.print((int)Nticks);DigiUSB.print(" _D=");DigiUSB.print((int)DurUs);
      StartRefreshMs=millis();while(millis()-StartRefreshMs<200) DigiUSB.refresh();
#else
      StartUs=micros();
      Prescaler=_timer_calc(RateTbl[Idx], 255, &Nticks);
      DurUs=micros()-StartUs;
      DigiUSB.print("\r p=");DigiUSB.print((int)Prescaler);DigiUSB.print("  N=");DigiUSB.print((int)Nticks);DigiUSB.print("  D=");DigiUSB.print((int)DurUs);
      StartRefreshMs=millis();while(millis()-StartRefreshMs<200) DigiUSB.refresh();
#endif
    }
    Go=0;
  }
  DigiUSB.refresh();
}

#ifdef NEW
/* New proposed _timer_calc() implementation for <virtualwire> library */
// Common function for setting timer ticks @ prescaler values for speed
// Returns prescaler index into {0, 0, 3, 6, 8, 10, 12} array
// and sets nticks to compare-match value if lower than max_ticks
// returns 0 & nticks = 0 on fault
uint8_t prescalers[] PROGMEM = {0, 0, 3, 6, 8, 10, 12}; /* Must be outside the function */
uint8_t _Mytimer_calc(uint16_t speed, uint16_t max_ticks, uint16_t *nticks)
{
    // Clock divider (prescaler) values - 0/4096: error flag
    /* Trick: use power of 2 rather than divisor values: only uint8_t table needed */
    uint8_t  prescaler=0; // index into array & return bit value
    uint32_t ulticks; // calculate by ntick overflow

    // Div-by-zero protection
    if (speed == 0)
    {
        // signal fault
        *nticks = 0;
        return 0;
    }
    // test increasing prescaler (divisor), decreasing ulticks until no overflow
    for (prescaler=1; prescaler < 7; prescaler += 1)
    {
        /* Trick: compute in frequency domain rather than in time domain: no need of floats */
        // Amount of time per CPU clock tick (in seconds)
    uint32_t clock_freq = F_CPU >> (uint8_t)pgm_read_byte(&prescalers[prescaler]);
        // Fraction of second needed to xmit one bit
    uint32_t bit_freq = (uint32_t)speed << 3;/* 8 samples */
        // number of prescaled ticks needed to handle bit time @ speed
    ulticks = clock_freq / bit_freq;
        // Test if ulticks fits in nticks bitwidth (with 1-tick safety margin)
        if ((ulticks > 1) && (ulticks < max_ticks))
        {
            break; // found prescaler
        }
        // Won't fit, check with next prescaler value
    }

    // Check for error
    if ((prescaler >= 6) || (ulticks < 2UL) || (ulticks > (uint32_t)max_ticks))
    {
        // signal fault
        *nticks = 0;
        return 0;
    }

    *nticks = (uint16_t)ulticks;
    return prescaler;
}
#else
/* Current _timer_calc() implementation in <virtualwire> library */
// Common function for setting timer ticks @ prescaler values for speed
// Returns prescaler index into {0, 1, 8, 64, 256, 1024} array
// and sets nticks to compare-match value if lower than max_ticks
// returns 0 & nticks = 0 on fault
static uint8_t _timer_calc(uint16_t speed, uint16_t max_ticks, uint16_t *nticks)
{
    // Clock divider (prescaler) values - 0/3333: error flag
    uint16_t prescalers[] = {0, 1, 8, 64, 256, 1024, 3333};
    uint8_t prescaler=0; // index into array & return bit value
    unsigned long ulticks; // calculate by ntick overflow

    // Div-by-zero protection
    if (speed == 0)
    {
        // signal fault
        *nticks = 0;
        return 0;
    }

    // test increasing prescaler (divisor), decreasing ulticks until no overflow
    for (prescaler=1; prescaler < 7; prescaler += 1)
    {
        // Amount of time per CPU clock tick (in seconds)
        float clock_time = (1.0 / (float(F_CPU) / float(prescalers[prescaler])));
        // Fraction of second needed to xmit one bit
        float bit_time = ((1.0 / float(speed)) / 8.0);
        // number of prescaled ticks needed to handle bit time @ speed
        ulticks = long(bit_time / clock_time);
        // Test if ulticks fits in nticks bitwidth (with 1-tick safety margin)
        if ((ulticks > 1) && (ulticks < max_ticks))
        {
            break; // found prescaler
        }
        // Won't fit, check with next prescaler value
    }

    // Check for error
    if ((prescaler == 6) || (ulticks < 2) || (ulticks > max_ticks))
    {
        // signal fault
        *nticks = 0;
        return 0;
    }

    *nticks = ulticks;
    return prescaler;
}
#endif

With "#define NEW" commented: (keep current implementation of _timer_calc() function in <virtualwire> library)
- code size: 4868 bytes
- max computation duration: 384 µs

With "#define NEW" uncommented: (enables new proposed implementation of _timer_calc() function for <virtualwire> library)
- code size: 4010 bytes
- max computation duration: 96 µs

The new implementation gives exactly the same prescaler and max tick for each tested rate.

With this new implementation, I'm able to use a DigiSpark with <Virtualwire>, <DigiUSB> and 4 software PWM pins.

In order to use <Virtualwire> whilst using <DigiUSB>, it's necessary to modify the <Virtualwire> interrupt handler:
Code: [Select]
#ifdef __AVR_ATtiny85__
//SIGNAL(TIM0_COMPA_vect)
ISR(TIM0_COMPA_vect, ISR_NOBLOCK) /* for DigiUSB */
#else // Assume Arduino Uno (328p or similar)

SIGNAL(TIMER1_COMPA_vect)
#endif // __AVR_ATtiny85__

<DigiUSB> is used only for debug/info purpose: I know the <virtualwire> may be disturbed during USB exchange...
The software PWM shouldn't disturb <virtualwire> since the PWM manager is called at the end of <virtualwire> ISR.
PWM frequency is 125Hz with a 2000 bauds <virtualwire> rate which is sufficient for my strip leds.

Is someone interested to test this optimization before proposing it to the <virtualwire> library maintainer? (I'm still waiting for my RF modules)

Thanks,

RC Navy.