Launchpad monochrome NTSC composite video
- December 1st, 2010
- Posted in Launchpad . MSP430 . Video
- Write comment
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:
- Output vSync signals at the beginning of every frame.
- After vSync, output hSync signals at the beginning of every scanline
- 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): 



That is sweet! Thanks for sharing. Did you generate the .h files or did you code them manually(unlikely).
I ran a 192×40 mono bitmap through this program (on the fourth setting — you’ll see):
http://sourceforge.net/projects/image2code/files/
This is great. You should send this to hackaday.com. I am sure it will get attention, since I haven’t seen any msp430 projects quite like this one yet.
Thanks! I actually just submitted it to them.
/action crosses fingers
How much program space is left over on the MSP430G when you load this and an image?
Very little. I chose an image resolution that would fill the rest of free flash space with the image. You could make the image smaller if you wanted to add code.
Is there enough program space left over for a connection to a serial ram chip and an external serial port? I was wondering if this could be used to produce a scrolling text display if the character maps were stored off-chip.
Alternatively, could the external serial port be used to upload frame data from another controller?
You could always make the image smaller to free up program space. I think finding the cpu time is a bigger issue. There’s probably enough time during V-Blank. You’d have to configure the USI for input during V-Blank before receiving serial, and then reconfigure it for output before using it to output the image. You may only get part of the image per frame. Also, the serial transmitter would have to be told when it’s OK to send.
I actually don’t think it would work. You’d have to cache one frame in memory, and there just isn’t enough room. I guess you could write the serial data to flash, but that sounds like a mess. A lot of problems come to mind.
I downloaded the source but it still refers to the external oscillator. Did I get the right file or is there a newer version that takes advantage of the DCO calibration data?
Hi Colin. If you calibrated your MSP430G and set up CCS according to the tutorial, then you can use the following:
Launchpad_TV_Out_for_calibrated.rar
Thanks! I mounted a video jack to the top of my Launchpad like this http://picasaweb.google.com/lh/photo/fZgzLwSmaiZ5c_WlyDptMw?feat=directlink and it worked, but the image was too narrow (missing horizontal resolution)… I added some more delay to Timer_A1 and now the TV output looks like it should.
Hey great! That setup looks really clean. It seems like you calibrated the DCO constants too. I’m really glad to hear from someone who got it working. I know using CCS in release mode (vs debug) or gnucc with optimizations can squish the video horizontally. Optimizations kill the timing. Do you think this might be the problem?
I did calibrate the DCO constants first, and I was using release mode in CCS too so that just might explain the change in timing.
Would love to try this, it looks fun!
Conceptually, do I understand correctly that you use the SPI MOSI line to drive the Video VOUT? The SPI shifts 16 bits out (12M/4=@3Mhz) before you have to reload it.. which is 12 times every horiztontal line (192/16)?
Regarding the reload, can the SPI generate an interrupt when its done shifting out so that the reload can be done in an ISR? Or perhaps is there a DMA that can feed the SPI every ~5us (16/3M)?
Hi ben. You should give it a shot and let me know how it works for you. I had (a certain sort of) fun making it.
You’re absolutely right about everything except that the main clock runs at 16MHz, making the shift out 4MHz. I’m glad you could understand the code. Issues with timing and the fact that I’m a relatively inexperienced coder makes it sort of messy.
I think your suggestion about using the USI interrupt is a good idea, and something I wish I would have done. Some thought would have to be given to making sure the timing is preserved. One clock cycle of misalignment is noticeable. I’m working on Pong right now, and I hope to have it finished by the end of the month. I’ll tinker with the interrupt. If I’m able to get it working I’ll be sure to include your suggestion.
Gotcha..
But doesn’t the act of reloading a new 16-bit word take longer than 250ns? i.e. If the spi outputs a new bit every 250ns (1/4mhz), then in-between words I would imagine that the time between the last shifted bit, reload, and the first shifted bit of the new word will be longer than 250ns. Is this true, or does it even matter?
Well, it takes 4 MCLK cycles to shift out one pixel. I think I remember that the USICNT load statement takes 4 MCLK cycles. If one iteration of the do loop takes 64 MCLK cycles, the program begins to reload the USI counter exactly when the last pixel begins to be shifted to the output. They get done at the same time so the USI keeps shifting out the pixels.
I think I could have actually just loaded the USICNT with 16 instead of 20 since they’re locked like that. I think that was leftover from debugging. Now that I look at it, I should probably have also put “USISR = currentRowPtr[wordCounter]; ” above the counter reload. I’m surprised that hasn’t led to any visible artifacts.
I feel like I’m learning new things all the time. Whenever I look at code that I only wrote a few months ago, I see a lot of things I should have done differently. I could stand to proofread and rewrite my code once in awhile too.
Thanks, I just got my launchpad, so I’m finally going to see this in action!
Although I forgot to order the 16Mhz oscillator.. doh! I do have a another board that has a 100mhz processor can probably toggle an i/o at 16mhz.. I’m not too handy with a soldering iron, so maybe that might be safer for me anyways!
You can calibrate the DCO so you don’t need an oscillator if you have the Launchpad crystal attached. I mention it in the post.
hey, I think this project is great.
can u explain more about this project, I want to know its application. IT works for what ? Can it use for real life and for what ? Thank u :)
Do you have any details on the pin configuration you used? There’s a lot here on the software side, but not the hardware.
Thanks!
At the bottom of the post is a schematic picture.
Most important thing to be kept in mind when working with soldering iron is not to touch the tip of the iron as it is extremely hot. Soldering material used for the purpose is an alloy of tin and lead and is called flux. Before you put solder over the required area, heat up the surface to be soldered by touching the tip of the hot iron. Application of the solder is called “tinning”since percentage of tin is more in the flux. However, flux can be of various types depending upon the things to be soldered. ..
Have a look at the helpful write-up on our own internet site
<="http://www.caramoan.ph/caramoan-cotivas-island/