Random Number Generation at MHz band

Hi,

I am trying to use my Arduino Mega board to create random pulses at around MHz. Yet, I realized every comment line (I used only digitalwrite() and random() comments) introduces a delay and decreases my output signal frequency. In average of random pulses, I can only achieved to read around 5kHz frequency from osciloscope at most. Is there any robust way to obtain random pulses at MHz levels with using any Arduino board?

Thanks..

Not possible using digitalWrite(), which takes about 4 microseconds to execute.

Using direct port access, it is possible to flip an output bit in 1 or 2 machine cycles. On an AVR-based Arduino, the command looks like this:

PINB = 1; //flip bit zero on Port B (if output)

That gives you 16 clock ticks or roughly 16 machine instructions. That may just be possible. Interrupt sources will have to be disabled (like millis / micros on timer 0 and all things Serial). The most difficult part will be finding a very fast random number generator. Anything of quality requires at least one multiply or at least one multibit shift. AVR processors not not especially good at either.

What do you mean by "random pulses"?

What's MHz, the pulse width or distance?
How "random" should it be? From µs to days? What pseudo random range and periodicity?

You may try a timer with some CTC pulse generator mode.

Another solution were a shift-out of stored patterns using SPI.

Hi @123geronimo

Possibly, but as @DrDiettrich says it depends what you mean by "random".

The problem is how to service the microcontroller's timer at MHz frequencies? Typically this would overwhelm the CPU.

The 32-bit ARM based Arduinos and Arduino compatible boards often have a Direct Memory Access (DMA) controller. This can be used to automatically load duty-cycle values from memory to the timer without CPU intervention or interrupts.

If you don't mind having a pseudorandom repeating pattern, you could generate a lookup table of duty-cycles and then get the DMA to continuously load the values into the timer at the beginning of each timer cycle. At the end of the table the DMA can be configured to loop back to the beginning.

I think one robust way would be a hardware pseudorandom bit generator clocked by a 1 MHz signal from a timer (the Arduin UNO timers can go up to 8 MHz).

1 Like

Hi @123geronimo

I tested using a lookup table and DMA approach using the SAMD21 microcontroller found on the Arduino Zero, MKR Series and Nano 33 IoT, plus a host of other similar Arduino compatible boards.

This functioned successfully. The DMA lifting the incrementing duty-cycle values from an array and loading them into the timer at the start of each timer cycle, at a frequency of 1MHz (1us period). The DMA is configured for continuous operation, so when the end of the array is reached, it starts over at the beginning again. Also, the DMA functions without any CPU intervention, leaving the processor completely free for other tasks.

If you replace the array with a larger lookup table with random duty-cycle values, you'll get a random (albeit repeating) output.

Here's the code:

// Send duty-cycle data at 1MHz on port pin PA14 (Arduino Zero D2) using TCC0 and the DMAC

volatile DmacDescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));
DmacDescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));
DmacDescriptor descriptor __attribute__ ((aligned (16)));

uint16_t pwmData[16] = { 2, 5, 8, 11, 14, 17, 20, 23, 26, 29, 32, 35, 38, 41, 44, 47 };

void setup() 
{
  GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN |        // Enable GCLK
                      GCLK_CLKCTRL_GEN_GCLK0 |    // Select GCLK0 at 48MHz
                      GCLK_CLKCTRL_ID_TCC0_TCC1;  // Use GCLK0 as clock source for TCC0 and TCC1
  
  // Configure TCC0 channel 0 output on port pin PA14 (Arduino Zero D2)
  PORT->Group[PORTA].PINCFG[14].bit.PMUXEN = 1;
  PORT->Group[PORTA].PMUX[14 >> 1].reg |= PORT_PMUX_PMUXE_F;
  
  TCC0->WAVE.reg = TCC_WAVE_WAVEGEN_NPWM;                           // Configure the TCC0 timer for Normal PWM (NPWM) mode
  while (TCC0->SYNCBUSY.bit.WAVE);                                  // Wait for synchronization
  
  TCC0->PER.reg = 47;                                               // Set period to 1us (1MHz)
  while (TCC0->SYNCBUSY.bit.PER);                                   // Wait for synchronization

  TCC0->CC[0].reg = 0;                                              // Set CC0 to duty-cycle to 0%
  while (TCC0->SYNCBUSY.bit.CC0);                                   // Wait for synchronization

  TCC0->CTRLA.bit.ENABLE = 1;                                       // Start the TCC0 timer
  while (TCC0->SYNCBUSY.bit.ENABLE);                                // Wait for synchronization
  
  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                // Set the descriptor section base address
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                // Set the write-back descriptor base adddress
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);      // Enable the DMAC and priority levels
 
  DMAC->CHID.reg = DMAC_CHID_ID(0);                                 // Select DMAC channel 0
  DMAC->CHCTRLB.reg = DMAC_CHCTRLB_LVL(0) |                         // Set the priority level to 0
                      DMAC_CHCTRLB_TRIGSRC(TCC0_DMAC_ID_OVF) |      // Set the trigger source to timer TCC0 overflow (OVF)
                      DMAC_CHCTRLB_TRIGACT_BEAT;                    // Trigger action on every beat
  descriptor.DESCADDR.reg = (uint32_t)&descriptor_section[0];       // Set up a circular descriptor
  descriptor.SRCADDR.reg = (uint32_t)&pwmData + sizeof(uint16_t) * 16;  // Read the PWM duty-cycle data
  descriptor.DSTADDR.reg = (uint32_t)&TCC0->CCB[0].reg;             // Copy it to the TCC0 capture compare buffered register on channel 0
  descriptor.BTCNT.reg = 16;                                        // This takes 16 beats
  descriptor.BTCTRL.reg = DMAC_BTCTRL_BEATSIZE_HWORD |              // Copy 16-bits (HWORDs or uint16_t)
                          DMAC_BTCTRL_SRCINC |                      // Increment source address
                          DMAC_BTCTRL_VALID;                        // Flag the descriptor as valid
  memcpy(&descriptor_section[0], &descriptor, sizeof(DmacDescriptor));  // Copy to the channel 0 descriptor

  DMAC->CHID.reg = DMAC_CHID_ID(0);                                 // Select DMAC channel 0
  DMAC->CHCTRLA.bit.ENABLE = 1;                                     // Enable DMAC channel 0
}

void loop() {}

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.