More progress on Servo PIO

pull/259/head
ZodiusInfuser 2022-02-14 23:46:51 +00:00
rodzic aca98ca747
commit de3cb52931
3 zmienionych plików z 340 dodań i 125 usunięć

Wyświetl plik

@ -2,6 +2,8 @@
#include "common/pimoroni_common.hpp"
#include <cstdio>
#include "hardware/gpio.h"
#include <vector>
#include <algorithm>
namespace servo {
@ -16,50 +18,47 @@ struct alignas(8) Transition {
Transition() : mask(0), delay(0) {};
};
static const uint NUM_BUFFERS = 3;
static const uint LOADING_ZONE_SIZE = 3;
struct Sequence {
uint32_t size;
Transition data[BUFFER_SIZE];
Sequence() : size(1), data({Transition()}) {};
};
Sequence sequences[NUM_BUFFERS];
uint sequence_index = 0;
//Sequence loading_zone = {3, {Transition(), Transition(), Transition()}}; //Need 6 words to create loading zone behaviour with normal FIFO
Sequence loading_zone = {5, {Transition(), Transition(), Transition(), Transition(), Transition()}}; //Need 6 words to create loading zone behaviour with normal FIFO
bool enter_loading_zone = false;
const bool use_loading_zone = true;
uint loading_zone_size = 3;
volatile uint read_index = 0;
volatile uint last_written_index = 0;
uint gpio = 15;
//Sequence loading_zone = {3, {Transition(), Transition(), Transition()}}; //Need 6 words to create loading zone behaviour with normal FIFO
//Sequence loading_zone = {5, {Transition(), Transition(), Transition(), Transition(), Transition()}}; //Need 6 words to create loading zone behaviour with normal FIFO
const bool use_loading_zone = true;
uint irq_gpio = 15;
uint write_gpio = 16;
uint sideset_gpio = 17;
void __isr pwm_dma_handler() {
// Clear the interrupt request.
dma_hw->ints0 = 1u << data_dma_channel;
// if(enter_loading_zone) {
// gpio_put(gpio+1, 1);
// uint32_t transitions = loading_zone.size * 2;
// uint32_t* buffer = (uint32_t *)loading_zone.data;
// dma_channel_set_trans_count(data_dma_channel, transitions, false);
// dma_channel_set_read_addr(data_dma_channel, buffer, true);
gpio_put(irq_gpio, 1); //TOREMOVE Just for debug
// enter_loading_zone = false;
// gpio_put(gpio+1, 0);
// }
// else {
gpio_put(gpio, 1);
uint32_t transitions = sequences[sequence_index].size * 2;
uint32_t* buffer = (uint32_t *)sequences[sequence_index].data;
// 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;
}
uint32_t transitions = sequences[read_index].size * 2;
uint32_t* buffer = (uint32_t *)sequences[read_index].data;
dma_channel_set_trans_count(data_dma_channel, transitions, false);
dma_channel_set_read_addr(data_dma_channel, buffer, true);
// For some reason sequence 0 is output to the PIO twice, rather than once, despite the below line shifting the index along...
// ^ Seemed related to filling an 8 long fifo buffer. Reducing to 4 removed it.
sequence_index = (sequence_index + 1) % NUM_BUFFERS;
gpio_put(gpio, 0);
//}
gpio_put(irq_gpio, 0); //TOREMOVE Just for debug
}
/***
@ -76,27 +75,37 @@ interrupt is fired, and the handler reconfigures channel A so that it is ready f
* */
MultiPWM::MultiPWM(PIO pio, uint sm, uint pin) : pio(pio), sm(sm) {
MultiPWM::MultiPWM(PIO pio, uint sm, uint pin_mask) : pio(pio), sm(sm), pin_mask(pin_mask) {
pio_program_offset = pio_add_program(pio, &multi_pwm_program);
gpio_init(gpio);
gpio_set_dir(gpio, GPIO_OUT);
gpio_init(gpio+1);
gpio_set_dir(gpio+1, GPIO_OUT);
gpio_init(irq_gpio);
gpio_set_dir(irq_gpio, GPIO_OUT);
gpio_init(write_gpio);
gpio_set_dir(write_gpio, GPIO_OUT);
for(uint i = pin; i < pin + 15; i++)
pio_gpio_init(pio, i);
if(pin_mask != 0) {
// Initialise all the pins this PWM will control
for(uint pin = 0; pin < 32; pin++) { // 32 is number of bits
if((pin_mask & (1u << pin)) != 0) {
pio_gpio_init(pio, pin);
pin_duty[pin] = 0u;
}
}
pio_gpio_init(pio, 17);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 17, true);
pio_sm_set_consecutive_pindirs(pio, sm, 17, 1, true);
// Set their default state and direction
pio_sm_set_pins_with_mask(pio, sm, 0x00, pin_mask);
pio_sm_set_pindirs_with_mask(pio, sm, pin_mask, pin_mask);
}
pio_gpio_init(pio, sideset_gpio);
pio_sm_set_consecutive_pindirs(pio, sm, sideset_gpio, 1, true);
pio_sm_config c = multi_pwm_program_get_default_config(pio_program_offset);
sm_config_set_out_pins(&c, pin, 17);
sm_config_set_sideset_pins(&c, 17);
sm_config_set_out_pins(&c, 0, irq_gpio); //TODO change this to be 32
sm_config_set_sideset_pins(&c, sideset_gpio);
sm_config_set_out_shift(&c, false, true, 32);
//sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX); // Joining the FIFOs makes the DMA interrupts occur earlier than we would like
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_NONE);
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);
@ -154,93 +163,19 @@ MultiPWM::MultiPWM(PIO pio, uint sm, uint pin) : pio(pio), sm(sm) {
irq_set_exclusive_handler(DMA_IRQ_0, pwm_dma_handler);
irq_set_enabled(DMA_IRQ_0, true);
{
Sequence& seq = sequences[0];
Transition* trans = seq.data;
trans[0].mask = (1u << 0);
trans[0].delay = 1000 - 1;
trans[1].mask = (1u << 1);
trans[1].delay = 1000 - 1;
trans[2].mask = (1u << 1);
trans[2].delay = 1000 - 1;
trans[3].mask = 0;
trans[3].delay = (20000 - 3000) - 1;
//if(use_loading_zone)
//trans[4].delay -= loading_zone.size;
seq.size = 4;
if(use_loading_zone){
trans[seq.size - 1].delay -= loading_zone_size;
for(uint i = 0; i < loading_zone_size; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
}
{
Sequence& seq = sequences[1];
Transition* trans = seq.data;
trans[0].mask = (1u << 5);
trans[0].delay = 10000 - 1;
trans[1].mask = 0;
trans[1].delay = (20000 - 10000) - 1;
//if(use_loading_zone)
//trans[1].delay -= loading_zone.size;
seq.size = 2;
if(use_loading_zone){
trans[seq.size - 1].delay -= loading_zone_size;
for(uint i = 0; i < loading_zone_size; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
}
{
Sequence& seq = sequences[2];
Transition* trans = seq.data;
uint count = 0;
uint last = 14;
for(uint i = 0; i < last; i++) {
trans[i].mask = (1u << i);
trans[i].delay = 1000 - 1;
count += 1000;
}
trans[last].mask = 0;
trans[last].delay = (20000 - count) - 1;
//if(use_loading_zone)
// trans[last].delay -= loading_zone.size;
seq.size = last + 1;
if(use_loading_zone){
trans[seq.size - 1].delay -= loading_zone_size;
for(uint i = 0; i < loading_zone_size; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
// Set up the transition buffers
for(uint i = 0; i < NUM_BUFFERS; i++) {
Sequence& seq = sequences[i];
seq = Sequence();
}
// Manually call the handler once, to trigger the first transfer
pwm_dma_handler();
for(uint i = 0; i < 32; i++) {
pin_duty[i] = 0;
}
//dma_start_channel_mask(1u << ctrl_dma_channel);
}
@ -255,13 +190,260 @@ MultiPWM::~MultiPWM() {
// 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
for(uint pin = 0; pin < 32; pin++) { // 32 is number of bits
if((1u << pin) != 0) {
gpio_set_function(pin, GPIO_FUNC_NULL);
}
}
}
bool MultiPWM::start(uint fps) {
//add_repeating_timer_ms(-(1000 / fps), dma_timer_callback, (void*)this, &timer);
bool MultiPWM::start(uint sequence_num) {
gpio_put(write_gpio, 1);
// 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 last index
// There's probably a single equation for this, but I couldn't work it out
uint write_index = (read_index + 1) % NUM_BUFFERS;
if(write_index == last_written_index) {
write_index = (write_index + 1) % NUM_BUFFERS;
}
switch(sequence_num) {
case 0:
{
Sequence& seq = sequences[write_index];
Transition* trans = seq.data;
trans[0].mask = (1u << 0);
trans[0].delay = 1000 - 1;
trans[1].mask = (1u << 1);
trans[1].delay = 1000 - 1;
trans[2].mask = (1u << 1);
trans[2].delay = 1000 - 1;
trans[3].mask = 0;
trans[3].delay = (20000 - 3000) - 1;
seq.size = 4;
if(use_loading_zone){
trans[seq.size - 1].delay -= LOADING_ZONE_SIZE;
for(uint i = 0; i < LOADING_ZONE_SIZE; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
}
break;
case 1:
{
Sequence& seq = sequences[write_index];
Transition* trans = seq.data;
trans[0].mask = (1u << 5);
trans[0].delay = 10000 - 1;
trans[1].mask = 0;
trans[1].delay = (20000 - 10000) - 1;
seq.size = 2;
if(use_loading_zone){
trans[seq.size - 1].delay -= LOADING_ZONE_SIZE;
for(uint i = 0; i < LOADING_ZONE_SIZE; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
}
break;
case 2:
{
Sequence& seq = sequences[write_index];
Transition* trans = seq.data;
uint count = 0;
uint last = 14;
for(uint i = 0; i < last; i++) {
trans[i].mask = (1u << i);
trans[i].delay = 1000 - 1;
count += 1000;
}
trans[last].mask = 0;
trans[last].delay = (20000 - count) - 1;
seq.size = last + 1;
if(use_loading_zone){
trans[seq.size - 1].delay -= LOADING_ZONE_SIZE;
for(uint i = 0; i < LOADING_ZONE_SIZE; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
}
break;
case 3:
default:
{
Sequence& seq = sequences[write_index];
Transition* trans = seq.data;
trans[0].mask = 0;
trans[0].delay = 20000 - 1;
seq.size = 1;
if(use_loading_zone){
trans[seq.size - 1].delay -= LOADING_ZONE_SIZE;
for(uint i = 0; i < LOADING_ZONE_SIZE; i++) {
trans[seq.size].mask = 0;
trans[seq.size].delay = 0;
seq.size += 1;
}
}
}
break;
}
last_written_index = write_index;
gpio_put(write_gpio, 0);
return true;
}
void MultiPWM::set_servo_duty(uint servo, uint32_t duty) {
if(servo > 0 && servo <= 18) {
pin_duty[servo - 1] = std::min<uint32_t>(duty, 20000);
}
}
struct myclass {
bool operator() (const TransitionData& i, const TransitionData& j) { return i.compare(j); }
} myobject;
void MultiPWM::sorted_insert(TransitionData array[], uint &size, const TransitionData &data) {
uint i;
for(i = size; (i > 0 && !array[i - 1].compare(data)); i--) {
array[i] = array[i - 1];
}
array[i] = data;
//printf("Added %d, %ld, %d\n", data.servo, data.time, data.state);
size++;
}
void MultiPWM::apply_servo_duty() {
gpio_put(write_gpio, 1);
const uint window = 20000;
TransitionData transitions[64];
uint data_size = 0;
// Go through each pin that we are assigned to
for(uint pin = 0; pin < 32; pin++) {
if(((1u << pin) & pin_mask) != 0) {
// If the duty is greater than zero, add a transition to high
if(pin_duty[pin] > 0) {
MultiPWM::sorted_insert(transitions, data_size, TransitionData(pin, 0, true));
}
// If the duty is less than the window size, add a transition to low
if(pin_duty[pin] < window) {
MultiPWM::sorted_insert(transitions, data_size, TransitionData(pin, pin_duty[pin], false));
}
}
}
//for(uint i = 0; i < data_size; i++) {
// printf("Added %d, %ld, %d\n", transitions[i].servo, transitions[i].time, transitions[i].state);
//}
// 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 last index
// There's probably a single equation for this, but I couldn't work it out
uint write_index = (read_index + 1) % NUM_BUFFERS;
if(write_index == last_written_index) {
write_index = (write_index + 1) % NUM_BUFFERS;
}
Sequence& seq = sequences[write_index];
seq.size = 0; // Reset the sequence, otherwise we end up appending, and weird things happen
//printf("Write Index = %d\n", write_index);
uint pin_states = 0;
uint current_time = 0;
uint data_start = 0;
while(data_start < data_size) {
uint next_time = window; // Set the next time to be the Window, initially
do {
// Is the time of this transition the same as the current time?
if(transitions[data_start].time <= current_time) {
// Yes, so update the state of this pin
if(transitions[data_start].state)
pin_states = pin_states | (1u << transitions[data_start].servo);
else
pin_states = pin_states & ~(1u << transitions[data_start].servo);
data_start++;
}
else {
// No, so set the next time to be the time of this transition, and break out of the loop
next_time = transitions[data_start].time;
break;
}
} while(data_start < data_size);
//#print("0b{:016b}".format(pin_mask))
// Add the transition
seq.data[seq.size].mask = pin_states;
seq.data[seq.size].delay = (next_time - current_time) - 1;
//printf("%#010x, %ld\n", pin_states, seq.data[seq.size].delay + 1);
seq.size++;
//#print((next_time - time) - 1)
current_time = next_time;
}
if(use_loading_zone) {
seq.data[seq.size - 1].delay -= LOADING_ZONE_SIZE;
for(uint i = 0; i < LOADING_ZONE_SIZE; i++) {
seq.data[seq.size].mask = 0;
seq.data[seq.size].delay = 0;
seq.size++;
}
}
last_written_index = write_index;
gpio_put(write_gpio, 0);
}
bool MultiPWM::stop() {
return true;//cancel_repeating_timer(&timer);
}

Wyświetl plik

@ -25,16 +25,34 @@ found here: https://github.com/raspberrypi/pico-examples/tree/master/pio/ws2812
namespace servo {
struct TransitionData {
uint8_t servo;
uint32_t time;
bool state;
TransitionData() : servo(0), time(0), state(false) {};
TransitionData(uint8_t servo, uint32_t time, bool new_state) : servo(servo), time(time), state(new_state) {};
bool compare(const TransitionData& other) const {
return time <= other.time;
}
};
class MultiPWM {
public:
MultiPWM(PIO pio, uint sm, uint pin);
~MultiPWM();
bool start(uint fps=60);
bool start(uint sequence_num=0);
bool stop();
void clear();
void set_servo_duty(uint servo, uint32_t duty);
void apply_servo_duty();
private:
static void sorted_insert(TransitionData array[], uint &size, const TransitionData &data);
PIO pio;
uint sm;
uint pio_program_offset;
uint pin_mask;
uint pin_duty[32];
};
}

Wyświetl plik

@ -40,6 +40,8 @@ WS2812 led_bar(N_LEDS, pio0, 0, servo2040::LED_DAT);
Button user_sw(servo2040::USER_SW, Polarity::ACTIVE_LOW, 0);
uint count = 0;
uint servo_seq = 0;
int main() {
stdio_init_all();
@ -47,7 +49,7 @@ int main() {
sleep_ms(5000);
MultiPWM pwms(pio1, 0, 0);
MultiPWM pwms(pio1, 0, 0b111111111111111);
int speed = DEFAULT_SPEED;
float offset = 0.0f;
@ -67,6 +69,19 @@ int main() {
led_bar.set_hsv(i, hue + offset, 1.0f, 0.5f);
}
count++;
if(count >= 1000) {
count = 0;
pwms.start(servo_seq);
servo_seq++;
if(servo_seq >= 4)
servo_seq = 0;
pwms.set_servo_duty(1, 1000);
pwms.apply_servo_duty();
}
//pwms.update(true);
// Sleep time controls the rate at which the LED buffer is updated