Showing posts with label transitions. Show all posts
Showing posts with label transitions. Show all posts

Monday

Arduino: High Speed (8Mhz) Signaling: NTSC TV Output



I recently purchased a cheap 7" color monitor with intentions of using it for FPV flying, but still have not received the transmitting equipment. Of course, in waiting, I began to wonder about interaction with an Arduino, and a few searches led me to the TVOut library, which achieves NTSC video output (B/W) using an Arduino. I downloaded the library, and the more I read through the code, the more I was intrigued by how exactly such fast and accurate timing was acheived.

To give an idea of the requirements, the horizonal sync signal occurs about 15750 cycles/second (every 63.5us), which is relatively easy for an Arduino to produce in itself. In between these signals however, the picture is drawn, which requires much faster signaling and more precise timing, into the Mhz+ range.

In addition to the TVOut library, I also found a simpler form of code here that uses the USART (Serial port) to form the video signal, using each bit in every byte sent to form individual pixels. The main problem I found with this code was timing. It uses sleep mode to help time the video display accurately, but there were still some issues with small video glitches that I could not resolve when replicating the code. It also uses manual writing of the hsync pulse, where the other method uses a timer generated signal.

The first thing to do was to understand the makeup of an NTSC signal. This page provided a straightforward explanation, along with the timing requirements. Then it was on to recreating a hybrid of the code mentioned above, in the simplest possible format. The first part was pretty straightforward, creating horizontal and vertical sync signals. An entire frame consists of 259 horizontal sync signals, and 3 vertical sync signals, which are simply inverted horizontal sync signals to make 262 lines. The following code will produce a sync signal on pin 12, which results in a blank black screen.


#define hLinesPerFrame  262
#define vSync 247
#define hSync 25

volatile unsigned int isrCounter = 0;

void setup(){
  pinMode(12,OUTPUT);
 

  //This sets up the timer to produce the horizontal sync pulses
  //ICR1 is the timer TOP value, OCR1B controls the hSync pulse width
  //OCR1A will control the timing of the video display
  ICR1 = 1019;

  OCR1A = 1;
  OCR1B = 30;
  TCCR1A =  _BV(WGM11) | _BV(COM1B1) | _BV(COM1B0);
  TCCR1B = _BV(WGM13) | _BV(WGM12) | _BV(CS10);
  TIMSK1 = _BV(OCIE1A); 
}


ISR(TIMER1_COMPA_vect){
  ++isrCounter;

  switch(isrCounter){         
          case vSync: OCR1B = 1019-hSync; break;

          case vSync+3: OCR1B = hSync; break;
          case hLinesPerFrame:  isrCounter = 0; break;
        }
  }


void loop(){ 

}

So that part is easy enough, a few lines of code, a 1k resistor from pin 12, and we have a basic NTSC signal with vertical and horizontal sync. Actually creating video will be a bit more complicated, and I've chosen an Arduino Mega due to the amount of SRAM (8K) available, which is needed for higher resolutions.

What I found in experimenting with different methods for timing, is that the timing of interrupts on a standard Arduino board can vary quite a bit. Using sleep mode to idle the board prior to triggering works 98-99% of the time, but still leaves a few interupts coming a bit late, producing glitches in the video. The method used in the TVOut library appears to work the best, as it monitors the counter to trigger at exactly the same time every time. Of course, it is written in assembly, and I made a slight modification to exit immediatly if the wait time has been missed. My explanation may be a slight bit off technically, but should be pretty close:

static void inline wait_until(uint8_t time) {

__asm__ __volatile__ (
    "sub    %[time], %[tcnt1l]\n\t" //Subtract the low byte of TCNT1 from the wait time
    "brmi   102f\n\t"               //If result is negative, we missed it. Branch to 102

 "100:\n\t"                     
    "subi    %[time], 3\n\t"         //Subtract 3 from time
    "brcc    100b\n\t"               //Loop until we get a negative
    "subi    %[time], 0-3\n\t"       //Add 3 back to our value
    "breq    101f\n\t"               //If equal to 0, branch to 101 and delay 1/16 of a uS (No OPeration)
    "dec    %[time]\n\t"            //Decrement our value by 1
    "breq    102f\n\t"               //If equal to 0, branch to 102 and don't delay
    "rjmp    102f\n"                 //Else Jump to the end
 "101:\n\t"
    "nop\n"
 "102:\n"

     :                                 //Output variables (none)
     : [time] "a" (time),              //Input variables
       [tcnt1l] "a" (TCNT1L)

);
 

}

That takes care of the basic signaling, and provides accurate enough timing to begin looking at displaying the video. I chose to use the USART to draw the video, as it simplifies things quite a bit when compared to using functions written in assembly, and should be able to use a baud rate of 8Mhz in Master SPI mode according to the datasheet. The USART also includes a double-buffering capability, which seems to make it more suitable here than using SPI.




We can calculate our BAUD rate, and the required setting for the UBBRn register at maximum speed:

16Mhz / 2(UBRRn + 1)  -->  16 / 2(0 + 1)  -->  16 / 2 -->  BAUD: 8Mhz  UBRRn: 0

Now, to configure the USART, only two lines of code are required in setup() :

  UCSR1C = _BV(UMSEL01) | _BV(UMSEL00); //Set USART to Master SPI mode
  UBRR1 = 0;                            //Baud rate = 8Mhz



Then to create a buffer for the pixel data of 5400 bytes:
    int totalBytes = (400/8) * 108;   
  byte data[totalBytes];

Now, all there is to do is start drawing pixels at the correct time, which (simplified) is done with the following lines of code, which get added to the TIMER1_COMPA_vector interrupt shown above.

wait_until(153);                      //See the assembly function above

UCSR1B |= _BV(TXEN1);                 //Enable USART TX

for (int x =0; x < 400/8; x++){       //Load bytes until we have drawn 1 horizontal line:
  while ( !(UCSR1A & _BV(UDRE1))){ }  //Wait until the USART is ready for more data
  UDR1 = data[x+start];               //Load a byte
}

  
UCSR1B = 0 ;
                                             //Disable the USART TX




...And thats all there is to it. Of course, the last part of it requires a bit more code to control the screen position and count lines, etc, but those are the fundamentals of producing NTSC video with an Arduino. A complete sketch is posted below.

Notes:
a: The timing of the video display in relation to the horizontal sync pulse is very important. It specifically triggers before the h-sync pulse ends so as to have enough time to smooth out variations in triggering.
b: The loading of data into the buffer (UDR1) must be done as quickly as possible, and requires efficient code to work at 8Mhz.
c: Using the USART requires accurate screen positioning due to the pulse created whenever it is enabled. This creates a vertical line, which can be positioned off screen via the wait timing.
d: This code does not use the defined standard timing for parts of the signal, due to better results with slightly modified settings. (ie: 2uS horizontal pulse instead of 4.7uS)
e: The maximum resolution is mainly limited by the memory available, as 1 byte is required for every 8 pixels. (400 x 108 uses 5.4KB)

Summary:
For me, this was an interesting look at the maximum limitations of signaling using an Arduino, and gave me an opportunity to learn a little more about assembly code as well. I found a great tutorial here showing how to view your Arduino code in assembly, and it was a great help in beginning to understand the code behind the code.
All in all, I am pretty happy with the results, as the methods used above seem pretty comparable to the TVOut libary, but with a bit higher resolution. The main benefits of using the USART are simplified code, as the baud rate can be changed to scale the video horizontally, and feeding it data is very straightforward.

The code used to create the above video can be found here and borrows heavily from the code it is based on.






Tuesday

Digital Data Encoding: Manchester, BMC and Frequency Modulation(FM)

Data generally needs to be encoded to enable the transmission of digital data across various mediums, and I've always had an interest in radio/wireless communication, so this was pretty much going to happen eventually. I've used some VirtualWire modules, and took an interest in how they work and how to apply the concepts learned from that code and other sources. This lead to the following sketches which encode data using a modified version of Biphase Mark Code (BMC) encoding, which is a specific form of Frequency Modulation, (FM) used to transfer digital data.


A Quick Look at BMC Encoding:

If you look up Biphase Mark Code online, you may be lead to information on Differential Manchester encoding, which has fundamental differences from BMC. You will also generally find excessively complex explanations of how it works, but it is actually quite simple. In the end, you basically send a bunch of binary data (1 and 0), and represent that data either with 1 long period or 2 short periods:

ie: 1 = Long, 0 = short,short  OR  0 = Long, 1 = short,short

The important factor is the length of time between transitions (The point at which the wave transitions from high to low or low to high). This image from wikipedia shows how the data is encoded into a wave:



How does it work?

This code is derived from concepts described here and in the VirtualWire Arduino library. The VirtualWire library appears to use a sampling based decode method and '4-to-6 bit' encoding of the data prior to transmission. Comparativly this was more of a learning exercise than a useful project, so is fairly simple, and uses timer based encoding.

Arduino Uno,Mega etc have an 'Input Capture' function that can trigger an interrupt and timestamp it, and I was interested in trying it out for this type of digital communication. This code uses that capability to calculate the difference between transitions and thus decode the binary data. Timer1 is used as a counter to provide the timestamp for the receiver, and used to generate the proper waveform for the transmitter.


Results:

I started off initially using Differential Manchester encoding, and was able to acheive ok results for wireless transmission. The results weren't very useable initially, so I tested a few different methods of modulation, ending up with BMC encoding producing the best results. BMC uses two small transitions for one bit, and one large for the other (0 or 1), so I tried it with only a single small transition, and seem to have the best results so far. I'm not sure if this fits in with another modulation technique, but it is FM either way.

Transmitter:

The data being sent is broken into bits and buffered into memory, while Timer1 is configured to run at a set frequency. A full pulse width is created to encode a 0, and a half pulse width is created to encode a 1, modulating the frequency to send data. The compare-output mode is toggled every cycle to produce the correct waveform, ensuring that positve pulses always follow negative pulses and vice-versa. 

Receiver:

The receiver uses the built-in Input Capture mode to timestamp the high and low transitions of the pulses being received, then calculates whether a 0 or 1 was received based on the timestamp. As the bits are received, they are loaded into a temporary byte. If that byte is equal to the 'start byte', it starts buffering data and looks for a 'stop byte'. Once received, it does a simple CRC check to ensure the data is not corrupt, and then makes the data available if successful.


Arduino Sketches:

 This seems to work well in testing, up to 32Kbps with a direct connection, and up to 7Kbps at close range with some very cheap 433Mhz VirtualWire capable modules I had sitting around. This was a pretty interesting task, and a good way to understand the general concepts behind the raw signaling that digital devices use to communicate. This is about as far as I plan to take this code for now, so I threw in a bunch of comment  s:

RX (receiver)  (Timer4, Pin 49 on Mega)
TX (transmitter) (Timer1, Pin9 on Uno,Nano,etc)


My Custom Zephyr + Arduino Build for Arduino Uno Q: Adding Zephyr Networking to the Mix

 My Custom Zephyr + Arduino Build for Arduino Uno Q: Adding Zephyr Networking to the Mix Using RF24Ethernet with native Zephyr networking  F...