HDSP-2011

Some time ago I got several HDSP-2011 displays. They look cool: ceramic, gold-plated pins and traces, sapphire glass… Driving them is not exactly straight-forward. Pixel data has to be shifted in, one column at a time, and then column pin has to be connected to approximately +3V supply. Columns need multiplexing, which means sending serial data (almost) continuously while displaying. I choose to use one of Atmega8 chips I have in my junk.

First approach was purely software based: bit-banging Data In and Clock inputs of HDSP-2011. Then I connected two displays in a daisy-chain (Data Out of the first display to Data In of the second one, and common Clock signal). I was thinking that maybe USART could be used to send the data, offloading the microcontroller. It could, but only when using newer Atmega chips (Atmega 48/88/168 has USART SPI Master mode). Wait a minute, why not use SPI interface instead? It’s confirmed working. So I used (and modified) Mike’s code.

Schematic was never drawn (the whole thing was re-soldered a few times). The connections are:

Atmega8 HDSP-2011
MOSI (PB3) Data In (pin 12)
SCK (PB5) Clock (pin 10)

Column pins are connected to emiter followers (FMMT493TA), driven by PD3 to PD7 Atmega pins (through 330Ω resistors). 5V supply is connected through two silicon diodes, effectively decreasing the voltage to required 2.8 ÷ 3.1V (diodes to 3.6V, transistor to 2.9V). Atmega is clocked at 16MHz, using quartz oscillator (not required, I needed a stable clock for other purpose).
And that’s it. The rest is in the software (warning: it’s quite messy):

#include <avr/io.h>
#include <avr/sleep.h>
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

#define SHIFT_REGISTER DDRB
#define SHIFT_PORT PORTB
#define DATA (1<<PB3)           //MOSI (SI)
#define LATCH (1<<PB2)          //SS   (RCK)
#define CLOCK (1<<PB5)          //SCK  (SCK)

#define COLUMN_REGISTER DDRD
#define COLUMN_PORT PORTD
#define COLUMN0 (1<<PD3)
#define COLUMN1 (1<<PD4)
#define COLUMN2 (1<<PD5)
#define COLUMN3 (1<<PD6)
#define COLUMN4 (1<<PD7)

volatile uint8_t bitplane = 0, column = 0, counter = 0;

volatile uint8_t framebuffer[] = { 
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00

};

void hdsp2011_put_pixel(int8_t x, int8_t y, int8_t color) {
        int8_t moddiv5 [] = { 
                0x00, 0x10, 0x20, 0x30, 0x40, 
                0x01, 0x11, 0x21, 0x31, 0x41, 
                0x02, 0x12, 0x22, 0x32, 0x42, 
                0x03, 0x13, 0x23, 0x33, 0x43, 
                0x04, 0x14, 0x24, 0x34, 0x44, 
                0x05, 0x15, 0x25, 0x35, 0x45, 
                0x06, 0x16, 0x26, 0x36, 0x46, 
                0x07, 0x17, 0x27, 0x37, 0x47
        };

        //int16_t pixnum = (x/5)*7 + (x%5)*56 + y;
        // Is the following faster than the line above?
        int16_t pixnum = (moddiv5[x] & 0x0F)*7 + (moddiv5[x]>>4)*56 + y;

        int16_t addr = pixnum >> 3;
        int8_t bit = pixnum & 0x07;
        // Clear the pixel
        framebuffer[addr] &= ~(1 << bit);
        framebuffer[addr+35] &= ~(1 << bit);
        // Draw new pixel
        if (color & 1) framebuffer[addr] |= (1 << bit);
        if (color & 2) framebuffer[addr+35] |= (1 << bit);

}
        volatile int8_t x = 0, y = 0, dx = 1, dy = 1, color = 2; 
        volatile uint8_t bnc_cntr = 0;

/***********************
 * PROGRAM STARTS HERE *
 ***********************/

int main(void) {
  // Setup IO
  SHIFT_REGISTER |= (DATA | LATCH | CLOCK);     // Set control pins as outputs
  SHIFT_PORT &= ~(DATA | LATCH | CLOCK);                // Set control pins low

  // Setup SPI
  // SPI enable, master, rising SCK trailing edge
  // *** upside-down
  //SPCR = (1<<SPE) | (1<<MSTR) | (1<<CPOL) | (1<<DORD);
  // *** normal
  SPCR = (1<<SPE) | (1<<MSTR) | (1<<CPOL);

  // Setup column port
  // Columns at bits 0 to 4 as outputs
  COLUMN_REGISTER = COLUMN0 | COLUMN1 | COLUMN2 | COLUMN3 | COLUMN4;
  // Drive columns blank
  COLUMN_PORT &= ~(COLUMN0 | COLUMN1 | COLUMN2 | COLUMN3 | COLUMN4);

  // Pull LATCH low (Important: this is necessary to start the SPI transfer!)
  SHIFT_PORT &= ~LATCH;
        
  // Timer prescaler: 
  // 001 1
  // 010 /8
  // 011 /64
  // 100 /256
  // 101 /1024
  // 110 ext. rising edge
  // 111 ext. falling edge
  TCCR0 |= (1<<CS01);// | (1<<CS00);
  //TCCR0 |= (1<<CS02);// | (1<<CS00);
  // Enable timer overflow interrupt
  TIMSK |= (1<<TOIE0);
  // Initialize timer
  TCNT0 = 0;
  
  // Enable interrupts
  sei();

  while(1) {
          sleep_enable();
          //sei();       
          sleep_cpu();
          sleep_disable();
  }
        return 0;
}

void frame_complete() {
        // Bouncing Pixel!
        if ((counter == 10)) {
                hdsp2011_put_pixel(x, y, (color-2) % 4);
                x += dx;
                y += dy;
                
                if (x < 0) {
                        x = 0;
                        //goto out_x;   // Use goto if you *need* to save a few bytes of flash
                        dx = -dx;
                        bnc_cntr++;
                }
                if(x > 39) { 
                        x = 39;
//out_x:
                        dx = -dx;
                        bnc_cntr++;
                }
                if (y < 0) {
                        y = 0;
                        //goto out_y;
                        dy = -dy;
                        bnc_cntr++;
                }
                if (y > 6) {
                        y = 6;
//out_y:
                        dy = -dy;
                        bnc_cntr++;
                }
                hdsp2011_put_pixel(x, y, 3);
                counter = 0;
                if (bnc_cntr > 64) { 
                        color++;
                        bnc_cntr = 0;
                }
        }
}

ISR(TIMER0_OVF_vect) {
        int8_t i;
        // Blank the display
        COLUMN_PORT &= ~(COLUMN0 | COLUMN1 | COLUMN2 | COLUMN3 | COLUMN4);

        for(i=6; i>=0; i--) {
                // There was simpler way, it looks better (on the display) this way though:
                if (bitplane == 0) {
                        SPDR = framebuffer[column*7 + i];
                } else {
                        if (bitplane < 4) {
                                SPDR = framebuffer[35 + column*7 + i];
                        } else {
                                SPDR = (framebuffer[column*7 + i] & framebuffer[35 + column*7 + i]);
                        }
                }

                /*
                switch (bitplane) {
                        case 0:
                                SPDR = framebuffer[column*7 + i];
                                break;
                        case 1:
                        case 2:
                        case 3:
                                SPDR = framebuffer[35+column*7 + i];
                                break;
                        default:
                                SPDR = (framebuffer[column*7 + i] & framebuffer[35+column*7 + i]);
                }
                */
                while(!(SPSR & (1<<SPIF)));
        }
        // *** upside-down
        //COLUMN_PORT |= (1 << (7-column));     // Select proper column (un-blank) 
        // *** normal
        COLUMN_PORT |= (8 << column);   // Select proper column (un-blank)
        bitplane++;
        if (bitplane > 6) {
                bitplane = 0;
                column++;
        }

        // Once every screen refresh:
        if (column == 5) {
                counter++;
                frame_complete(); // This could run in a main loop, keeping ISR short, but...
                column = 0;
        }
}

Quick explanation: ISR routine sends “framebuffer” data to the HDSP-2011s using SPI. It also allows 3 levels of brightness, independently for each pixel. Organisation is bitplane-wise (with hard-coded offset of 35). This allows displaying 5×7 fonts while using little CPU cycles (just a few OR operations). The plan was (is!) to make anti-aliased fonts, or using brightness levels as phosphor display emulation.

RTL-SDR upconverter

Some time ago I purchased TECSUN PL-600. It was nice radio to start with. Then I got FUNCube Dongle, and it was great fun to play with SDR. I connected both devices: FUNCube dongle to AM IF of TECSUN receiver. This was my first SDR setup able to receive HF band. As we all know, this is the band where most fun is!

After that, Remco encouraged me to experiment with diode mixers.

This is initial version of the converter: something I started with, and it was performing nicely since January until September:

I used SCM-1 mixer, almost any Mini Circuits mixer would do. Choices are: SBL-1, SRA-1, ADE-1 or RMS-1.
100nF ceramic and 16µF electrolytic capacitors near generator should be enough. It could be then powered directly from USB. Here it was powered from “general 12V supply”, so I wanted good noise isolation.

My test board:

converter

  • 70-to-125MHz filter is a disaster
  • there should be capacitor between generator and attenuator (was added later)
  • capacitor between generator output and mixer should be gone (was later moved to generator)

The best generators for LO (local oscillator) were those in metal cans. Plastic ones (I tested only two EPSON) had too large noise, visible around every stronger signal.

In final version 3.3V 100.000M CO19025 was used. It worked on 5V supply for almost 4 months non-stop.
SARONIX 75.0000MHz was used before, also in metal can, and it was also good.

Antenna used was K9AY with bistable relays as direction switches (material for another article).

Overall performance was good. Converter covered all bands, worked with both FUNCube Dongle PRO and RTL-SDR. The signal was too broadband though, and sometimes RTL-SDR overloaded. Mostly annoying were strong AM stations, and LO leaking through the mixer. To solve “AM stations” problem I built another input filter: band-pass 3500 to 5500 kHz. Believe me, most interesting things happen between 3 and 6­ – 7 MHz (sample). Also, next version of converter allowed me to receive weaker stations, but that’s another story.

The Borg style clock

Few days ago I discussed with a friend about making Nixie clock. After a while we concluded that nothing compares to neon discharge light, not even 1970-s LED matrix displays. And green colour looks Borg-ish. That gave me the idea…

Image

Image

Yep, those are green 5×7 1970’s LED matrix displays I had in my junkyard. And this will be The Borg style clock. I don’t like those tiny SMD transistors though. They simply don’t belong here.

Nice thing about using matrix displays is that they can display text or graphics, not just numbers. They have no driver, so it’s possible to address any particular pixel. There’s also drawback: it’s tricky to make them work. More effort, more possibilities.

After another evening:

Image

More photos here. That’s just a part of driver! Most part however: three stacked 74ALS373, de-soldered from some old board. There are 5 segments, 7×5 each, which gives 7×25 LEDs. Cathodes are connected as rows, anodes are columns. The last left alone resistor is 25th column, left to be connected to microcontroller’s GPIO. Rows will be connected to ground through transistors driven by shift register.

Now I’m waiting for replacement transistors. I just hope I didn’t make any mistake there!