const byte datapin = PB5;
const byte clockpin = PB4;
const byte latchpin = PB3;
const byte digits = 8;

volatile uint8_t isr_buffer[digits];
volatile uint8_t isr_digit = 0;

// which pin on the 1st shift register illuminates which digit?
uint8_t digit_pin_map[digits] = {
  1<<7, 1<<0, 1<<1, 1<<2,
  1<<6, 1<<3, 1<<4, 1<<5
};

SIGNAL(TIM0_COMPA_vect) {
  shiftOut(datapin, clockpin, LSBFIRST, isr_buffer[isr_digit]);
  shiftOut(datapin, clockpin, LSBFIRST, digit_pin_map[isr_digit]);
  digitalWrite(latchpin, HIGH);
  digitalWrite(latchpin, LOW);
  if (++isr_digit == sizeof(isr_buffer))
    isr_digit = 0;
}

void setup() {
  pinMode(datapin, OUTPUT);
  pinMode(clockpin, OUTPUT);
  pinMode(latchpin, OUTPUT);
  // piggy back on the existing Timer0
  OCR0A = 0xAF;
  TIMSK |= _BV(OCIE0A);
}

void loop() {
  uint32_t n = 0;
  while (1) {
    write_7seg(n++, 16);
  }
}

// which pin on the 2nd shift register illuminates which segment?
enum SegmentBits {
  seg_a = 0b10000000, seg_b = 0b00100000, seg_c = 0b00001000,
  seg_d = 0b00000010, seg_e = 0b00000001, seg_f = 0b01000000,
  seg_g = 0b00010000, seg_dp = 0b00000100
};

const uint8_t segment_map[] = {
  seg_a | seg_b | seg_c | seg_d | seg_e | seg_f, // 0
  seg_b | seg_c, // 1
  seg_a | seg_b | seg_g | seg_e | seg_d, // 2
  seg_a | seg_b | seg_g | seg_c | seg_d, // 3
  seg_f | seg_g | seg_b | seg_c, // 4
  seg_a | seg_f | seg_g | seg_c | seg_d, // 5
  seg_a | seg_f | seg_e | seg_d | seg_c | seg_g, // 6
  seg_a | seg_b | seg_c, // 7
  seg_a | seg_b | seg_c | seg_d | seg_e | seg_f | seg_g, // 8
  seg_a | seg_b | seg_c | seg_d | seg_f | seg_g, // 9

  seg_e | seg_f | seg_a | seg_b | seg_c | seg_g, // A
  seg_f | seg_e | seg_d | seg_c | seg_g, // b
  seg_g | seg_e | seg_d, // c
  seg_g | seg_e | seg_d | seg_c | seg_b, // d
  seg_a | seg_f | seg_g | seg_e | seg_d, // E
  seg_a | seg_f | seg_g | seg_e, // F
};

// base can be 2 to 16.
void write_7seg(uint32_t num, uint8_t base) {
  for (uint8_t i = 0; i < digits; i++) {
    byte digit = num % base;
    num /= base;
    isr_buffer[i] = ~segment_map[digit];
  }
  // overflow? set all the decimal points
  if (num != 0) {
    for (uint8_t i = 0; i < digits; i++)
      isr_buffer[i] ^= seg_dp;
  }
}
ATTINY8520PU
74HC595
74HC595