Getting video out from your Adafruit Trinket or DigiSpark (or ANY ATTINY85)

11224347_913681192034970_90837413382715523_oWhile pouring through some videos and posts on just what the tiny little ATTiny85 chip can do, I ran across a HackADay post about someone generating NTSC video on VHF channel 3 (US channel 3.)  I was intrigued.  So, I tried it and got mixed results and, as I have only a couple of old analog sets, I thought it was kind of impractical to pursue, but cool anyway.  So, I wondered if just composite out would be better and, to my delight, the same person who posted the video also had done composite out with an ATTiny84.  It should work on the 85 as well, and, it does.

So, I found his code and downloaded it.  Mr. Lohr is a GENIUS, plain and simple.  Thank you, sir!

The code did not work well at first. I had to tweak it a bit to work with my hardware, especially the little monitor.  It still is not perfect, the video is not centered, but is fairly steady and bright…my first attempts were not very bright and not very steady. I am guessing it needs to be tweaked for the monitor it will be used on, but I have not tested on more than one.

All you need is something like a Trinket from Adafruit or a digispark. I used both, but destroyed the digispark when I attempted to use an external power source.  It does not like more than five volts.  Specs say 3.5 to 12volts, I used 9. My mistake. 12182942_913681798701576_4759339394040268204_o

The Trinket works great. I am using the 5volt version with USB.  Any ATTiny85 based controller should work, though.

Connecting it is simple: Pin 3 to video ground, Pin 4 to video center pin.  Download the code to your device, connect it to the monitor and reset it. You should see the video demo.

Now, there are some major caveats:

  • it, so far, only does text
  • you have 13 characters by 6 lines
  • character spacing is mono
  • there’s no video memory, each frame is drawn on the fly
  • the ‘video memory’ is simply a string array
  • there is no formatting
  • the character set is stored in flash

So, why bother with such limitations? Well, for starters, cheap composite monitors are easy to get WP_20151025_11_19_10_Rich_LIand use and the Trinket/digispark are both under ten bucks, so you can have an application with video out for very little money.  If nothing else, this is a great lesson in how video works. Charles Lohr did a fantastic job with the code.  He’s a genius. Did I mention that?

Any way, the code is posted below.  Let us know if you get it working and what you do with it.

 

 

/*
    Copyright (C) 2014 <>< Charles Lohr


    Permission is hereby granted, free of charge, to any person obtaining a
    copy of this software and associated documentation files (the "Software"),
    to deal in the Software without restriction, including without limitation
    the rights to use, copy, modify, merge, publish, distribute, sublicense,
    and/or sell copies of the Software, and to permit persons to whom the
    Software is furnished to do so, subject to the following conditions:

    The above copyright notice and this permission notice shall be included
    in all copies or substantial portions of the Software.

    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
    OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
    IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
    CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
    TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
    SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/


#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include "ntscfont.h"

 

void delay_ms(uint32_t time) {
  uint32_t i;
  for (i = 0; i < time; i++) {
    _delay_ms(1);
  }
}

#define NOOP asm volatile("nop" ::)

void NumToText( char * c, uint8_t a )
{
    c[0] = (a/100)+'0';
    c[1] = ((a/10)%10)+'0';
    c[2] = (a%10)+'0';
    c[3] = 0;
}
void NumToText4( char * c, uint16_t a )
{
    c[0] = (a/1000)+'0';
    c[1] = ((a/100)%10)+'0';
    c[2] = ((a/10)%10)+'0';
    c[3] = (a%10)+'0';
    c[4] = 0;
}


EMPTY_INTERRUPT(TIM0_OVF_vect );


int main( )
{
    cli();

    CLKPR = 0x80;    /*Setup CLKPCE to be receptive*/
    CLKPR = 0x00;    /*No scalar*/

    PLLCSR = _BV(PLLE) | _BV( PCKE );
//    PLLCSR |= _BV(LSM);

    DDRB = _BV(1);

    DDRB |= _BV(3);
    DDRB |= _BV(4);
    PORTB |= _BV(1);

    TCCR1 = _BV(CS10);// | _BV(CTC1); //Clear on trigger.
    GTCCR |= _BV(PWM1B) |  _BV(COM1B0);// | _BV(COM1B1);
    OCR1B = 2;
    OCR1C = 3;
    DTPS1 = 0;
    DT1B = _BV(0) | _BV(4);

    TCCR0A = 0;
    TCCR0B = _BV(CS01);
    TIMSK |= _BV(TOIE0);

OSCCAL = 215;

//    OSCCAL=186;


#define POWERSET

#ifdef POWERSET
#define NTSC_HI  {    DDRB=0;}
#define NTSC_LOW {    DDRB=_BV(4); }
#define NTSC_VH  {    DDRB=_BV(3); }
#elif defined( POWERSET2 )
#define NTSC_VH  {    DDRB=0;}
#define NTSC_LOW {    DDRB=_BV(4)|_BV(3); }
#define NTSC_HI  {    DDRB=_BV(3); }
#elif defined( POWERSET3 )
#define NTSC_VH  {    DDRB=0; }
#define NTSC_HI   { DDRB=_BV(3); }
#define NTSC_LOW   {    DDRB=_BV(4)|_BV(3); }
#else

//Experimental mechanisms for changing power. Don't work.
#define NTSC_VH  {    OCR1C = 3; TCNT1 = 0; }
#define NTSC_HI  {    OCR1C = 6; TCNT1 = 0;}
#define NTSC_LOW {    OCR1C = 0; TCNT1 = 0;}

#endif
    uint8_t line, i;

    #define TIMEOFFSET .12 //.12
    #define CLKOFS .12

    uint8_t frame = 0, k, ctll;
    char stdsr[8*13];
    sprintf( stdsr, "Fr: " );
    sprintf( stdsr+8,  "HalfByte" );
    sprintf( stdsr+16, "  Blog  " );
    sprintf( stdsr+24, "        " );
    sprintf( stdsr+32, " Trinket" );
    sprintf( stdsr+40, "AdaFruit" );
    sprintf( stdsr+48, "        " );
    sprintf( stdsr+56, "        " );
    sprintf( stdsr+64, "        " );
    sprintf( stdsr+72, "        " );
    sprintf( stdsr+80, "        " );
    sprintf( stdsr+88, "        " );


    ADMUX =/* _BV(REFS1)  |  _BV(ADLAR) | */ 1; //1 = PB2
    ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADPS2) | _BV(ADPS1);

#define LINETIME 21 //Linetime of 7..20 is barely valid. So,
//#define WAITTCNT while(TCNT0);
#define RESETCNT {TCNT0 = LINETIME; TIFR|=_BV(TOV0); GTCCR|=PSR0;sei();}

//#define WAITTCNT while(!(TIFR&_BV(TOV0)));
#define WAITTCNT sleep_cpu();

//#define WAITTCNT fintcnt();

     sleep_enable();
    sei();

    uint16_t ovax; //0..1024 = 0...5v
    uint8_t  msd;
    uint8_t  lsd;

    while(1)
    {

        frame++;
        //H = 1./15734.264 = 63.555 / 2 = 31.7775
        for( line = 0; line < 6; line++ )
        { NTSC_LOW; _delay_us(2.3-TIMEOFFSET); NTSC_HI; _delay_us(29.5-TIMEOFFSET-CLKOFS); }
        for( line = 0; line < 6; line++ )
        { NTSC_LOW; _delay_us(27.1-TIMEOFFSET); NTSC_HI; _delay_us(4.7-TIMEOFFSET-CLKOFS); }
        for( line = 0; line < 6; line++ )
        { NTSC_LOW; _delay_us(2.3-TIMEOFFSET); NTSC_HI; _delay_us(29.5-TIMEOFFSET-CLKOFS); }

        for( line = 0; line < 39; line++ )
        {
            RESETCNT;
            NTSC_LOW;
            _delay_us(4.7-TIMEOFFSET);
            NTSC_HI;

            //Do whatever you want.
            //sprintf( stdsr, "%d", frame );
            switch (line)
            {
            case 0:
                NumToText( stdsr+4, frame );
                break;
            case 1:
                ovax = ADC;
                ovax = ovax * 49 + (ovax>>1);
                ovax/=10;
                break;
            case 2:
                NumToText( stdsr+24, ovax/1000 );
                stdsr[27] = '.';
                break;
            case 5:
                NumToText4( stdsr+27, ovax );
                stdsr[27] = '.';

                break;
            }

            WAITTCNT;
            //_delay_us(58.8-TIMEOFFSET-CLKOFS);
        }

        for( line = 0; line < 2; line++ )
        {
            RESETCNT;
            NTSC_LOW;
            _delay_us(4.7-TIMEOFFSET);
            NTSC_HI;
            WAITTCNT;
        }

        for( line = 0; line < 220; line++ )
        {
            RESETCNT;
            NTSC_LOW; _delay_us(4.7-TIMEOFFSET);
            NTSC_HI; _delay_us(8-TIMEOFFSET-CLKOFS);

//#define LINETEST
#ifdef LINETEST
            NTSC_VH; _delay_us(8-TIMEOFFSET-CLKOFS);
            NTSC_HI; _delay_us(44.5);
#else
            ctll = line>>2;
    
            for( k = 0; k < 8; k++ )
            {
      // draw the character, one pixel at a time
            uint8_t ch = pgm_read_byte( &font_8x8_data[(stdsr[k+((ctll>>3)<<3)]<<3)] + (ctll&0x07) );
            for( i = 0; i < 8; i++ )
            {
                if( (ch&1) ) //if pixel is dark...
                {
                    NTSC_VH;
                }
                else
                {
                    NTSC_HI;  // pixel is lit
                    NOOP;
                }
                ch>>=1;
                NOOP; NOOP; NOOP; NOOP;
            }
                    NTSC_HI;

            }

            NTSC_HI; //_delay_us(4.7-TIMEOFFSET-CLKOFS);
            WAITTCNT;
#endif

//        NTSC_HI; _delay_us(46-TIMEOFFSET-CLKOFS);

//            NTSC_VH; _delay_us(32-TIMEOFFSET-CLKOFS);
//            NTSC_HI; _delay_us(19.8-TIMEOFFSET-CLKOFS);
        }
    }
   
    return 0;
}

 

Download the original code and the font file here. You will need the font file to make this work.

Mr. Lohr’s YouTube Channel

Real do it yourself computer using Arduino or…how to make your own game console

IMG_3043Ever since I was a kid, there were two things I’ve always wanted to build: a computer and my own video game ‘console.’ Now, I grew up in the seventies and eighties, so both of these things were pretty crude, some even crude when they were new (RCA Cosmac, I’m looking at you!) While in the intervening years, I did ‘build’ both, I cheated in doing so. With the computer, ‘building’ one was simply buying premade cards and a motherboard and installing them, hardly building one. With the video game ‘console’, I assembled a ‘pong’ style game from a kit (which I wrote about here.)

Now that I have discovered the wonderful world of microcontrollers, I can, finally, actually build both of them.mk121pal

Today’s microcontrollers are as powerful, if not more so, than yesterday’s microcomputers. For instance, the ATMega 328 is every bit as capable as the 8080, one of the mainstays in the 1970’s. Because of this, you can build real computers that are very small and require little power. They also require substantially fewer parts to work and be useful.

While I am no electronics engineer, these chips are simple enough for even someone like myself to design and build a working computer that can, subsequently, become a game console.  Companies like Adafruit, iConstrux, Spark Fun, Arduino and others all have components that are geared toward these nifty little devices. Adafruit, for example, sells the Nunchucky, which is a tiny little board that allows you to easily interface a Nintendo Wii Nunchuck controller. These controllers are, themselves, very cool and underrated. They feature accelerometers, a joystick and two buttons and are easily read by these little processors. And, with the Nunchucky, you do not need to cut its cable.

IMG_3070While it is old news, it is new news to me…the Arduino’s are capable of limited video generation and, hence, limited graphics. Now, while the graphics ability will never threaten nVIdia or AMD, they will give the aforementioned RCA Cosmac a run for its money. 

Armed with this knowledge, the Nunchuck/Nunchucky, some basic soldering skill and enough knowledge of electronics to be somewhat dangerous, I have set out to build that computer/game console.

I am actually doing two consoles: a handheld for my stepson and a somewhat more capable one as my exercise in designing and constructing said system. The handheld will utilize a small joystick, a Nokia 5110 LCD and a speaker in addition to the Mega328 Mini Pro.

Unfortunately, I ordered a great deal of my parts from eBay and just about all are shipping from China, apparently by foot as they have not all arrived.

IMG_3041However, since my Uno did arrive, I’ve been able to design and build the Half Byte game shield and start working on a prototype game, based on the Super Mario games. No, the game will not be made public, but, perhaps, a modified version with different characters may be. I do not have license to distribute any copyrighted material from Nintendo.

The game shield currently features the audio and video out connectors, the 1kohm and 470ohm resistors that allow the video to work (along with a library for the Arduino), the Nunchucky board, Arduino I/O pins and the header for the Parallax Serial LCD (which uses digital pin 2.) I may add digitized sound output as well and, perhaps, blinky LED’s and support for my custom three button controller.

arduinovideoThe tricky thing with the video is that it is all handled by the processor. The resistors ‘fool’ most televisions into ‘thinking’ there is a legit video signal. The 1k resistor handles the sync while the other handles the video itself. It was very easy to do and only involved the sacrifice of one video cable (and, since then, I acquired actual connectors so I can use better cables.)  Because the processor does all of the work, and has a very limited memory space (2k of ram, tops) the resolution is a paltry 120×96, though I had to back it down a bit to about 100×96, I needed a bit more ram. There is a common library, called TVOUT, available that most people use, though a few have written their own. The common library provides for three different fonts, lines, circles, inverse video and other niceties. It also can display bitmaps, though they MUST be correctly formatted and converting a bitmap for use is a bit of a pain. You must first resize the bitmap to fit the tiny screen, then you have to save it as a monochrome bitmap, process that file through another program that creates the hex codes that go into an array in the Arduino PROGRAM memory (NOT RAM, but the Flash memory that stores your code.) I’ll post more instructions in a later post.

proto Mario 1So, once I got the video and audio up and working, I played around with some code. First, the TVOUT demo then my own code. The TVOUT demo is pretty cool with a nice rotation sequence. It also displays the bitmap for the schematic to generate the video, an interesting inclusion.

My ‘mario’ game began as a demo. I wanted to move an image around, controlled by the Nunchuck. I wanted to use Spongebob, but I could never get him to look right in such low resolution. Now, how damned hard is it to represent a SPONGE? UGH.  Mario looked much more recognizable and that’s what I went with. Once I had my bitmaps done, moving them around was easy.  I now have Mario, a Koopa Troopa and a Warp Pipe in my level.  I am using another game called Poofy for some direction. Poofy is a side scroller for Arduino that uses some interesting methods and code that are in the public domain (and developed by the people who brought us Hackvision, an Arduino based console similar to what I am doing.)

My next step with the game is a scrolling world, Mario’s ‘weapon’ and a story.

I will be documenting the process, so stay tuned!

For quicker updates, follow Half-Byte on Facebook.

Links: