Hey, HDD Clock people!

I haven’t forgotten about you! I’ve been working the last couple of days on rewriting the clock code for the TI-Launchpad. I plan to release the source and build instructions sometime in January. I’ll update you on my progress soon.

My First FPGA Project — Larson Scanner

I’ve decided to take a little break from MSP430 and learn something new. FPGA’s have always intrigued me, so I picked up a Nexys 2 from Digilent and started reading a book called FPGA Prototyping by Verilog Examples by Pong P. Chu. (That reminds me, I still need to finish Pong for the MSP430.)

I read several chapters to familiarize myself with the hardware and language, but nothing beats learning by doing. My summer construction job was rained out today, so I decided to grab a Rockstar energy drink and write my first Verilog project. I figured a Kitt light/Larson Scanner/Cylon eye would be something I could figure out in a few hours. I’m not too sure how “correct” my code is, so I’d love to hear some suggestions.

module main
  (
  input wire clk,
  output wire [2:0] position,
  output wire direction,
  output wire [7:0] Led
    );
   
   
   // symbolic state declaration
  localparam   rising = 1'b1,
          falling = 1'b0;
              
  reg [24:0] position_state_count, brightness_state_count;
  reg dir_state_reg, dir_state_next;
  reg [2:0] position_state_reg, position_state_next;
  reg [3:0] brightnesses [7:0];
//  reg [2:0] brightnesses_next [7:0];
  
  // state register
  always @(posedge clk)
    begin
      dir_state_reg <= dir_state_next;
      position_state_reg <= position_state_next;
    end
  
  // next position state logic
  always @(posedge clk)
    if(position_state_count == 7500000)
      begin
        position_state_count = 0;
        if(dir_state_reg)
          position_state_next = position_state_reg + 1;
        else
          position_state_next = position_state_reg - 1;
      end
    else
      position_state_count = position_state_count + 1;
      
  // next direction state logic
  always @(posedge clk)
    if(position_state_reg == 7)
      dir_state_next = falling;
    else if(position_state_reg == 0)
      dir_state_next = rising;
      
//  assign position = position_state_reg;
//  assign direction = dir_state_reg;

  // brightness state logic
  always @(posedge clk)
    begin
      if(brightness_state_count == 1500000)
        begin
          brightness_state_count = 0;
          if(brightnesses[0] > 0)
            brightnesses[0] = brightnesses[0] - 1;
          if(brightnesses[1] > 0)
            brightnesses[1] = brightnesses[1] - 1;
          if(brightnesses[2] > 0)
            brightnesses[2] = brightnesses[2] - 1;
          if(brightnesses[3] > 0)
            brightnesses[3] = brightnesses[3] - 1;
          if(brightnesses[4] > 0)
            brightnesses[4] = brightnesses[4] - 1;
          if(brightnesses[5] > 0)
            brightnesses[5] = brightnesses[5] - 1;
          if(brightnesses[6] > 0)
            brightnesses[6] = brightnesses[6] - 1;
          if(brightnesses[7] > 0)
            brightnesses[7] = brightnesses[7] - 1;
        end
      else
        brightness_state_count = brightness_state_count + 1;
      brightnesses[position_state_reg] = 15;
    end
  
  // instantiate led pwm logic
  pwm led_ctl0 (.brightness(brightnesses[0]), .out(Led[0]), .clk(clk));
  pwm led_ctl1 (.brightness(brightnesses[1]), .out(Led[1]), .clk(clk));
  pwm led_ctl2 (.brightness(brightnesses[2]), .out(Led[2]), .clk(clk));
  pwm led_ctl3 (.brightness(brightnesses[3]), .out(Led[3]), .clk(clk));
  pwm led_ctl4 (.brightness(brightnesses[4]), .out(Led[4]), .clk(clk));
  pwm led_ctl5 (.brightness(brightnesses[5]), .out(Led[5]), .clk(clk));
  pwm led_ctl6 (.brightness(brightnesses[6]), .out(Led[6]), .clk(clk));
  pwm led_ctl7 (.brightness(brightnesses[7]), .out(Led[7]), .clk(clk));

endmodule

module pwm
  (
    input wire clk,
    // 16 levels of brightness
    input wire [3:0] brightness,
    output wire out
   );
  
  reg [2:0] counter_reg;
  wire [2:0] counter_next;
  
  always @(posedge clk)
    counter_reg <= counter_next;
  
  assign counter_next = counter_reg + 1;
  assign out =   (brightness == 4'b1111) ? 1'b1 :
              (brightness > counter_reg) ? 1'b1 : 1'b0;

endmodule

Constraints for Nexys 2:

# clock pin for Nexys 2 Board
NET "clk"   LOC = "B8"; # Bank = 0, Pin name = IP_L13P_0/GCLK8, Type = GCLK, Sch name = GCLK0

# Leds
NET "Led<0>"  LOC = "J14"; # Bank = 1, Pin name = IO_L14N_1/A3/RHCLK7, Type = RHCLK/DUAL, Sch name = JD10/LD0
NET "Led<1>"  LOC = "J15"; # Bank = 1, Pin name = IO_L14P_1/A4/RHCLK6, Type = RHCLK/DUAL, Sch name = JD9/LD1
NET "Led<2>"  LOC = "K15"; # Bank = 1, Pin name = IO_L12P_1/A8/RHCLK2, Type = RHCLK/DUAL, Sch name = JD8/LD2
NET "Led<3>"  LOC = "K14"; # Bank = 1, Pin name = IO_L12N_1/A7/RHCLK3/TRDY1, Type = RHCLK/DUAL, Sch name = JD7/LD3
NET "Led<4>"  LOC = "E17"; # Bank = 1, Pin name = IO, Type = I/O, Sch name = LD4? s3e500 only
NET "Led<5>"  LOC = "P15"; # Bank = 1, Pin name = IO, Type = I/O, Sch name = LD5? s3e500 only
NET "Led<6>"  LOC = "F4";  # Bank = 3, Pin name = IO, Type = I/O, Sch name = LD6? s3e500 only
NET "Led<7>"  LOC = "R4";  # Bank = 3, Pin name = IO/VREF_3, Type = VREF, Sch name = LD7? s3e500 only

Generating Random Numbers

I was looking for example code for RF2500 last night and came across some TI code for using the ADC as a random number generator. The function was in assembly, so I rewrote it in C:

bool getRandomBit(){
  ADC10CTL1 |= INCH_5;
  ADC10CTL0 |= SREF_1 + ADC10SHT_1 + REFON + ADC10ON;
  ADC10CTL0 |= ENC + ADC10SC;
  while(ADC10CTL1 & ADC10BUSY);
  return ADC10MEM & 0x01;
}

Pin 1.5 is floating unconnected, and is measured by the adc. The LSB is used as the random bit. There’s a twist though. Pin 1.4 is also floating unconnected, and is used as Vref+, so the top end of the range is floating as well. I thought that was pretty clever. Nice, TI!

I wrote a few console programs to help me visualize the randomness. It turned out the generator was biased toward producing 0′s. This function used with the previous function seemed to remove the bias:

EDIT: it was just dumb luck that the following function removed the bias. I did the math afterward, and this 0′s the bias when getRandomBit() returns a 1 30% of the time. I guess that means the ’0′ to ’1′ ratio of getRandomBit() is close to 7 to 3. Not great. I’m mulling over the idea of using laser speckle as a cheap way to generate the randomness.

bool get0BiasRandomBit(){
  if(getRandomBit()){
    if(getRandomBit())
      return 0;
    else
      return 1;
  }
  else{
    if(getRandomBit())
      return 1;
    else
      return 0;
  }
}

The following examples require the Full-duplex software UART for launchpad library, which was assembled by Rickta59.

To test for bias, I displayed a meandering line in a serial console. If I get more 0′s than 1′s, the line should slowly skew to the left or right:

#include "msp430g2231.h"
#include "config.h"
#include "softserial.h"
#include <stdbool.h>

#define CONSOLE_WIDTH          80

bool getRandomBit();
bool get0BiasRandomBit();

void main(){
  int linePositon = CONSOLE_WIDTH / 2;
  char cursorPosition;
  
  
  DCOCTL = CALDCO_16MHZ;
  BCSCTL1 = CALBC1_16MHZ;
  
  WDTCTL = WDTPW + WDTHOLD;          // Stop WDT
  
  SoftSerial_init();
  _enable_interrupts();
  
  while(1){
    for(cursorPosition = 0; cursorPosition < linePositon; cursorPosition++)
      SoftSerial_xmit('8');
    
    SoftSerial_xmit(' ');
    cursorPosition++;
    while(cursorPosition < CONSOLE_WIDTH){
      SoftSerial_xmit('8');
      cursorPosition++;
    }
    
    if(get0BiasRandomBit())
      linePositon++;
    else
      linePositon--;
    
    if(linePositon < 0)
      linePositon = CONSOLE_WIDTH + linePositon;
    else if(linePositon >= CONSOLE_WIDTH - 1)
      linePositon = linePositon - CONSOLE_WIDTH;
  }
}

bool get0BiasRandomBit(){
  if(getRandomBit()){
    if(getRandomBit())
      return 0;
    else
      return 1;
  }
  else{
    if(getRandomBit())
      return 1;
    else
      return 0;
  }
}

bool getRandomBit(){
  ADC10CTL1 |= INCH_5;
  ADC10CTL0 |= SREF_1 + ADC10SHT_1 + REFON + ADC10ON;
  ADC10CTL0 |= ENC + ADC10SC;
  while(ADC10CTL1 & ADC10BUSY);
  return ADC10MEM & 0x01;
}

and some example output from this code:

This sends a comma-separated list of random ints to the console:

#include "msp430g2231.h"
#include "config.h"
#include "softserial.h"
#include <string.h>

int adcGenRand16();
void reverse(char s[]);
void itoa(int n, char s[]);
void txString(char string[]);

void main(){
  int random;
  char string[7];
  
  DCOCTL = CALDCO_16MHZ;
  BCSCTL1 = CALBC1_16MHZ;
  
  WDTCTL = WDTPW + WDTHOLD;          // Stop WDT
  
  SoftSerial_init();
  _enable_interrupts();
  
  while(1){
    random = adcGenRand16();
    itoa(random, string);
    txString(string);
  }
}

void txString(char string[]){
  int iString = 0;
  while(string[iString] != 0){
    SoftSerial_xmit(string[iString]);
    iString++;
  }
  SoftSerial_xmit(',');
  SoftSerial_xmit(' ');
}

int adcGenRand16(){
  char bit;
  unsigned int random;
  
  for(bit = 0; bit < 16; bit++){
    ADC10CTL1 |= INCH_5;
    ADC10CTL0 |= SREF_1 + ADC10SHT_1 + REFON + ADC10ON;
    ADC10CTL0 |= ENC + ADC10SC;
    while(ADC10CTL1 & ADC10BUSY);
    random <<= 1;
    random |= (ADC10MEM & 0x01);
  }
  return random;
}

 /* itoa:  convert n to characters in s */
 void itoa(int n, char s[])
 {
     int i, sign;
 
     if ((sign = n) < 0)  /* record sign */
         n = -n;          /* make n positive */
     i = 0;
     do {       /* generate digits in reverse order */
         s[i++] = n % 10 + '0';   /* get next digit */
     } while ((n /= 10) > 0);     /* delete it */
     if (sign < 0)
         s[i++] = '-';
     s[i] = '\0';
     reverse(s);
 }
 
 /* reverse:  reverse string s in place */
 void reverse(char s[])
 {
     int i, j;
     char c;
 
     for (i = 0, j = strlen(s)-1; i<j; i++, j--) {
         c = s[i];
         s[i] = s[j];
         s[j] = c;
     }
 }

I guess I got a little sidetracked from my original purpose of getting started with the RF2500, but I had fun.

MIDI Synth is polyphonic

I did some work on the MIDI synth to make it polyphonic. With the help of George Lucas and John Williams, I was able to win the 43oh.com April 2011 Project of the Month Contest. Check out my thread on the 43oh forums for the details and to find out what this has to do with George Lucas.

It uses a cheap DAC breakout from Sparkfun and an LM386.

If you use MSP430 for fun or profit, the 43oh forums are a great resource with helpful people, so check them out!

MSP430 TV output update

Just a quick update. With the new MSP430G’s and their bigger flash space, I was able to increase the resolution of my TV output program. The resolution is now 192×240. When the new 16K MSP430G’s arrive, I plan on increasing this to 384×240, which will finally approach the native aspect ratio.

I just had to change a couple lines of code to get this going, and include the larger image file. I used an MSP430G2452 with 8K flash.

NOTE: This should be compiled in CCS under debug mode. Using release mode or different compilers will likely require adjustment of the software delay toward the bottom of the code.

Here’s the code.

The example images seen below:
miss_nature_close.h
miss_nature_wide.h

If you want to make your own images to use, first create a 192×240 monochrome bitmap. To convert the bitmap into a header.h file, I used a program called Image2Code from CrystalFontz. Use this setting to get the proper image format:

Arduino-like analogRead() and timing functions for the MSP430

I saw this article on Hack a Day today for a library that supports some Arduino-like functions. I thought I’d share a couple of code snippets I have for analogRead(), millis(), and delayMillis(). The delay function on the HaD article used a software delay loop. My millis() and delayMillis() use the WDT.

Here’s my take on the time functions:


#include "msp430g2231.h"

#define MCLK_FREQUENCY      1000000
#define WDT_DIVIDER        512

const unsigned long WDT_FREQUENCY = MCLK_FREQUENCY / WDT_DIVIDER;
volatile unsigned long wdtCounter = 0;

unsigned long millis(){
  return wdtCounter / ((float)WDT_FREQUENCY / 1000);
}

void delayMillis(unsigned long milliseconds){
  unsigned long wakeTime = wdtCounter + (milliseconds * WDT_FREQUENCY / 1000);
  while(wdtCounter < wakeTime);
}

void main(void){
  DCOCTL = CALDCO_1MHZ;
  BCSCTL1 = CALBC1_1MHZ;
  WDTCTL = WDTPW + WDTTMSEL + WDTIS1;
  IE1 |= WDTIE;
  _BIS_SR(GIE);
  
  // toggle pin every second
  P1DIR |= BIT0;
  while(1){
    P1OUT ^= BIT0;
    delayMillis(1000);
  }
}
    
#pragma vector=WDT_VECTOR
__interrupt void watchdog_timer(void){
  wdtCounter++;
}

And here’s the analog functions:


#include "msp430g2231.h"

#define ANALOG_PIN      4

unsigned int analogRead(){
  ADC10CTL0 |= ADC10SC;
  while(ADC10CTL1 & ADC10BUSY);
  return ADC10MEM;
}

void analogPinSelect(unsigned int pin){
  if(pin < 8){
    ADC10CTL0 &= ~ENC;
    ADC10CTL1 = pin << 12;
    ADC10CTL0 = ADC10ON + ENC + ADC10SHT_0;
  }
}

void main(void){
  unsigned int analogValue;
  
  DCOCTL = CALDCO_1MHZ;
  BCSCTL1 = CALBC1_1MHZ;
  WDTCTL = WDTPW + WDTHOLD;
  
  // read adc repeatedly
  analogPinSelect(ANALOG_PIN);
  while(1){
    analogValue = analogRead();
  }
}

They’re not pure Arduino, but close.

MSP430 Launchpad Simple MIDI Synth

It’s been awhile since I’ve posted anything, but I’ve been working on several projects. I’ve just been feeling lazy about writing them up. I thought this one might be the most enjoyable and useful to people. I’m going to keep it short and sweet, so please comment if you have any questions.

This is a simple square wave synthesizer. It has a standard midi interface, so it could be used with a midi keyboard or another midi controller.

Here’s the BOM:
1x TI Launchpad or another MSP430
1x 280 ohm resistor (can use internal pull-up instead)
1x 220 ohm resistor
1x Sharp PC900V optoisolator
1x Speaker (I just bought one from RadioShack)
1x MIDI female jack
1X diode
A midi cable and some kind of midi controller

You could probably get away with not using the optoisolator, but I felt like adhering to spec with the hardware. If you want to use another MSP430, be sure it has a USI and not a USCI, or you’ll have to rewrite some of the code. Also, the code expects to have the DCO calibrated for 16MHz. I think an approximation would work too, but if you’re too far off the midi clock will skew and the notes could be detuned as well.

The speaker gets hooked up to Launchpad ground and P1.2.

On the software side, the USI runs constantly and oversamples the midi data from the optoisolator. The oversampling is resolved with something like a state machine(?). I tried to use the USI properly, but I always seem to run into issues in using it with clockless serial. There’s probably a cleaner way to read the midi data, but it works, so I don’t mind. It’s all yours to fix!

Once we’re able to read the MIDI, it’s just a matter of properly adjusting the period of TimerA, the square wave sound generator, to get the proper note. The synth is monophonic, so some care is also taken to handle and store simultaneous on-notes.

Here’s the MIDI Synth main.c. This compiles on CCS.

By the way, you can use one optoisolator circuit to send data to multiple launchpads. Just assign each LP a different MIDI channel by changing this line of code:
#define MIDI_CHANNEL 0

So I hooked it up to my midi keyboard. That was OK. Then I downloaded some midi files and routed them through a USB – MIDI converter with Ableton Live. Now that gave me a warm, nerdy feeling inside.
Check it out:
Get Back

Zelda

Tainted Love

Try Again

TV Output Prototype


It still needs a battery holder. I might just buy one.

Adding the “missing” DCO calibration constants to MSP430G series

I’ve seen many posts in support forums about people having trouble getting their Launchpad/Value Line MSP430′s to run at 16MHz. The simplest way to do this is to set the BCS registers to the factory-calibrated DCO constants. Unfortunately, TI only includes one calibration for the Value Line, and it’s 1MHz. There’s speculation that this is what gives the G-series its “value.” A few of us at Four-Three-oh! had been discussing getting these constants back into flash, and we have a solution!

The process is pretty quick and painless, and I’ve posted a step-by-step guide on the 43oh forums.

You’ll need a watch crystal like the one that comes with launchpad, the code from the forum post, and the TI DCO library I previously adapted to work with the G-series. If you’re using launchpad and already have the crystal populated, you should be up and running at 16MHz in no time.

Launchpad monochrome NTSC composite video


Source code.

EDIT: I have updated the TV Output program to work with the newer 8K MSP430 Value Line chips. The resolution is much higher now.

EDIT: Now that we know how to flash DCO calibrations to MSP430G, this works without an external oscillator. That brings the external component list down to 2 resistors. Nice! Here’s the code to use if you have your constants calibrated.

I finally had time to get TV output working on my Launchpad over Thanksgiving break. I have a lot of half-finished projects sitting around, but this one has really been bugging me. This was sort of a self-enrichment project, so all the code is my own, however I got several ideas from the write-ups of other projects.

Right now it only displays one 1 image, stored in flash. If you want to change the image, you have to reprogram the chip. The image resolution is 192×40. I know that isn’t an ideal aspect ratio for televisions, but I was somewhat limited in my choices. I will explain shortly.

If you want to brush up on your composite video, I used http://www.batsocks.co.uk/readme/video_timing.htm as a reference throughout the project. The page is for PAL, but they give NTSC info as well. The general format is the same between NTSC and PAL, but the timings are just a little different. I’m using the “fake progressive” trick on that page, adapted for NTSC. I don’t want to get too far into the format, as it would probably double the size of this post. You probably only need a basic understanding of hSync, vSync and how a scanline is drawn.

The microcontroller must do three things with correct timing in order to output composite video:

  1. Output vSync signals at the beginning of every frame.
  2. After vSync, output hSync signals at the beginning of every scanline
  3. Output video data after hSync on every visible scanline.

The real trick was figuring out the right way to use hardware to accomplish as much of this as possible. Using hardware clears up flash memory to fit the image, and allows me to get the really important hSync timings right.

First, let’s look at vSync. vSync pulse timing accuracy doesn’t seem to be terribly important, at least on the TV I’m using. For that reason, I have vSync implemented in software. I should note that the constant TICKS_HSYNC here has nothing to do with hSync. It just happens that the duration of hSync is the same as the duration of a pulse used in vSync. Sorry if it’s confusing.


// vsync broad sync pulse section
void vSyncTripleBroad(){
  int count;
  for(count = 0; count < 3; count++){
    while(TAR < TICKS_HALF_SCANLINE - TICKS_HSYNC){}
    P1OUT = SYNC;
    while(TAR < TICKS_HALF_SCANLINE){}
    P1OUT = 0;
    while(TAR < TICKS_SCANLINE - TICKS_HSYNC){}
     P1OUT = SYNC;
                while(TAR > TICKS_SCANLINE - TICKS_HSYNC){}
    P1OUT = 0;
  }
}

// vsync short sync pulse section
void vSyncTripleShort(){
  int count;
  for (count = 0; count < 3; count++){
    while(TAR < TICKS_SHORT_SYNC){}
    P1OUT = SYNC;
    while(TAR < TICKS_HALF_SCANLINE){}
    P1OUT = 0;
    while(TAR < TICKS_HALF_SCANLINE + TICKS_SHORT_SYNC){}
     P1OUT = SYNC;
     while(TAR > TICKS_HALF_SCANLINE + TICKS_SHORT_SYNC){}
    P1OUT = 0;
  }
}

These functions just toggle the sync pin at the right times to produce the vSync signal.

// software vSync
if(scanline == 0){
  P1OUT = 0;
  P1SEL = 0;    // let software control P1.5 instead of TACCR0
  vSyncTripleShort();
  vSyncTripleBroad();
  vSyncTripleShort();
  scanline = 8;    // vSync takes several scanlines
  P1SEL = SYNC;    // let TimerA handle hSync
}

At the beginning of the first scanline, control of the sync pin is handed over to software. We then use the above functions to produce the entire vSync section (scanlines 0 – 8). Finally, we allow TimerA to control the sync pin and handle hSync. The time spent on these software loops could be used to add functionality (i.e. SW serial, on-the-fly image generation, etc). vSync could be implemented with TimerA, but TimerA is used for hSync timing. I think it could be done, but it would probably be messy.

The next part of the frame is the vertical blanking period. These are scanlines after vSync, but “above” the visible part of the screen. The scanlines in this section are basically hSync pulses that are not followed by any image data. Nothing really has to be done in software here, as hardware handles hSync.

To produce hardware hSync, TimerA CCR0 is set to the scanline width in clock ticks, and CCR1 is set to the width of hSync. At the end of a scanline, which is the beginning of the next scanline, TAR hits CCR0 and resets to 0. This causes TimerA to bring the sync pin low. When TAR hits CCR1, the sync pin is pulled high again. This creates the hSync pulse at the beginning of every scanline. All the remaining scanlines in the frame will have this hSync pulse.

This takes care of all the sync pulses.

After the vertical blanking period, the scanlines are visible, and it’s time to start sending pixels. Enabling the CCR1 interrupt allows its ISR to run right after the end of every hSync on every scanline. That’s almost the perfect place to start sending out image data. There’s a small blanking period after hSync, so if it started drawing the image right away, it would be to the left of the screen. This is handled with a short software delay.

I tried using software to send the image initially, but I was left unsatisfied with the maximum horizontal resolution of 30 pixels. A comment on a previous post led me back to Batsocks, and their Britishly-titled TellyMate Shield. There, I found out they were using hardware spi to output the image. I realized I could just load the USISR with image data and let it do the rest.

The USI must be clocked to output at the correct bitrate. The slower the clock, the wider the pixels horizontally. Wider pixels mean lower horizontal resolution. This leads to my choice of aspect ratio. If I increased the USI clock divider, I would halve the number of horizontal pixels. This would leave more memory for more vertical pixels, but lead to an aspect ratio taller than wide. A clock divider of 4 and a resolution of 192×40 ended up being the best choice. Here’s the CCR1 ISR that handles drawing the image:


// After vSync, this ISR begins USI output of the image.
#pragma vector=TIMERA1_VECTOR
__interrupt void Timer_A1 (void){
  int wordCounter = 0;
  TAIV = 0;        // Clear TimerA's interrupt vector register;

  USICTL0 |= USIOE;    // USI output enabled
  USICTL0 |= USIPE6;    // Port 1.6 USI data out

  while (TAR < TICKS_HBLANK){}
        do{
                USICNT |= 20;    // arbitrary number > 16.  Keeps USI running
    USISR = currentRowPtr[wordCounter];
    wordCounter++;

    // software delay allowing full USI shift out
    sleep = 0;
    while(sleep < 2)
      sleep++;
    _nop();
    _nop();
    _nop();
    _nop();
    _nop();
    _nop();
    _nop();
  }while(wordCounter < WORDS_SCANLINE);

  if(subRowCounter == ROW_HEIGHT){
    subRowCounter = 0;
    imageOffset += WORDS_SCANLINE;
  }
  subRowCounter++;
  currentRowPtr = &imagePtr[imageOffset];

  while(TAR < TICKS_RIGHT_EDGE){}  // Wait for edge of screen
  USICTL0 &= ~(USIPE6 + USIOE);  // Release control of video pin to software
}

After waiting for hBlank, the USI is started, and loaded with first 16 bit word of image data. It must be loaded again precisely when it runs out, every 64 MCLK cycles. Remember USICLK = MCLK / 4, and 4 * 16 = 64. I wouldn’t exactly call my code self-documenting, so here’s an explanation of the variables and constants for this function:

  • currentRowPtr points to the address of the current row of the image array.
  • wordCounter is the index of the word to be loaded into the USISR.
  • WORDS_SCANLINE (=12) is how many 16 bit words per scanline. 16 * 12 = 192, our horizontal resolution
  • subrowCounter/ROW_HEIGHT – since there are many more scanlines than rows in the image, the same row must be drawn for several scanlines. I’m calling these scanlines subrows, and ROW_HEIGHT is the number of scanline repetitions per image row.
  • imageOffset just moves the image pointer forward along the image by row.

So that’s the general idea of how it works. There are parts of the program I haven’t covered, but they mostly deal with setting up hardware and directing the program flow.

Almost forgot the schematic…

I’d be happy to answer any questions and take any suggestions about the code or my blog. I hope this program is useful or fun for someone! You could make an electronic business card, a holiday greeting, or trick your friends.

Here’s the source code with some example images.

One note — this must be compiled as c++. I had to explicitly tell CCS to do this, so add the files to a new project, and then edit the project properties as follows (click to expand):

Return top