Never used that device, but as far as I'm aware you'll have to program the DMA yourself?.
If I'm not wandering too far off-topic... may I ask if anyone has seen an example of generating NTSC monochrome video from a block of RAM using DMA on a Q43 device, please?
TIA... Stay safe. Happy Holidays. Cheerful regards, Mike
void DMA5_Initialize(void)
{
//DMA Instance Selection : 0x04
DMASELECT = 0x04;
//Source Address : vsync
DMAnSSA = &vsync;
//Destination Address : &DAC1DATL
DMAnDSA = &DAC1DATL;
//DMODE unchanged; DSTP not cleared; SMR GPR; SMODE incremented; SSTP not cleared;
DMAnCON1 = 0x02;
//Source Message Size : 31
DMAnSSZ = 31;
//Destination Message Size : 1
DMAnDSZ = 1;
//Start Trigger : SIRQ TMR4;
DMAnSIRQ = 0x5B;
//Abort Trigger : AIRQ None;
DMAnAIRQ = 0x00;
// Clear Destination Count Interrupt Flag bit
PIR12bits.DMA5DCNTIF = 0;
// Clear Source Count Interrupt Flag bit
PIR12bits.DMA5SCNTIF = 0;
// Clear Abort Interrupt Flag bit
PIR12bits.DMA5AIF = 0;
// Clear Overrun Interrupt Flag bit
PIR12bits.DMA5ORIF =0;
PIE12bits.DMA5DCNTIE = 1;
DMA5_SetDCNTIInterruptHandler(DMA5_DefaultInterruptHandler);
PIE12bits.DMA5SCNTIE = 1;
DMA5_SetSCNTIInterruptHandler(DMA5_DefaultInterruptHandler);
PIE12bits.DMA5AIE = 0;
PIE12bits.DMA5ORIE = 0;
//EN enabled; SIRQEN enabled; DGO not in progress; AIRQEN disabled;
DMAnCON0 = 0xC0;
}
/*
* File: ntsc.h
* Author: root
*
* Created on December 20, 2020, 10:05 AM
*/
#ifndef NTSC_H
#define NTSC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <xc.h>
#include "mcc_generated_files/dma5.h"
#include "mcc_generated_files/tmr4.h"
#include "mcc_generated_files/dac1.h"
#define SYNC_LEVEL 0
#define BLANK_LEVEL 15
#define BLACK_LEVEL 25
#define VIDEO_LEVEL 50
extern uint8_t vsync[256];
extern uint8_t hsync[256];
extern volatile uint32_t vcounts, vfcounts;
void ntsc_init(void);
#ifdef __cplusplus
}
#endif
#endif /* NTSC_H */
#include "ntsc.h"
volatile uint32_t vcounts = 0, vfcounts = 0;
volatile bool h_mode = true, mode_init = false; // horizonal scan
void vcntd(void);
void vcnts(void);
void vint(void);
void ntsc_init(void)
{
uint8_t count = 0, vramp = BLACK_LEVEL;
TMR4_Start();
TMR4_StopTimer(); // init timer 4 as the data pacer for DAC updates
DMA5_SetDCNTIInterruptHandler(vcntd);
DMA5_SetSCNTIInterruptHandler(vcnts);
/*
* setup the static V, H and video patterns for DMA and TM4 clocking
*/
for (count = 0; count < 4; count++) {
vsync[count] = SYNC_LEVEL;
hsync[count] = BLANK_LEVEL;
}
for (count = 4; count < 8; count++) {
vsync[count] = BLANK_LEVEL;
hsync[count] = SYNC_LEVEL;
}
for (count = 8; count < 25; count++) {
vsync[count] = vramp;
hsync[count] = SYNC_LEVEL;
vramp = vramp + 2;
if (vramp > VIDEO_LEVEL) {
vramp = VIDEO_LEVEL;
}
}
for (count = 25; count < 40; count++) {
vsync[count] = BLANK_LEVEL;
hsync[count] = SYNC_LEVEL;
}
DMA5_StopTransfer();
DAC1_SetOutput(128);
TMR4_SetInterruptHandler(vint); // set dma start trigger ISR
TMR4_Start(); // get the ball rolling
}
void vcntd(void) // each DMA transfer code, 31 total bytes
{
vcounts++;
}
void vcnts(void) // each scan line interrupt code, 262 total for scan lines and V sync
{
vfcounts++;
if (h_mode) { // Horizontal sync (hsync) pulse: Start each scanline with 0.3V, then 0V for 4.7us (microseconds), and then back to 0.3V.
if (vfcounts >= 243) {
vcounts = 0;
vfcounts = 0;
h_mode = false;
mode_init = true;
}
if (mode_init) {
mode_init = false;
DMASELECT = 0x04;
DMAnCON0bits.EN = 0;
DMAnSSA = &hsync;
DMAnCON0bits.EN = 1;
}
} else { // Vertical sync (vsync) pulse: Lines 243-262 of each frame (off the bottom of the TV) start with 0.3V for 4.7us, and the rest is 0V.
if (vfcounts >= 20) {
vcounts = 0;
vfcounts = 0;
h_mode = true;
mode_init = true;
}
if (mode_init) {
mode_init = false;
DMASELECT = 0x04;
DMAnCON0bits.EN = 0;
DMAnSSA = &vsync;
DMAnCON0bits.EN = 1;
}
}
}
void vint(void) // timer 4 interrupt, dma trigger
{
DMA5_StartTransfer();
}
// System Arbiter Configuration
ISRPR = 0x01; // Interrupt Service Routine Priority;
MAINPR = 0x02; // Main Routine Priority;
DMA1PR = 0x00; // DMA1 Priority;
DMA2PR = 0x03; // DMA2 Priority;
SCANPR = 0x04; // Scanner Priority;
I'm so not a fan of MCC. Writing your own code is most of the fun of playing with micros. That and quite honestly I'm so sick and tired of all of the efforts being made to remove all barriers to entry into the embedded world.
Yes was I a beginner at one time? I sure was, as were all of you. But I learned on a 16F628A and a socketed programmer, writing my code in Notepad and using the MPASM standalone GUI. That was my starting point. I didn't copy example or open source code off the 'net. I didn't use any "code configurators" or the like. I learned it bare metal on a bread board.
Anyway...I digress.
Thanks for all the input. I was just curious to know if the compiler itself made use of the DMA hardware automagically like it does with the FSR/INDF combo when dealing with arrays and pointers. I see now that it doesn't, which is a good thing as I can use the hardware without worrying about running into compiler resource conflicts.
One thing I'll say that I learned the hard way is that in the case of the Q84, the DMA hardware wants the system arbiter priority lock bit set before it will work. Tore my hair out for 2-3 days before I figured that one out.
void DMA5_SetDMAPriority(uint8_t priority)
{
// This function is dependant on the PR1WAY CONFIG bit
PRLOCK = 0x55;
PRLOCK = 0xAA;
PRLOCKbits.PRLOCKED = 0;
DMA5PR = priority;
PRLOCK = 0x55;
PRLOCK = 0xAA;
PRLOCKbits.PRLOCKED = 1;
}
Now if you had used the MCC generated DMA arbiter priority setting function you wouldn't have lost 2-3 days.
C:void DMA5_SetDMAPriority(uint8_t priority) { // This function is dependant on the PR1WAY CONFIG bit PRLOCK = 0x55; PRLOCK = 0xAA; PRLOCKbits.PRLOCKED = 0; DMA5PR = priority; PRLOCK = 0x55; PRLOCK = 0xAA; PRLOCKbits.PRLOCKED = 1; }
Most of us are long past the point of discovering how to do X in a general abstract way on a generic abstract controller. MCC is really not for beginners, it's for experienced coders that need to get real work done. I use the MCC to generate a initial device independent framework to start the actual programming work. When a specialized controller routine is needed I write it, when a generic routine can be used then it is.
I didn't really "lose 2-3 days" as I'm just writing generic code as a means of learning the chip. It was more so a learning exercise for me. Using MCC I would have learned nothing. Doing it by hand forced me to have to read the datasheet and go through the motions required to learn. So in the end I learned something.
Now if I were on a deadline that would be a different story.
The prime problem with DMA based data transfers that have time constraints like NTSC signals is the pacing method as pure software triggered DMA runs at the memory bus speed between modules, flash, eeprom or ram. In my example above Timer 4 sets the time slice for each DMA transfer to the DAC. With a Q43 the shortest practical (the CPU and memory bus is ISR bound) time slice for DAC updates is about 5us if main is to have any CPU time between DMA and onboard DAC memory system usage with the arbitrator of the system bus sharing the bus using assigned priorities from 0 (highest) to 7 (lowest).
So what we need is a timing pacer that doesn't use controller memory or cpu resources.
If you use an external device (DAC with SPI) then the serial data transfer time plus memory transfer overhead will be the pacing speed. So if you need 500ns for each complete DMA to SPI transfer then the time splice to calculate the data array size need for the NTSC signal will be in 500ns byte patterns. During most of this 500ns the memory bus will be free for MAIN execution.
/*
* File: ntsc.h
* Author: root
*
* Created on December 20, 2020, 10:05 AM
*/
#ifndef NTSC_H
#define NTSC_H
#ifdef __cplusplus
extern "C" {
#endif
#include <xc.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include "mcc_generated_files/dma5.h"
#include "mcc_generated_files/pin_manager.h"
#define DMA_M 0x04 // DMA modules number
#define SYNC_LEVEL 0 // clear all PORTB bits
#define BLANK_LEVEL 1 // PORTB bit 0 set
#define BLACK_LEVEL 1 // "
#define VIDEO_LEVEL 2 // PORTB bit 1
#define DMA_B 473 // timing adjustment of H sync pulses for 63.2us
#define V_BUF_SIZ 512 // data buffer array size
#define S_COUNT 247 // scanlines
#define H_SYNC 3 // number of H sync lines
#define H_COUNT 12 // post H sync scanlines
#define S_END 37 // H scan pulse
#define B_START 48 // H front-porch
#define V_START 48 // Video start
#define V_DOTS 160 // scanline video dot position
#define V_END 400 // Video end
#define V_H DMA_B/2
enum s_mode_t {
sync0, sync1, sync2, sync3, sync_error
};
extern uint8_t vsync[V_BUF_SIZ];
extern uint8_t hsync[V_BUF_SIZ];
extern volatile uint8_t vbuffer[V_BUF_SIZ], *vbuf_ptr;
extern volatile uint32_t vcounts;
extern volatile uint8_t vfcounts, scan_line;
extern volatile bool ntsc_vid, ntsc_flip;
extern volatile enum s_mode_t s_mode;
void ntsc_init(void);
/*
* NTSC state machine options
*
* scan_line: Set to zero to display data on all scan lines or [1..S_COUNT] to display only on that scan line
* nstc_vid: Blank video when scan_line is set to zero for all lines
* ntsc_flip: use alternative scan_line buffer
*/
#ifdef __cplusplus
}
#endif
#endif /* NTSC_H */
#include "ntsc.h"
volatile uint32_t vcounts = 0;
volatile uint8_t vfcounts = 0, scan_line = 0;
volatile bool ntsc_vid = true, ntsc_flip = false;
volatile enum s_mode_t s_mode;
volatile uint8_t vbuffer[V_BUF_SIZ], *vbuf_ptr;
void vcntd(void);
void vcnts(void);
/*
* setup the data formats and hardware for the DMA engine
*/
void ntsc_init(void)
{
uint16_t count = 0;
DMA5_StopTransfer();
vbuf_ptr = vsync;
SLRCONB = 0xff; // reduce PORTB slewrate
DMA5_SetDMAPriority(0);
DMA5_SetDCNTIInterruptHandler(vcnts);
DMASELECT = DMA_M;
DMAnCON0bits.EN = 0;
DMAnSSA = (volatile uint24_t) vbuf_ptr;
DMAnSSZ = DMA_B;
DMAnDSZ = DMAnSSZ;
DMAnCON0bits.EN = 1;
/*
* setup the static V, H and video patterns for DMA transfer engine to PORTB
*/
for (count = 0; count < B_START; count++) {
vsync[count] = SYNC_LEVEL;
vbuffer[count] = SYNC_LEVEL;
hsync[count] = SYNC_LEVEL;
}
for (count = S_END; count < B_START; count++) {
vsync[count] = BLANK_LEVEL;
vbuffer[count] = BLANK_LEVEL;
hsync[count] = SYNC_LEVEL;
}
for (count = V_START; count < V_END; count++) {
vsync[count] = BLANK_LEVEL;
vbuffer[count] = BLANK_LEVEL;
hsync[count] = SYNC_LEVEL;
if ((count % 8)) { // add a bit of default texture
if (count > V_DOTS)
vsync[count] += VIDEO_LEVEL; // set bit 1 of PORTB
} else {
if (!(count % 8)) { // add a bit of default texture
if (count > V_DOTS)
vbuffer[count] += VIDEO_LEVEL; // set bit 1 of PORTB
}
}
}
for (count = V_END; count < (V_BUF_SIZ - 1); count++) {
vsync[count] = BLANK_LEVEL;
vbuffer[count] = BLANK_LEVEL;
hsync[count] = SYNC_LEVEL;
}
for (count = (DMA_B - 5); count < (V_BUF_SIZ - 1); count++) {
hsync[count] = BLANK_LEVEL;
}
for (count = V_H; count < (V_H + 10); count++) {
hsync[count] = BLANK_LEVEL; // double speed H pulses
}
// default scan mode to all lines
s_mode = sync1;
/*
* kickstart the DMA engine
*/
DMA5_StartTransfer();
}
/*
* routine not used
*/
void vcntd(void) // each DMA transfer interrupt
{
vcounts++;
}
/*
* NTSC DMA state machine
* ISR triggered by the completed DMA transfer of the data buffer to PORTB
* Generates the required HV sync for fake-progressive NTSC scanning on most modern TV sets
* http://people.ece.cornell.edu/land/courses/ece5760/video/gvworks/GV%27s%20works%20%20NTSC%20demystified%20-%20B&W%20Video%20and%20Sync%20-%20Part%201.htm
*/
void vcnts(void) // each scan line interrupt, 262 total for scan lines and V sync
{
vfcounts++;
IO_RB4_Toggle();
switch (s_mode) {
case sync0: // H sync and video, one line
if (vfcounts >= S_COUNT) { // 243
vfcounts = 0;
s_mode = sync2;
DMASELECT = DMA_M;
DMAnCON0bits.EN = 0;
DMAnSSA = (volatile uint24_t) & hsync;
DMAnSSZ = DMA_B;
DMAnDSZ = DMAnSSZ;
DMAnCON0bits.EN = 1;
} else {
if (vfcounts == scan_line) {
IO_RB1_SetDigitalOutput();
} else {
IO_RB1_SetDigitalInput();
}
}
break;
case sync1: // H sync and video, all lines
if (vfcounts >= S_COUNT) {
vfcounts = 0;
s_mode = sync2;
DMASELECT = DMA_M;
DMAnCON0bits.EN = 0;
DMAnSSA = (volatile uint24_t) & hsync;
DMAnSSZ = DMA_B;
DMAnDSZ = DMAnSSZ;
DMAnCON0bits.EN = 1;
} else {
if (ntsc_vid) {
IO_RB1_SetDigitalOutput();
} else {
IO_RB1_SetDigitalInput();
}
}
break;
case sync2: // V sync and no video
if (vfcounts >= H_SYNC) {
vfcounts = 0;
s_mode = sync3;
DMASELECT = DMA_M;
DMAnCON0bits.EN = 0;
DMAnSSA = (volatile uint24_t) vbuf_ptr;
DMAnSSZ = DMA_B;
DMAnDSZ = DMAnSSZ;
DMAnCON0bits.EN = 1;
IO_RB1_SetDigitalInput(); // turn-off video bits
}
break;
case sync3: // H sync and no video
if (vfcounts >= H_COUNT) {
vfcounts = 0;
if ((bool) scan_line) {
s_mode = sync0;
} else {
s_mode = sync1;
}
DMASELECT = DMA_M;
DMAnCON0bits.EN = 0;
if (ntsc_flip) {
vbuf_ptr = vbuffer;
} else {
vbuf_ptr = vsync;
}
DMAnSSA = (volatile uint24_t) vbuf_ptr;
DMAnSSZ = DMA_B;
DMAnDSZ = DMAnSSZ;
DMAnCON0bits.EN = 1;
}
break;
default:
vfcounts = 0;
s_mode = sync1;
DMASELECT = DMA_M;
DMAnCON0bits.EN = 0;
ntsc_flip = false;
vbuf_ptr = vsync;
DMAnSSA = (volatile uint24_t) vbuf_ptr;
DMAnSSZ = DMA_B;
DMAnDSZ = DMAnSSZ;
DMAnCON0bits.EN = 1;
IO_RB1_SetDigitalOutput(); // video bits, on
break;
}
/*
* re-trigger the state machine for a new scanline
*/
DMA5_StartTransfer();
}
#pragma warning disable 520
#pragma warning disable 1498
#include <stdlib.h>
#include <stdbool.h>
#include "mcc_generated_files/mcc.h"
#include "mcc_generated_files/tmr5.h"
#include "qconfig.h"
#include "vtouch.h"
#include "vtouch_build.h"
#include "timers.h"
#include "eadog.h"
#include "ntsc.h"
volatile uint16_t tickCount[TMR_COUNT];
char buffer[256];
void rxtx_handler(void);
void led_flash(void);
/*
* main program loop processing
*/
void rxtx_handler(void) // timer & serial data transform functions are handled here
{
if (TimerDone(TMR_DIS)) {
BLED_Toggle();
sprintf(buffer, "%lu,%u ", vcounts++, vfcounts);
eaDogM_WriteStringAtPos(3, 0, buffer);
sprintf(buffer, "%NTSC ");
eaDogM_WriteStringAtPos(0, 0, buffer);
StartTimer(TMR_DIS, 500);
}
}
/*
Main application
*/
void main(void)
{
// Initialize the device
SYSTEM_Initialize();
TMR5_SetInterruptHandler(led_flash);
ntsc_init();
// Enable high priority global interrupts
INTERRUPT_GlobalInterruptHighEnable();
// Enable low priority global interrupts.
INTERRUPT_GlobalInterruptLowEnable();
SPI1CON0bits.EN = 1;
init_display();
sprintf(buffer, "%s ", build_version);
eaDogM_WriteStringAtPos(0, 0, buffer);
sprintf(buffer, "%s ", build_date);
eaDogM_WriteStringAtPos(1, 0, buffer);
sprintf(buffer, "%s ", build_time);
eaDogM_WriteStringAtPos(2, 0, buffer);
BLED_SetLow();
StartTimer(TMR_DIS, 500);
TMR6_Stop(); // disable software timers to stop scan-line jitter
while (true) {
// Add your application code
// rxtx_handler();
}
}
/*
* This runs in the timer5 ISR
*/
void led_flash(void)
{
LED2_Toggle();
ntsc_flip = !ntsc_flip;
scan_line++;
}
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?
We use cookies and similar technologies for the following purposes:
Do you accept cookies and these technologies?