Well, don't leave me hanging! What problem did you find with the characters? Maybe your module is not the same one. Looking at your "0" which is 0xc0, it is the same.
Anyway, I'm long past that code now. Here is the latest version which uses timer2 instead of micros() for timing. Again, if there is something that I have missed, or a possible module incompatibility, I would like to know exactly what it is.
// controls 4 digit 7 segment display
// Unknown manufacturer - labelled "4-bit LED Digital Tube Module"
// working timer interrupt version 1.03
// 2015-05-09 by aarg
// extended character definitions:
#define LETTER_G 16
#define LETTER_H 17
#define LETTER_J 18
#define LETTER_L 19
#define LETTER_P 20
#define LETTER_U 21
#define LETTER_SPACE 22
#define LETTER_MINUS 23
const byte numDigits = 4;
// control pin definitions:
int shiftClock = 5; //module pin label SCLK
int latchClock = 6; //module pin label RCLK
int displayOut = 7; //module pin label DIO
// count variables:
unsigned long previousCountUp = 0; // will store last time of count
const long intervalCountUp = 100; // interval at which to count (milliseconds)
unsigned long previousText = 0; // will store last time of count
const int intervalOnText = 1200; // interval at which to count (milliseconds)
const unsigned long intervalOffText = 8000; // interval at which to count (milliseconds)
unsigned long intervalText = intervalOnText; // interval at which to count (milliseconds)
unsigned long previousDecimalPoint = 0; // will store last time of count
const int intervalDecimalPoint = 80; // interval at which to count (milliseconds)
// demo variables
byte PIGS[][numDigits] =
{
{LETTER_H, 0xE, LETTER_L, LETTER_P},
{LETTER_P, 1, LETTER_G, 5},
{LETTER_G, LETTER_U, LETTER_L, LETTER_P},
{LETTER_J, LETTER_U, LETTER_G, 5},
{LETTER_SPACE, 0, 0xf, LETTER_SPACE},
{5, LETTER_L, 0, LETTER_P}
};
byte testDigits[numDigits] = {0};
byte* displayLocation = testDigits;
int showText = 6;
int charCount = -1150;
void setup ()
{
// configure outputs:
pinMode(shiftClock, OUTPUT);
pinMode(latchClock, OUTPUT);
pinMode(displayOut, OUTPUT);
//******************************
// timer code
// Timer 2 - gives us the display segment refresh interval
TCCR2A = 0; // reset Timer 2
TCCR2B = 0;
TCCR2A = bit (WGM21) ; // configure as CTC mode
// 16 MHz clock (62.5 nS per tick) - prescaled by 256
// counter increments every 16 µS.
// There are 8 segments and 4 digits to scan every 1/60th of a second
// so we count 32 of them at 60Hz, giving a display refresh interval of 512 µS.
#define scanRateHz 60 // tested, maximum that works is 88 using Wire library and Serial
#define displayScanCount 1000000L / numDigits / 8 / scanRateHz / 16
OCR2A = displayScanCount; // count up to 32 @ 60Hz
// Timer 2 - interrupt on match (ie. every segment refresh interval)
TIMSK2 = bit (OCIE2A); // enable Timer2 Interrupt
TCNT2 = 0; // counter to zero
// Reset prescalers
GTCCR = bit (PSRASY); // reset prescaler now
// start Timer 2
TCCR2B = bit (CS21) | bit (CS22) ; // prescaler of 256
// ***** end of timer setup ******************************
} // end of setup()
void loop()
{
// no need to call the display scan routine, as it is timer driven
// Here is some non-blocking test code...
//
// see if it's time to increment the counter
unsigned long currentMillis = millis();
if (currentMillis - previousCountUp >= intervalCountUp)
{
previousCountUp = currentMillis;
// actions to perform for this interval:
if (++charCount > 10010)
{
charCount = -1010;
}
modulePrint(charCount);
// modulePrint() can be used to display any valid integer value at any time.
// So it could also display sensor readings or other information.
}
if (currentMillis - previousText >= intervalText)
{
previousText = currentMillis;
// actions to perform for this interval:
showText = ++showText % 7;
if (showText == 6)
{
displayLocation = testDigits;
intervalText = intervalOffText;
}
else
{
displayLocation = PIGS[showText];
intervalText = intervalOnText;
}
}
if (currentMillis - previousDecimalPoint >= intervalDecimalPoint)
{
previousDecimalPoint = currentMillis;
// actions to perform for this interval:
for (int i=0; i<numDigits; i++)
{
PIGS[showText%6][random(4)] |= 0x80;
PIGS[showText%6][random(4)] &= 0x7f;
}
}
} // end of loop()
//***********************************************
//
// display update ISR
//
// every time this routine is called, 16 bits are shifted into the display
// and latched.
// The first 8 bits is the segment data, and first 4 bits of the second byte are
// the segment select.
ISR (TIMER2_COMPA_vect)
{
// segment list to make a seven segment font
const byte NUM_PLUS_SYMBOL_FONT[] = {
0b00111111, // 0
0b00000110, // 1
0b01011011, // 2
0b01001111, // 3
0b01100110, // 4
0b01101101, // 5
0b01111101, // 6
0b00000111, // 7
0b01111111, // 8
0b01101111, // 9
0b01110111, // A
0b01111100, // b
0b00111001, // C
0b01011110, // d
0b01111001, // E
0b01110001, // F
0b00111101, // (71) G
0b01110110, // (72) H
0b00011110, // (74) J
0b00111000, // (76) L
0b01110011, // (80) P
0b00111110, // (85) U
0b00000000, // (32) <space>
0b01000000, // (45) -
};
static byte digit = 0;
static byte segment = 0x80;
byte tempDigit = displayLocation[digit];
// send segment data
shiftOut(displayOut, shiftClock, MSBFIRST,
~(segment & ((NUM_PLUS_SYMBOL_FONT[tempDigit & 0x7f]) | (tempDigit & 0x80))) );
// send digit select data
shiftOut(displayOut, shiftClock, MSBFIRST, 8 >> digit);
// data is now in the display shift register, so latch to LEDs
digitalWrite(latchClock, LOW);
digitalWrite(latchClock, HIGH);
// increment variables to select the next segment and possibly the next digit:
//
segment = segment >> 1;
if (segment == 0)
{
segment = 0x80;
digit++;
if (digit >= numDigits)
digit = 0;
}
}
// *********** end of display update ISR *****************
// Print an integer to the display buffer.
// Values between -999 and 9999 will be displayed, else an error message.
//
void modulePrint(int val)
{
const byte HIGH_ERROR[] = {LETTER_H, 1, LETTER_G, LETTER_H};
const byte LOW_ERROR[] = {LETTER_MINUS, LETTER_L, 0, LETTER_MINUS};
// validate number size for 4 digits:
if (val > 9999)
memcpy(testDigits, HIGH_ERROR, 4); //high number error message
else if (val < -999)
memcpy(testDigits, LOW_ERROR, 4); //low number error message
//negative number
else if (val < 0)
{
// prepare first symbol to print when digits are exhausted
boolean needsMinusSign = true;
int posval = -val; //use the absolute value of the number
for (int i = 3; i >= 0; i--)
{
if (posval > 0)
testDigits[i] = posval % 10; // digits to print
else if (needsMinusSign)
{
testDigits[i] = LETTER_MINUS; // print one minus sign
needsMinusSign = false;
}
else
testDigits[i] = LETTER_SPACE; // the rest are spaces
posval /= 10;
}
}
else // it is a positive number
{
for (int i = 3; i >= 0; i--)
{
if (val > 0 || i == 3)
testDigits[i] = val % 10; // digits to print
else
testDigits[i] = LETTER_SPACE; // the rest are spaces
val /= 10;
}
}
}
I abandoned the inverted bit logic for font definitions, to make them easier to maintain. It also enabled me to adapt a font from another source. 
I'm putting this out early to meet your response. As such, it lacks complete documentation of all the features. Here are some changes:
- display driver supports a decimal point attribute for each character. If bit 7 of the byte in the display buffer is set, the decimal point will be displayed.
- indexed display location. A pointer is set to the display location. You can change the display buffer location just by changing the pointer, thus you can support multiple display "windows"
-improved numeric print function. Has range check and error display, also supports negative numbers
-allows the use of delay() and other blocking code in loop() - not that you should do that...