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**Main Sketch*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!
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
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 );}
Thanks for your help!
Statistics: Posted by thinksilicon — Mon Mar 11, 2024 11:46 pm — Replies 1 — Views 14