Tips & Tricks

Date: 2022-10-16
categories: avr;

Contents

This page is mirrored from the original Digistump/Digispark documentation, and is preserved here in case the original is taken offline.

Here are some neat tricks to do unusual things with your digispark

Detect if system clock was calibrated

When the digispark boots up, it uses the USB signal to calibrate its clock to 16.5mhz. When powered by an external power supply this doesn't happen and the clock speed is closer to 16.0mhz, with roughly 10% accuracy. If you want to detect if the clock was calibrated before starting your program you can use these two functions:

#include <avr/boot.h>
byte read_factory_calibration(void) {
  byte SIGRD = 5; // for some reason this isn't defined...
  byte value = boot_signature_byte_get(1);
  return value;
}

boolean is_clock_calibrated(void) {
  return read_factory_calibration() != OSCCAL;
}

If your code is very clock-sensitive and you're feeling adventurous you might like to upgrade the bootloader on your digispark using the Burn Bootloader menu item in the latest Digispark software (1.0.4 or later). This newer bootloader version perminantly calibrates the CPU speed to 16.5mhz (the above function will always return true with these upgraded digisparks), which makes timing more accurate and speeds up USB initialization a little bit when using USB libraries. Upgrading the bootloader is currently an unsupported modification and does void your warranty, so do it at your own risk. A new bootloader version and endorsed upgrade path might be made available in the future.

Underclock the CPU to save power

The amount of power used by the Digispark's AVR microprocessor is linearly related to clock speed, so you can reduce power use considerably by choosing the 8mhz or 1mhz board options in the latest version of the Digispark Arduino app. 1mhz clock speed uses about one sixteenth as much power as the normal 16mhz. Note that lower clock speeds (especially 1mhz) are likely to be incompatible with some libraries, so your milage may vary.

Be aware that the power indicator LED uses a fair bit of power, so it's worth disabling this if you need to be very careful, and in current versions of the Digispark hardware a couple of milliamps are consumed constantly by the USB connector circuitry, even if you aren't using the USB interface.

The power lost through the USB connector maybe able to be fixed in a future version, but no promises.

Reboot in to the bootloader from your program

In some situations you might find it difficult to unplug and reconnect the digispark in order to replace its program. You can use the function below to reboot in to the bootloader without needing to disconnect the digispark physically.

void reboot(void) {
  noInterrupts(); // disable interrupts which could mess with changing prescaler
  CLKPR = 0b10000000; // enable prescaler speed change
  CLKPR = 0; // set prescaler to default (16mhz) mode required by bootloader
  void (*ptrToFunction)(); // allocate a function pointer
  ptrToFunction = 0x0000; // set function pointer to bootloader reset vector
  (*ptrToFunction)(); // jump to reset, which bounces in to bootloader
}

Get rid of 5 second startup delay

When the digispark is powered up, it waits 5 seconds to give your computer a chance to upload a different program. For some projects this delay is less than ideal. For these situations we have another version of the bootloader which only accepts uploads if you connect D5 to ground with a wire or a button before plugging the digispark in. You can add an 'update' button to your project for times you want to change its software, and use the button for other stuff in your app, or use the reboot to bootloader code above to make it reboot and accept a program without having to unplug and replug the device.

Instructions are on the forum for Mac and Windows. Linux users should follow the Mac instructions pretty much. Note that this is an unsupported modification and does currently void your digispark's warranty. So far nobody seems to have had any trouble with it though.

The newest version of the jumper firmware can be found here:

https://github.com/Bluebie/micronucleus-t85/tree/master/upgrade/releases

Update 2022: https://github.com/micronucleus/micronucleus

Debug with blinkenlights

"So... has the bootloader finished?" "Should I expect to see signs of life yet or not?" "Does my function not work or is it not getting called?"

Playing with new toys is fun, but sometimes [[http://digistump.com/board/index.php/topic,365.0.html|puzzling]], and debugging on embedded systems is often very limited. I find it useful to add lines that blink an LED to notify me when execution has reached interesting points, like having it blink once near the top of ''setup()'' to tell me my program is now running, then incrementally more along the path I expect and finally settle into a regular "heartbeat" in ''loop()''.

Except that would add precious bytes to my program and lots of ''delay()''s so my feeble human eyes can distinguish the blinks. We can use preprocessor macros to have our cake and eat it too!

#define DEBUG 0  // Set to 1 to enable, 0 to disable

#if DEBUG
#define DebugPin 1  // Digispark model A onboard LED
#define DebugBlink 75
#define DebugPause 300
#define debugDelay(ms) delay(ms)  // Change if you need a special delay function (e.g. if you use libraries that need regular refresh calls)

void _debugBlink(int n) {
  for ( int i = 0 ; i < n ; i++ ) {
    digitalWrite(DebugPin, HIGH);
    debugDelay(DebugBlink);
    digitalWrite(DebugPin, LOW);
    debugDelay(DebugBlink);
  }
  debugDelay(DebugPause);
}

void _debugSetup() {
  pinMode(DebugPin, OUTPUT);
}

#define debugBlink(n) _debugBlink(n)  // Do the blink when DEBUG is set
#define debugSetup() _debugSetup()
#else
#define debugBlink(n)  // Make the calls disappear when DEBUG is 0
#define debugSetup()
#endif

If you include the code above, you can call the debugSetup() macro early in your setup(), as a macro it "expands" to either a call to the real function if debug is enabled or nothing - even the function never actually got compiled. debugBlink(n) works the same way, sprinkle them into code you're trying to follow and when debugging is on you get a light show driven by the progress of your program. I've posted a complete example in this forum thread.

Use unneeded pins as power/ground pins

There's only one ground pin and one +5V pin on the digispark and they'll often be connected to an external power supply.

You can use an I/O pin as a ground/+5V if you have some left and the current that flow in it is low (a few milliamps if you want the pin to stay around 0V/+5V. If you don't care about the exact voltage you can go up to 40mA per pin according to the datasheet but it's good to keep a safety margin).

Configure the pin as an output and set its value to either LOW or HIGH (for ground or +5V).

For example to connect a button between pin 0(set as input) and pin 2(acts as ground):

#define button 0
#define gnd 2

void setup() {                
  pinMode (button, INPUT);
  digitalWrite (button, HIGH); // enable pullup
  pinMode(gnd, OUTPUT);
  digitalWrite (gnd, LOW); // use this pin as a ground
}

Be careful not to connect two output pins together though.

Remember it's a C-style language

Use == not = for tests!

if (a = 0) { /* code */ } // set a to be 0, then check if 0 isn't 0, and since it isn't, never run the code
if (a == 0) { /* code */ } // check if a is zero, and if that is true, run the code

Single = is always used to copy the right thing in to the left thing's memory, and double equals == is always used for checking equality of two things. It's a common bug worth looking out for if your program isn't working as you'd expect. It's generally a bad idea to ever use a single = inside a conditional if statement.

loop() is invoked each time

Variables defined in loop() get reinitialized every time... there is no magic goto the top. So:

void loop () {
  // don't use this buggy code!
  int second; // first bug here! (see explanation below)
  second = second + 1;
  
  if (second = 15) { // second bug here! (see explanation above)
     // this code runs every loop, because second is set to 15, and 15 is non-zero, so is truthy.
     // effectively the same as writing:
     // second = 15;
     // if (15 != 0) {
     //   ...
     // }
  }
  delay(1000);
}

If you really want something to run once 15 seconds after you power it up, you need something more like

void loop () {
  // use "static" to remember this value from one loop to the next.
  // Also, initialize it to something reasonable.
  static int seconds = 0;
  
  if ( 15 == seconds ) { // use the correct "==" for comparison.
     // this code runs only the 15th time through the loop.
     Serial.write("hey, it's been 15 seconds!");
  }
  delay(1000);
  seconds = seconds + 1;
}

Even the above code has a minor bug -- the Arduino Uno uses 16 bits in the "int", so a "seconds" count stored in an "int" rolls over. So it prints the message once 15 seconds after power-up, as desired, and then prints it out again (!) roughly every 18 hours.

Hardware & Software PWM

Introduction

The Digispark (based on the ATtiny85 microcontroller) has 4 built-in hardware PWM.

Currently, only 3 of them are usable in the arduino environment:

If the user wants to keep USB capabilty (with library) in his/her sketch, only 2 hardware PWM pins are available since Pin4 is used for USB.

In this context, it's impossible, for example, to create a sketch which controls a RGB strip LED through USB using -only- the available hardware PWM pins since 3 PWM pins are required (one per color).

There is a solution: Software PWM.

Software PWM is a technic which emulates hardware PWM by software.

This means:

Hardware PWM

To use hardware PWM in your sketch, use the analogWrite() function.

In the default implementation of PWM in arduino IDE, hardware PWM frequency is quite low:

Digispark @ 16.5MHz
Digispark PinPWM Frequency (Hz)
Pin0504
Pin1504
Pin41007

By setting FAVOR_PHASE_CORRECT_PWM to 0 in /arduino-1.0x/hardware/digispark/cores/tiny/core_build_options.h/ file, it's possible to double the frequency on Pin1:

Digispark @ 16.5MHz
Digispark PinPWM Frequency (Hz)
Pin0504
Pin11007
Pin41007

But for some applications, this may be not sufficient. For example, if the Digispark is used as Electronic Speed Controller for brushed motors, using hardware PWM which such a low frequency is not perfect: the ESC will be noisy (audible) since PWM frequency is within the audio range.

Another application where "high" frequency is required: Digital Analog Converter. Using a PWM pin followed with a simple RC low pass filter, it's very easy to build a DAC. Using a high PWM frequency will increase the response time and will reduce the ripple.

How to increase Hardware PWM Frequency?

The usual way to increase PWM frequency consists in changing the assigned timer prescaler. This is not sufficient: micros(), millis() and delay() will be broken since these functions rely on the timer which is also used for PWM.

New Digispark IDE release (may 2013) introduces a new capability: Hardware PWM frequency adjustment without breaking micros(), millis() and delay().

Simply by setting the new MS_TIMER_TICK_EVERY_X_CYCLES symbol in /arduino-1.0x/hardware/digispark/cores/tiny/wiring.c file to a value lower than 64 (the default arduino value), it's possible to increase the hardware PWM frequency.

The maximum reachable frequency for Pin1 and Pin4 is obtained with:

Digispark @ 16.5MHz
Digispark PinPWM Frequency (Hz)
Pin032227
Pin164453
Pin464453

Note: Frequencies for other MS_TIMER_TICK_EVERY_X_CYCLES values are easy to compute:

Take the above frequency values and divide them with MS_TIMER_TICK_EVERY_X_CYCLES. For example, if MS_TIMER_TICK_EVERY_X_CYCLES is set to 8, the obtained frequencies are 8 time smaller.

Software PWM

New Digispark IDE release (may 2013) introduces a new library: , a tiny Software PWM library.

To use software PWM in your sketch, use the TinySoftPwm_analogWrite() function (just add the TinySoftPwm_ prefix to the common analogWrite() function).

library features

The TinySoftPwm library allows to use a Digispark to control through USB a RGB strip led. The PWM frequency is higher enough for human eye perception.

The PWM Manager of the TinySoftPwm library can be called from the ISR of the version of the VirtualWire delivered with the new Digispark IDE release (may 2013): nice to control remotely a RGB strip led through cheap RF links!

Let's mix Hardware and Software PWM!

TinySoftPwm library can cohabit with Hardware PWM and with DigiUSB: see demo sketch below.

#include <TinySoftPwm.h>
#include <DigiUSB.h>

#define HW_PWM_PIN               0 /* Used to check HW PWM with analogWrite() */
#define SW_PWM_BUILT_IN_LED_PIN  1 /* Digispark Model A (Rev2) built-in LED pin number (Change it to 2 for Model B) */
#define TIME_TEST_PIN            5 /* Used to check with oscilloscope micros(), millis() are still OK */

void setup()
{
   TinySoftPwm_begin(128, 0); /* 128 x TinySoftPwm_process() calls before overlap (Frequency tuning), 0 = PWM init for all declared pins */
   pinMode(TIME_TEST_PIN, OUTPUT);
   DigiUSB.begin(); 
}

void loop()
{
static uint32_t PwmStartUs=micros();
static uint32_t PwmStartMs=millis();
static uint8_t  Pwm=0;
static int8_t   Dir=1;
static boolean  State=LOW;
static uint32_t BlinkStartMs=millis();

  /***********************************************************/
  /* Call TinySoftPwm_process() with a period of 60 us       */
  /* The PWM frequency = 128 x 60 # 7.7 ms -> F # 130Hz      */
  /* 128 is the first argument passed to TinySoftPwm_begin() */
  /***********************************************************/
  if((micros() - PwmStartUs) >= 60)
  {
    /* We arrive here every 60 microseconds */
    PwmStartUs=micros();
    TinySoftPwm_process(); /* This function shall be called periodically (like here, based on micros(), or in a timer ISR) */
  }
  
  /*************************************************************/
  /* Increment/decrement PWM on LED Pin with a period of 10 ms */
  /*************************************************************/
  if((millis()-PwmStartMs) >= 10)
  {
    /* We arrived here every 10 milliseconds */
    PwmStartMs=millis();
    Pwm+=Dir; /* increment or decrement PWM depending of sign of Dir */
    TinySoftPwm_analogWrite(SW_PWM_BUILT_IN_LED_PIN, Pwm); /* Software PWM: Update built-in LED for Digispark */
    analogWrite(HW_PWM_PIN, Pwm); /* Copy Pwm duty cycle to Hardware PWM */
    if(Pwm==255) Dir=-1; /* if PWM reaches the maximum: change direction */
    if(Pwm==0)   Dir=+1; /* if PWM reaches the minimum: change direction */
  }

  /* Blink half period = 5 ms */
  if(millis()-BlinkStartMs>=5)
  {
    BlinkStartMs=millis();
    digitalWrite(TIME_TEST_PIN, State);
    State=!State;
  }
  
  /* Check USB is still working */
  if(DigiUSB.available()) /* Just hit "Enter" in the digiterm to check USB */
  {
    DigiUSB.read(); /* just to clear the Rx buffer */
    DigiUSB.println(F("DigiUSB is still alive!"));
  }
  DigiUSB.refresh();

}