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?
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.
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).
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() {}