2nd State Machine + DMA for low frequencies

master
Tony 2022-04-30 16:01:37 +01:00
rodzic 33d69bc8c6
commit 5b06ab9b8e
4 zmienionych plików z 205 dodań i 111 usunięć

Wyświetl plik

@ -2,7 +2,8 @@ add_executable(pio_rotary_encoder)
pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_rotary_encoder.pio)
pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_blink.pio)
pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/pio_DAC.pio)
pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/FastDAC.pio)
pico_generate_pio_header(pio_rotary_encoder ${CMAKE_CURRENT_LIST_DIR}/SlowDAC.pio)
target_sources(pio_rotary_encoder PRIVATE pio_rotary_encoder.cpp)

Wyświetl plik

@ -1,9 +1,12 @@
.program pio_DAC
.program pio_FastDAC
; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is
; empty. Write the data to the MOV pin group.
.wrap_target
pull block
mov pins, osr
pull block ; 1 machine cycle
mov pins, osr ; 1 machine cycle
; ===================
; 2 machine cycles
; ===================
.wrap
% c-sdk {
@ -11,13 +14,13 @@
// configures the SM to output on a particular pin.
// Note: No divider is specified for the SM, so it will default to the same speed as the CPU.
void pio_DAC_program_init(PIO pio, uint sm, uint offset, uint pin) {
void pio_FastDAC_program_init(PIO pio, uint sm, uint offset, uint pin) {
for (uint i=2; i<7; i++) { pio_gpio_init(pio, i); }
pio_sm_set_consecutive_pindirs(pio, sm, 2, 5, true);
pio_sm_config c = pio_DAC_program_get_default_config(offset);
pio_sm_config c = pio_FastDAC_program_get_default_config(offset);
// sm_config_set_clkdiv(&c, div); // Set the clock divider for the state machine
sm_config_set_out_pins(&c, 2, 5);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
%}

Wyświetl plik

@ -0,0 +1,28 @@
.program pio_SlowDAC
; Repeatedly get one word of data from the TX FIFO, stalling when the FIFO is
; empty. Write the data to the MOV pin group.
.wrap_target
pull block ; 1 machine cycle
mov pins, osr ; 1 machine cycle
nop [31] ; 32 machine cycles
nop [29] ; 30 machine cycles
; ===================
; 64 machine cycles
; ===================
.wrap
% c-sdk {
// this is a raw helper function for use by the user which sets up the GPIO output, and
// configures the SM to output on a particular pin.
// Note: No divider is specified for the SM, so it will default to the same speed as the CPU.
void pio_SlowDAC_program_init(PIO pio, uint sm, uint offset, uint pin) {
for (uint i=2; i<7; i++) { pio_gpio_init(pio, i); }
pio_sm_set_consecutive_pindirs(pio, sm, 2, 5, true);
pio_sm_config c = pio_SlowDAC_program_get_default_config(offset);
// sm_config_set_clkdiv(&c, div); // Set the clock divider for the state machine
sm_config_set_out_pins(&c, 2, 5);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

Wyświetl plik

@ -7,7 +7,8 @@
#include "hardware/dma.h"
#include "pio_rotary_encoder.pio.h"
#include "pio_blink.pio.h"
#include "pio_DAC.pio.h"
#include "FastDAC.pio.h"
#include "SlowDAC.pio.h"
// Ref. Commands to use when my useless laptop crashes causing VSCode to trash the environment...
// cd ./build
@ -43,8 +44,7 @@ public:
// Note: the program starts after the jump table -> initial_pc = 16
pio_sm_set_enabled(pio, sm, true); // enable the state machine
printf("PIO:0, SM:%d running 'rotarty encoder' @ %dHz\n", sm, freq);
printf("PIO:0 SM:%d - Rotary encoder' @ %dHz\n\n", sm, freq);
}
void set_rotation(int _rotation) { // set the current rotation to a specific value
@ -78,18 +78,18 @@ public:
blink_forever(PIO pio, uint sm, uint offset, uint pin, uint freq, uint blink_div) {
blink_program_init(pio, sm, offset, pin, blink_div);
pio_sm_set_enabled(pio, sm, true);
printf("PIO:0, SM:%d running 'blink' @ %dHz\n", sm, freq);
printf("PIO:0 SM:%d - Blink @ %dHz\n", sm, freq);
}
};
class DAC_write {
/* class DAC_write {
public:
DAC_write(PIO pio, uint sm, uint offset, uint pin) {
pio_DAC_program_init(pio, sm, offset, pin);
printf("PIO:%d, SM:%d running 'DAC'\n", 1, sm);
pio_FastDAC_program_init(pio, sm, offset, pin);
printf("PIO:%d, SM:%d running 'FastDAC'\n", 1, sm);
// pio->txf[sm] = clock_get_hz(clk_sys) / (2 * freq); // Write to FIFO
}
};
}; */
// Global variables...
int RotaryEncoder::rotation; // Initialize static members of class Rotary_encoder
@ -101,10 +101,10 @@ int NixieBuffer[3] = { 6, 7, 8 }; // Values to be displayed on
// - Tube1=>10's
// - Tube2=>100's
int raw_sin[sine_table_size] ;
unsigned short DAC_data[sine_table_size] __attribute__ ((aligned(2048))) ; // Align DAC data
unsigned short DAC_data[sine_table_size] __attribute__ ((aligned(2048))) ; // Align DAC data
const uint32_t transfer_count = sine_table_size ; // Number of DMA transfers per event
static inline void dma_channel_set_timer0(uint32_t timerval) { // Modify the TIMER0 register of the dma channel
const uint32_t transfer_count = sine_table_size ; // Number of DMA transfers per event
static inline void dma_channel_set_timer0(uint32_t timerval) { // Modify the TIMER0 register of the dma channel
dma_hw->timer[0] = timerval;
}
@ -122,62 +122,46 @@ void WriteCathodes (int Data) {
}
int main() {
stdio_init_all(); // needed for printf
set_sys_clock_khz(280000, true); // Overclocking the core by a factor of 2 allows 1MHz from DAC
stdio_init_all(); // needed for printf
set_sys_clock_khz(280000, true); // Overclocking the core by a factor of 2 allows 1MHz from DAC
int scan = 0, lastval, temp;
static const float blink_freq = 16000; // Reduce SM clock to keep flash visible...
float blink_div = (float)clock_get_hz(clk_sys) / blink_freq; // ... calculate the required blink SM clock divider
static const float rotary_freq = 16000; // Clock speed reduced to eliminate rotary encoder jitter...
float rotary_div = (float)clock_get_hz(clk_sys) / rotary_freq; //... then calculate the required rotary encoder SM clock divider
// Leaving this here, as it may yet be the best way to adjust the signal frequency...
// static const float DAC_freq = 5000000; // 37KHz (measured)
// float DAC_div = (float)clock_get_hz(clk_sys) / DAC_freq; //... then calculate the required rotary encoder SM clock divider
// float DAC_div = 1;
static const float blink_freq = 16000; // Reduce SM clock to keep flash visible...
float blink_div = (float)clock_get_hz(clk_sys) / blink_freq; // ... calculate the required blink SM clock divider
static const float rotary_freq = 16000; // Clock speed reduced to eliminate rotary encoder jitter...
float rotary_div = (float)clock_get_hz(clk_sys) / rotary_freq; //... then calculate the required rotary encoder SM clock divider
float DAC_freq;
float DAC_div;
// Set up the GPIO pins...
const uint Onboard_LED = PICO_DEFAULT_LED_PIN; // Debug use - intialise the Onboard LED...
const uint Onboard_LED = PICO_DEFAULT_LED_PIN; // Debug use - intialise the Onboard LED...
gpio_init(Onboard_LED);
gpio_set_dir(Onboard_LED, GPIO_OUT);
// Initialise the Nixie cathodes...
for ( uint i = 0; i < sizeof(NixieCathodes) / sizeof( NixieCathodes[0]); i++ ) {
gpio_init(NixieCathodes[i]);
gpio_set_dir(NixieCathodes[i], GPIO_OUT); // Set as output
gpio_set_dir(NixieCathodes[i], GPIO_OUT); // Set as output
}
// Initialise the Nixe anodes...
for ( uint i = 0; i < sizeof(NixieAnodes) / sizeof( NixieAnodes[0]); i++ ) {
gpio_init(NixieAnodes[i]);
gpio_set_dir(NixieAnodes[i], GPIO_OUT); // Set as output
gpio_set_dir(NixieAnodes[i], GPIO_OUT); // Set as output
}
// Initialise the rotary encoder...
for ( uint i = 0; i < sizeof(RotaryEncoder) / sizeof( EncoderPorts[0]); i++ ) {
gpio_init(EncoderPorts[i]);
gpio_set_dir(EncoderPorts[i], GPIO_IN); // Set as input
gpio_pull_up(EncoderPorts[i]); // Enable pull up
gpio_set_dir(EncoderPorts[i], GPIO_IN); // Set as input
gpio_pull_up(EncoderPorts[i]); // Enable pull up
}
// Set up the State machines...
PIO pio = pio0;
uint offset = pio_add_program(pio, &pio_blink_program);
blink_forever my_blinker(pio, 0, offset, 25, blink_freq, blink_div); // SM0=>onboard LED
RotaryEncoder my_encoder(16, rotary_freq); // the A of the rotary encoder is connected to GPIO 16, B to GPIO 17
my_encoder.set_rotation(0); // Zero the rotatry encoder rotation
// Select a PIO and find a free state machine on it (erroring if there are none).
// Configure the state machine to run our program, and start it, using the helper function we included in our .pio file.
pio = pio1;
offset = pio_add_program(pio, &pio_DAC_program);
uint sm_DAC = pio_claim_unused_sm(pio, true);
DAC_write my_DAC(pio, sm_DAC, offset, 2); // DAC State machine, first GPIO=>2
// Build sine table
unsigned short DAC_data[sine_table_size] __attribute__ ((aligned(2048))) ;
int i ;
for (i=0; i<(sine_table_size); i++){
// raw_sin[i] = (int)(2047 * sin((float)i*6.283/(float)sine_table_size) + 2047); // 12 bit
raw_sin[i] = (int)(15 * sin((float)i*6.283/(float)sine_table_size) + 15); // 5 bit
DAC_data[i] = (raw_sin[i] & 0x0fff) ;
DAC_data[i] = raw_sin[i] ; // memory alligned data
DAC_data[i] = (raw_sin[i] & 0x0fff) ;
DAC_data[i] = raw_sin[i] ; // memory alligned data
}
// Confirm memory alignment
@ -185,94 +169,172 @@ int main() {
printf("\nFirst: %x", &DAC_data[1]);
printf("\nSecond: %x\n\n", &DAC_data[2]);
// Get 2 x free DMA channels - panic() if there are none
// ctrl_chan loads control blocks into data_chan, which executes them.
int ctrl_chan = dma_claim_unused_channel(true);
int data_chan = dma_claim_unused_channel(true);
printf("DMA ctrl channel=%d\n", ctrl_chan);
printf("DMA data channel=%d\n", data_chan);
// Set up the State machines...
PIO pio = pio0;
uint offset = pio_add_program(pio, &pio_blink_program);
blink_forever my_blinker(pio, 0, offset, 25, blink_freq, blink_div); // SM0=>onboard LED
// Setup the control channel...
// The control channel transfers two words into the data channel's control
// registers, then halts. The write address wraps on a two-word
// (eight-byte) boundary, so that the control channel writes the same two
// registers when it is next triggered.
dma_channel_config c = dma_channel_get_default_config(ctrl_chan); // default configs
channel_config_set_transfer_data_size(&c, DMA_SIZE_32); // 32-bit txfers
channel_config_set_read_increment(&c, false); // no read incrementing
channel_config_set_write_increment(&c, false); // no write incrementing
RotaryEncoder my_encoder(16, rotary_freq); // the A of the rotary encoder is connected to GPIO 16, B to GPIO 17
my_encoder.set_rotation(17); // Lowest frequency that will work with FastDAC.pio
// Select a PIO and find a free state machine on it (erroring if there are none).
// Configure the state machine to run our program, and start it, using the helper function we included in our .pio file.
pio = pio1;
offset = pio_add_program(pio, &pio_FastDAC_program);
uint sm_FastDAC = pio_claim_unused_sm(pio, true);
pio_FastDAC_program_init(pio, sm_FastDAC, offset, 2);
offset = pio_add_program(pio, &pio_SlowDAC_program);
uint sm_SlowDAC = pio_claim_unused_sm(pio, true);
pio_SlowDAC_program_init(pio, sm_SlowDAC, offset, 2);
// Get 2 x free DMA channels for the Fast DAC - panic() if there are none
int fast_ctrl_chan = dma_claim_unused_channel(true);
int fast_data_chan = dma_claim_unused_channel(true);
printf("FastDAC:\n");
printf("PIO:%d SM:%d\n", 1, sm_FastDAC);
printf("DMA:%d ctrl channel\n", fast_ctrl_chan);
printf("DMA:%d data channel\n\n", fast_data_chan);
// Setup the Fast DAC control channel...
// The control channel transfers two words into the data channel's control registers, then halts. The write address wraps on a two-word
// (eight-byte) boundary, so that the control channel writes the same two registers when it is next triggered.
dma_channel_config fc = dma_channel_get_default_config(fast_ctrl_chan); // default configs
channel_config_set_transfer_data_size(&fc, DMA_SIZE_32); // 32-bit txfers
channel_config_set_read_increment(&fc, false); // no read incrementing
channel_config_set_write_increment(&fc, false); // no write incrementing
dma_channel_configure(
ctrl_chan,
&c,
&dma_hw->ch[data_chan].al1_transfer_count_trig, // txfer to transfer count trigger
fast_ctrl_chan,
&fc,
&dma_hw->ch[fast_data_chan].al1_transfer_count_trig, // txfer to transfer count trigger
&transfer_count,
1,
false
);
// Setup the data channel...
// Setup the Fast DAC data channel...
// 32 bit transfers. Read address increments after each transfer.
dma_channel_config c2 = dma_channel_get_default_config(data_chan); // DREQ to Timer 0 is selected, so the DMA is throttled to audio rate
channel_config_set_transfer_data_size(&c2, DMA_SIZE_32); // 32-bit txfers
channel_config_set_read_increment(&c2, true); // increment the read adddress, don't increment write address
channel_config_set_write_increment(&c2, false);
channel_config_set_dreq(&c2, pio_get_dreq(pio, sm_DAC, true)); // Transfer when PIO SM TX FIFO has space
channel_config_set_chain_to(&c2, ctrl_chan); // chain to the controller DMA channel
channel_config_set_ring(&c2, false, 9); // 1 << 9 byte boundary on read ptr
// set wrap boundary. This is why we needed alignment!
fc = dma_channel_get_default_config(fast_data_chan);
channel_config_set_transfer_data_size(&fc, DMA_SIZE_32); // 32-bit txfers
channel_config_set_read_increment(&fc, true); // increment the read adddress, don't increment write address
channel_config_set_write_increment(&fc, false);
channel_config_set_dreq(&fc, pio_get_dreq(pio, sm_FastDAC, true)); // Transfer when PIO SM TX FIFO has space
channel_config_set_chain_to(&fc, fast_ctrl_chan); // chain to the controller DMA channel
channel_config_set_ring(&fc, false, 9); // 1 << 9 byte boundary on read ptr
// set wrap boundary. This is why we needed alignment!
dma_channel_configure(
data_chan, // Channel to be configured
&c2, // The configuration we just created
&pio->txf[sm_DAC], // Write to FIFO
DAC_data, // The initial read address (AT NATURAL ALIGNMENT POINT)
sine_table_size, // Number of transfers; in this case each is 2 byte.
false // Don't start immediately.
fast_data_chan, // Channel to be configured
&fc, // The configuration we just created
&pio->txf[sm_FastDAC], // Write to FIFO
DAC_data, // The initial read address (AT NATURAL ALIGNMENT POINT)
sine_table_size, // Number of transfers; in this case each is 2 byte.
false // Don't start immediately.
);
// Get 2 x free DMA channels for the Slow DAC - panic() if there are none
int slow_ctrl_chan = dma_claim_unused_channel(true);
int slow_data_chan = dma_claim_unused_channel(true);
printf("SlowDAC:\n");
printf("PIO:%d SM:%d\n", 1, sm_SlowDAC);
printf("DMA:%d ctrl channel\n", slow_ctrl_chan);
printf("DMA:%d data channel\n\n", slow_data_chan);
// Setup the Slow DAC control channel...
// The control channel transfers two words into the data channel's control registers, then halts. The write address wraps on a two-word
// (eight-byte) boundary, so that the control channel writes the same two registers when it is next triggered.
dma_channel_config sc = dma_channel_get_default_config(slow_ctrl_chan); // default configs
channel_config_set_transfer_data_size(&sc, DMA_SIZE_32); // 32-bit txfers
channel_config_set_read_increment(&sc, false); // no read incrementing
channel_config_set_write_increment(&sc, false); // no write incrementing
dma_channel_configure(
slow_ctrl_chan,
&sc,
&dma_hw->ch[slow_data_chan].al1_transfer_count_trig, // txfer to transfer count trigger
&transfer_count,
1,
false
);
// Setup the slow DAC data channel...
// 32 bit transfers. Read address increments after each transfer.
sc = dma_channel_get_default_config(slow_data_chan);
channel_config_set_transfer_data_size(&sc, DMA_SIZE_32); // 32-bit txfers
channel_config_set_read_increment(&sc, true); // increment the read adddress, don't increment write address
channel_config_set_write_increment(&sc, false);
channel_config_set_dreq(&sc, pio_get_dreq(pio, sm_SlowDAC, true)); // Transfer when PIO SM TX FIFO has space
channel_config_set_chain_to(&sc, slow_ctrl_chan); // chain to the controller DMA channel
channel_config_set_ring(&sc, false, 9); // 1 << 9 byte boundary on read ptr
// set wrap boundary. This is why we needed alignment!
dma_channel_configure(
slow_data_chan, // Channel to be configured
&sc, // The configuration we just created
&pio->txf[sm_SlowDAC], // Write to FIFO
DAC_data, // The initial read address (AT NATURAL ALIGNMENT POINT)
sine_table_size, // Number of transfers; in this case each is 2 byte.
false // Don't start immediately.
);
// We could choose to go and do something else whilst the DMA is doing its thing.
// In this case the processor has nothing else to do, so we just wait for the DMA to finish.
dma_channel_wait_for_finish_blocking(data_chan);
dma_channel_wait_for_finish_blocking(fast_data_chan);
dma_channel_wait_for_finish_blocking(slow_data_chan);
// start the control channel...
dma_start_channel_mask(1u << ctrl_chan) ;
// Setup data on DAC output...
int DAC_count = 0, DAC_val;
bool BitSet;
while (true) { // infinite loop to print the current rotation
while (true) { // Infinite loop to print the current rotation
if (my_encoder.get_rotation() != lastval) {
temp = my_encoder.get_rotation();
printf("rotation=%d\n", temp);
if (temp >= 17) {
// If DAC_div exceeds 2^16 (65,536), the registers wrap around, and the State Machine clock will be incorrect.
// A slower version of the DAC State Machine is used for frequencies below 17Hz, allowing the DAC_div to be kept
// within range.
// FastDAC
DAC_freq = temp*256; // Target frequency...
DAC_div = (float)clock_get_hz(clk_sys) / DAC_freq; // ...calculate the required rotary encoder SM clock divider
pio_sm_set_clkdiv(pio1, sm_FastDAC, DAC_div );
pio_sm_set_enabled(pio, sm_SlowDAC, false); // Stop the SlowDAC State MAchine
pio_sm_set_enabled(pio, sm_FastDAC, true); // Start the FastDAC State Machine
dma_start_channel_mask(1u << fast_ctrl_chan); // Start the FastDAC DMA channel
} else {
// SlowDAC
DAC_freq = temp*256; // Target frequency...
DAC_div = (float)clock_get_hz(clk_sys) / DAC_freq; // ...calculate the required rotary encoder SM clock divider
DAC_div = DAC_div / 32; // Adjust to keep DAC_div within useable range
pio_sm_set_clkdiv(pio1, sm_SlowDAC, DAC_div );
pio_sm_set_enabled(pio, sm_FastDAC, false); // Stop the FastDAC State Machine
pio_sm_set_enabled(pio, sm_SlowDAC, true); // Start the SlowDAC State MAchine
dma_start_channel_mask(1u << slow_ctrl_chan); // Start the SlowDAC DMA channel
}
printf("Rotation: %03d - SM Div: %8.1f - SM Clk: %06.0gHz - Fout: %3.0fHz\n",temp, DAC_div, DAC_freq, DAC_freq/256);
lastval = temp;
NixieBuffer[0] = temp % 10 ; // finished with temp, so ok to destroy it
NixieBuffer[0] = temp % 10 ; // finished with temp, so ok to destroy it
temp /= 10 ;
NixieBuffer[1] = temp % 10 ;
temp /= 10 ;
NixieBuffer[2] = temp % 10 ;
}
if (scan==0) {
gpio_put(NixieAnodes[2], 0) ; // Turn off previous anode
WriteCathodes(NixieBuffer[0]); // Set up new data on cathodes (Units)
gpio_put(NixieAnodes[0], 1) ; // Turn on current anode
}
if (scan==1) {
gpio_put(NixieAnodes[0], 0) ; // Turn off previous anode
WriteCathodes(NixieBuffer[1]); // Set up new data on cathodes (10's)
gpio_put(NixieAnodes[1], 1) ; // Turn on current anode
}
if (scan==2) {
gpio_put(NixieAnodes[1], 0) ; // Turn off previous anode
WriteCathodes(NixieBuffer[2]); // Set up new data on cathodes (100's)
gpio_put(NixieAnodes[2], 1) ; // Turn on current anode
switch (scan) {
case 0:
gpio_put(NixieAnodes[2], 0) ; // Turn off previous anode
WriteCathodes(NixieBuffer[0]); // Set up new data on cathodes (Units)
gpio_put(NixieAnodes[0], 1) ; // Turn on current anode
break;
case 1:
gpio_put(NixieAnodes[0], 0) ; // Turn off previous anode
WriteCathodes(NixieBuffer[1]); // Set up new data on cathodes (10's)
gpio_put(NixieAnodes[1], 1) ; // Turn on current anode
break;
case 2:
gpio_put(NixieAnodes[1], 0) ; // Turn off previous anode
WriteCathodes(NixieBuffer[2]); // Set up new data on cathodes (100's)
gpio_put(NixieAnodes[2], 1) ; // Turn on current anode
break;
}
scan++;
if (scan == 3) { scan = 0; }
DAC_count++;
if (DAC_count == 256) { DAC_count = 0; }
sleep_ms(7); // Pause: Short enough to avoid Nixie tube flicker
// Long enough to avoid Nixie tube bluring
sleep_ms(7); // Pause: Short enough to avoid Nixie tube flicker
// Long enough to avoid Nixie tube bluring
}
}