2022-02-20 11:00:36 +00:00
|
|
|
|
#include "pwm_cluster.hpp"
|
2022-02-17 22:38:59 +00:00
|
|
|
|
#include <cstdio> // TOREMOVE once done debugging
|
|
|
|
|
#include "hardware/gpio.h" // TOREMOVE once done debugging
|
|
|
|
|
#include "hardware/clocks.h"
|
2022-02-20 11:00:36 +00:00
|
|
|
|
#include "pwm_cluster.pio.h"
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-15 15:15:52 +00:00
|
|
|
|
// Uncomment the below line to enable debugging
|
2022-02-15 20:14:59 +00:00
|
|
|
|
#define DEBUG_MULTI_PWM
|
2022-02-15 15:15:52 +00:00
|
|
|
|
|
2022-02-20 11:00:36 +00:00
|
|
|
|
namespace pimoroni {
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#ifdef DEBUG_MULTI_PWM
|
|
|
|
|
static const uint DEBUG_SIDESET = 17;
|
|
|
|
|
#endif
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
|
|
|
|
int data_dma_channel;
|
|
|
|
|
int ctrl_dma_channel;
|
|
|
|
|
Sequence sequences[NUM_BUFFERS];
|
2022-03-13 16:04:19 +00:00
|
|
|
|
Sequence loop_sequences[NUM_BUFFERS];
|
2022-02-11 11:40:04 +00:00
|
|
|
|
uint sequence_index = 0;
|
|
|
|
|
|
2022-02-14 23:46:51 +00:00
|
|
|
|
volatile uint read_index = 0;
|
|
|
|
|
volatile uint last_written_index = 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
uint irq_gpio = 15;
|
|
|
|
|
uint write_gpio = 16;
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
|
|
|
|
void __isr pwm_dma_handler() {
|
2022-02-17 22:38:59 +00:00
|
|
|
|
// Clear the interrupt request.
|
|
|
|
|
dma_hw->ints0 = 1u << data_dma_channel;
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-17 22:38:59 +00:00
|
|
|
|
gpio_put(irq_gpio, 1); //TOREMOVE Just for debug
|
2022-02-14 23:46:51 +00:00
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
Sequence* seq;
|
|
|
|
|
|
2022-02-17 22:38:59 +00:00
|
|
|
|
// If new data been written since the last time, switch to reading that buffer
|
|
|
|
|
if(last_written_index != read_index) {
|
|
|
|
|
read_index = last_written_index;
|
2022-03-13 16:04:19 +00:00
|
|
|
|
seq = &sequences[read_index];
|
2022-02-17 22:38:59 +00:00
|
|
|
|
}
|
2022-03-06 22:48:13 +00:00
|
|
|
|
else {
|
2022-03-13 16:04:19 +00:00
|
|
|
|
seq = &loop_sequences[read_index];
|
2022-03-06 22:48:13 +00:00
|
|
|
|
}
|
2022-02-14 23:46:51 +00:00
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
dma_channel_set_trans_count(data_dma_channel, seq->size << 1, false);
|
|
|
|
|
dma_channel_set_read_addr(data_dma_channel, seq->data, true);
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-17 22:38:59 +00:00
|
|
|
|
gpio_put(irq_gpio, 0); //TOREMOVE Just for debug
|
2022-02-11 11:40:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/***
|
|
|
|
|
* From RP2040 datasheet
|
|
|
|
|
* *
|
|
|
|
|
* One disadvantage of this technique is that we don’t start to reconfigure the channel until some time after the channel
|
|
|
|
|
makes its last transfer. If there is heavy interrupt activity on the processor, this may be quite a long time, and therefore
|
|
|
|
|
quite a large gap in transfers, which is problematic if we need to sustain a high data throughput.
|
|
|
|
|
This is solved by using two channels, with their CHAIN_TO fields crossed over, so that channel A triggers channel B when it
|
|
|
|
|
completes, and vice versa. At any point in time, one of the channels is transferring data, and the other is either already
|
|
|
|
|
configured to start the next transfer immediately when the current one finishes, or it is in the process of being
|
|
|
|
|
reconfigured. When channel A completes, it immediately starts the cued-up transfer on channel B. At the same time, the
|
|
|
|
|
interrupt is fired, and the handler reconfigures channel A so that it is ready for when channel B completes.
|
|
|
|
|
* */
|
|
|
|
|
|
|
|
|
|
|
2022-03-02 18:59:17 +00:00
|
|
|
|
PWMCluster::PWMCluster(PIO pio, uint sm, uint pin_mask)
|
|
|
|
|
: pio(pio)
|
|
|
|
|
, sm(sm)
|
|
|
|
|
, pin_mask(pin_mask & ((1u << NUM_BANK0_GPIOS) - 1))
|
2022-03-07 16:26:20 +00:00
|
|
|
|
, channel_count(0)
|
2022-03-07 22:27:43 +00:00
|
|
|
|
, channels(nullptr)
|
2022-03-02 18:59:17 +00:00
|
|
|
|
, wrap_level(0) {
|
2022-02-21 00:04:36 +00:00
|
|
|
|
|
2022-03-08 14:26:57 +00:00
|
|
|
|
// Create the channel mapping
|
2022-03-07 22:27:43 +00:00
|
|
|
|
for(uint pin = 0; pin < NUM_BANK0_GPIOS; pin++) {
|
|
|
|
|
if(bit_in_mask(pin, pin_mask)) {
|
|
|
|
|
channel_to_pin_map[channel_count] = pin;
|
2022-03-07 16:26:20 +00:00
|
|
|
|
channel_count++;
|
|
|
|
|
}
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
2022-03-07 22:27:43 +00:00
|
|
|
|
|
2022-03-08 14:26:57 +00:00
|
|
|
|
// Initialise all the channels this PWM will control
|
2022-03-07 22:27:43 +00:00
|
|
|
|
if(channel_count > 0) {
|
|
|
|
|
channels = new ChannelState[channel_count];
|
|
|
|
|
}
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2022-03-02 18:59:17 +00:00
|
|
|
|
PWMCluster::PWMCluster(PIO pio, uint sm, uint pin_base, uint pin_count)
|
|
|
|
|
: pio(pio)
|
|
|
|
|
, sm(sm)
|
|
|
|
|
, pin_mask(0x00000000)
|
2022-03-07 16:26:20 +00:00
|
|
|
|
, channel_count(0)
|
2022-03-07 22:27:43 +00:00
|
|
|
|
, channels(nullptr)
|
2022-03-02 18:59:17 +00:00
|
|
|
|
, wrap_level(0) {
|
2022-02-21 00:04:36 +00:00
|
|
|
|
|
2022-03-08 14:26:57 +00:00
|
|
|
|
// Create the pin mask and channel mapping
|
2022-03-02 17:36:00 +00:00
|
|
|
|
uint pin_end = MIN(pin_count + pin_base, NUM_BANK0_GPIOS);
|
2022-03-07 22:27:43 +00:00
|
|
|
|
for(uint pin = pin_base; pin < pin_end; pin++) {
|
|
|
|
|
pin_mask |= (1u << pin);
|
|
|
|
|
channel_to_pin_map[channel_count] = pin;
|
2022-03-07 16:26:20 +00:00
|
|
|
|
channel_count++;
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 14:26:57 +00:00
|
|
|
|
// Initialise all the channels this PWM will control
|
|
|
|
|
if(channel_count > 0) {
|
|
|
|
|
channels = new ChannelState[channel_count];
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PWMCluster::PWMCluster(PIO pio, uint sm, const uint8_t *pins, uint32_t length)
|
|
|
|
|
: pio(pio)
|
|
|
|
|
, sm(sm)
|
|
|
|
|
, pin_mask(0x00000000)
|
|
|
|
|
, channel_count(0)
|
|
|
|
|
, channels(nullptr)
|
|
|
|
|
, wrap_level(0) {
|
|
|
|
|
|
|
|
|
|
// Create the pin mask and channel mapping
|
|
|
|
|
for(uint i = 0; i < length; i++) {
|
|
|
|
|
uint8_t pin = pins[i];
|
|
|
|
|
if(pin < NUM_BANK0_GPIOS) {
|
|
|
|
|
pin_mask |= (1u << pin);
|
|
|
|
|
channel_to_pin_map[channel_count] = pin;
|
|
|
|
|
channel_count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialise all the channels this PWM will control
|
2022-03-07 22:27:43 +00:00
|
|
|
|
if(channel_count > 0) {
|
|
|
|
|
channels = new ChannelState[channel_count];
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-02 18:59:17 +00:00
|
|
|
|
PWMCluster::PWMCluster(PIO pio, uint sm, std::initializer_list<uint8_t> pins)
|
|
|
|
|
: pio(pio)
|
|
|
|
|
, sm(sm)
|
|
|
|
|
, pin_mask(0x00000000)
|
2022-03-07 16:26:20 +00:00
|
|
|
|
, channel_count(0)
|
2022-03-07 22:27:43 +00:00
|
|
|
|
, channels(nullptr)
|
2022-03-02 18:59:17 +00:00
|
|
|
|
, wrap_level(0) {
|
2022-02-21 00:04:36 +00:00
|
|
|
|
|
2022-03-08 14:26:57 +00:00
|
|
|
|
// Create the pin mask and channel mapping
|
2022-02-21 00:04:36 +00:00
|
|
|
|
for(auto pin : pins) {
|
|
|
|
|
if(pin < NUM_BANK0_GPIOS) {
|
|
|
|
|
pin_mask |= (1u << pin);
|
2022-03-07 22:27:43 +00:00
|
|
|
|
channel_to_pin_map[channel_count] = pin;
|
2022-03-07 16:26:20 +00:00
|
|
|
|
channel_count++;
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-08 14:26:57 +00:00
|
|
|
|
// Initialise all the channels this PWM will control
|
2022-03-07 22:27:43 +00:00
|
|
|
|
if(channel_count > 0) {
|
|
|
|
|
channels = new ChannelState[channel_count];
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
PWMCluster::~PWMCluster() {
|
|
|
|
|
dma_channel_unclaim(data_dma_channel);
|
|
|
|
|
dma_channel_unclaim(ctrl_dma_channel);
|
|
|
|
|
pio_sm_set_enabled(pio, sm, false);
|
|
|
|
|
#ifdef DEBUG_MULTI_PWM
|
|
|
|
|
pio_remove_program(pio, &debug_pwm_cluster_program, pio_program_offset);
|
|
|
|
|
#else
|
|
|
|
|
pio_remove_program(pio, &pwm_cluster_program, pio_program_offset);
|
|
|
|
|
#endif
|
|
|
|
|
#ifndef MICROPY_BUILD_TYPE
|
|
|
|
|
// pio_sm_unclaim seems to hardfault in MicroPython
|
|
|
|
|
pio_sm_unclaim(pio, sm);
|
|
|
|
|
#endif
|
|
|
|
|
// Reset all the pins this PWM will control back to an unused state
|
2022-03-07 22:27:43 +00:00
|
|
|
|
for(uint channel = 0; channel < channel_count; channel++) {
|
|
|
|
|
gpio_set_function(channel_to_pin_map[channel], GPIO_FUNC_NULL);
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
2022-03-07 22:27:43 +00:00
|
|
|
|
|
|
|
|
|
delete[] channels;
|
2022-02-21 00:04:36 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool PWMCluster::init() {
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#ifdef DEBUG_MULTI_PWM
|
2022-02-20 11:00:36 +00:00
|
|
|
|
pio_program_offset = pio_add_program(pio, &debug_pwm_cluster_program);
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#else
|
2022-02-20 11:00:36 +00:00
|
|
|
|
pio_program_offset = pio_add_program(pio, &pwm_cluster_program);
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#endif
|
2022-02-17 22:38:59 +00:00
|
|
|
|
|
|
|
|
|
gpio_init(irq_gpio);
|
|
|
|
|
gpio_set_dir(irq_gpio, GPIO_OUT);
|
|
|
|
|
gpio_init(write_gpio);
|
|
|
|
|
gpio_set_dir(write_gpio, GPIO_OUT);
|
|
|
|
|
|
|
|
|
|
// Initialise all the channels this PWM will control
|
2022-03-07 22:27:43 +00:00
|
|
|
|
for(uint channel = 0; channel < channel_count; channel++) {
|
|
|
|
|
pio_gpio_init(pio, channel_to_pin_map[channel]);
|
2022-02-17 22:38:59 +00:00
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-17 22:38:59 +00:00
|
|
|
|
// Set their default state and direction
|
2022-02-21 00:04:36 +00:00
|
|
|
|
pio_sm_set_pins_with_mask(pio, sm, 0x00, pin_mask);
|
|
|
|
|
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
|
2022-02-15 20:14:59 +00:00
|
|
|
|
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#ifdef DEBUG_MULTI_PWM
|
2022-02-17 22:38:59 +00:00
|
|
|
|
pio_gpio_init(pio, DEBUG_SIDESET);
|
|
|
|
|
pio_sm_set_consecutive_pindirs(pio, sm, DEBUG_SIDESET, 1, true);
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#endif
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#ifdef DEBUG_MULTI_PWM
|
2022-02-20 11:00:36 +00:00
|
|
|
|
pio_sm_config c = debug_pwm_cluster_program_get_default_config(pio_program_offset);
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#else
|
2022-02-20 11:00:36 +00:00
|
|
|
|
pio_sm_config c = pwm_cluster_program_get_default_config(pio_program_offset);
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#endif
|
2022-02-17 22:38:59 +00:00
|
|
|
|
sm_config_set_out_pins(&c, 0, irq_gpio); //TODO change this to be 32
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#ifdef DEBUG_MULTI_PWM
|
2022-02-17 22:38:59 +00:00
|
|
|
|
sm_config_set_sideset_pins(&c, DEBUG_SIDESET);
|
2022-02-15 15:15:52 +00:00
|
|
|
|
#endif
|
2022-02-17 22:38:59 +00:00
|
|
|
|
sm_config_set_out_shift(&c, false, true, 32);
|
|
|
|
|
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE); // We actively do not want a joined FIFO even though we are not needing the RX
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float div = clock_get_hz(clk_sys) / 5000000;
|
|
|
|
|
sm_config_set_clkdiv(&c, div);
|
|
|
|
|
|
|
|
|
|
pio_sm_init(pio, sm, pio_program_offset, &c);
|
|
|
|
|
pio_sm_set_enabled(pio, sm, true);
|
|
|
|
|
|
|
|
|
|
data_dma_channel = dma_claim_unused_channel(true);
|
|
|
|
|
/*ctrl_dma_channel = dma_claim_unused_channel(true);
|
|
|
|
|
|
|
|
|
|
dma_channel_config ctrl_config = dma_channel_get_default_config(ctrl_dma_channel);
|
|
|
|
|
channel_config_set_transfer_data_size(&ctrl_config, DMA_SIZE_32);
|
|
|
|
|
//channel_config_set_read_increment(&ctrl_config, false);
|
|
|
|
|
//channel_config_set_write_increment(&ctrl_config, false);
|
|
|
|
|
channel_config_set_read_increment(&ctrl_config, true);
|
|
|
|
|
channel_config_set_write_increment(&ctrl_config, true);
|
|
|
|
|
channel_config_set_ring(&ctrl_config, true, 3); // 1 << 3 byte boundary on write ptr
|
|
|
|
|
channel_config_set_ring(&ctrl_config, false, 3); // 1 << 3 byte boundary on read ptr
|
|
|
|
|
|
|
|
|
|
dma_channel_configure(
|
|
|
|
|
ctrl_dma_channel,
|
|
|
|
|
&ctrl_config,
|
|
|
|
|
//The below two work
|
|
|
|
|
//&dma_hw->ch[data_dma_channel].al1_transfer_count_trig,
|
|
|
|
|
//&transfer_count,
|
|
|
|
|
//1,
|
|
|
|
|
//These two do not
|
|
|
|
|
//&dma_hw->ch[data_dma_channel].al3_read_addr_trig,
|
|
|
|
|
//&((uint32_t *)buffer),
|
|
|
|
|
&dma_hw->ch[data_dma_channel].al3_transfer_count, // Initial write address
|
|
|
|
|
&control_blocks[0],
|
|
|
|
|
2,
|
|
|
|
|
false
|
|
|
|
|
);*/
|
|
|
|
|
|
|
|
|
|
dma_channel_config data_config = dma_channel_get_default_config(data_dma_channel);
|
|
|
|
|
channel_config_set_bswap(&data_config, false);
|
|
|
|
|
channel_config_set_dreq(&data_config, pio_get_dreq(pio, sm, true));
|
|
|
|
|
channel_config_set_transfer_data_size(&data_config, DMA_SIZE_32);
|
|
|
|
|
channel_config_set_read_increment(&data_config, true);
|
|
|
|
|
//channel_config_set_chain_to(&data_config, ctrl_dma_channel);
|
|
|
|
|
//channel_config_set_ring(&data_config, false, 7);
|
|
|
|
|
|
|
|
|
|
dma_channel_configure(
|
|
|
|
|
data_dma_channel,
|
|
|
|
|
&data_config,
|
|
|
|
|
&pio->txf[sm],
|
|
|
|
|
NULL,
|
|
|
|
|
0,
|
|
|
|
|
false);
|
|
|
|
|
|
|
|
|
|
dma_channel_set_irq0_enabled(data_dma_channel, true);
|
|
|
|
|
|
|
|
|
|
// Configure the processor to run dma_handler() when DMA IRQ 0 is asserted
|
|
|
|
|
irq_set_exclusive_handler(DMA_IRQ_0, pwm_dma_handler);
|
|
|
|
|
irq_set_enabled(DMA_IRQ_0, true);
|
|
|
|
|
|
|
|
|
|
// Set up the transition buffers
|
|
|
|
|
for(uint i = 0; i < NUM_BUFFERS; i++) {
|
|
|
|
|
Sequence& seq = sequences[i];
|
|
|
|
|
seq = Sequence();
|
2022-02-20 15:12:02 +00:00
|
|
|
|
seq.data[0].delay = 10; // Need to set a delay otherwise a lockup occurs when first changing frequency
|
2022-03-13 16:04:19 +00:00
|
|
|
|
Sequence& looping_seq = loop_sequences[i];
|
|
|
|
|
looping_seq = Sequence();
|
|
|
|
|
looping_seq.data[0].delay = 10; // Need to set a delay otherwise a lockup occurs when first changing frequency
|
2022-02-17 22:38:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Manually call the handler once, to trigger the first transfer
|
|
|
|
|
pwm_dma_handler();
|
|
|
|
|
|
|
|
|
|
//dma_start_channel_mask(1u << ctrl_dma_channel);
|
2022-02-21 00:04:36 +00:00
|
|
|
|
return true;
|
2022-02-14 23:46:51 +00:00
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-03-07 16:26:20 +00:00
|
|
|
|
uint8_t PWMCluster::get_chan_count() const {
|
|
|
|
|
return channel_count;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
uint8_t PWMCluster::get_chan_pin(uint8_t channel) const {
|
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
return channel_to_pin_map[channel];
|
2022-03-07 16:26:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
uint32_t PWMCluster::get_chan_level(uint8_t channel) const {
|
2022-03-07 22:27:43 +00:00
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
return channels[channel].level;
|
2022-03-07 16:26:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
void PWMCluster::set_chan_level(uint8_t channel, uint32_t level, bool load) {
|
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
channels[channel].level = level;
|
|
|
|
|
if(load)
|
|
|
|
|
load_pwm();
|
2022-03-07 16:26:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
uint32_t PWMCluster::get_chan_offset(uint8_t channel) const {
|
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
return channels[channel].offset;
|
2022-03-07 16:26:20 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
void PWMCluster::set_chan_offset(uint8_t channel, uint32_t offset, bool load) {
|
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
channels[channel].offset = offset;
|
2022-02-17 22:38:59 +00:00
|
|
|
|
if(load)
|
|
|
|
|
load_pwm();
|
2022-02-15 20:14:59 +00:00
|
|
|
|
}
|
2022-02-14 23:46:51 +00:00
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
bool PWMCluster::get_chan_polarity(uint8_t channel) const {
|
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
return channels[channel].polarity;
|
2022-02-15 20:14:59 +00:00
|
|
|
|
}
|
2022-02-14 23:46:51 +00:00
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
void PWMCluster::set_chan_polarity(uint8_t channel, bool polarity, bool load) {
|
|
|
|
|
assert(channel < channel_count);
|
|
|
|
|
channels[channel].polarity = polarity;
|
|
|
|
|
if(load)
|
|
|
|
|
load_pwm();
|
2022-02-14 23:46:51 +00:00
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
uint32_t PWMCluster::get_wrap() const {
|
|
|
|
|
return wrap_level;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void PWMCluster::set_wrap(uint32_t wrap, bool load) {
|
|
|
|
|
wrap_level = MAX(wrap, 1); // Cannot have a wrap of zero!
|
|
|
|
|
if(load)
|
|
|
|
|
load_pwm();
|
2022-02-14 23:46:51 +00:00
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-15 20:14:59 +00:00
|
|
|
|
// These apply immediately, so do not obey the PWM update trigger
|
2022-02-20 11:00:36 +00:00
|
|
|
|
void PWMCluster::set_clkdiv(float divider) {
|
2022-02-17 22:38:59 +00:00
|
|
|
|
pio_sm_set_clkdiv(pio, sm, divider);
|
2022-02-15 20:14:59 +00:00
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
|
2022-02-15 20:14:59 +00:00
|
|
|
|
// These apply immediately, so do not obey the PWM update trigger
|
2022-02-20 11:00:36 +00:00
|
|
|
|
void PWMCluster::set_clkdiv_int_frac(uint16_t integer, uint8_t fract) {
|
2022-02-17 22:38:59 +00:00
|
|
|
|
pio_sm_set_clkdiv_int_frac(pio, sm, integer, fract);
|
2022-02-14 23:46:51 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-20 11:00:36 +00:00
|
|
|
|
void PWMCluster::load_pwm() {
|
2022-02-17 22:38:59 +00:00
|
|
|
|
gpio_put(write_gpio, 1);
|
|
|
|
|
|
|
|
|
|
uint data_size = 0;
|
2022-03-13 16:04:19 +00:00
|
|
|
|
uint looping_data_size = 0;
|
2022-02-17 22:38:59 +00:00
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
uint pin_states = 0; // Start with all pins low
|
2022-03-06 22:48:13 +00:00
|
|
|
|
|
2022-03-06 23:34:54 +00:00
|
|
|
|
// Check if the data we last wrote has been picked up by the DMA yet?
|
2022-03-13 16:04:19 +00:00
|
|
|
|
const bool read_since_last_write = (read_index == last_written_index);
|
2022-03-06 23:34:54 +00:00
|
|
|
|
|
2022-02-17 22:38:59 +00:00
|
|
|
|
// Go through each channel that we are assigned to
|
2022-03-07 22:27:43 +00:00
|
|
|
|
for(uint channel = 0; channel < channel_count; channel++) {
|
|
|
|
|
ChannelState &state = channels[channel];
|
2022-03-06 22:48:13 +00:00
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
// Invert this channel's initial state if it's polarity invert is set
|
|
|
|
|
if(state.polarity) {
|
2022-03-10 05:55:56 +00:00
|
|
|
|
pin_states |= (1u << channel_to_pin_map[channel]); // Set the pin
|
2022-03-07 22:27:43 +00:00
|
|
|
|
}
|
2022-03-06 22:48:13 +00:00
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
const uint channel_start = state.offset;
|
|
|
|
|
const uint channel_end = (state.offset + state.level);
|
|
|
|
|
const uint channel_wrapped_end = channel_end % wrap_level;
|
2022-03-07 22:27:43 +00:00
|
|
|
|
|
|
|
|
|
// If the data has been read, copy the channel overruns from that sequence. Otherwise, keep the ones we had previously stored.
|
|
|
|
|
if(read_since_last_write) {
|
|
|
|
|
// This condition was added to deal with cases of load_pwm() being called multiple
|
|
|
|
|
// times between DMA reads, and thus loosing memory of the previous sequence's overruns
|
|
|
|
|
state.overrun = state.next_overrun;
|
|
|
|
|
}
|
|
|
|
|
state.next_overrun = 0u; // Always clear the next channel overruns, as we are loading new data
|
|
|
|
|
|
|
|
|
|
// Did the previous sequence overrun the wrap level?
|
|
|
|
|
if(state.overrun > 0) {
|
|
|
|
|
// Flip the initial state so the pin starts "high" (or "low" if polarity inverted)
|
2022-03-10 05:55:56 +00:00
|
|
|
|
pin_states ^= (1u << channel_to_pin_map[channel]);
|
2022-03-07 22:27:43 +00:00
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
// Is our end level before our start level?
|
|
|
|
|
if(channel_wrapped_end < channel_start) {
|
|
|
|
|
// Yes, so add a transition to "low" (or "high" if polarity inverted) at the end level, rather than the overrun (so our pulse takes effect earlier)
|
|
|
|
|
PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, channel_wrapped_end, state.polarity));
|
|
|
|
|
}
|
|
|
|
|
else if(state.overrun < channel_start) {
|
|
|
|
|
// No, so add a transition to "low" (or "high" if polarity inverted) at the overrun instead
|
2022-03-07 22:27:43 +00:00
|
|
|
|
PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, state.overrun, state.polarity));
|
2022-02-17 22:38:59 +00:00
|
|
|
|
}
|
2022-03-07 22:27:43 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
// Is the state level greater than zero, and the start level within the wrap?
|
2022-03-07 22:27:43 +00:00
|
|
|
|
if(state.level > 0 && channel_start < wrap_level) {
|
|
|
|
|
// Add a transition to "high" (or "low" if polarity inverted) at the start level
|
|
|
|
|
PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, channel_start, !state.polarity));
|
2022-03-13 16:04:19 +00:00
|
|
|
|
PWMCluster::sorted_insert(looping_transitions, looping_data_size, TransitionData(channel, channel_start, !state.polarity));
|
2022-03-06 22:48:13 +00:00
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
// If the channel has overrun the wrap level, record by how much
|
2022-03-13 16:04:19 +00:00
|
|
|
|
if(channel_wrapped_end < channel_start) {
|
|
|
|
|
state.next_overrun = channel_wrapped_end;
|
2022-02-17 22:38:59 +00:00
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
}
|
2022-03-07 22:27:43 +00:00
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
// Is the state level within the wrap?
|
2022-03-07 22:27:43 +00:00
|
|
|
|
if(state.level < wrap_level) {
|
2022-03-13 16:04:19 +00:00
|
|
|
|
// Is the end level within the wrap too?
|
|
|
|
|
if(channel_end < wrap_level) {
|
|
|
|
|
// Add a transition to "low" (or "high" if polarity inverted) at the end level
|
|
|
|
|
PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, channel_end, state.polarity));
|
|
|
|
|
}
|
2022-03-10 05:55:56 +00:00
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
// Add a transition to "low" (or "high" if polarity inverted) at the wrapped end level
|
|
|
|
|
PWMCluster::sorted_insert(looping_transitions, looping_data_size, TransitionData(channel, channel_wrapped_end, state.polarity));
|
|
|
|
|
}
|
2022-02-17 22:38:59 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
gpio_put(write_gpio, 0); //TOREMOVE
|
|
|
|
|
|
2022-03-06 13:12:34 +00:00
|
|
|
|
// Introduce "Loading Zone" transitions to the end of the sequence to
|
|
|
|
|
// prevent the DMA interrupt firing many milliseconds before the sequence ends.
|
2022-03-13 16:04:19 +00:00
|
|
|
|
uint32_t zone_inserts = MIN(LOADING_ZONE_SIZE, wrap_level - LOADING_ZONE_POSITION);
|
|
|
|
|
for(uint32_t i = zone_inserts + LOADING_ZONE_POSITION; i > LOADING_ZONE_POSITION; i--) {
|
|
|
|
|
PWMCluster::sorted_insert(transitions, data_size, TransitionData(wrap_level - i));
|
|
|
|
|
PWMCluster::sorted_insert(looping_transitions, looping_data_size, TransitionData(wrap_level - i));
|
2022-03-06 13:12:34 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
gpio_put(write_gpio, 1); //TOREMOVE
|
|
|
|
|
|
2022-02-17 22:38:59 +00:00
|
|
|
|
// Read | Last W = Write
|
|
|
|
|
// 0 | 0 = 1 (or 2)
|
|
|
|
|
// 0 | 1 = 2
|
|
|
|
|
// 0 | 2 = 1
|
|
|
|
|
// 1 | 0 = 2
|
|
|
|
|
// 1 | 1 = 2 (or 0)
|
|
|
|
|
// 1 | 2 = 0
|
|
|
|
|
// 2 | 0 = 1
|
|
|
|
|
// 2 | 1 = 0
|
|
|
|
|
// 2 | 2 = 0 (or 1)
|
|
|
|
|
|
|
|
|
|
// Choose the write index based on the read and last written indices (using the above table)
|
|
|
|
|
uint write_index = (read_index + 1) % NUM_BUFFERS;
|
|
|
|
|
if(write_index == last_written_index) {
|
|
|
|
|
write_index = (write_index + 1) % NUM_BUFFERS;
|
|
|
|
|
}
|
|
|
|
|
|
2022-03-13 16:04:19 +00:00
|
|
|
|
populate_sequence(transitions, data_size, sequences[write_index], pin_states);
|
|
|
|
|
populate_sequence(looping_transitions, looping_data_size, loop_sequences[write_index], pin_states);
|
2022-02-17 22:38:59 +00:00
|
|
|
|
|
|
|
|
|
// Update the last written index so that the next DMA interrupt picks up the new sequence
|
|
|
|
|
last_written_index = write_index;
|
|
|
|
|
|
|
|
|
|
gpio_put(write_gpio, 0); //TOREMOVE
|
2022-02-11 11:40:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-03-07 22:27:43 +00:00
|
|
|
|
// Derived from the rp2 Micropython implementation: https://github.com/micropython/micropython/blob/master/ports/rp2/machine_pwm.c
|
2022-03-10 06:58:26 +00:00
|
|
|
|
bool PWMCluster::calculate_pwm_factors(float freq, uint32_t& top_out, uint32_t& div256_out) {
|
2022-03-07 22:27:43 +00:00
|
|
|
|
bool success = false;
|
|
|
|
|
uint32_t source_hz = clock_get_hz(clk_sys) / PWM_CLUSTER_CYCLES;
|
|
|
|
|
|
|
|
|
|
// Check the provided frequency is valid
|
|
|
|
|
if((freq >= 0.01f) && (freq <= (float)(source_hz >> 1))) {
|
2022-03-10 06:58:26 +00:00
|
|
|
|
uint64_t div256_top = (uint64_t)((float)((uint64_t)source_hz << 8) / freq);
|
2022-03-07 22:27:43 +00:00
|
|
|
|
uint64_t top = 1;
|
|
|
|
|
|
|
|
|
|
while(true) {
|
|
|
|
|
// Try a few small prime factors to get close to the desired frequency.
|
2022-03-10 06:58:26 +00:00
|
|
|
|
if((div256_top >= (11 << 8)) && (div256_top % 11 == 0) && (top * 11 <= MAX_PWM_CLUSTER_WRAP)) {
|
|
|
|
|
div256_top /= 11;
|
|
|
|
|
top *= 11;
|
|
|
|
|
}
|
|
|
|
|
else if((div256_top >= (7 << 8)) && (div256_top % 7 == 0) && (top * 7 <= MAX_PWM_CLUSTER_WRAP)) {
|
|
|
|
|
div256_top /= 7;
|
|
|
|
|
top *= 7;
|
|
|
|
|
}
|
|
|
|
|
else if((div256_top >= (5 << 8)) && (div256_top % 5 == 0) && (top * 5 <= MAX_PWM_CLUSTER_WRAP)) {
|
|
|
|
|
div256_top /= 5;
|
2022-03-07 22:27:43 +00:00
|
|
|
|
top *= 5;
|
|
|
|
|
}
|
2022-03-10 06:58:26 +00:00
|
|
|
|
else if((div256_top >= (3 << 8)) && (div256_top % 3 == 0) && (top * 3 <= MAX_PWM_CLUSTER_WRAP)) {
|
|
|
|
|
div256_top /= 3;
|
2022-03-07 22:27:43 +00:00
|
|
|
|
top *= 3;
|
|
|
|
|
}
|
2022-03-10 06:58:26 +00:00
|
|
|
|
else if((div256_top >= (2 << 8)) && (top * 2 <= MAX_PWM_CLUSTER_WRAP)) {
|
|
|
|
|
div256_top /= 2;
|
2022-03-07 22:27:43 +00:00
|
|
|
|
top *= 2;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Only return valid factors if the divisor is actually achievable
|
2022-03-10 06:58:26 +00:00
|
|
|
|
if(div256_top >= 256 && div256_top <= (UINT8_MAX << 8)) {
|
2022-03-07 22:27:43 +00:00
|
|
|
|
top_out = top;
|
2022-03-10 06:58:26 +00:00
|
|
|
|
div256_out = div256_top;
|
2022-03-07 22:27:43 +00:00
|
|
|
|
|
|
|
|
|
success = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return success;
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-20 11:00:36 +00:00
|
|
|
|
bool PWMCluster::bit_in_mask(uint bit, uint mask) {
|
2022-02-17 22:38:59 +00:00
|
|
|
|
return ((1u << bit) & mask) != 0;
|
2022-02-11 11:40:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
2022-02-20 11:00:36 +00:00
|
|
|
|
void PWMCluster::sorted_insert(TransitionData array[], uint &size, const TransitionData &data) {
|
2022-03-13 16:04:19 +00:00
|
|
|
|
uint i = size;
|
|
|
|
|
for(; (i > 0 && array[i - 1].level > data.level); i--) {
|
2022-02-17 22:38:59 +00:00
|
|
|
|
array[i] = array[i - 1];
|
|
|
|
|
}
|
|
|
|
|
array[i] = data;
|
|
|
|
|
size++;
|
2022-02-11 11:40:04 +00:00
|
|
|
|
}
|
2022-03-13 16:04:19 +00:00
|
|
|
|
|
|
|
|
|
void PWMCluster::populate_sequence(const TransitionData transitions[], const uint &data_size, Sequence &seq_out, uint &pin_states_in_out) const {
|
|
|
|
|
seq_out.size = 0; // Reset the sequence, otherwise we end up appending and weird things happen
|
|
|
|
|
|
|
|
|
|
if(data_size > 0) {
|
|
|
|
|
uint data_index = 0;
|
|
|
|
|
uint current_level = 0;
|
|
|
|
|
|
|
|
|
|
// Populate the selected write sequence with pin states and delays
|
|
|
|
|
while(data_index < data_size) {
|
|
|
|
|
uint next_level = wrap_level; // Set the next level to be the wrap, initially
|
|
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
const TransitionData &transition = transitions[data_index];
|
|
|
|
|
|
|
|
|
|
// Is the level of this transition at the current level being checked?
|
|
|
|
|
if(transition.level <= current_level) {
|
|
|
|
|
// Yes, so add the transition state to the pin states mask, if it's not a dummy transition
|
|
|
|
|
if(!transition.dummy) {
|
|
|
|
|
if(transition.state)
|
|
|
|
|
pin_states_in_out |= (1u << channel_to_pin_map[transition.channel]);
|
|
|
|
|
else
|
|
|
|
|
pin_states_in_out &= ~(1u << channel_to_pin_map[transition.channel]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
data_index++; // Move on to the next transition
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// No, it is higher, so set it as our next level and break out of the loop
|
|
|
|
|
next_level = transition.level;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
} while(data_index < data_size);
|
|
|
|
|
|
|
|
|
|
// Add the transition to the sequence
|
|
|
|
|
seq_out.data[seq_out.size].mask = pin_states_in_out;
|
|
|
|
|
seq_out.data[seq_out.size].delay = (next_level - current_level) - 1;
|
|
|
|
|
seq_out.size++;
|
|
|
|
|
|
|
|
|
|
current_level = next_level;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
// There were no transitions (either because there was a zero wrap, or no channels because there was a zero wrap?),
|
|
|
|
|
// so initialise the sequence with something, so the PIO functions correctly
|
|
|
|
|
seq_out.data[seq_out.size].mask = 0u;
|
|
|
|
|
seq_out.data[seq_out.size].delay = wrap_level - 1;
|
|
|
|
|
seq_out.size++;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-02-11 11:40:04 +00:00
|
|
|
|
}
|