Sunday, October 19, 2014

Updated Minima Rotary Encoder Driver


I have implemented support for rotary encoders in the Minima and provided integration of it into Eldon Brown's (WA0UWH) fine work on the Minima controller code.  The encoder works very smoothly and is very resilient to noisy, bouncing contacts on the mechanical encoders typically used.  That said, if you can avoid it, you should stay away from cheap mechanical encoders.  Spend a little more money on the main interface to your radio if you can.

The implementation uses pin-change interrupts for both the A and B inputs from the encoder.  A side effect of this is that you cannot pick completely arbitrary pins for the A and B inputs to the Arduino.  They must both be on the same port (PORTB, PORTC, PORTD).  My minima shield uses digital pins 8 and 9 freed up by replacing the 6 wire LCD with an i2c display.  Another option would be to use PD6 and PD7.  PD7 is currently targeted to be used by the RF386 amplifier.  However, the tuning control pot input A2 could be used freeing up PD6 and PD7 (both on PORTD) for use by the rotary encoder.

When an interrupt is received, the encoder is read and if it has changed, a 3ms debounce timer is reset.  As long as the pins continue to interrupt, the debounce timeout continues to be reset.  When the debounce timer expires, the encoder hasn't changed in the last 3ms and the value is processed.

I have provided integration to Eldon Brown's (WA0UWH) radiono code and completed initial testing.  I have turned it over to him to release with his code if he wishes to do so.

In general there are three basic functions that need to be implemented to support rotary encoders.

  1. Set up pin change interrupts for encoder A and B inputs.
  2. Set up debounce timer
  3. Implement interrupt service routines (ISR) for pin change and timer

The following bunch of noise is to try to minimize the number of changes required in order to change the pins the encoder is connected to.  The implementation represents a balance between providing pin change support for a Minima radio that can be configured as compared to a general purpose (large) library that can be configured in any way desired on any hardware platform.  If you wish to use analogue aliases for encoder pin numbers (A0-A5) you should instead use 14-19 (the digital pin equivalent numbers).

It should be noted that all of this (currently) only works on the ATMega328 such as is found in the Minima, Arduino UNO, etc.  Specifically support for the ATMega2560 is not yet implemented.

// Encoder pins
#define ENC_A_PIN 8  // Change these as desired, but both pins must be on 
#define ENC_B_PIN 9  // the same port (PB, PC, PD)

// Pin change interrupts
// Digital pin     PC Pin     Vector      Ctl Reg Port PCICR Bit Pin Mask Reg
// D0-D7           PCINT16-23 PCINT2_vect PCIR2   PD   PCIE2     PCMSK2
// D8-D13          PCINT0-5   PCINT0_vect PCIR0   PB   PCIE0     PCMSK0
// D14-D19 (A0-A5) PCINT8-13  PCINT1_vect PCIR1   PC   PCIE1     PCMSK1

// Define the interrupt vector, interrupt enable bit, PCINTpin values
#if (ENC_A_PIN) >= 0 && (ENC_A_PIN) <= 7
  #define VECTOR    PCINT2_vect
  #define PCMask    PCMSK2
  #define PCICRbit  (1<<PCIE2)
  #define PCINTpinA (1<<(ENC_A_PIN))
  #define PCINTpinB (1<<(ENC_B_PIN))
#endif
#if (ENC_A_PIN) >= 8 && (ENC_A_PIN) <= 13
  #define VECTOR    PCINT0_vect
  #define PCMask    PCMSK0
  #define PCICRbit  (1<<PCIE0)
  #define PCINTpinA (1<<(ENC_A_PIN-8))
  #define PCINTpinB (1<<(ENC_B_PIN-8))
#endif
#if (ENC_A_PIN) >= 14 && (ENC_A_PIN) <= 19
  #define VECTOR    PCINT1_vect
  #define PCMask    PCMSK1
  #define PCICRbit  (1<<PCIE1)
  #define PCINTpinA (1<<(ENC_A_PIN-14))
  #define PCINTpinB (1<<(ENC_B_PIN-14))
#endif

Ok, so all that noise is defining a bunch of things that change when you change the encoder pin numbers.  Specifically, you need to define the interrupt vector, the pin mask used and the pin change interrupt enable bit used.

The encoder returns a Gray code which I normalize to a 0..3 value.  Specific patterns of values are compared to determine the encoder direction of rotation.

// Encoder patterns for clockwise and anti-clockwise rotation
static const int CCW_DIR[] = {0x01, 0x03, 0x00, 0x02};
static const int  CW_DIR[] = {0x02, 0x00, 0x03, 0x01};

static int      last_code;  // Last stable encoder output
static int  encoder_count;  // tracks incremental rotation CW (+), CCW (-)

// Structure used to track encoder pin state in ISR
typedef struct 
{
  union 
  {
    byte encoder;
    struct 
    {
      int enc_A:1;
      int enc_B:1;
    };
  };
} PINSTATUS_t;

// Returns current encoder pin reading normalized to 0..3 value.
static inline int getEncoderValue(void)
{
  int value = 0;
  
  if (digitalRead(ENC_A_PIN)) value += 1;
  if (digitalRead(ENC_B_PIN)) value += 2;
    
  return value;
}

The pin change interrupt service routine (ISR) now needs to be defined.  We only look at the current value of the encoder pins and compare it to the last value read.  If it has changed, we just reset the 3ms debounce timer.  We are not going to process the encoder value until it has not changed for 3 ms.

// Encoder pin change ISR - If a pin changed, reset debounce timer
ISR(VECTOR)
{
  static PINSTATUS_t last;
  byte prior;

  prior = last.encoder;
  last.enc_A = digitalRead(ENC_A_PIN);  // Get encoder current pin values
  last.enc_B = digitalRead(ENC_B_PIN);
  
  if (prior != last.encoder)
  {
    TCNT1  = 0;
    TIMSK1 |= (1 << OCIE1A);
  } 
}

The 3 ms timer is implemented using timer 1.  I chose this timer because internal libraries for Arduino use timer 0 (the delay() function for example) and I plan to use timer 2 in my CW keyer code going forward.  Timer 1 is a 16 bit timer and is set up to provide an interrupt after 3 ms.

Currently, I decode and debounce 4 edges per detent on the encoder.  I could increment the encoder for each of these events which might make sense for an encoder without detents.  However, getting an increment/decrement of 4 for every click of the encoder detent would be confusing, and not useful, so currently I only return an increment for every 4 encoder edges decoded resulting in incrementing by one per detent click.  This could be used to speed up tuning when moving a long distance, but that is for another day...

// Timer ISR - We get here if the debounce timer times out.  Compare the new
// code with the previous one to see which direction the encoder has turned.
// Update the rotation counter to reflect the change.  The timer and interrupt
// are left disabled.  I can get four counts per detent and effectively divide
// this by 4 to get one tick per detent.
ISR(TIMER1_COMPA_vect)
{
  int new_code = getEncoderValue();
  
  if (new_code != last_code)
  {
    if (new_code == CW_DIR[last_code]) {
      if (0x00 == new_code) encoder_count++;
    } else if (new_code == CCW_DIR[last_code]) {
      if (0x00 == new_code) encoder_count--;
    }
    last_code = new_code;
  }
  TIMSK1 &= ~(1 << OCIE1A);  // Disable Timer
}

The initialization code for the encoder sets up pin change interrupts and Timer 1.  No magic here.

// Initialize the encoder
void initEncoder()
{
  cli();
  
  // Encoder pins
  pinMode(ENC_A_PIN, INPUT_PULLUP);
  pinMode(ENC_B_PIN, INPUT_PULLUP);
  
  // Set pin change interrupt enable and pin mask for encoder pins
  PCMask |= PCINTpinA;    // Set pin mask for A and B inputs
  PCMask |= PCINTpinB;
  PCICR  |= PCICRbit;     // Enable interrupts on correct port
  
  // Set timer 1 for 3 ms debounce timeout
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register 3 ms
  OCR1A = 48000;
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
  // Set bits for no prescaler
  TCCR1B |= (1 << CS10); 
  // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);
  sei();
  
  // Set the initial value from the encoder
  last_code = getEncoderValue();
  encoder_count = 0;
}

All that is left is to read and return a signed value indicating the direction and amount that the encoder has moved.  If this is called in the main loop of your sketch, it will typically return a +/- 1 value depending on the latency of your main loop.  In Eldon's Minima controller, it nicely returns a single increment or decrement per call.  The only magic here is to disable interrupts while reading the multi-byte value of the encoder in order to prevent the value from being changed before all bytes can be read.

// Return encoder count.  Negative - Anti-clockwise, positive - clockwise
int getEncoderDir()
{
  uint8_t oldSREG = SREG;
  cli();
  int val = encoder_count;
  encoder_count = 0;
  SREG = oldSREG;
  return val;
}

As always, your mileage may vary, but I hope you find this code useful.  I am happy to help if anyone has problems with it.  I will not be publishing this separately, but I expect that Eldon will use the integration in his Minima controller and publish it in due course after he has had a chance to review it.

I am happy to help with any problems.  Drop me an email note at ko7m at arrl dot net and I will do my best to help you with any issues.

Wednesday, October 1, 2014

Minima working with rotary encoder

I have gotten Wayne, NB6M's Minima hardware working with a rotary encoder, thanks to the great work by Eldon, WA0UWH.  Eldon has come up with a clever way to implement an encoder on top of his button structure that only requires a single input pin on the ATMega328 to handle all 7 buttons, plus the rotary encoder.  Check out his blog for all the details.

I am sitting here listening to 75 meters tonight to the group that meets around 3.885 MHz on AM.  Most of these guys are running old AM ham gear.


I have replaced the audio section with an LA4425 power amplifier chip.  This is a 5W amplifier that typically is found in a automotive applications.  It requires very few external parts and has sufficient drive to comfortably drive an unpowered speaker.


I have inserted this circuit between the volume control wiper and the speaker.  Here is a shot of the ugly-style build in Wayne's Minima.


I like the end result, but it actually has a little more gain than it needs for this application.  It is nice not needing to use amplified speakers just to hear the radio.  I have not yet screwed down the component.  It runs cool enough in this application, but the additional heat sink couldn't hurt.

Saturday, September 27, 2014

Fun with irDA

Recently, I picked up a fun little bit of kit that consists of a small irDA remote and an irDA receiver.





I did a bunch of reading about irDA remote controls and it is a bit mind boggling.  It is clear that there is a bunch of legacy junque that has been drug along for years in the implementation of irDA remote controls.  The little breakout board with the irDA receiver on it kindly includes a visible light LED that toggles in response to anything the sensor sees.  This makes it easy to verify that it is seeing your remote control.

Taking at look at the output from the sensor on the scope we see this kind of information being transmitted for one of the buttons on the remote.



There are a bunch of different encodings for these remote controls and I had no interest in determining exactly what code format this remote uses.  I did find an Arduino library or ten that would decode the data stream.  With that in hand, I was able to find the codes for all the buttons on the remote, listed below top/left to bottom/right.


Power        - 0xffa25d
Mode         - 0xff629d
Mute         - 0xffe21d
Play         - 0xff22dd
Rewind       - 0xff02fd
Fast Forward - 0xffc23d
EQ           - 0xffe01f
Minus        - 0xffa857
Plus         - 0xff906f
0            - 0xff6897
Random       - 0xff9867
U/SD         - 0xffb04f
1            - 0xff30cf
2            - 0xff18e7
3            - 0xff7a85
4            - 0xff10ef
5            - 0xff38c7
6            - 0xff5aa5
           - 0xff42bd
           - 0xff4ab5
           - 0xff52ad


So, with all this fun in mind, I decided to set up the irDA remote to work with Minima.  I added the code to initialize the irDA receiver and in the btnDown function added receiving the button codes above and mapping them to Minima's 7 button scheme implemented by Eldon, WA0UWH.  For now, I just mapped the buttons as follows:

Mode -- FN (button 1)
Rewind - Left (button 2)
Fast Forward - Right (button 3)
Minus - Down (button 6)
Plus - Up (button 5)

So, with this simple change, I am able to move the cursor to select the frequency digit to change, move up and down through the ham band select and toggle RIT mode.  It would be simple enough to add direct frequency entry and map the rest of the buttons.

This is just a proof of concept bit of fun on a Saturday afternoon when I should really be out playing in the sun before it runs away and hides for the rest of the year...

Here is the Minima controller shield mounted on an ATMega2560 with the irDA breakout board jumpered to digital pin 3.  The display is an i2c 20x4 LCD on my Minima front panel.


Saturday, September 20, 2014

Minima Controller Shield - mounting options

Well, I am not certain yet how I am going to mount things in the long run, but for now I have stacked the display, Uno and Shield on the back of the panel.  My wires are deliberately long until I make a final decision, but for now I have just coiled them up.



I will most likely fashion another panel a little bit larger that will have room for more buttons as well as the rotary encoder at which time the current tuning pot will become the speed control for a keyer.  I have located some scrap aluminum, so I think it is the next best step.

Friday, September 19, 2014

Minima working with new shield

This evening, I hacked poor Wayne, NB6M's Minima to accept VFO energy from my Minima Controller shield.  As a first hack, it worked fine and I was able to test drive the receiver from my controller.



I disconnected Wayne's Si570 output and brought the coax out to an SMA connector that I hooked up with a short SMA jumper cable to my VFO output.  Other than being out of calibration, it seems to work fine.



So far so good...  I want to try using the second Si570 as the BFO, but more surgery is necessary in order to replace the BFO oscillator.  I think I will wait until I am more awake...

Tuesday, September 16, 2014

Minima Displays

I have been playing around with a number of display options for the Minima.  I found an interesting little board locally for about $12 in single quantities and on eBay for about $5 that has 8 digits of seven segment display, eight tri-colour LEDs and eight push buttons.  It is based on the Titan Micro LED driver chip TM1638.  Here is a good write-up on the device, I won't attempt to replicate it here.




I very quickly modified the Minima code to utilize this display.  It could very easily be further adapted to support the various buttons.  Some of the LCD second line indicators (RX/TX, LSB/USB, etc) could be indicated using tri-colour LEDs.  

I thought folks might be interested in seeing this device and I wanted to get a feel for how difficult it would be to integrate into the Minima code.  Out-of-box, it requires three of the scarce pins on the Arduino, but this can be easily resolved by expanding the I/O pins using a i2c device very similar to what is done with LCD backpacks for i2c.

Here is a photograph of my UNO running Minima version ko7m-AD with modifications for displaying the frequency.  My Minima controller shield is in place but not currently powered.



Anyone interested in further details on this display board should see the links above.  if you have trouble integrating this into your Minima code, drop me a note by email or by comment here and I will do my best to help you succeed.

ko7m at arrl dot net

Sunday, September 14, 2014

Arduino Programming

It is interesting to me to note the minimum application size given the Arduino development environment.  I created this minimum sketch:

void setup() 
{
  // put your setup code here, to run once:
}

void loop() 
{
  // put your main code here, to run repeatedly:
}

The development environment reported the following for my Arduino UNO R3.

Sketch uses 466 bytes (1%) of program storage space. Maximum is 32,256 bytes.

Global variables use 9 bytes (0%) of dynamic memory, leaving 2,039 bytes for local variables. Maximum is 2,048 bytes.

Interesting...  Using avr-size to report the same information:

   text    data     bss     dec     hex filename
    466       0       9     475     1db sketch_sep14a.cpp.elf

So this sketch that does nothing requires 466 bytes of code (start-up/shut-down) and 9 bytes of initialized memory.

Using obj-dump to look at the symbol table and different blocks, we find pretty typical initialization of vector tables, copying initialization data to RAM, default handlers and support for debugging in different modes.  

Too bad the Arduino IDE doesn't actually provide a debugger, but nice it is there if you want to step outside of the Arduino IDE (which I do recommend).

The program main entry point on Arduino is still main() like in any C program, but it is provided for you by the IDE.  As can be seen below, it calls the init function (also provided), then your setup followed by your loop function.  Upon return, the call stack frame pointer R28:r29 is examined and if non-zero, the device is reset through the vector at address zero.  Otherwise it just continues to call your loop function.


000000a6 <setup>:
  a6: 08 95        ret

000000a8 <loop>:
  a8: 08 95        ret

000000aa <main>:
  aa: cf 93        push r28
  ac: df 93        push r29
  ae: 0e 94 ac 00  call 0x158 ; 0x158 <init>
  b2: 0e 94 53 00  call 0xa6 ; 0xa6 <setup>
  b6: c0 e0        ldi r28, 0x00 ; 0
  b8: d0 e0        ldi r29, 0x00 ; 0
  ba: 0e 94 54 00  call 0xa8 ; 0xa8 <loop>
  be: 20 97        sbiw r28, 0x00 ; 0
  c0: e1 f3        breq .-8       ; 0xba <main+0x10>
  c2: 0e 94 00 00  call 0 ; 0x0 <__vectors>
  c6: f9 cf        rjmp .-14      ; 0xba <main+0x10>


Explore some of the other tools that are provided with your Arduino development environment.  Here is most of the output from obj-dump with the big blocks of hex dumps and disassemblies of debugging blocks removed.


sketch_sep14a.cpp.elf:     file format elf32-avr
sketch_sep14a.cpp.elf
architecture: avr:5, flags 0x00000112:
EXEC_P, HAS_SYMS, D_PAGED
start address 0x00000000

Program Header:
    LOAD off    0x00000074 vaddr 0x00000000 paddr 0x00000000 align 2**0
         filesz 0x000001d2 memsz 0x000001d2 flags r-x
    LOAD off    0x00000246 vaddr 0x00800100 paddr 0x00800100 align 2**0
         filesz 0x00000000 memsz 0x00000009 flags rw-

Sections:
Idx Name          Size      VMA       LMA       File off  Algn
  0 .text         000001d2  00000000  00000000  00000074  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
  1 .bss          00000009  00800100  00800100  00000246  2**0
                  ALLOC
  2 .debug_aranges 000000b0  00000000  00000000  00000246  2**0
                  CONTENTS, READONLY, DEBUGGING
  3 .debug_pubnames 000000cd  00000000  00000000  000002f6  2**0
                  CONTENTS, READONLY, DEBUGGING
  4 .debug_info   000005e4  00000000  00000000  000003c3  2**0
                  CONTENTS, READONLY, DEBUGGING
  5 .debug_abbrev 00000296  00000000  00000000  000009a7  2**0
                  CONTENTS, READONLY, DEBUGGING
  6 .debug_line   00000722  00000000  00000000  00000c3d  2**0
                  CONTENTS, READONLY, DEBUGGING
  7 .debug_frame  000000e0  00000000  00000000  00001360  2**2
                  CONTENTS, READONLY, DEBUGGING
  8 .debug_str    00000250  00000000  00000000  00001440  2**0
                  CONTENTS, READONLY, DEBUGGING
  9 .debug_loc    0000019b  00000000  00000000  00001690  2**0
                  CONTENTS, READONLY, DEBUGGING
 10 .debug_ranges 00000090  00000000  00000000  0000182b  2**0
                  CONTENTS, READONLY, DEBUGGING
SYMBOL TABLE:
00000000 l    d  .text 00000000 .text
00800100 l    d  .bss 00000000 .bss
00000000 l    d  .debug_aranges 00000000 .debug_aranges
00000000 l    d  .debug_pubnames 00000000 .debug_pubnames
00000000 l    d  .debug_info 00000000 .debug_info
00000000 l    d  .debug_abbrev 00000000 .debug_abbrev
00000000 l    d  .debug_line 00000000 .debug_line
00000000 l    d  .debug_frame 00000000 .debug_frame
00000000 l    d  .debug_str 00000000 .debug_str
00000000 l    d  .debug_loc 00000000 .debug_loc
00000000 l    d  .debug_ranges 00000000 .debug_ranges
00000084 l       .text 00000000 .do_copy_data_start
00000080 l       .text 00000000 .do_copy_data_loop
00000094 l       .text 00000000 .do_clear_bss_start
00000092 l       .text 00000000 .do_clear_bss_loop
00000000 l    df *ABS* 00000000 sketch_sep14a.cpp
0000003f l       *ABS* 00000000 __SREG__
0000003e l       *ABS* 00000000 __SP_H__
0000003d l       *ABS* 00000000 __SP_L__
00000034 l       *ABS* 00000000 __CCP__
00000000 l       *ABS* 00000000 __tmp_reg__
00000001 l       *ABS* 00000000 __zero_reg__
00000000 l    df *ABS* 00000000 main.cpp
0000003f l       *ABS* 00000000 __SREG__
0000003e l       *ABS* 00000000 __SP_H__
0000003d l       *ABS* 00000000 __SP_L__
00000034 l       *ABS* 00000000 __CCP__
00000000 l       *ABS* 00000000 __tmp_reg__
00000001 l       *ABS* 00000000 __zero_reg__
00000000 l    df *ABS* 00000000 wiring.c
0000003f l       *ABS* 00000000 __SREG__
0000003e l       *ABS* 00000000 __SP_H__
0000003d l       *ABS* 00000000 __SP_L__
00000034 l       *ABS* 00000000 __CCP__
00000000 l       *ABS* 00000000 __tmp_reg__
00000001 l       *ABS* 00000000 __zero_reg__
00800108 l     O .bss 00000001 timer0_fract
000001d0 l       .text 00000000 __stop_program
00000000 l    df *ABS* 00000000 hooks.c
0000003f l       *ABS* 00000000 __SREG__
0000003e l       *ABS* 00000000 __SP_H__
0000003d l       *ABS* 00000000 __SP_L__
00000034 l       *ABS* 00000000 __CCP__
00000000 l       *ABS* 00000000 __tmp_reg__
00000001 l       *ABS* 00000000 __zero_reg__
000000a2  w      .text 00000000 __vector_22
00800100 g     O .bss 00000004 timer0_overflow_count
000000a2  w      .text 00000000 __vector_1
00800104 g     O .bss 00000004 timer0_millis
00000068 g       .text 00000000 __trampolines_start
000001d2 g       .text 00000000 _etext
000000a2  w      .text 00000000 __vector_24
000000a8 g     F .text 00000002 loop
000000a2  w      .text 00000000 __vector_12
000000a2 g       .text 00000000 __bad_interrupt
000001d2 g       *ABS* 00000000 __data_load_end
000000a2  w      .text 00000000 __vector_6
00000068 g       .text 00000000 __trampolines_end
000000a2  w      .text 00000000 __vector_3
000000a2  w      .text 00000000 __vector_23
000001d2 g       *ABS* 00000000 __data_load_start
00000068 g       .text 00000000 __dtors_end
00800109 g       .bss 00000000 __bss_end
000000a2  w      .text 00000000 __vector_25
000000a2  w      .text 00000000 __vector_11
00000068  w      .text 00000000 __init
00000000  w      *UND* 00000000 _Z14serialEventRunv
000000a2  w      .text 00000000 __vector_13
000000a2  w      .text 00000000 __vector_17
000000a2  w      .text 00000000 __vector_19
000000a2  w      .text 00000000 __vector_7
0000008a g       .text 00000000 __do_clear_bss
00810000 g       .debug_aranges 00000000 __eeprom_end
00000000 g       .text 00000000 __vectors
00800100 g       .text 00000000 __data_end
00000000  w      .text 00000000 __vector_default
000000a2  w      .text 00000000 __vector_5
00000158 g     F .text 00000076 init
00000068 g       .text 00000000 __ctors_start
00000074 g       .text 00000000 __do_copy_data
00800100 g       .bss 00000000 __bss_start
000000aa g     F .text 0000001e main
000000a2  w      .text 00000000 __vector_4
00000000  w      *ABS* 00000000 __heap_end
000000a2  w      .text 00000000 __vector_9
000000a2  w      .text 00000000 __vector_2
000000a2  w      .text 00000000 __vector_21
000000a2  w      .text 00000000 __vector_15
000000a6 g     F .text 00000002 setup
00000068 g       .text 00000000 __dtors_start
00000068 g       .text 00000000 __ctors_end
000008ff  w      *ABS* 00000000 __stack
00800100 g       .text 00000000 _edata
00800109 g       .debug_aranges 00000000 _end
000000a2  w      .text 00000000 __vector_8
000001ce  w      .text 00000000 exit
000001ce g       .text 00000000 _exit
000000a2  w      .text 00000000 __vector_14
000000a2  w      .text 00000000 __vector_10
000000c8 g     F .text 00000090 __vector_16
00800100 g       .text 00000000 __data_start
000000a2  w      .text 00000000 __vector_18
000000a2  w      .text 00000000 __vector_20

Hex dump of sections deleted...

Disassembly of section .text:

00000000 <__vectors>:
   0: 0c 94 34 00 jmp 0x68 ; 0x68 <__ctors_end>
   4: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
   8: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
   c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  10: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  14: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  18: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  1c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  20: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  24: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  28: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  2c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  30: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  34: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  38: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  3c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  40: 0c 94 64 00 jmp 0xc8 ; 0xc8 <__vector_16>
  44: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  48: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  4c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  50: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  54: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  58: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  5c: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  60: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>
  64: 0c 94 51 00 jmp 0xa2 ; 0xa2 <__bad_interrupt>

00000068 <__ctors_end>:
  68: 11 24       eor r1, r1
  6a: 1f be       out 0x3f, r1 ; 63
  6c: cf ef       ldi r28, 0xFF ; 255
  6e: d8 e0       ldi r29, 0x08 ; 8
  70: de bf       out 0x3e, r29 ; 62
  72: cd bf       out 0x3d, r28 ; 61

00000074 <__do_copy_data>:
  74: 11 e0       ldi r17, 0x01 ; 1
  76: a0 e0       ldi r26, 0x00 ; 0
  78: b1 e0       ldi r27, 0x01 ; 1
  7a: e2 ed       ldi r30, 0xD2 ; 210
  7c: f1 e0       ldi r31, 0x01 ; 1
  7e: 02 c0       rjmp .+4       ; 0x84 <.do_copy_data_start>

00000080 <.do_copy_data_loop>:
  80: 05 90       lpm r0, Z+
  82: 0d 92       st X+, r0

00000084 <.do_copy_data_start>:
  84: a0 30       cpi r26, 0x00 ; 0
  86: b1 07       cpc r27, r17
  88: d9 f7       brne .-10     ; 0x80 <.do_copy_data_loop>

0000008a <__do_clear_bss>:
  8a: 11 e0       ldi r17, 0x01 ; 1
  8c: a0 e0       ldi r26, 0x00 ; 0
  8e: b1 e0       ldi r27, 0x01 ; 1
  90: 01 c0       rjmp .+2       ; 0x94 <.do_clear_bss_start>

00000092 <.do_clear_bss_loop>:
  92: 1d 92       st X+, r1

00000094 <.do_clear_bss_start>:
  94: a9 30       cpi r26, 0x09 ; 9
  96: b1 07       cpc r27, r17
  98: e1 f7       brne .-8       ; 0x92 <.do_clear_bss_loop>
  9a: 0e 94 55 00 call 0xaa ; 0xaa <main>
  9e: 0c 94 e7 00 jmp 0x1ce ; 0x1ce <_exit>

000000a2 <__bad_interrupt>:
  a2: 0c 94 00 00 jmp 0 ; 0x0 <__vectors>

000000a6 <setup>:
  a6: 08 95       ret

000000a8 <loop>:
  a8: 08 95       ret

000000aa <main>:
  aa: cf 93       push r28
  ac: df 93       push r29
  ae: 0e 94 ac 00 call 0x158 ; 0x158 <init>
  b2: 0e 94 53 00 call 0xa6 ; 0xa6 <setup>
  b6: c0 e0       ldi r28, 0x00 ; 0
  b8: d0 e0       ldi r29, 0x00 ; 0
  ba: 0e 94 54 00 call 0xa8 ; 0xa8 <loop>
  be: 20 97       sbiw r28, 0x00 ; 0
  c0: e1 f3       breq .-8       ; 0xba <main+0x10>
  c2: 0e 94 00 00 call 0 ; 0x0 <__vectors>
  c6: f9 cf       rjmp .-14     ; 0xba <main+0x10>

000000c8 <__vector_16>:
  c8: 1f 92       push r1
  ca: 0f 92       push r0
  cc: 0f b6       in r0, 0x3f ; 63
  ce: 0f 92       push r0
  d0: 11 24       eor r1, r1
  d2: 2f 93       push r18
  d4: 3f 93       push r19
  d6: 8f 93       push r24
  d8: 9f 93       push r25
  da: af 93       push r26
  dc: bf 93       push r27
  de: 80 91 04 01 lds r24, 0x0104
  e2: 90 91 05 01 lds r25, 0x0105
  e6: a0 91 06 01 lds r26, 0x0106
  ea: b0 91 07 01 lds r27, 0x0107
  ee: 30 91 08 01 lds r19, 0x0108
  f2: 01 96       adiw r24, 0x01 ; 1
  f4: a1 1d       adc r26, r1
  f6: b1 1d       adc r27, r1
  f8: 23 2f       mov r18, r19
  fa: 2d 5f       subi r18, 0xFD ; 253
  fc: 2d 37       cpi r18, 0x7D ; 125
  fe: 20 f0       brcs .+8       ; 0x108 <__vector_16+0x40>
 100: 2d 57       subi r18, 0x7D ; 125
 102: 01 96       adiw r24, 0x01 ; 1
 104: a1 1d       adc r26, r1
 106: b1 1d       adc r27, r1
 108: 20 93 08 01 sts 0x0108, r18
 10c: 80 93 04 01 sts 0x0104, r24
 110: 90 93 05 01 sts 0x0105, r25
 114: a0 93 06 01 sts 0x0106, r26
 118: b0 93 07 01 sts 0x0107, r27
 11c: 80 91 00 01 lds r24, 0x0100
 120: 90 91 01 01 lds r25, 0x0101
 124: a0 91 02 01 lds r26, 0x0102
 128: b0 91 03 01 lds r27, 0x0103
 12c: 01 96       adiw r24, 0x01 ; 1
 12e: a1 1d       adc r26, r1
 130: b1 1d       adc r27, r1
 132: 80 93 00 01 sts 0x0100, r24
 136: 90 93 01 01 sts 0x0101, r25
 13a: a0 93 02 01 sts 0x0102, r26
 13e: b0 93 03 01 sts 0x0103, r27
 142: bf 91       pop r27
 144: af 91       pop r26
 146: 9f 91       pop r25
 148: 8f 91       pop r24
 14a: 3f 91       pop r19
 14c: 2f 91       pop r18
 14e: 0f 90       pop r0
 150: 0f be       out 0x3f, r0 ; 63
 152: 0f 90       pop r0
 154: 1f 90       pop r1
 156: 18 95       reti

00000158 <init>:
 158: 78 94       sei
 15a: 84 b5       in r24, 0x24 ; 36
 15c: 82 60       ori r24, 0x02 ; 2
 15e: 84 bd       out 0x24, r24 ; 36
 160: 84 b5       in r24, 0x24 ; 36
 162: 81 60       ori r24, 0x01 ; 1
 164: 84 bd       out 0x24, r24 ; 36
 166: 85 b5       in r24, 0x25 ; 37
 168: 82 60       ori r24, 0x02 ; 2
 16a: 85 bd       out 0x25, r24 ; 37
 16c: 85 b5       in r24, 0x25 ; 37
 16e: 81 60       ori r24, 0x01 ; 1
 170: 85 bd       out 0x25, r24 ; 37
 172: ee e6       ldi r30, 0x6E ; 110
 174: f0 e0       ldi r31, 0x00 ; 0
 176: 80 81       ld r24, Z
 178: 81 60       ori r24, 0x01 ; 1
 17a: 80 83       st Z, r24
 17c: e1 e8       ldi r30, 0x81 ; 129
 17e: f0 e0       ldi r31, 0x00 ; 0
 180: 10 82       st Z, r1
 182: 80 81       ld r24, Z
 184: 82 60       ori r24, 0x02 ; 2
 186: 80 83       st Z, r24
 188: 80 81       ld r24, Z
 18a: 81 60       ori r24, 0x01 ; 1
 18c: 80 83       st Z, r24
 18e: e0 e8       ldi r30, 0x80 ; 128
 190: f0 e0       ldi r31, 0x00 ; 0
 192: 80 81       ld r24, Z
 194: 81 60       ori r24, 0x01 ; 1
 196: 80 83       st Z, r24
 198: e1 eb       ldi r30, 0xB1 ; 177
 19a: f0 e0       ldi r31, 0x00 ; 0
 19c: 80 81       ld r24, Z
 19e: 84 60       ori r24, 0x04 ; 4
 1a0: 80 83       st Z, r24
 1a2: e0 eb       ldi r30, 0xB0 ; 176
 1a4: f0 e0       ldi r31, 0x00 ; 0
 1a6: 80 81       ld r24, Z
 1a8: 81 60       ori r24, 0x01 ; 1
 1aa: 80 83       st Z, r24
 1ac: ea e7       ldi r30, 0x7A ; 122
 1ae: f0 e0       ldi r31, 0x00 ; 0
 1b0: 80 81       ld r24, Z
 1b2: 84 60       ori r24, 0x04 ; 4
 1b4: 80 83       st Z, r24
 1b6: 80 81       ld r24, Z
 1b8: 82 60       ori r24, 0x02 ; 2
 1ba: 80 83       st Z, r24
 1bc: 80 81       ld r24, Z
 1be: 81 60       ori r24, 0x01 ; 1
 1c0: 80 83       st Z, r24
 1c2: 80 81       ld r24, Z
 1c4: 80 68       ori r24, 0x80 ; 128
 1c6: 80 83       st Z, r24
 1c8: 10 92 c1 00 sts 0x00C1, r1
 1cc: 08 95       ret

000001ce <_exit>:
 1ce: f8 94       cli

000001d0 <__stop_program>:
 1d0: ff cf       rjmp .-2       ; 0x1d0 <__stop_program>

Disassembly of section .bss:

00800100 <__bss_start>:
  800100: 00 00       nop
...

00800104 <timer0_millis>:
  800104: 00 00       nop
...

00800108 <timer0_fract>:
...

Deleted very long disassembly of the debug support sections.