| Author |
Message |
|
|
Posted: Jun 12, 2012 - 06:40 PM |
|

Joined: Jul 19, 2011
Posts: 47
Location: Haifa, Israel
|
|
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.
Code:
// TV Out for xmega
// (C) Itai Nahshon 2012
// nahshon <at> actcom <dot> co <dot> il
#include <stdio.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#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
}
|
|
|
| |
|
|
|
|
|
Posted: Jun 12, 2012 - 10:07 PM |
|


Joined: Feb 13, 2007
Posts: 1025
Location: Gillies, Ontario
|
|
|
|
|
|
|
Posted: Jun 13, 2012 - 06:14 PM |
|

Joined: Jul 19, 2011
Posts: 47
Location: Haifa, Israel
|
|
Sorry, no screen shot.
Image is just static, so it would be boring... |
|
|
| |
|
|
|
|
|
Posted: Jun 13, 2012 - 06:48 PM |
|


Joined: Jul 18, 2005
Posts: 62290
Location: (using avr-gcc in) Finchingfield, Essex, England
|
|
|
Quote:
so it would be boring...
Not for micro engineers who can appreciate how amazing the achievement is! |
_________________
|
| |
|
|
|
|
|
Posted: Jun 14, 2012 - 12:45 AM |
|

Joined: Jul 19, 2011
Posts: 47
Location: Haifa, Israel
|
|
|
|
|
|
|
Posted: Jun 18, 2012 - 03:15 AM |
|

Joined: Jul 19, 2011
Posts: 47
Location: Haifa, Israel
|
|
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 |
|
|
| |
|
|
|
|
|
Posted: Aug 01, 2012 - 05:12 AM |
|

Joined: Nov 28, 2004
Posts: 3552
Location: San Diego, Ca
|
|
|
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
|
| |
|
|
|
|
|
Posted: Aug 01, 2012 - 11:00 AM |
|


Joined: Sep 04, 2007
Posts: 356
Location: Oxford (England)
|
|
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
|
| |
|
|
|
|
|
Posted: Aug 01, 2012 - 01:39 PM |
|

Joined: Jan 24, 2008
Posts: 517
|
|
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  |
|
|
| |
|
|
|
|
|