Analog video with xmega

Go To Last Post
9 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

After having some difficulties using DMA, I'm happy to post a working program here. Things are not complete yet but possibly useful as a reference.

Note - DMA is using DMA_CH_TRIGSRC_TCD0_CCA_gc to output the first byte in a line. This gives a consistent timing for the whole line. An interrupt handler (TCD0_CCA_vect) is changing the trigger to DMA_CH_TRIGSRC_USARTD1_DRE_gc to output 15 more bytes.

This would be impossible without a logic analyser. I use the Open Workbench Logic Sniffer. My best spent $50.

Comments and suggestions are welcome.

// TV Out for xmega
// (C) Itai Nahshon 2012
// nahshon  actcom  co  il

#include 
#include 
#include 

#define XTAL16		// use 16 MHz crystal
void Config32MHzClock(void);

#define TIMER_PRESCALER 64
// #define TICKS(n)	((uint16_t)(((uint32_t)(n)*((F_CPU/1000000)))/(TIMER_PRESCALER)))
#define TICKS(n)	((n)/2)		// Microseconds to timer 'ticks' converter

#define TIMER	TCD0
#define PORT	PORTD
#define USART	USARTD1
#define DMAX	DMA.CH0

// Hardware used:
// Boston Android EVAL-04 XMega Development Board + 16 MHz crystal (and 2x 10pf capacitors)
//
// Output signals:
// PORTD0 - Used to flash LED
// PORTD1 - Video sync output (connect with 1 KOhm resistor to video)
// PORTD7 - Video signal output (connect with 330 Ohm resistor to video)
// PORTD5 - Video clock
// PORTD4 - Debugoutput (to see interrupt handler timing on a logic analyzer)
uint8_t blank_bits[] = { [0 ... 15] 0xff };
#define DATASIZE 16

/*
 * Logo.xbm contains a 128x64 pixel B/W image atored as single array of 1024 bytes, LSB first.
 * Created with The Gimp, saved as XBM file.
 * static unsigned char Logo_bits[1024];
 */
#include "Logo.xbm"

/*
 * PAL timing, as described here:
 * http://martin.hinner.info/vga/pal.html
 */
#define NORMAL_LINE_SYNC	TICKS(4)
#define NORMAL_LINE_LEN		TICKS(64)
#define NORMAL_LINE_COUNT	305
#define SYNC_HALFLINE_LEN	TICKS(32)

#define VIDEO_START_IN_LINE	TICKS(16)	// when to start DMA

const uint16_t Field1_sync[] = {
	[ 0 ...  5] TICKS(2),	// Pre-equalizing
	[ 6 ... 10] TICKS(30),	// Vertical sync
	[11 ... 15] TICKS(2),	// Post-equalizing
};
#define FILED1_SYNC_LEN	16

const uint16_t Field2_sync[] = {
	[ 0 ...  4] TICKS(2),	// Pre-equalizing
	[ 5 ...  9] TICKS(30),	// Vertical sync
	[10 ... 13] TICKS(2),	// Post-equalizing
};
#define FILED2_SYNC_LEN	14

int main(void) {
	Config32MHzClock();

	PORT.DIR = 0xff;
	PORT.PIN1CTRL |= PORT_INVEN_bm;
	PORT.PIN7CTRL |= PORT_INVEN_bm;

	// Initialize Usart as SPI Master
	USART.BAUDCTRLA = 4;			// 3.2 Mbit/sec
	USART.BAUDCTRLB = 0;
	USART.CTRLA = 0;			// No interrupt
	USART.CTRLB = USART_TXEN_bm;		// tx only
	USART.CTRLC = USART_CMODE_MSPI_gc|_BV(2);// SPI, lsb first, rising edge;

	// Initialize DMA
	DMA.CTRL = 0;
	DMA.CTRL = DMA_RESET_bm;		// reset DMA controller
	while (DMA.CTRL & DMA_RESET_bm)		// wait reset complete
		;

	// configure DMA controller
	DMA.CTRL = DMA_ENABLE_bm;
	// channel 0
	// **** TODO: reset dma channels (?)
	DMA.CH0.REPCNT = 1;
	DMA.CH0.CTRLA = DMA_CH_BURSTLEN_1BYTE_gc /*| DMA_CH_SINGLE_bm*/ | DMA_CH_REPEAT_bm;
	DMA.CH0.ADDRCTRL = DMA_CH_SRCDIR_INC_gc | DMA_CH_DESTDIR_FIXED_gc;
	DMA.CH0.DESTADDR0 = (( (uint16_t) &USART.DATA) >> 0) & 0xFF;
	DMA.CH0.DESTADDR1 = (( (uint16_t) &USART.DATA) >> 8) & 0xFF;
	DMA.CH0.DESTADDR2 = 0;
	// Interrupts
	//DMA.CH0.CTRLB = DMA_CH_ERRINTLVL_HI_gc|DMA_CH_TRNINTLVL_HI_gc;
	DMA.CH0.CTRLB = 0;	// No interrupt!

	// Initialize timer
	TIMER.CTRLA = TC_CLKSEL_DIV64_gc;
	TIMER.CTRLB = TC_WGMODE_SS_gc;
	TIMER.PER = NORMAL_LINE_LEN-1;

	TIMER.CCA = VIDEO_START_IN_LINE;
	TIMER.CCB = NORMAL_LINE_SYNC;

	// Interrupt on overflow and CCA
	TIMER.INTCTRLA = TC_OVFINTLVL_HI_gc;
	TIMER.INTCTRLB = TC_CCAINTLVL_HI_gc;

	// Enable timer
	TIMER.CTRLB = TC0_CCBEN_bm|TC_WGMODE_SS_gc;

	// Enable the DMA
//	DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm;

	// Enable interrupts
	PMIC.CTRL = (PMIC_HILVLEN_bm|PMIC_MEDLVLEX_bm|PMIC_LOLVLEX_bm);
	sei();

	// Now blink LED at 2Hz, use timer TCC0 
	TCC0.CTRLA = 0x7;   // clk/1024
	while(1) {
		PORTD.OUT ^= _BV(0);
		while(TCC0.CNT < 7812) // roughly 250ms
			asm("nop");
		TCC0.CNT=0; // reset
	}
}

uint16_t mode_limit[] = { NORMAL_LINE_COUNT, FILED2_SYNC_LEN, NORMAL_LINE_COUNT, FILED1_SYNC_LEN };
uint16_t count = 0;
uint8_t mode = 0;

ISR(TCD0_OVF_vect) {
	PORT.OUT |= 0x10;	// Debug!

	if((mode & 0x01) == 0) {
		// Normal line, prepare to display some data
		uint8_t *linedata;
		uint8_t effective_line = (count - 29) / 4;

		if(effective_line >= 64)
			linedata = blank_bits;	// below 29 or above 29+4*64
		else
			linedata = &Logo_bits[DATASIZE * effective_line];

		// Prepare timer
		TIMER.PER = NORMAL_LINE_LEN-1;
		TIMER.CCA = VIDEO_START_IN_LINE;
		TIMER.CCB = NORMAL_LINE_SYNC;

		// Prepare DMA to start on CCB;
		DMA.CH0.CTRLB |= DMA_CH_ERRIF_bm;
		DMA.CH0.CTRLB |= DMA_CH_TRNIF_bm;
		DMA.CH0.CTRLA &= DMA_CH_ENABLE_bm;

		//DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_TCD0_CCB_gc;
		DMA.CH0.REPCNT = 1;
		DMA.CH0.CTRLA = DMA_CH_BURSTLEN_1BYTE_gc | DMA_CH_SINGLE_bm | DMA_CH_REPEAT_bm;
		//DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_USARTD1_DRE_gc;
		DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_TCD0_CCA_gc;
		DMA.CH0.TRFCNT = DATASIZE;
		DMA.CH0.SRCADDR0 = (( (uint16_t) &linedata[0]) >> 0) & 0xFF;
		DMA.CH0.SRCADDR1 = (( (uint16_t) &linedata[0]) >> 8) & 0xFF;
		DMA.CH0.SRCADDR2 = 0;
		DMA.CH0.CTRLA |= DMA_CH_ENABLE_bm;
	}
	else {
		if(mode & 0x02)
			TIMER.CCB = Field1_sync[count];
		else
			TIMER.CCB = Field2_sync[count];

		TIMER.PER = SYNC_HALFLINE_LEN-1;
		// Large number! (so we don't get the interrupt)
		TIMER.CCA = TICKS(99);
	}

	if(++count >= mode_limit[mode]) {
		count = 0;
		mode = (mode + 1) & 0x03;
	}

	PORT.OUT &= ~0x10;	// Debug!
}

ISR(TCD0_CCA_vect) {
	PORT.OUT |= 0x10;	// Debug!

	// Change trigger of DMA channel
	DMA.CH0.TRIGSRC = DMA_CH_TRIGSRC_USARTD1_DRE_gc;

	PORT.OUT &= ~0x10;	// Debug!
}

void Config32MHzClock(void)
{
#ifdef XTAL16
	// External 16 MHz crystal
	CCP = CCP_IOREG_gc;
	CLK.LOCK = 0;

	// External 16,000,000kHz oscillator initialization
	OSC.XOSCCTRL = OSC_FRQRANGE_12TO16_gc|OSC_XOSCSEL_XTAL_16KCLK_gc;
	// Enable the oscillator
	OSC.CTRL |= OSC_XOSCEN_bm;

	// Wait for the external oscillator to stabilize
	while (!(OSC.STATUS & OSC_XOSCRDY_bm))
		;

	// Enable PLL and set it to multiply by 2 to get 32MHZ
	OSC.PLLCTRL = OSC_PLLSRC_XOSC_gc|OSC_PLLFAC1_bm;
	OSC.CTRL |= OSC_PLLEN_bm;

 	// Wait for the PLL to stabilize
	while (!(OSC.STATUS & OSC_PLLRDY_bm))
		;

	// Select PLL for the system clock source
	CCP = CCP_IOREG_gc;
	CLK.CTRL = CLK_SCLKSEL_PLL_gc;

 	// Disable unused clocks
	CCP = CCP_IOREG_gc;
	OSC.CTRL = OSC_XOSCEN_bm|OSC_PLLEN_bm;

	// No prescaler
	CCP = CCP_IOREG_gc;
	CLK.PSCTRL = 0;

	CCP = CCP_IOREG_gc;
	CLK.LOCK = 1;
#else
	// Internal 32 MHz oscilator
	CCP = CCP_IOREG_gc; //Security Signature to modify clock
	// initialize clock source to be 32MHz internal oscillator (no PLL)
	OSC.CTRL = OSC_RC32MEN_bm; // enable internal 32MHz oscillator
	while(!(OSC.STATUS & OSC_RC32MRDY_bm)); // wait for oscillator ready
	CCP = CCP_IOREG_gc; //Security Signature to modify clock
	CLK.CTRL = 0x01; //select sysclock 32MHz osc
#endif
}

Attachment(s): 

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Nice to see another video project, thanks. Do you have a screenshot of the output?

Brad

I Like to Build Stuff : http://www.AtomicZombie.com

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Sorry, no screen shot.
Image is just static, so it would be boring...

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:

so it would be boring...

Not for micro engineers who can appreciate how amazing the achievement is!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

:D
I've added code for a Spektrum satellite receiver.
Here's the video:
http://www.youtube.com/watch?v=wFvacqHAQWk

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I now have some OSD ability. Increased resolution to 212X128 meaning I use 3392 (of the available 4K) just for the frame buffer.

I found that interrupt response times are not consistent enough to handle horizontal timing. Need to find a solution better than polling in long-long loops.

http://youtu.be/Oh-u3cDeg0M

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Quote:
I found that interrupt response times are not consistent enough to handle horizontal timing. Need to find a solution better than polling in long-long loops.
Maybe it'll work better if you convert key code sections to asm and link those routines into your C code.

1) Studio 4.18 build 716 (SP3)
2) WinAvr 20100110
3) PN, all on Doze XP... For Now
A) Avr Dragon ver. 1
B) Avr MKII ISP, 2009 model
C) MKII JTAGICE ver. 1

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Here are a couple of ideas to 'fix' the interrupt response times:
1. Go to sleep before the interrupt fires.
This means that no instruction is running when the interrupt fires and the latency is fixed.

2. Compare the Timer
Switch your timer to clock 1:1 with the CPU.
When you get into your interrupt routine, read the timer and have a short, variable, delay based on what you read.

See this thread for a brief discussion on the latter.
Here's another thread about normalising interrupt timing.
I've got a TV-output XMega project on my XMega example code page.

Note that if you're using an external source for hsync, then you'll never get perfect results simply because your clock will never be quite in sync with the source. On an XMega, you'll probably be able to get close enough.

Also note that you might hit issues with the USART bit clock - it seems to keep the underlying bit clock running even when it's not transmitting, hence all transmissions are half-bit aligned with one another, so even if you get your interrupt nicely jitter-free, the USART output could still scupper you! You've probably avoided this problem by having your scanline width an integer multiple of the USART clock.

Nigel Batten
www.batsocks.co.uk

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

The only way to get a perfectly accurate sync with an external video clock is to use a PLL. Well, there is one other way actually, connect a timing crystal to the hsync via a 1M resistor so that it is "encouraged" to sync with it :-)