Tips n Tricks
Here's a compilation of various tips and tricks I came across over the years. Feel free to use and re-use as much as you like.
set, clear and toggle macros
With AVR micros an often cryptic method is used to set, clear or toggle bits. Take for instance the following example to set a digital port to output and set it to a digital '1'.
#DEFINE LEDPIN PB0 // define LED pin DDRB |= (1 << LEDPIN); // set to output PORTB |= (1 << LEDPIN); // write a digital '1'
With these macros writing a digital '1' is now as simple as:
// Some helper MACROS #define SET |= #define CLR &=~ #define TOG ^= #DEFINE LEDPIN PB0 // define LED pin DDRB SET (1 << LEDPIN); // set to output PORTB SET (1 << LEDPIN); // write a digital '1'
bitwise operations
Similar to the above, here are some commands to set, clear or toggle a specific bit:
- set (to 1) bit 2 (the third bit from the right) of variable a
a |= (1<<2);
- clear (to 0) bit 2 (the third bit from the right) of variable a
a &= ~(1<<2);
- toggle bit 2 (the third bit from the right) of variable a
a ^= (1<<2);
Below is a set of macros that works with ANSI C to do bit operations:
#define bit_get(p,m) ((p) & (m)) #define bit_set(p,m) ((p) |= (m)) #define bit_clear(p,m) ((p) &= ~(m)) #define bit_flip(p,m) ((p) ^= (m)) #define bit_write(c,p,m) (c ? bit_set(p,m) : bit_clear(p,m)) #define BIT(x) (0x01 << (x)) #define LONGBIT(x) ((unsigned long)0x00000001 << (x))
byte type
In some programming environments the 'byte' type is readily available. It's usually just an unsigned char but if you don't want to search-and-replace every instance of byte with unsigned char in your code then here's a simple type definition that creates the byte type.
typedef unsigned char byte; //create byte type
rounding and decimals
Float values sometimes have too many decimals. Here are some generic rounding formulas:
fl = (int) (fl + 0.5f)/1.0f; // round to 0 decimals fl = (int) (fl * 10 + 0.5f)/10.0f; // round to 1 decimals fl = (int) (fl * 100 + 0.5f)/100.0f; // round to 2 decimals
AVR Sleep Mode
A recent project I built using an ATtiny85 micro was working fine and using about 4mA in idle mode. The circuit and its peripherals were powered through a 78L05 so I was wondering by how much I could lower the current draw by using the sleep function of the micro.
Here's how I configured sleep mode to bring the current draw down to 2.4mA. This suggests that the voltage regulator still takes some energy to run at very low levels..
The technique below uses the expiry of the on-board watchdog timer to generate an interrupt which wakes the micro.
Step 1 - import the sleep, watchdog and interrupt libraries
Location: at the top of your code
#include <avr/sleep.h> #include <avr/wdt.h> #include <avr/interrupt.h>
Step 2 - add some helper functions
Location: above the main() block
/* NOTES: ATtiny85 ATtiny84 Watchdog Timer Register: WDTCR WDTCSR */ void wdtEnable(void) { wdt_reset(); cli(); MCUSR = 0x00; WDTCR |= _BV(WDCE) | _BV(WDE); WDTCR = _BV(WDIE) | _BV(WDP2); // 250ms sei(); } //disable the wdt void wdtDisable(void) { wdt_reset(); cli(); MCUSR = 0x00; WDTCR |= _BV(WDCE) | _BV(WDE); WDTCR = 0x00; sei(); } void system_sleep() { ACSR |= _BV(ACD); //disable the analog comparator ADCSRA &= ~_BV(ADEN); //disable ADC set_sleep_mode(SLEEP_MODE_PWR_DOWN); sleep_enable(); wdtEnable(); //start the WDT //turn off the brown-out detector. //must have an ATtiny45 or ATtiny85 rev C or later for software to be able to disable the BOD. //current while sleeping will be <0.5uA if BOD is disabled, <25uA if not. cli(); mcucr1 = MCUCR | _BV(BODS) | _BV(BODSE); //turn off the brown-out detector mcucr2 = mcucr1 & ~_BV(BODSE); MCUCR = mcucr1; MCUCR = mcucr2; sei(); //ensure interrupts enabled so we can wake up again sleep_cpu(); //go to sleep //----zzzz----zzzz----zzzz----zzzz cli(); //wake up here, disable interrupts sleep_disable(); wdtDisable(); //don't need the watchdog while we're awake sei(); //enable interrupts again (but INT0 is disabled above) } // insert the ISR function outside the main() loop, all the way at the bottom of the code ISR(WDT_vect) {} //don't need to do anything here when the WDT wakes the MCU
Step 3 - insert code to configure the sleep/watchdog registers once at startup
Location: at the top of the main() block
wdt_reset(); wdtDisable();
Step 4 - add your sleep command at the idle position(s)
Location: normaly where you would have a delay at the end of a loop in main()
system_sleep();
Step 5 - disable BOD in the command to flash the code to the micro or in software (not all models supported!)
-U hfuse:w:0xDF:m
custom functions and libraries
Here's a simple function that makes it easy to blink out a value. Handy if you only have one LED as a means for feedback.
// With this code, a value like 302 is blinked as: // // <short> <short> <short> <long> <short> <short> void shortblink(uint8_t ledpin) { digitalWrite(ledpin,HIGH); _delay_ms(50); digitalWrite(ledpin,LOW); _delay_ms(500); } void longblink(uint8_t ledpin) { digitalWrite(ledpin,HIGH); _delay_ms(600); digitalWrite(ledpin,LOW); _delay_ms(500); } void numberblink(uint16_t value, uint8_t ledpin) { // blinks a number (from 0 - 65535) to a LED uint16_t divisor = 10000; uint8_t blinked=0; while (divisor >= 10) { if (value > divisor) { // do some blinking while (value >= divisor) { shortblink(ledpin); blinked=1; if (value >= divisor) value = value - divisor; } } else { // value is less than divisor, probably encountered a (middle) zero // blink long if we've blinked before if (blinked == 1) longblink(ledpin); } _delay_ms(1000); divisor = divisor / 10; } if (value == 0) { // always long blink last zero longblink(ledpin); } else { while (0 < value) { // blink ones shortblink(ledpin); value=value-1; } } }