Quantcast
Channel: Raspberry Pi Forums
Viewing all articles
Browse latest Browse all 4726

SDK • Timer triggered DMA to PIO

$
0
0
Hi,
it appears the Pico doesn't support phase shift on their PWM so I'd like to implement it by just bit banging. I only need to run it at 100 Hz, but want 16 channels.
My plan:
Have an array with 256 steps of my PWM signal, each bit in the array fields represents one IO port. DMA cycles through the array and writes the bitpattern to the IO port. I pre-populate or update the array when I want to change the duty cycle.

I can't write to the SIO register directly with DMA so I've created a 3-liner PIO which will pull from the FIFO and write to the pins register. The PIO seems to work when I write to it in my main code just using pio->txf[sm] = 1;

When I try to create a DMA channel, triggered by a DMA timer it doesn't work.

Here's my code so far:
*PIO*

Code:

#pragma once#if !PICO_NO_HARDWARE#include "hardware/pio.h"#endif// -------- //// pin_ctrl //// -------- //#define pin_ctrl_wrap_target 0#define pin_ctrl_wrap 2static const uint16_t pin_ctrl_program_instructions[] = {            //     .wrap_target    0x80a0, //  0: pull   block                          0x6010, //  1: out    pins, 16                       0x0000, //  2: jmp    0                                      //     .wrap};#if !PICO_NO_HARDWAREstatic const struct pio_program pin_ctrl_program = {    .instructions = pin_ctrl_program_instructions,    .length = 3,    .origin = -1,};static inline pio_sm_config pin_ctrl_program_get_default_config(uint offset) {    pio_sm_config c = pio_get_default_sm_config();    sm_config_set_wrap(&c, offset + pin_ctrl_wrap_target, offset + pin_ctrl_wrap);    return c;}static inline void pin_crtl_program_init(PIO pio, uint sm, uint offset, uint pin_start) {    pio_sm_config c = pin_ctrl_program_get_default_config(offset);    // Map the state machine's OUT pin group to one pin, namely the `pin`    // parameter to this function.    sm_config_set_out_pins(&c, pin_start, 16);    // Set this pin's GPIO function (connect PIO to the pad)    for( uint pin = pin_start; pin < pin_start + 16; pin++ ) {        pio_gpio_init(pio, pin);    }    // Set the pin direction to output at the PIO    pio_sm_set_consecutive_pindirs(pio, sm, pin_start, 16, true);    //sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);    sm_config_set_clkdiv(&c, 1.0f );    sm_config_set_out_shift(&c, false, true, 32);    // Load our configuration, and jump to the start of the program    pio_sm_init(pio, sm, offset, &c);}#endif
*Main Sketch*

Code:

#include <Arduino.h>#include "hardware/gpio.h"#include "hardware/dma.h"#include "hardware/timer.h"#include "hardware/irq.h"#include "hardware/pio.h"#include "pin_crtl.pio.h"// Where do PWM pins start#define FIRST_PWM_PIN 9// Which LED to light#define PWM_TEST_PIN 1uint8_t pwm_brightness[101];volatile uint32_t steps[256];uint8_t cnt;int8_t dir = 1;uint8_t start = 0;uint8_t stop = 0;PIO pio = pio0;uint8_t sm = 0;uint dma_transfer_init( PIO pio, uint sm ) {  uint pwm_dma_chan = dma_claim_unused_channel( true );  Serial.print( "Claimed DMA channel " );  Serial.println( pwm_dma_chan );  dma_channel_config pwm_dma_chan_config = dma_channel_get_default_config( pwm_dma_chan );  // Copy 32 bits at a time  channel_config_set_transfer_data_size( &pwm_dma_chan_config, DMA_SIZE_32 );  // Increment read address  channel_config_set_read_increment( &pwm_dma_chan_config, true );  // leave write address fixed (we want to write the same PIO FIFO)  channel_config_set_write_increment( &pwm_dma_chan_config, false );  // Set up a ring to wrap afer 1024 bytes (256*4 byte in our pwm_steps array)  channel_config_set_ring( &pwm_dma_chan_config, false, 10 );  // Configure our timer  uint timer = dma_claim_unused_timer( true );  if( timer == -1 ) {    Serial.println( "Could not claim timer!!" );  } else {    Serial.print( "Claimed timer " );    Serial.println( timer );  }  // Timer will run at CPU_F * (numerator / denominator)  // We want a PWM base frequency of 100 Hz with 256 steps  // The timer needs to run at 100*256 = 25.6 kHz  // 125 MHz * ( n / d ) = 25.6 kHz => n = 16, d = 78125  // Function only takes 16 bit integers, so we just going to  // half both values, ending up with a slightly higher frequency  dma_timer_set_fraction( timer, 8, 39062 );  // copy values whenever our timer triggers  channel_config_set_dreq( &pwm_dma_chan_config, dma_get_timer_dreq( timer ) );  dma_channel_configure(      pwm_dma_chan,      &pwm_dma_chan_config,      &pio->txf[sm],   // Write to PIO TX FIFO      steps,           // read values from pwm-steps variable      256,             // 256 values to copy      false  );  while( dma_channel_is_busy( pwm_dma_chan ) ) {}  return pwm_dma_chan;}void fillGammaCorrectedPWMValues( volatile uint8_t pwmValues[101], double gamma = 2.2 ) {    for (int i = 0; i < 101; ++i) {        double brightness = i / 100.0; // Convert index to brightness level (0 to 1)        uint8_t pwmValue = static_cast<uint8_t>(pow(brightness, gamma) * 255.0);        pwmValues[i] = pwmValue;    }}void setup() {  // Initialize serial communication for debugging  Serial.begin(115200);  while (!Serial);    delay(100);  Serial.print(F("\nStarting TimerInterruptTest on ")); Serial.println(BOARD_NAME);  Serial.println( "Set pins as output" );  for( uint8_t pin = PWM_TEST_PIN; pin < PWM_TEST_PIN + 16; pin++ ) {    pinMode( pin, OUTPUT );  }  Serial.println( "Fill brightness table" );  fillGammaCorrectedPWMValues( pwm_brightness );  // set everything to 0 to start  Serial.println( "Fill steps with 0" );  for( uint16_t i = 0; i < 256; i++ ) {    steps[i] = 0;  }  Serial.print( "Load PIO: " );  // Load the pin_ctrl program into the PIO  uint offset = pio_add_program(pio, &pin_ctrl_program);  Serial.println( offset );  // Start the PIO  Serial.println( "Init PIO" );  pin_crtl_program_init(pio, sm, offset, FIRST_PWM_PIN );  Serial.println( "Init DMA channel " );  uint dma_chan = dma_transfer_init( pio, sm );  // Start DMA channel  dma_channel_start( dma_chan );  // Set the state machine running  pio_sm_set_enabled(pio, sm, true);}void loop() {  // Fill out steps array with current start and stop positions  for( uint16_t step = 0; step < 256; step++ ) {    if( step < start || step >= stop ) {      steps[ step ] = 0;    } else {      steps[ step ] = (1<<PWM_TEST_PIN);    }  }  // advance our stop position  cnt += dir;  stop = pwm_brightness[ cnt ];  if( cnt == 100 || cnt == 0 ) {    dir *= -1;  }    Serial.print( "Step " );  Serial.print( cnt );  Serial.print( ": GPIO " );  char buffer[16];  sprintf( buffer, "%08x", sio_hw->gpio_out );  Serial.print( buffer );  Serial.print( " Steps " );  for( uint8_t i = 0; i < 30; i++ ) {    Serial.print( ( steps[i] & (1<<PWM_TEST_PIN) ) ? "1" : "0" );  }  Serial.print( F("    \r") );  delay( 50 );}
The outputs flash once after the program is started, as if some data was moved once, but can't really tell what this is about. I feel like it's only a small piece that's missing but I'm at the end of my wits here.

Thanks for your help!

Statistics: Posted by thinksilicon — Mon Mar 11, 2024 11:46 pm — Replies 1 — Views 14



Viewing all articles
Browse latest Browse all 4726

Trending Articles