Very fast switching (laser control)

Hi guys,

I try to control 4 different lasers at a very fast rate (~1MHz). I need to choose which of the four are on/off for each 1us (laser diodes are capable of toggling at these speeds, that's not a problem). I can control the lasers by just giving a 3.3V-5V signal. Is there a way to do this WITHOUT using FPGA.

I tried pin manipulation and it can achieve high speed toggling but it is just toggle. I want to control which pins are HIGH for each 1us.

Thanks!

Is it a pre-defined sequence that you could store in memory or is it a sequence that you have to calculate on-the-fly?

If the four output pins are on the same port (0-7, 8-13, or A0-A5) you can write all four at the same time using direct register access.

how do you know which one to turn on when ?
you need to account for the time it takes to do those decisions as well (if that's the case)
how strict is the 1µs duration?

It is a pre-defined sequence and I want it as big as possible (depends on the storage on the controller).

The thing with using direct register access, I could not get it work time-precise. According to their initial state (on/off), direct register access takes longer/shorter.

The sequence is predefined, and it should be always 1us between two signals.

1us duration is not completely strict but at worst I need 2us duration.

If the sequence is predefined then try out the RP2040 and it's PIO. It's cheap and you can have quite large lookup tables on it. I have worked successfully with the PIO up to 3MHz while doing DSP .

I agree, rp2040 is great for this. His PIO is act as small FPGA inside the processor. I'm just trying to output predefined sequences to pins through dma and pio, in this case frequencies of tens of megahertz are easily achievable (theoretically up to half of the system clock)

+1 indeed for the RP2040

a 16MHz 328P might fall short of the requirements

That should not be the case, if using something like:

void loop() {
  PINB = 0b11<<5;
  for (int i=0; i<NumSamples; i++) {
    PORTB = pgm_read_byte(&MySequence[i]);
    asm("nop\nnop\n");
  }
}

That looks like it would work, barely. That gives me real close to a 500kHz square wave (on two pins), given MySequence = {0, 0b101,...}

It seems not very fast... Maybe it's because reading from progmem? Have you tried from RAM?
I didn’t try it on the AVR, but on the STM32 bluepil, when writing directly to the registers, I got about 4-5 MHz, and on the rp2040 at least 15 MHz (I can’t check further, since I haven't so fast oscilloscope :slight_smile: )

A 500kHz square wave is a transition every 1us, which was the request.
1us is 16 clocks on the AVR, and the progmem access takes 3 clocks, so that’s probably “reasonable”; you might shave off a couple cycles if you’re very careful/sneaky or use asm, but I’m pretty sure a new sample every 330ns would be impossible.
A faster clock rate and easier pointer math certainly makes going faster much easier.

Hi b707,

Could you please share your code, so that I can try it on my own setup?

Thanks!

Did the all transitions take same duration? When I tried it (with a little different code) some transitions took more time than the others and which board did you try on?

Thanks!

Hi, could you please share your code?

Thanks!

Code for what board? I tried it for STM32F1/F4 bluepill and RPi 2040.
I am playing with RGB diode panels and this task is typical for this application, so I will be glad to help.
But it's delayed until the end of weekend.

Thanks for being motivated for help. I was curios about the code for STM32F1/F4 bluepill actually. If that would work I could buy one.

As far as I could tell. For my test, I didn't disable interrupts, which means that every ~1ms, a timer interrupt is going to come along and mess up the timing. Or if I sent it Serial Traffic.
The test was done on an Uno.

Good evening, I have prepared for you an example code that switches two pins using a bit mask with a frequency of 1 MHz
The board stm32F401CC (blackpill)

/*  This example demonstrates using of two matched DMA transfers 
 *  for switching two pins according to a bit pattern. One DMA channel 
 *  is used to turn on the pins in accordance with the values read from 
 *  the array, the second channel resets the state of the pins before 
 *  the next switch. The switching frequency 1 MHz as set by the timer setup.
 *  
 *     b707   2022             board: STM32F401cc blackpill 
 *  Arduino STM32 support from: https://github.com/rogerclarkmelbourne/Arduino_STM32
  */

// Pins for output pulses
#define PIN1 PA0
#define PIN2 PA1

// Array of pin states
const byte nRows = 6;  // number of pin states in array
uint8_t mux_mask[nRows] = {0b00000001,
                           0b00000011,
                           0b00000010,
                           0b00000011,
                           0b00000001,
                           0b00000000};


volatile uint32 *muxsetreg;   // pin register placeholder
uint32 clr_mask;              // bitmask placeholder

// DMA channel config
const dma_dev* spiDmaDev = DMA2;          // we use DMA2 because only it can transfer from memory to GPIO on STM32F4
dma_channel spiDmaChannel = DMA_CH6;      // DMA channel for TIM1 requests
dma_stream  datTxDmaStream = DMA_STREAM2; // TIM1 CH2
dma_stream  clkTxDmaStream = DMA_STREAM6; // TIM1 CH3

#define DMD_TIMER TIMER1
#define DMD_TIMER_BASE TIMER1_BASE
#define TIM_PERIOD  84        // TIM_PERIOD = F_CPU/ 1000000 

void setup() {
    // GPIO setup
    pinMode(PIN1, OUTPUT);
    pinMode(PIN2, OUTPUT);
   
    muxsetreg = portSetRegister(PIN1);                                 // pin set/reset register GPIOx->BSRR
    clr_mask = digitalPinToBitMask(PIN1) | digitalPinToBitMask(PIN2);  // bitmask for clearing pins
    clr_mask = clr_mask << 16;

    // timer setup
    timer_init(DMD_TIMER);
    timer_pause(DMD_TIMER);
    
    DMD_TIMER_BASE->DIER = (1 << 11)|(1 << 10);  //CH2 & CH3 DMA request enable
   
    DMD_TIMER_BASE->PSC = 0;                     // Timer with system clock
    DMD_TIMER_BASE->ARR = (TIM_PERIOD  - 1);     //  F_CPU/ 1 000 000 = 1 MHz cycle
    DMD_TIMER_BASE->CCR2 = TIM_PERIOD/2 -10 ;    // dma request for set pins states
    DMD_TIMER_BASE->CCR3 = TIM_PERIOD - 10;      // dma request for reset
   
    // dma setup
    // 1st DMA stream
    dma_init(spiDmaDev);
    dma_setup_transfer(spiDmaDev, datTxDmaStream, spiDmaChannel, DMA_SIZE_8BITS,(uint32_t*)muxsetreg,  (uint8_t*)mux_mask, NULL, (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_FROM_MEM));
    dma_set_num_transfers(spiDmaDev, datTxDmaStream, nRows);
    dma_enable(spiDmaDev, datTxDmaStream);
    // 2 nd dma stream
    dma_setup_transfer(spiDmaDev, clkTxDmaStream, spiDmaChannel, DMA_SIZE_32BITS, (uint32_t*)muxsetreg,  (uint32_t*)&clr_mask, NULL, (DMA_MINC_MODE | DMA_CIRC_MODE | DMA_FROM_MEM));
    dma_set_num_transfers(spiDmaDev, clkTxDmaStream, 1);
    dma_enable(spiDmaDev, clkTxDmaStream);
    
    DMD_TIMER_BASE->CNT = 0;
    DMD_TIMER_BASE->CR1 = (1 << 0);// Start TIM1
}

void loop() {
  // put your main code here, to run repeatedly:

}

Work result:

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