Go Down

Topic: Arduino DUE and fast memory output (Read 363 times) previous topic - next topic

davideIO

Hello,

I need to deal with a PAL video signal. For that purpose the LM1881 circuit is used to get sync signals.

(The Tvout library is interesting but is not appropriate for my project.)

Sync signals (15 kHz and 50 Hz) are input to Arduino DUE with interrupts. Inside these interrupts some counters are incremented or reset. These counters will then address an Arduino internal memory array.

The idea is to output memory single bit content very fast based on a 4-6 MHz clock generated by Arduino.

After some tests the results are not satisfying. My code based on Arduino DUE is not fast enough.

Questions :
1. Is my project realistic with Arduino DUE but without deep Assembler coding?
2. Should I go with another board? Which one?
3. Should I consider using external peripheral logic?
4. Should I consider doing it with an FPGA or is that overkill?

Thank you for support.

ard_newbie


How about posting your code (between code tags) ?

westfw

#2
Mar 08, 2018, 01:08 am Last Edit: Mar 08, 2018, 06:50 am by westfw
Quote
The idea is to output memory single bit content very fast based on a 4-6 MHz clock generated by Arduino.
Bit-banging an IO port is not something that most ARMs are very good at.  The Due in particular has the GPIO ports on a relatively slow bus, so it takes a couple of cycles for each write (and then there are a couple of cycles delay before the value actually gets to the pin.)  There's also a fair amount of non-determinism due to things like flash memory wait states and caching.   Most of the successful video experiments end up using something like the SPI peripheral or the USART in synchronous mode (or more recently IIC), which can shift bits out at systemclock/3 or so.  Usually with DMA.

ard_newbie

there are a couple of seconds delay before the value actually gets to the pin
Where did you see that ?

westfw

#4
Mar 08, 2018, 06:49 am Last Edit: Mar 08, 2018, 06:53 am by westfw
Oops.  Should read "couple of cyles."  (now fixed)  I  might be mis-reading figure 31-4 "Output Line Timings"?  It looks more like they're talking about a delay before the SET/CLEAR registers are usable after a direct write...

davideIO

Thank you for sharing your experience.

Here is my code, really for playing purpose.
Code: [Select]


//outputs
const byte MEMORY_OUT_PIN = 13;
const byte CLOCK_OUTPUT_PIN = 7;
//inputs
const byte SYNC_PIN = 2;
const byte GLOBAL_RESET_PIN = 3;
const byte CLOCK_INPUT_PIN = 4;

const short COLS = 10;
const short ROWS = 5;
bool bufferMemory_Ex[COLS][ROWS];
volatile int icol = 0;
volatile int irow = 0;
bool syncIsHigh = false;

Pio* memoryOutPort;
unsigned int memoryPinMask;

Pio* syncInPort;
unsigned int syncPinMask;
unsigned int syncPinMask_Negated;

void setup()
{
  //setup for memory output
  pinMode(MEMORY_OUT_PIN, OUTPUT);
  digitalWrite(MEMORY_OUT_PIN, LOW);
  memoryOutPort = g_APinDescription[MEMORY_OUT_PIN].pPort;
  memoryPinMask = g_APinDescription[MEMORY_OUT_PIN].ulPin;
  memoryOutPort->PIO_SODR = memoryPinMask;
  memoryOutPort->PIO_OWDR = ~memoryPinMask;
  memoryOutPort->PIO_OWER = memoryPinMask;

  //setup for clock output
  pinMode(CLOCK_OUTPUT_PIN, OUTPUT);
  int32_t mask_PWM_pin = digitalPinToBitMask(CLOCK_OUTPUT_PIN);
  REG_PMC_PCER1 = 1 << 4;                         // activate clock for PWM controller
  REG_PIOC_PDR |= mask_PWM_pin;                   // activate peripheral functions for pin (disables all PIO functionality)
  REG_PIOC_ABSR |= mask_PWM_pin;                  // choose peripheral option B
  REG_PWM_CLK = 0;                                // choose clock rate, 0 -> full MCLK as reference 84MHz
  REG_PWM_CMR6 = 0 << 9;                          // select clock and polarity for PWM channel (pin7) -> (CPOL = 0)
  REG_PWM_CPRD6 = 20;                             // initialize PWM period -> T = value/84MHz (value: up to 16bit), value=10 -> 8.4MHz
  REG_PWM_CDTY6 = 10;                             // initialize duty cycle, REG_PWM_CPRD6 / value = duty cycle, for 10/5 = 50%
  REG_PWM_ENA = 1 << 6;                           // enable PWM on PWM channel (pin 7 = PWML6)
  REG_PWM_DIS = 1 << 6;                           //at power-up the clock is disabled

  //setup for inputs and interrupts
  pinMode(GLOBAL_RESET_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(GLOBAL_RESET_PIN), GLOBAL_RESET_NEG, FALLING);
  pinMode(SYNC_PIN, INPUT);
 
  attachInterrupt(digitalPinToInterrupt(SYNC_PIN), SYNC_CHANGE, RISING);
  syncInPort = g_APinDescription[SYNC_PIN].pPort;
  syncPinMask = g_APinDescription[SYNC_PIN].ulPin;
  syncPinMask_Negated = ~syncPinMask;
 
  pinMode(CLOCK_INPUT_PIN, INPUT);
  attachInterrupt(digitalPinToInterrupt(CLOCK_INPUT_PIN), CLOCK_INPUT_CHANGE, RISING);

  //initialize memory buffer
  InitializeMemory();
  FillRow(1);
  Serial.begin(115200);
}

unsigned long prevMilliOperation = 0;
const unsigned long interval_ForSerialOutput = 2000;
unsigned long currentMillis = 0;
void loop()
{
  currentMillis = millis();
  if ((currentMillis - prevMilliOperation) >= interval_ForSerialOutput)
  {
    prevMilliOperation = currentMillis;

    //    Serial.println(syncPinMask, BIN);
    //    Serial.println(syncPinMask_Negated, BIN);
    //    Serial.println(syncInPort->PIO_PDSR, BIN);
    Serial.println((syncInPort->PIO_PDSR & syncPinMask) == syncPinMask, DEC);
    Serial.print(icol, DEC);
    Serial.print(";");
    Serial.println(irow, DEC);
  }

  //enable or disable clock output
  if (syncIsHigh)
  {
    REG_PWM_ENA = 1 << 6;
  }
  else
  {
    REG_PWM_DIS = 1 << 6;
  }
}

void InitializeMemory()
{
  for (int i = 0; i < COLS; i++)
  {
    for (int j = 0; j < ROWS; j++)
    {
      bufferMemory_Ex[i][j] = false;
    }
  }
}

void FillRow(int rowIndexToFill)
{
  for (int i = 0; i < COLS; i++)
  {
    bufferMemory_Ex[i][rowIndexToFill] = false;
  }
}

void GLOBAL_RESET_NEG()
{
  // reset all counters
  icol = 0;
  irow = 0;
}

void SYNC_CHANGE()
{
  syncIsHigh = ((syncInPort->PIO_PDSR & syncPinMask) == syncPinMask);
  irow++;
  if (irow >= ROWS)
  {
    irow = 0;
  }
}

void CLOCK_INPUT_CHANGE()
{
  if (syncIsHigh)
  {
    //output memory of current indexes
    if (bufferMemory_Ex[icol][irow])
    {
      memoryOutPort->PIO_SODR = memoryPinMask;
    }
    else
    {
      memoryOutPort->PIO_CODR = memoryPinMask;
    }
    //increment memory col index
    icol++;
    if (icol >= COLS)
    {
      icol = 0;
    }
  }
}


Is there any board that I can go with for my project? Maybe FPGA based?

ard_newbie

#6
Mar 08, 2018, 02:10 pm Last Edit: Mar 08, 2018, 02:45 pm by ard_newbie
A few thoughts about your code:

Do not use magic numbers, it's a pain to debug. Instead, use names provided in Sam3x datasheet and header files ( here PWM.h):

https://android.googlesource.com/platform/external/arduino-ide/+/f876b2abdebd02acfa4ba21e607327be4f9668d4/hardware/arduino/sam/system/CMSIS/Device/ATMEL/sam3xa/include/component


Line 44 :  You have selected Master Clock divided by 512 ( the slowest before MCK/1024) !  
To select  the highest clock for PWM peripheral, write:
// Set PWM clock to MCK/1 = 84 MHz
PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(1);  

Here is an example sketch of PWM programming with PWMH2 and PWML2:

Code: [Select]

/***********************************************************************************************/
/*                 PWMH2 and PWML2   -   PWM frequency = 20 KHz                                */
/***********************************************************************************************/

void setup () {

  // PWM Set-up on pins PC7 and PA20 (Arduino Pins 39(PWMH2) and 43(PWML2)): see Datasheet chap. 38.5.1 page 973
  PMC->PMC_PCER1 |= PMC_PCER1_PID36;                   // PWM power ON  
  PWM->PWM_DIS = PWM_DIS_CHID2;                        // Disable PWM channel 2

  // Select Instance=PWM; Signal=PWMH2 (channel 2); I/O Line=PC7 (P7, Arduino pin 39, see pinout diagram) ; Peripheral type B
  PIOC->PIO_PDR |= PIO_PDR_P7;                          // Set the pin to the peripheral PWM, not the GPIO
  PIOC->PIO_ABSR |= PIO_PC7B_PWMH2;                     // Set PWM pin perhipheral type B

  // Select Instance=PWM; Signal=PWML2 (channel 2); I/O Line=PA20 (P20, Arduino pin 43, see pinout diagram) ; Peripheral type B
  PIOA->PIO_PDR |= PIO_PDR_P20;                          // Set the pin to the peripheral PWM, not the GPIO
  PIOA->PIO_ABSR |= PIO_PA20B_PWML2;                    // Set PWM pin perhipheral type B

  PWM->PWM_CLK = PWM_CLK_PREA(0) | PWM_CLK_DIVA(42);   // Set the PWM clock rate to 2MHz (84MHz/42). Waveform left aligned
  PWM->PWM_CH_NUM[2].PWM_CMR = PWM_CMR_CPRE_CLKA;      // The period is left aligned, clock source as CLKA on channel 2
  PWM->PWM_CH_NUM[2].PWM_CPRD = 100;                   // Channel 2 : Set the PWM frequency 2MHz/ CPRD = F ;
  PWM->PWM_CH_NUM[2].PWM_CDTY = 50;                    // Channel 2: Set the PWM duty cycle to x%= (CDTY/ CPRD)  * 100 % ;

  PWM->PWM_ENA = PWM_ENA_CHID2;

  // Alternately, you can use this format :  REG_PWM_CPRD2 = 100;
}

void loop() {
  
}


Lines 47 and 48: what is the interest to enable PWM channel 6 and immediately after, disable PWM channel 6 ?

Line 127: you are using a comparison test (==) and there is no if() …

There are also some weird mixings between pinMode() and direct port programming , but I don't think this could be a big issue...



uptoolate

#8
Mar 28, 2018, 06:03 pm Last Edit: Mar 30, 2018, 05:20 pm by uptoolate
Bit-banging an IO port is not something that most ARMs are very good at.  The Due in particular has the GPIO ports on a relatively slow bus, so it takes a couple of cycles for each write (and then there are a couple of cycles delay before the value actually gets to the pin.)  There's also a fair amount of non-determinism due to things like flash memory wait states and caching.   Most of the successful video experiments end up using something like the SPI peripheral or the USART in synchronous mode (or more recently IIC), which can shift bits out at systemclock/3 or so.  Usually with DMA.

In digging through the SAM3X8E datasheets and related docs for 10 min... it seems like it might be feasible to disable caching on flash (code) memory accesses. I know it would result in a performance hit, but detailed timing (bit-banging anything) should benefit from enhanced determinism... right?

I haven't found any existing code (yet) in the CM3 branch that ever sets the MPU (memory protection unit) registers at all. Such as the RNR, RBAR, RASR (which contains a cache enable bit). Does this mean the DUE boots up and runs with some factory-default values in the MPU?

Go Up