kopia lustrzana https://github.com/oddwires/RP2040-code
937 wiersze
66 KiB
C++
937 wiersze
66 KiB
C++
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include "pico/stdlib.h"
|
|
#include "pico/binary_info.h"
|
|
#include "hardware/spi.h"
|
|
#include "hardware/clocks.h"
|
|
#include "hardware/dma.h"
|
|
#include "blink.pio.h"
|
|
#include "DAC.pio.h"
|
|
|
|
#include "hardware/gpio.h" // Required for manually toggling GPIO pins (clock)
|
|
|
|
//////////////////////////////////////
|
|
// Define GPIO connections for Pico...
|
|
//////////////////////////////////////
|
|
|
|
// Note: The SPI Port only works through specific pins, so these connections are defined first.
|
|
// SPI Port connections...
|
|
#define SPI_PORT spi0 // Using SPI port 0
|
|
// ┌──────────┬───────────────┬─────────────┐────────────────┐
|
|
// │ PGA2040 │ Connection │ MCP41010 │ Display module │
|
|
// ├──────────┼───────────────┼─────────────┤────────────────┤
|
|
#define PIN_RX 16 // │ GPIO 16 │ SPI0 RX │ (unused) │ (unused) │
|
|
#define Display_CS 17 // │ GPIO 17 │ Chip select │ (unused) │ SS1 (white) │
|
|
#define PIN_CLK 18 // │ GPIO 18 │ SPI0 Clock │ SCK (Pin 2) │ SCK (blue) │
|
|
#define PIN_TX 19 // │ GPIO 19 │ SPI0 TX │ SI (Pin 3) │ SDI (green) │
|
|
#define Level_CS 20 // │ GPIO 20 │ Chip select │ CS (Pin 1) │ (unused) │
|
|
// └──────────┴───────────────┴─────────────┘────────────────┘
|
|
|
|
#define _DAC_A 0 // DAC channel alias
|
|
#define _DAC_B 1 // DAC channel alias
|
|
#define _Up 1
|
|
#define _Down -1
|
|
#define _Sine_ 0 // Permited values for variable WaveForm_Type
|
|
#define _Square_ 1
|
|
#define _Triangle_ 2
|
|
#define _Time_ 3
|
|
#define _Funct_ 4
|
|
#define _Phase_ 5
|
|
#define _Freq_ 6
|
|
#define _Level_ 7
|
|
#define _Duty_ 8
|
|
#define _Range_ 9
|
|
#define _Harmonic_ 10
|
|
#define eof 255 // EOF in stdio.h -is -1, but getchar returns int 255 to avoid blocking
|
|
#define CR 13
|
|
#define BitMapSize 256 // Match X to Y resolution
|
|
#define MWidth 12 // Width of terminal command margin (in columns)
|
|
|
|
//#define SysClock 125 // System clock for 0.488 MHz DAC output (Pico default)
|
|
//#define SysClock 250 // System clock x 2 for 0.977 MHz DAC output
|
|
#define SysClock 280 // Overclock for 1.000 MHz DAC output
|
|
|
|
// Data for clock face generated by Excel spreadsheet...
|
|
uint8_t FaceX[] = {235,239,243,247,251,255,255,254,254,254,254,254,254,254,253,253,253,252,252,251,251,250,250,249,248,248,247,246,245,244,244,243,242,241,240,239,221,224,227,231,234,238,236,235,234,233,232,230,229,228,226,225,224,222,221,219,218,216,214,213,211,209,208,206,204,203,201,199,197,195,193,182,184,186,188,190,191,190,188,186,184,182,180,178,176,174,172,170,167,165,163,161,159,157,155,153,150,148,146,144,142,139,137,135,133,131,128,128,128,128,128,128,126,124,122,120,117,115,113,111,109,107,104,102,100,98,96,94,92,90,87,85,83,81,79,77,75,73,71,69,67,75,73,71,69,67,65,64,62,60,58,56,54,53,51,49,47,46,44,43,41,39,38,36,35,33,32,31,29,28,27,25,24,23,22,20,36,33,30,26,23,19,18,17,16,15,14,13,12,12,11,10,9,9,8,7,7,6,6,5,5,4,4,4,3,3,3,3,3,3,2,22,18,14,10,6,2,2,3,3,3,3,3,3,4,4,4,5,5,6,6,7,7,8,9,9,10,11,12,12,13,14,15,16,17,18,36,33,30,26,23,19,20,22,23,24,25,27,28,29,31,32,33,35,36,38,39,41,43,44,46,47,49,51,53,54,56,58,60,62,64,75,73,71,69,67,65,67,69,71,73,75,77,79,81,83,85,87,90,92,94,96,98,100,102,
|
|
104,107,109,111,113,115,117,120,122,124,126,128,128,128,128,128,128,131,133,135,137,139,142,144,146,148,150,153,155,157,159,161,163,165,167,170,172,174,176,178,180,182,184,186,188,190,182,184,186,188,190,191,193,195,197,199,201,203,204,206,208,209,211,213,214,216,218,219,221,222,224,225,226,228,229,230,232,233,234,235,236,221,224,227,231,234,238,239,240,241,242,243,244,244,245,246,247,248,248,249,250,250,251,251,252,252,253,253,253,254,254,254,254,254,254,254} ;
|
|
uint8_t FaceY[] = {128,128,128,128,128,128,128,126,124,122,120,117,115,113,111,109,107,104,102,100,98,96,94,92,90,87,85,83,81,79,77,75,73,71,69,67,75,73,71,69,67,65,64,62,60,58,56,54,53,51,49,47,46,44,43,41,39,38,36,35,33,32,31,29,28,27,25,24,23,22,20,36,33,30,26,23,19,18,17,16,15,14,13,12,12,11,10,9,9,8,7,7,6,6,5,5,4,4,4,3,3,3,3,3,3,2,22,18,14,10,6,2,2,3,3,3,3,3,3,4,4,4,5,5,6,6,7,7,8,9,9,10,11,12,12,13,14,15,16,17,18,36,33,30,26,23,19,20,22,23,24,25,27,28,29,31,32,33,35,36,38,39,41,43,44,46,47,49,51,53,54,56,58,60,62,64,75,73,71,69,67,65,67,69,71,73,75,77,79,81,83,85,87,90,92,94,96,98,100,102,104,107,109,111,113,115,117,120,122,124,126,128,128,128,128,128,128,131,133,135,137,139,142,144,146,148,150,153,155,157,159,161,163,165,167,170,172,174,176,178,180,182,184,186,188,190,182,184,186,188,190,191,193,195,197,199,201,203,204,206,208,209,211,213,214,216,218,219,221,222,224,225,226,228,229,230,232,233,234,235,236,221,224,227,231,234,238,239,240,241,242,243,244,244,245,246,247,248,248,249,250,250,251,251,252,
|
|
252,253,253,253,254,254,254,254,254,254,254,235,239,243,247,251,255,254,254,254,254,254,254,254,253,253,253,252,252,251,251,250,250,249,248,248,247,246,245,244,244,243,242,241,240,239,221,224,227,231,234,238,236,235,234,233,232,230,229,228,226,225,224,222,221,219,218,216,214,213,211,209,208,206,204,203,201,199,197,195,193,182,184,186,188,190,191,190,188,186,184,182,180,178,176,174,172,170,167,165,163,161,159,157,155,153,150,148,146,144,142,139,137,135,133,131} ;
|
|
// (Number of pixels: 421)
|
|
|
|
// Store clock hands co-ordinates...
|
|
uint8_t HandsX[192] = {} ; // Each hand requires 64 bytes - 3x64=192
|
|
uint8_t HandsY[192] = {} ;
|
|
int Hours=0, Mins=0, Secs=0, Angle, StartX, StartY, Radius ;
|
|
float Radians ;
|
|
|
|
int tmp ;
|
|
|
|
char MarginFW[MWidth+1], MarginVW[MWidth+1] ; // Fixed Width & Variable Width strings to create a fixed margin
|
|
unsigned short DAC_channel_mask = 0 ; // Binary mask to simultaneously start all DMA channels
|
|
const uint32_t transfer_count = BitMapSize ; // Number of DMA transfers per event
|
|
const float _2Pi = 6.283; // 2*Pi
|
|
int ParmCnt = 0, Parm[4], WaveForm_Type ; // Storage for 4 command line parameters
|
|
int SelectedChan, c, i = 0, dirn = 1, result ;
|
|
int MarginCount = 0 ; // Manual count of characters written to terminal - required to maintain margins
|
|
float MaxDACfreq ;
|
|
char inStr[30], outStr[2500], ResultStr[3000], LastCmd[30] ; // outStr large enough to contain the HelpText string
|
|
|
|
static void MCP41020_Write (uint8_t _ctrl, uint8_t _data) ; // Forward definitions
|
|
static void SPI_Display_Write(int _data) ;
|
|
|
|
class DAC {
|
|
public:
|
|
PIO pio; // Class wide var to share value with setter function
|
|
unsigned short DAC_data[BitMapSize] __attribute__ ((aligned(2048))) ; // Align DAC data (2048d = 0800h)
|
|
int Funct, Range, PIOnum ;
|
|
int Level, Freq, Phase, DutyC, Harm, RiseT ;
|
|
uint StateMachine, ctrl_chan, data_chan, GPIO, SM_WrapBot, SM_WrapTop ; // Variabes used by the getter function...
|
|
char name ; // Name of this instance
|
|
float DAC_div ;
|
|
|
|
void StatusString () {
|
|
// Report the status line for the current DAC object, aligned to current margin settings.
|
|
char Str1[4], Str2[200], Margin[40] ; // ! Max line length = 100 chars !
|
|
if (Range == 1) strcpy(Str1," Hz") ; // Asign multiplier suffix
|
|
if (Range == 1000) strcpy(Str1,"KHz") ;
|
|
if (Range == 1000000) strcpy(Str1,"MHz") ;
|
|
tmp = strlen(inStr) ; // Get number of command line characters
|
|
// Handle the instances where the length of the command line exceeds the Margin width...
|
|
if (MarginCount >= MWidth) {
|
|
printf("\n") ; // Start a newline
|
|
strcpy(inStr,"") ; // Clear the string
|
|
MarginCount = 0 ; // Update the length variable
|
|
}
|
|
MarginVW[MWidth - MarginCount] = '\0' ; // Calculate padding required for command characters and cursor
|
|
if (MarginCount == 0) { strcpy(Margin,MarginFW) ; } // Fixed Width margin if no command characters
|
|
else { strcpy(Margin,MarginVW) ; } // Varable Width margin if command characters are present
|
|
switch ( Funct ) { // Calculate status sting...
|
|
case _Sine_:
|
|
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Wave:Sine Harmonic:%d\n", Margin, name, Freq, Str1, Phase, Level, Harm) ;
|
|
break;
|
|
case _Triangle_:
|
|
if ((RiseT == 0) || (RiseT == 100)) {
|
|
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Wave:Sawtooth\n", Margin, name, Freq, Str1, Phase, Level) ;
|
|
} else {
|
|
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Wave:Triangle Rise time:%d%%\n", Margin, name, Freq, Str1, Phase, Level, RiseT) ;
|
|
}
|
|
break;
|
|
case _Square_:
|
|
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Wave:Square Duty cycle:%d%%\n", Margin, name, Freq, Str1, Phase, Level, DutyC) ;
|
|
break ;
|
|
case _Time_:
|
|
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Time\n", Margin, name, Freq, Str1, Phase, Level) ;
|
|
}
|
|
strcat(ResultStr,Str2) ;
|
|
inStr[0] = '\0' ; // Reset input string
|
|
MarginCount = 0 ;
|
|
}
|
|
|
|
// Setter functions...
|
|
void ReInit () {
|
|
// Re-initialises DMA channels to their initial state.
|
|
// Note: 1) DMA channels are not restarted, allowing for atomic (simultaneous) restart of both DAC channels later.
|
|
// 2) Cannot use dma_hw->abort on chained DMA channels, so using disable and re-enable instead.
|
|
// 3) This needs to be performed across both DAC channels to ensure phase sync is maintained.
|
|
// Disable both DMA channels associated with this DAC...
|
|
hw_clear_bits(&dma_hw->ch[data_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
hw_clear_bits(&dma_hw->ch[ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
// Reset the data transfer DMA's to the start of the data Bitmap...
|
|
dma_channel_set_read_addr(data_chan, &DAC_data[0], false);
|
|
// Re-enable both DMA channels associated with this DAC...
|
|
hw_set_bits(&dma_hw->ch[data_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
hw_set_bits(&dma_hw->ch[ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
}
|
|
|
|
int Set(int _type, int _val) {
|
|
switch (_type) {
|
|
case _Freq_:
|
|
Freq = _val ; // Frequency (numeric)
|
|
ReInit() ; // Stop and reset the DAC channel (no restart)
|
|
DACspeed(Freq * Range) ; // Update State machine run speed
|
|
break ;
|
|
case _Phase_:
|
|
Phase = _val ; // Phase shift (0->355 degrees)
|
|
ReInit() ; // Stop and reset the DAC channel (no restart)
|
|
DataCalc() ; // Recalc Bitmap and apply new phase value
|
|
break ;
|
|
case _Level_:
|
|
if (_val > 100) _val = 100 ; // Limit max val to 100%
|
|
Level = _val ;
|
|
MCP41020_Write(SelectedChan, Level) ; // Control byte for the MCP42010 just happens to be the same value as the SelectedChan variable
|
|
StatusString () ; // Update the terminal session
|
|
break ;
|
|
case _Sine_:
|
|
Funct = _Sine_ ;
|
|
Harm = _val ; // Optional command line parameter (default to zero if not provided)
|
|
DataCalc() ;
|
|
break ;
|
|
case _Square_:
|
|
Funct = _Square_ ;
|
|
DutyC = _val ; // Optional command line parameter (default to 50% if not provided)
|
|
DataCalc() ;
|
|
break ;
|
|
case _Triangle_:
|
|
Funct = _Triangle_ ;
|
|
RiseT = _val ; // Optional command line parameter (default to 50% if not provided)
|
|
DataCalc() ;
|
|
break ;
|
|
case _Time_:
|
|
Funct = _Time_ ;
|
|
DataCalc() ;
|
|
}
|
|
return (_val) ;
|
|
}
|
|
|
|
int Bump(int _type, int _dirn) {
|
|
// _type = Frequency / Phase / Level, Duty, _dirn = Up / Down (_Up = 1, _Down = -1)
|
|
int val = 0 ;
|
|
if (_type == _Freq_) {
|
|
if ((Freq*Range==0) && (_dirn==_Down)) { // Attempt to bump below lower limit
|
|
MarginVW[MWidth - MarginCount] = '\0' ; // Calculate padding required for command characters and cursor
|
|
strcpy(ResultStr,MarginVW) ;
|
|
strcat(ResultStr,"Error - Minimum Frequency\n") ;
|
|
}
|
|
// TBD - remove hardcoded Max frequency
|
|
else if ((Freq*Range==1000000) && (_dirn==_Up)) { // Attempt to bump above upper limit
|
|
// else if ((Freq*Range>=MaxDACfreq) && (_dirn==_Up)) { // Attempt to bump above upper limit
|
|
MarginVW[MWidth - MarginCount] = '\0' ; // Calculate padding required for command characters and cursor
|
|
strcpy(ResultStr,MarginVW) ;
|
|
strcat(ResultStr,"Error - Maximum Frequency\n") ;
|
|
}
|
|
else { // Not at max or min value...
|
|
Freq += _dirn ; // ... bump
|
|
if ((Freq == 1000) && (_dirn == _Up)) { // Range transition point
|
|
Freq = 1 ; // Reset count
|
|
if (Range == 1) Range = 1000 ; // either Hz=>KHz
|
|
else if (Range == 1000) Range = 1000000 ; // or KHz=>MHz
|
|
}
|
|
if ((Freq==0) && (Range!=1) && (_dirn==_Down)) { // Range transition point
|
|
Freq = 999 ; // Reset count
|
|
if (Range == 1000) Range = 1 ; // either KHz=>Hz
|
|
else if (Range == 1000000) Range = 1000 ; // or MHz=>KHz
|
|
}
|
|
val = Freq ;
|
|
DACspeed(Freq * Range) ; }
|
|
}
|
|
if (_type == _Phase_) {
|
|
Phase += _dirn ;
|
|
if (Phase == 360) Phase = 0 ; // Top Endwrap
|
|
if (Phase < 0 ) Phase = 359 ; // Bottom Endwrap
|
|
val = Phase ;
|
|
DataCalc(); } // Update Bitmap data to include new DAC phase
|
|
if (_type == _Level_) {
|
|
Level += _dirn ;
|
|
if (Level > 100) { Level = 0 ; } // Top endwrap
|
|
if (Level < 0 ) { Level = 100 ; } // Bottom endwrap
|
|
val = Level ;
|
|
MCP41020_Write(SelectedChan, Level) ; // Control byte for the MCP42010 just happens to be the same value as the SelectedChan variable
|
|
StatusString () ; } // Update the terminal session
|
|
if (_type == _Square_) {
|
|
DutyC += _dirn ;
|
|
if (DutyC > 100) { DutyC = 0 ; } // Top endwrap
|
|
if (DutyC < 0 ) { DutyC = 100 ; } // Bottom endwrap
|
|
val = DutyC ;
|
|
DataCalc(); } // Update Bitmap with new Duty Cycle value
|
|
if (_type == _Triangle_) {
|
|
RiseT += _dirn ;
|
|
if (RiseT > 100) { RiseT = 0 ; } // Top endwrap
|
|
if (RiseT < 0 ) { RiseT = 100 ; } // Bottom endwrap
|
|
val = RiseT ;
|
|
DataCalc(); } // Update Bitmap with new Duty Cycle value
|
|
if (_type == _Sine_) {
|
|
Harm += _dirn ;
|
|
if (Harm > 10) { Harm = 0 ; } // Top endwrap
|
|
if (Harm < 0 ) { Harm = 9 ; } // Bottom endwrap
|
|
val = Harm ;
|
|
DataCalc(); } // Update Bitmap with new Sine harmonic value
|
|
return (val) ;
|
|
}
|
|
|
|
void DACspeed (int _frequency) {
|
|
// If DAC_div exceeds 2^16 (65,536), the registers wrap around, and the State Machine clock will be incorrect.
|
|
// A slow version of the DAC State Machine is used for frequencies below 17Hz, allowing the value of DAC_div to
|
|
// be kept within range.
|
|
float DAC_freq = _frequency * BitMapSize; // Target frequency...
|
|
DAC_div = 2 * (float)clock_get_hz(clk_sys) / DAC_freq; // ...calculate the PIO clock divider required for the given Target frequency
|
|
float Fout = 2 * (float)clock_get_hz(clk_sys) / (BitMapSize * DAC_div); // Actual output frequency
|
|
if (_frequency >= 34) { // Fast DAC ( Frequency range from 34Hz to 999Khz )
|
|
SM_WrapTop = SM_WrapBot ; // SM program memory = 1 op-code
|
|
pio_sm_set_wrap (pio, StateMachine, SM_WrapBot, SM_WrapTop) ; // Fast loop (1 clock cycle)
|
|
// If the previous frequency was < 33Hz, we will have just shrunk the assembler from 4 op-codes down to 1.
|
|
// This leaves the State Machine program counter pointing outside of the new WRAP statement, which crashes the SM.
|
|
// To avoid this, we need to also reset the State Machine program counter...
|
|
pio->sm[StateMachine].instr = SM_WrapBot ; // Reset State Machine PC to start of code
|
|
pio_sm_set_clkdiv(pio, StateMachine, DAC_div); // Set the State Machine clock
|
|
} else { // Slow DAC ( 1Hz=>33Hz )
|
|
DAC_div = DAC_div / 64; // Adjust DAC_div to keep within useable range
|
|
DAC_freq = DAC_freq * 64;
|
|
SM_WrapTop = SM_WrapBot + 3 ; // SM program memory = 4 op-codes
|
|
pio_sm_set_wrap (pio, StateMachine, SM_WrapBot, SM_WrapTop) ; // slow loop (64 clock cycles)
|
|
// If the previous frequency was >= 34Hz, we will have just expanded the assembler code from 1 op-code up to 4.
|
|
// The State Machine program counter will still be pointing to an op-code within the new WRAP statement, so will not crash.
|
|
pio_sm_set_clkdiv(pio, StateMachine, DAC_div); // Set the State Machine clock speed
|
|
}
|
|
StatusString () ; // Update the terminal session
|
|
}
|
|
|
|
void DataCalc () {
|
|
int i, j, v_offset = BitMapSize/2 - 1; // Shift sine waves up above X axis
|
|
int _phase;
|
|
float a,b,x1,x2,g1,g2;
|
|
|
|
// Scale the phase shift to match data size...
|
|
_phase = Phase * BitMapSize / 360 ; // Input range: 0 -> 360 (degrees)
|
|
// Output range: 0 -> 255 (bytes)
|
|
switch (Funct) {
|
|
case _Sine_:
|
|
Harm = Harm % 10; // Sine harmonics cycles after 7
|
|
for (i=0; i<BitMapSize; i++) {
|
|
// Add the phase offset and wrap data beyond buffer end back to the buffer start...
|
|
j = ( i + _phase ) % BitMapSize; // Horizontal index
|
|
a = v_offset * sin((float)_2Pi*i / (float)BitMapSize); // Fundamental frequency...
|
|
if (Harm >= 1) { a += v_offset/3 * sin((float)_2Pi*3*i / (float)BitMapSize); } // Add 3rd harmonic
|
|
if (Harm >= 2) { a += v_offset/5 * sin((float)_2Pi*5*i / (float)BitMapSize); } // Add 5th harmonic
|
|
if (Harm >= 3) { a += v_offset/7 * sin((float)_2Pi*7*i / (float)BitMapSize); } // Add 7th harmonic
|
|
if (Harm >= 4) { a += v_offset/9 * sin((float)_2Pi*9*i / (float)BitMapSize); } // Add 9th harmonic
|
|
if (Harm >= 5) { a += v_offset/11 * sin((float)_2Pi*11*i / (float)BitMapSize); } // Add 11th harmonic
|
|
if (Harm >= 6) { a += v_offset/13 * sin((float)_2Pi*13*i / (float)BitMapSize); } // Add 13th harmonic
|
|
if (Harm >= 7) { a += v_offset/15 * sin((float)_2Pi*15*i / (float)BitMapSize); } // Add 15th harmonic
|
|
if (Harm >= 8) { a += v_offset/17 * sin((float)_2Pi*17*i / (float)BitMapSize); } // Add 17th harmonic
|
|
if (Harm >= 9) { a += v_offset/19 * sin((float)_2Pi*19*i / (float)BitMapSize); } // Add 19th harmonic
|
|
DAC_data[j] = (int)(a)+v_offset; // Sum all harmonics and add vertical offset
|
|
}
|
|
break;
|
|
case _Square_:
|
|
b = DutyC * BitMapSize / 100; // Convert % to value
|
|
for (i=0; i<BitMapSize; i++) {
|
|
j = ( i + _phase ) % BitMapSize; // Horizontal index
|
|
if (b <= i) { DAC_data[j] = 0; } // First section low
|
|
else { DAC_data[j] = 255; } // Second section high
|
|
}
|
|
break;
|
|
case _Triangle_:
|
|
x1 = (RiseT * BitMapSize / 100) -1; // Number of data points to peak
|
|
x2 = BitMapSize - x1; // Number of data points after peak
|
|
g1 = (BitMapSize - 1) / x1; // Rising gradient (Max val = BitMapSize -1)
|
|
g2 = (BitMapSize - 1) / x2; // Falling gradient (Max val = BitMapSize -1)
|
|
for (i=0; i<BitMapSize; i++) {
|
|
j = ( i + _phase ) % BitMapSize; // Horizontal index
|
|
if (i <= x1) { DAC_data[j] = i * g1; } // Rising section of waveform...
|
|
if (i > x1) { DAC_data[j] = (BitMapSize - 1) - ((i - x1) * g2); } // Falling section of waveform
|
|
}
|
|
break ;
|
|
}
|
|
StatusString () ; // Update the terminal session
|
|
}
|
|
|
|
public:
|
|
// Each DAC channel consists of...
|
|
// BitMap data => DMA => FIFO => State Machine => GPIO pins => R-2-R module
|
|
// Note: The PIO clock dividers are 16-bit integer, 8-bit fractional, with first-order delta-sigma for the fractional divider.
|
|
// This means the clock divisor can vary between 1 and 65536, in increments of 1/256.
|
|
// If DAC_div exceeds 2^16 (65,536), the registers will wrap around, and the State Machine clock will be incorrect.
|
|
// For frequencies below 34Hz, an additional 63 op-code delay is inserted into the State Machine assembler code. This slows
|
|
// down the State Machine operation by a factor of 64, keeping the value of DAC_div within range.
|
|
// Parameters...
|
|
// _name = Name of this DAC channel instance
|
|
// _pio = Required PIO channel
|
|
// _GPIO = Port connecting to the MSB of the R-2-R resistor network.
|
|
// Constructor
|
|
int DAC_chan(char _name, PIO _pio, uint _GPIO) {
|
|
pio = _pio, GPIO = _GPIO, name = _name ; // Copy parameters to class vars
|
|
PIOnum = pio_get_index(pio) ; // Print friendly value
|
|
Funct = _Sine_, Freq = 100, Level = 50 ; // Start-up default values...
|
|
Range = 1, Harm = 0, DutyC = 50, RiseT = 50 ;
|
|
name == 'A' ? Phase=0 : Phase=180 ; // Set Phase difference between channels
|
|
int _offset;
|
|
StateMachine = pio_claim_unused_sm(_pio, true); // Find a free state machine on the specified PIO - error if there are none.
|
|
ctrl_chan = dma_claim_unused_channel(true); // Find 2 x free DMA channels for the DAC (12 available)
|
|
data_chan = dma_claim_unused_channel(true);
|
|
|
|
// Configure the state machine to run the DAC program...
|
|
_offset = pio_add_program(_pio, &pio_DAC_program); // Use helper function included in the .pio file.
|
|
SM_WrapBot = _offset;
|
|
pio_DAC_program_init(_pio, StateMachine, _offset, _GPIO);
|
|
|
|
// Setup the 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(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,
|
|
&fc,
|
|
&dma_hw->ch[data_chan].al1_transfer_count_trig, // txfer to transfer count trigger
|
|
&transfer_count,
|
|
1,
|
|
false
|
|
);
|
|
// Setup the DAC data channel...
|
|
// 32 bit transfers. Read address increments after each transfer.
|
|
fc = dma_channel_get_default_config(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
|
|
channel_config_set_write_increment(&fc, false); // don't increment write address
|
|
channel_config_set_dreq(&fc, pio_get_dreq(_pio, StateMachine, true)); // Transfer when PIO SM TX FIFO has space
|
|
channel_config_set_chain_to(&fc, ctrl_chan); // chain to the controller DMA channel
|
|
channel_config_set_ring(&fc, false, 9); // 8 bit DAC 1<<9 byte boundary on read ptr. This is why we needed alignment!
|
|
dma_channel_configure(
|
|
data_chan, // Channel to be configured
|
|
&fc, // The configuration we just created
|
|
&_pio->txf[StateMachine], // Write to FIFO
|
|
DAC_data, // The initial read address (AT NATURAL ALIGNMENT POINT)
|
|
BitMapSize, // Number of transfers; in this case each is 2 byte.
|
|
false // Don't start immediately. All 4 control channels need to start simultaneously
|
|
// to ensure the correct phase shift is applied.
|
|
);
|
|
DAC_channel_mask += (1u << ctrl_chan) ; // Save details of DMA control channel to global variable. This facilitates
|
|
// atomic restarts of both channels, and ensures phase lock between channels.
|
|
DataCalc() ; // Populate bitmap data.
|
|
DACspeed(Freq * Range) ; // Initialise State MAchine clock speed.
|
|
|
|
return(StateMachine);
|
|
}
|
|
};
|
|
|
|
class blink_forever { // Class to initialise a state machine to blink a GPIO pin
|
|
PIO pio ; // Class wide variables to share value with setter function
|
|
public:
|
|
uint pioNum, StateMachine, Freq, _offset ;
|
|
blink_forever(PIO _pio) {
|
|
pio = _pio; // transfer parameter to class wide var
|
|
pioNum = pio_get_index(_pio);
|
|
StateMachine = pio_claim_unused_sm(_pio, true); // Find a free state machine on the specified PIO - error if there are none.
|
|
_offset = pio_add_program(_pio, &pio_blink_program);
|
|
blink_program_init(_pio, StateMachine, _offset, PICO_DEFAULT_LED_PIN );
|
|
pio_sm_set_enabled(_pio, StateMachine, true);
|
|
}
|
|
|
|
// Setter function...
|
|
void Set_Frequency(int _frequency){
|
|
Freq = _frequency; // Copy parm to class var
|
|
// Frequency scaled by 2000 as blink.pio requires this number of cycles to complete...
|
|
float DAC_div = (float)clock_get_hz(clk_sys) /((float)_frequency*2000);
|
|
pio_sm_set_clkdiv(pio, StateMachine, DAC_div); // Set the State Machine clock speed
|
|
}
|
|
};
|
|
|
|
bool Repeating_Timer_Callback(struct repeating_timer *t) {
|
|
int i, steps=64, MidX=128, MidY=128 ;
|
|
// Bump the time...
|
|
if ((++Secs)>59) Secs=0 ; // Always bump seconds
|
|
if (Secs==0) { if ((++Mins)>59 ) Mins=0 ; } // Bump minutes when seconds = 0
|
|
if ((Mins==0) && (Secs==0)) { if ((++Hours)>24) Hours=0 ; } // Bump hours when minutes and seconds = 0
|
|
|
|
// Calculate seconds hand...
|
|
i=0, Radius=127 ; // Radius=Length of seconds hand
|
|
Angle=270-(Secs*6) ; // Angle in degrees, shifted 90 degree anti-clockwise
|
|
Radians=Angle*3.14159/180 ; // Angle in radians
|
|
StartX=Radius*cos(Radians)+MidX ;
|
|
StartY=Radius*sin(Radians)+MidY ;
|
|
while(i<steps) { HandsX[i]=StartX+i*(MidX-StartX)/steps ;
|
|
HandsY[i]=StartY+i*(MidY-StartY)/steps ;
|
|
i++ ; }
|
|
// Calculate minutes hand...
|
|
i=0, Radius=95 ; // Radius=Length of minutes hand
|
|
Angle=270-(Mins*6) ; // Angle in degrees, shifted 90 degree anti-clockwise
|
|
Radians=Angle*3.14159/180 ; // Angle in radians
|
|
StartX=Radius*cos(Radians)+MidX ;
|
|
StartY=Radius*sin(Radians)+MidY ;
|
|
i=0 ;
|
|
while(i<steps) { HandsX[i+steps]=StartX+i*(MidX-StartX)/steps ;
|
|
HandsY[i+steps]=StartY+i*(MidY-StartY)/steps ;
|
|
i++ ; }
|
|
// Calculate hours hand...
|
|
i=0, Radius=64 ; // Radius=Length of hours hand
|
|
// Note: Hours hand progresses between hours in 5 partial increments, each measuring 12 minutes.
|
|
// Each 12 minute increment adds an additional 6 degrees of rotation to the hours hand.
|
|
Angle=5*(270-(((Hours%12)*6)+(Mins/12)%5)) ; // Angle in degrees, shifted 90 degree anti-clockwise,
|
|
// and scaled by 5 to provide range 0=>12
|
|
Radians=Angle*3.14159/180 ; // Angle in radians
|
|
StartX=Radius*cos(Radians)+MidX ;
|
|
StartY=Radius*sin(Radians)+MidY ;
|
|
while(i<steps) { HandsX[i+2*steps]=StartX+i*(MidX-StartX)/steps ;
|
|
HandsY[i+2*steps]=StartY+i*(MidY-StartY)/steps ;
|
|
i++ ; }
|
|
|
|
// printf("%s%d:%d:%d - %d\n",MarginFW,Hours,Mins,Secs,tmp) ; // Debug
|
|
|
|
return true;
|
|
}
|
|
|
|
void VerText () {
|
|
// Print version info aligned to current margin settings...
|
|
tmp = strlen(inStr) ; // Get number of command line characters
|
|
if (tmp != 0) tmp ++ ; // If there are characters, Bump to also allow for cursor
|
|
MarginVW[MWidth - tmp] = '\0' ; // Calculate padding required for command characters and cursor
|
|
sprintf(ResultStr, "%s|--------------------|\n"
|
|
"%s| Function Generator |\n"
|
|
"%s| Version 1.0.0 |\n"
|
|
"%s| 11th August 2023 |\n"
|
|
"%s|--------------------|\n",
|
|
MarginVW, MarginFW, MarginFW, MarginFW, MarginFW ) ;
|
|
}
|
|
|
|
void HlpText () {
|
|
// Print Help text aligned to current margin settings...
|
|
// Note: Following string requires '%%%%' to print '%'
|
|
// HelpText string is copied to outStr using sprintf - this reduces '%%%%' to '%%'
|
|
// outStr is sent to terminal using printf - this reduces '%%' to '%'
|
|
tmp = strlen(inStr) ; // Get number of command line characters
|
|
if (tmp != 0) tmp ++ ; // If there are characters, Bump to also allow for cursor
|
|
MarginVW[MWidth - tmp] = '\0' ; // Calculate padding required for command characters and cursor
|
|
sprintf(ResultStr, "%sHelp...\n"
|
|
"%s? - Help\n"
|
|
"%sV - Version\n"
|
|
"%sI - Info\n"
|
|
"%sS - Status\n"
|
|
"%s<A/B/C>si - Sine wave (default = no harmonics)\n"
|
|
"%s<A/B/C>sin - Sine wave +nth harmonic ( 0->9 )\n"
|
|
"%s<A/B/C>si+ - Sine wave harmonic + 1\n"
|
|
"%s<A/B/C>si- - Sine wave harmonic - 1\n"
|
|
"%s<A/B/C>sq - Square wave (default = 50%%%% duty cycle)\n"
|
|
"%s<A/B/C>sqnnn - Square wave with nnn%%%% duty cycle\n"
|
|
"%s<A/B/C>sq+ - Square wave duty cycle + 1%%%%\n"
|
|
"%s<A/B/C>sq- - Square wave duty cycle - 1%%%%\n"
|
|
"%s<A/B/C>tr - Triangle wave\n"
|
|
"%s<A/B/C>trnnn - Triangle wave with nnn%%%% rise time\n"
|
|
"%s<A/B/C>tr+ - Triangle wave rise time + 1%%%%\n"
|
|
"%s<A/B/C>tr- - Triangle wave rise time - 1%%%%\n"
|
|
"%s<A/B/C>sw - Sweep frequency (Low, High, Speed, Pause)\n"
|
|
"%s<A/B/C>frnnn - Frequency = nnn ( 0->999 )\n"
|
|
"%s<A/B/C>fr+ - Frequency + 1\n"
|
|
"%s<A/B/C>fr- - Frequency - 1\n"
|
|
"%s<A/B/C>phnnn - Phase = nnn ( 0->359 degrees )\n"
|
|
"%s<A/B/C>ph+ - Phase + 1\n"
|
|
"%s<A/B/C>ph- - Phase - 1\n"
|
|
"%s<A/B/C>lennn - Level = nnn ( 0->100%%%% )\n"
|
|
"%s<A/B/C>le+ - Level + 1\n"
|
|
"%s<A/B/C>le- - Level - 1\n"
|
|
"%swhere...\n"
|
|
"%s<A/B/C> = DAC channel A,B or Both\n"
|
|
"%snnn = Three digit numeric value\n",
|
|
MarginVW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW,
|
|
MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW,
|
|
MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW,
|
|
MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW, MarginFW ) ;
|
|
}
|
|
|
|
void SysInfo (DAC DACobj[], blink_forever LED_blinky) {
|
|
// Print System Info and resource allocation detils, aligned to current margin settings...
|
|
// Note: 1) The following string requires '%%%%' to print '%' because...
|
|
// a) ResultStr is copied to outStr using sprintf - this reduces '%%%%' to '%%'
|
|
// b) outStr is sent to terminal using printf - this reduces '%%' to '%'
|
|
// 2) There seems to be some upper limit to sprintf, so the string is split into two smaller parts.
|
|
tmp = strlen(inStr) ; // Get number of command line characters
|
|
if (tmp != 0) tmp ++ ; // If there are characters, Bump to also allow for cursor
|
|
MarginVW[MWidth - tmp] = '\0' ; // Calculate padding required for command characters and cursor
|
|
// First part of string...
|
|
sprintf(ResultStr,"%s|----------------------------------------------------------|\n"
|
|
"%s| System Info... |\n"
|
|
"%s|----------------------------------------------------------|\n"
|
|
"%s| RP2040 clock frequency: %7.3fMHz |\n"
|
|
"%s| Max DAC frequency: %7.3fMHz |\n"
|
|
"%s|----------------------------|-----------------------------|\n"
|
|
"%s| LED blinker | |\n"
|
|
"%s|----------------------------| |\n"
|
|
"%s| PIO: %2d | |\n"
|
|
"%s| State machine: %2d | |\n"
|
|
"%s| GPIO: %2d | |\n"
|
|
"%s| Frequency: %2dHz | |\n"
|
|
"%s|----------------------------|-----------------------------|\n"
|
|
"%s| DAC Channel A | DAC Channel B |\n"
|
|
"%s|----------------------------|-----------------------------|\n"
|
|
"%s| Level: %3d%%%% | Level: %3d%%%% |\n"
|
|
"%s| Frequency: %3d | Frequency: %3d |\n"
|
|
"%s| Multiplier: %7d | Multiplier: %7d |\n"
|
|
"%s| Phase: %3d | Phase: %3d |\n"
|
|
"%s| Duty cycle: %3d%%%% | Duty cycle: %3d%%%% |\n"
|
|
"%s| Sine harmonic: %1d | Sine harmonic: %1d |\n"
|
|
"%s| Triangle Rise: %3d%%%% | Triangle Rise: %3d%%%% |\n",
|
|
MarginVW, MarginFW, MarginFW,
|
|
MarginFW, (float)clock_get_hz(clk_sys)/1000000,
|
|
MarginFW, MaxDACfreq/1000000,
|
|
MarginFW, MarginFW, MarginFW,
|
|
MarginFW, LED_blinky.pioNum,
|
|
MarginFW, LED_blinky.StateMachine,
|
|
MarginFW, PICO_DEFAULT_LED_PIN,
|
|
MarginFW, LED_blinky.Freq,
|
|
MarginFW, MarginFW, MarginFW,
|
|
MarginFW, DACobj[_DAC_A].Level, DACobj[_DAC_B].Level,
|
|
MarginFW, DACobj[_DAC_A].Freq, DACobj[_DAC_B].Freq,
|
|
MarginFW, DACobj[_DAC_A].Range, DACobj[_DAC_B].Range,
|
|
MarginFW, DACobj[_DAC_A].Phase, DACobj[_DAC_B].Phase,
|
|
MarginFW, DACobj[_DAC_A].DutyC, DACobj[_DAC_B].DutyC,
|
|
MarginFW, DACobj[_DAC_A].Harm, DACobj[_DAC_B].Harm,
|
|
MarginFW, DACobj[_DAC_A].RiseT, DACobj[_DAC_B].RiseT
|
|
) ;
|
|
printf(ResultStr) ; // Print first part of string
|
|
// Second part of string...
|
|
sprintf(ResultStr,"%s|----------------------------|-----------------------------|\n"
|
|
"%s| Divider: %10.3f | Divider: %10.3f |\n"
|
|
"%s|----------------------------|-----------------------------|\n"
|
|
"%s| PIO: %d | PIO: %d |\n"
|
|
"%s| State machine: %d | State machine: %d |\n"
|
|
"%s| GPIO: %d->%d | GPIO: %d->%d |\n"
|
|
"%s| *BM size: %8d | *BM size: %8d |\n"
|
|
"%s| *BM start: %x | *BM start: %x |\n"
|
|
"%s| Wrap Bottom: %2x | Wrap Bottom: %2x |\n"
|
|
"%s| Wrap Top: %2x | Wrap Top: %2x |\n"
|
|
"%s| DMA ctrl: %2d | DMA ctrl: %2d |\n"
|
|
"%s| DMA data: %2d | DMA data: %2d |\n"
|
|
"%s|----------------------------|-----------------------------|\n"
|
|
"%s *BM = Bit map\n",
|
|
MarginFW,
|
|
MarginFW, DACobj[_DAC_A].DAC_div, DACobj[_DAC_B].DAC_div,
|
|
MarginFW,
|
|
MarginFW, DACobj[_DAC_A].PIOnum, DACobj[_DAC_B].PIOnum,
|
|
MarginFW, DACobj[_DAC_A].StateMachine, DACobj[_DAC_B].StateMachine,
|
|
MarginFW, DACobj[_DAC_A].GPIO, DACobj[_DAC_A].GPIO+7,
|
|
DACobj[_DAC_B].GPIO, DACobj[_DAC_B].GPIO+7,
|
|
MarginFW, BitMapSize, BitMapSize,
|
|
MarginFW, (int)&DACobj[_DAC_A].DAC_data[0],
|
|
(int)&DACobj[_DAC_B].DAC_data[0],
|
|
MarginFW, DACobj[_DAC_A].SM_WrapBot, DACobj[_DAC_B].SM_WrapBot,
|
|
MarginFW, DACobj[_DAC_A].SM_WrapTop, DACobj[_DAC_B].SM_WrapTop,
|
|
MarginFW, DACobj[_DAC_A].ctrl_chan, DACobj[_DAC_B].ctrl_chan,
|
|
MarginFW, DACobj[_DAC_A].data_chan, DACobj[_DAC_B].data_chan,
|
|
MarginFW, MarginFW
|
|
) ; // Printing the final part of the string is handled by the calling routine.
|
|
// This prevents the 'unknown command' mechanism from triggering.
|
|
}
|
|
|
|
static inline void cs_select(int _gpio) {
|
|
asm volatile("nop \n nop \n nop");
|
|
gpio_put(_gpio, 0); // Active low
|
|
asm volatile("nop \n nop \n nop");
|
|
}
|
|
|
|
static inline void cs_deselect(int _gpio) {
|
|
asm volatile("nop \n nop \n nop");
|
|
gpio_put(_gpio, 1);
|
|
asm volatile("nop \n nop \n nop");
|
|
}
|
|
|
|
static void SPI_Display_Write(int _data) {
|
|
uint8_t buff[2];
|
|
buff[0] = _data / 256; // MSB data
|
|
buff[1] = _data % 256; // LSB data
|
|
cs_select(Display_CS);
|
|
spi_write_blocking(SPI_PORT, buff, 2);
|
|
cs_deselect(Display_CS);
|
|
}
|
|
|
|
static void MCP41020_Write (uint8_t _ctrl, uint8_t _data) {
|
|
// Add a control bit to select a 'Digi-Pot Write' command.
|
|
// Scale the data byte to be in the range 0->255.
|
|
// Transmit data over the SPI bus to the Digi-Pot.
|
|
uint8_t buff[2];
|
|
|
|
// Depending on wiring, the MCP41020 Digi-Pot may have the channel selection bits reversed. If so, we will need to...
|
|
// if (_ctrl == 0x01) _ctrl = 0x02 ; // Swap channel A/B bits
|
|
// else if (_ctrl == 0x02) _ctrl = 0x01 ; // Note: Do not change if both channels are selected (code = 0x03)
|
|
|
|
buff[0] = _ctrl | 0x10 ; // Set command bit to Write data
|
|
buff[1] = _data * 2.55 ; // Scale data byte (100%->255)
|
|
cs_select(Level_CS) ; // Transmit data to Digi-Pot
|
|
spi_write_blocking(SPI_PORT, buff, 2) ;
|
|
cs_deselect(Level_CS) ;
|
|
}
|
|
|
|
static void getLine() {
|
|
char *pPos = (char *)inStr ; // Pointer to start of Global input string
|
|
int count = 0 ;
|
|
while(1) {
|
|
c = getchar();
|
|
if (c == eof || c == '\n' || c == '\r') break ; // Non blocking exit
|
|
putchar(c); // FullDuplex echo
|
|
*pPos++ = c ; // Bump pointer, store character
|
|
count ++ ;
|
|
}
|
|
*pPos = '\0' ;
|
|
MarginCount += count ; // Track number of characters on current line
|
|
return ;
|
|
}
|
|
|
|
int SetVal(DAC DACobj[], int _Parm) {
|
|
// Common code for setting frequency, duty cycle, phase, waaveform and level.
|
|
// Handles options to set a specific value or bump up/down...
|
|
if (inStr[3] == '+') { // Bump up and grab result for SPI display...
|
|
if (SelectedChan & 0b01) result = DACobj[_DAC_A].Bump(_Parm,_Up);
|
|
if (SelectedChan & 0b10) result = DACobj[_DAC_B].Bump(_Parm,_Up) ;
|
|
} else if (inStr[3] == '-') { // Bump down and grab result for SPI display...
|
|
if (SelectedChan & 0b01) result = DACobj[_DAC_A].Bump(_Parm,_Down) ;
|
|
if (SelectedChan & 0b10) result = DACobj[_DAC_B].Bump(_Parm,_Down) ;
|
|
} else { // Not a bump, so set the absolute value from Parm[0]...
|
|
if (SelectedChan & 0b01) result = DACobj[_DAC_A].Set(_Parm,Parm[0]) ;
|
|
if (SelectedChan & 0b10) result = DACobj[_DAC_B].Set(_Parm,Parm[0]) ;
|
|
dma_start_channel_mask(DAC_channel_mask); // Atomic restart both DAC channels
|
|
}
|
|
return result ;
|
|
}
|
|
|
|
int main() {
|
|
bool InvX=false, InvY=false ; // Clock mode flags to allow inverted output
|
|
set_sys_clock_khz(SysClock*1000, true) ; // Set Pico clock speed
|
|
MaxDACfreq = clock_get_hz(clk_sys) / BitMapSize ; // Calculate Maximum DAC output frequency for given CPU clock speed
|
|
stdio_init_all() ;
|
|
spi_init(SPI_PORT, 500000); // Set SPI0 at 0.5MHz.
|
|
gpio_set_function(PIN_CLK, GPIO_FUNC_SPI);
|
|
gpio_set_function(PIN_TX, GPIO_FUNC_SPI);
|
|
|
|
// Chip select is active-low, so initialise to a driven-high state...
|
|
gpio_init(Display_CS);
|
|
gpio_set_dir(Display_CS, GPIO_OUT);
|
|
gpio_put(Display_CS, 1);
|
|
gpio_init(Level_CS);
|
|
gpio_set_dir(Level_CS, GPIO_OUT);
|
|
gpio_put(Level_CS, 1);
|
|
|
|
// Setting Max slew rate and gpio drive strength keeps output linear at high frequencies...
|
|
for (int i=0; i<16; i++) {
|
|
gpio_set_slew_rate(i, GPIO_SLEW_RATE_FAST);
|
|
gpio_set_drive_strength(i, GPIO_DRIVE_STRENGTH_12MA);
|
|
}
|
|
|
|
// Initialise remaining SPI connections...
|
|
gpio_set_dir(PIN_CLK, GPIO_OUT);
|
|
gpio_set_dir(PIN_TX, GPIO_OUT);
|
|
|
|
struct repeating_timer timer;
|
|
add_repeating_timer_ms(-1000, Repeating_Timer_Callback, NULL, &timer); // 7ms - Short enough to prevent Nixie tube flicker
|
|
|
|
memset(MarginFW,' ',MWidth) ; // Initialise Fixed Width margin...
|
|
MarginFW[MWidth] = '\0' ; // ... and terminate
|
|
memset(MarginVW,' ',MWidth) ; // Initialise Variable Width margin...
|
|
MarginVW[MWidth] = '\0' ; // ... and terminate
|
|
ResultStr[0] = '\0' ; // Reset string
|
|
|
|
// Instantiate objects to control the various State Machines...
|
|
// Note: Both DAC channels need to be on the same PIO to achieve
|
|
// Atomic restarts for accurate phase sync.
|
|
DAC DACobj[2]; // Array to hold the two DAC channel objects
|
|
DACobj[_DAC_A].DAC_chan('A',pio1,0); // First DAC channel object in array - resistor network connected to GPIO0->8
|
|
DACobj[_DAC_B].DAC_chan('B',pio1,8); // Second DAC channel object in array - resistor network connected to GPIO8->16
|
|
blink_forever LED_blinky(pio0); // Onboard LED blinky object
|
|
|
|
strcpy(LastCmd,"?") ; // Hitting return will give 'Help'
|
|
|
|
SPI_Display_Write(SysClock) ; // Pico system clock speed (in MHz)
|
|
MCP41020_Write(0x3, 50) ; // Both channels -> 50% output level
|
|
|
|
LED_blinky.Set_Frequency(1); // Flash LED at 1Hz- waiting for USB connection
|
|
|
|
while (!stdio_usb_connected()) { sleep_ms(100); } // Wait for USB connection...
|
|
|
|
LED_blinky.Set_Frequency(10); // Flash LED at 10Hz - USB connected.
|
|
SPI_Display_Write(DACobj[_DAC_A].Freq) ; // Frequency => SPI display
|
|
|
|
// Send (optional) start-up messages to terminal...
|
|
VerText() ; // Version text
|
|
printf(ResultStr) ; // Update terminal
|
|
|
|
// Atomic Restart - starting all 4 DMA channels simultaneously ensures phase sync between both DAC channels
|
|
dma_start_channel_mask(DAC_channel_mask);
|
|
|
|
while(1) {
|
|
ParmCnt=0, Parm[0]=0, Parm[1]=0, Parm[2]=0, Parm[3]=0 ; // Reset all command line parameters
|
|
memset(MarginVW,' ',MWidth) ; // Re-initialise Variable Width margin...
|
|
MarginVW[MWidth] = '\0' ; // ... and terminate
|
|
ResultStr[0] = '\0' ; // Reset string
|
|
printf(">") ; // Command prompt
|
|
MarginCount = 1 ; // Reset count and bump for command prompt
|
|
|
|
getLine() ; // Fetch command line
|
|
|
|
// Zero length string = 'CR' pressed...
|
|
if (strlen(inStr) == 0) { strcpy(inStr,LastCmd) ; // Repeat last command
|
|
printf("%s", inStr) ; }
|
|
|
|
// One character commands...
|
|
if (strlen(inStr) == 1) {
|
|
if (inStr[0] == '?') HlpText() ; // Help text
|
|
if (inStr[0] == 'V') VerText() ; // Version text
|
|
if (inStr[0] == 'S') {
|
|
DACobj[_DAC_A].StatusString() ;
|
|
DACobj[_DAC_B].StatusString() ;
|
|
}
|
|
if (inStr[0] == 'I') SysInfo(DACobj, LED_blinky); // TBD - inconsitant - make these global ??
|
|
}
|
|
|
|
// For all remaining commands, the first character selects DAC channel A or B...
|
|
if (inStr[0] == 'A') { SelectedChan = 0b0001; } // Channel A only
|
|
if (inStr[0] == 'B') { SelectedChan = 0b0010; } // Channel B only
|
|
if (inStr[0] == 'C') { SelectedChan = 0b0011; } // Channel A & B
|
|
|
|
// ...and if we aren't bumping a value, there will be one or more numeric parameters...
|
|
if ((strlen(inStr) != 0) && (inStr[2] != '+') && (inStr[2] != '-')) {
|
|
i = 2 ; // Skip chars 0, 1 and 2
|
|
while (i++ < strlen(inStr) ) { // Starts at char 3
|
|
if ( inStr[i] == 'H' ) { // Hz suffix
|
|
if (SelectedChan & 0b01) DACobj[_DAC_A].Range = 1 ;
|
|
if (SelectedChan & 0b10) DACobj[_DAC_B].Range = 1 ;
|
|
}
|
|
else if ( inStr[i] == 'K' ) { // KHz suffix
|
|
if (SelectedChan & 0b01) DACobj[_DAC_A].Range = 1000 ;
|
|
if (SelectedChan & 0b10) DACobj[_DAC_B].Range = 1000 ;
|
|
// If the command suffix has been entered as 'KHz', then the next loop itteration will detect the 'H' and
|
|
// overwrite the 'K'. Writing a space to the next char prevents, this from happening.
|
|
if (inStr[i+1] == 'H') inStr[i+1] = ' ' ; // Overwrite next char with space
|
|
}
|
|
else if ( inStr[i] == 'M' ) { // MHz suffix
|
|
if (SelectedChan & 0b01) DACobj[_DAC_A].Range = 1000000 ;
|
|
if (SelectedChan & 0b10) DACobj[_DAC_B].Range = 1000000 ;
|
|
// If the command suffix has been entered as 'MHz', then the next loop itteration will detect the 'H' and
|
|
// overwrite the 'M'. Writing a space to the next char prevents, this from happening.
|
|
if (inStr[i+1] == 'H') inStr[i+1] = ' ' ; // Overwrite next char with space
|
|
}
|
|
else if ( inStr[i] == ',' ) { ParmCnt++ ; } // Next parameter
|
|
else if (isdigit(inStr[i])) { Parm[ParmCnt] *= 10; // Next digit. Bump the existing decimal digits
|
|
Parm[ParmCnt] += inStr[i] - '0'; } // Convert character to integer and add
|
|
}
|
|
}
|
|
|
|
// Next two chars select the command...
|
|
if ((inStr[1]=='p')&(inStr[2]=='h')) SetVal(DACobj,_Phase_) ; // Phase
|
|
if ((inStr[1]=='l')&(inStr[2]=='e')) SetVal(DACobj,_Level_) ; // Level
|
|
if ((inStr[1]=='s')&(inStr[2]=='i')) SetVal(DACobj,_Sine_) ; // Sine wave (optional harmonic parameter)
|
|
if ((inStr[1]=='f')&(inStr[2]=='r')) { // Frequency
|
|
SetVal(DACobj,_Freq_) ; // Set value
|
|
dma_start_channel_mask(DAC_channel_mask); // Atomic restart both DAC channels
|
|
}
|
|
|
|
// The next two commands need different default values...
|
|
if (strlen(inStr)==3) Parm[0] = 50 ; // If no value provided, set default to 50
|
|
if ((inStr[1]=='s')&(inStr[2]=='q')) SetVal(DACobj,_Square_) ; // Set Square wave (optional duty cycle parameter)
|
|
if ((inStr[1]=='t')&(inStr[2]=='r')) SetVal(DACobj,_Triangle_) ; // Set Triangle wave (optional duty cycle parameter)
|
|
|
|
if ((inStr[1]=='t')&(inStr[2]=='i')) { // Time display...
|
|
DACobj[_DAC_A].Set(_Phase_, 0) ; // Phase lock
|
|
DACobj[_DAC_B].Set(_Phase_, 0) ;
|
|
|
|
for (int gpio = 0; gpio < 16; gpio++) { // Grabs the GPIO back from the State machines
|
|
gpio_init(gpio);
|
|
gpio_set_dir(gpio, GPIO_OUT);
|
|
}
|
|
|
|
// Disable both DMA channels associated with this DAC...
|
|
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_A].data_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_B].data_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
// Reset the data transfer DMA's to the start of the data Bitmap...
|
|
dma_channel_set_read_addr(DACobj[_DAC_A].data_chan, &DACobj[_DAC_A].DAC_data[0], false);
|
|
dma_channel_set_read_addr(DACobj[_DAC_B].data_chan, &DACobj[_DAC_B].DAC_data[0], false);
|
|
|
|
pio_sm_set_enabled(pio1,0,false) ; // disable State machine 0 !! HARD CODED !!
|
|
pio_sm_set_enabled(pio1,1,false) ; // disable State machine 1
|
|
|
|
gpio_clr_mask(0xff) ; // clear first 16 GPIO outputs
|
|
|
|
ResultStr[0] = '\0' ; // String also used as a flag, so needs to be cleared
|
|
while (ResultStr[0] == '\0') { // exit on keypress
|
|
float Radians ;
|
|
// Draw the clock face...
|
|
for (int i=0; i<sizeof(FaceX); i++) {
|
|
if (InvX) { gpio_put_masked(0x00ff,FaceX[i]) ; } // Write inverted data byte to DAC A
|
|
else { gpio_put_masked(0x00ff,255-FaceX[i]) ; } // Write non-inverted data byte to DAC A
|
|
if (InvY) { gpio_put_masked(0xff00,FaceY[i]<<8) ; } // Write inverted data byte to DAC B
|
|
else { gpio_put_masked(0xff00,255-FaceY[i]<<8) ; } // Write non-inverted data byte to DAC B
|
|
sleep_us(2) ; // Pause for on-screen persistance
|
|
}
|
|
// Draw the clock hands...
|
|
for (i=0; i<192; i++) { // 3 hands @ 64 pixels each = 192
|
|
if (InvX) { gpio_put_masked(0x00ff,HandsX[i]) ; } // Write inverted data byte to DAC A
|
|
else { gpio_put_masked(0x00ff,255-HandsX[i]) ; } // Write non-inverted data byte to DAC A
|
|
if (InvY) { gpio_put_masked(0xff00,HandsY[i]<<8) ; } // Write inverted data byte to DAC B
|
|
else { gpio_put_masked(0xff00,255-HandsY[i]<<8) ; } // Write non-inverted data byte to DAC B
|
|
sleep_us(2) ; // Pause for on-screen persistance
|
|
}
|
|
|
|
c = getchar_timeout_us (0); // Non-blocking char input
|
|
if (c!=EOF) { // c=EOF if no input
|
|
if ((c=='x') or (c=='X')) {
|
|
InvX = !InvX ;
|
|
if (InvX) printf("%sX axis inverted.\n>",MarginVW) ; // Print current status
|
|
else printf("%sX axis not inverted.\n>",MarginVW) ;
|
|
}
|
|
else if ((c=='y') or (c=='Y')) {
|
|
InvY = !InvY ;
|
|
if (InvY) printf("%sY axis inverted.\n>",MarginVW) ; // Print current status
|
|
else printf("%sY axis not inverted.\n>",MarginVW) ;
|
|
}
|
|
if ((c=='S') or (c=='s')) { // Set time
|
|
printf("%sSet time (format HH:MM:SS)\n%s",MarginVW, MarginFW ) ;
|
|
getLine() ; // Get the console input
|
|
Parm[0]=0, Parm[1]=0, Parm[2]=0, Parm[3]=0 ; // Reset all command line parameters
|
|
i=0, ParmCnt=0 ; // Reset all command line counters
|
|
while (i<strlen(inStr) ) {
|
|
if ((inStr[i]==':')||(inStr[i]==',')) { // Next parameter
|
|
ParmCnt++ ; }
|
|
else if (isdigit(inStr[i])) {
|
|
Parm[ParmCnt] *= 10; // Next digit. Bump the existing decimal digits
|
|
Parm[ParmCnt] += inStr[i] - '0'; } // Convert character to integer and add
|
|
i++ ; // Next character
|
|
}
|
|
inStr[0]='\0' ; // Reset input buffer
|
|
Hours=Parm[0]%24 ; Mins=Parm[1]%60 ; Secs=Parm[2]%60 ; // Set the time from parameters
|
|
|
|
printf("\n%sClock set to %02d:%02d:%02d\n>",MarginFW,Hours,Mins,Secs) ;
|
|
}
|
|
else if ((c=='q') or (c=='Q')) {
|
|
strcpy(ResultStr," Quit clock mode\n") ; // Prevents error message
|
|
// Re-enable both DMA channels associated with this DAC...
|
|
hw_set_bits(&dma_hw->ch[DACobj[_DAC_A].data_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
hw_set_bits(&dma_hw->ch[DACobj[_DAC_B].data_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
|
|
pio_sm_set_enabled(pio1,0,true) ; // Re-enable State machine 0 !! HARD CODED !!
|
|
pio_sm_set_enabled(pio1,1,true) ; // Re-enable State machine 1
|
|
// Re-init the state machines ????
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// The final command is a continual loop creating the sweep function...
|
|
if ((inStr[1] == 's') & (inStr[2] == 'w')) { // Sweep
|
|
// Parm[0]=Low frequency, Parm[1]=High frequency, Parm[2]=Scan speed, Parm[3]=Low/High pause
|
|
i = Parm[0];
|
|
for (;;) {
|
|
if (SelectedChan & 0b01) result = DACobj[_DAC_A].Set(_Freq_,i) ; // Set frequency, display status
|
|
if (SelectedChan & 0b10) result = DACobj[_DAC_B].Set(_Freq_,i) ; // Set frequency, display status
|
|
dma_start_channel_mask(DAC_channel_mask); // Atomic restart all 4 DMA channels...
|
|
printf(ResultStr) ; // Update terminal
|
|
ResultStr[0] = '\0' ; // Reset the string variable
|
|
SPI_Display_Write(i); // Update SPI display
|
|
if (i==Parm[0]) { dirn = 1;
|
|
sleep_ms(Parm[3]); }
|
|
if (i>=Parm[1]) { dirn =-1;
|
|
sleep_ms(Parm[3]); }
|
|
dma_start_channel_mask(DAC_channel_mask); // Atomic restart both DAC channels
|
|
i = i + dirn;
|
|
c = getchar_timeout_us (0); // Non-blocking char input
|
|
if ((c>=32) & (c<=126)) {
|
|
strcpy(ResultStr," Exit sweep mode\n") ; // Prevents error message
|
|
break; } // exit on keypress
|
|
sleep_ms(Parm[2]); // Speed of scan
|
|
}
|
|
}
|
|
|
|
if (strlen(ResultStr) == 0) { // No result can only mean unrecognised command
|
|
strcpy(MarginVW,MarginFW) ; // Reset Variable Width margin
|
|
tmp = strlen(inStr) ;
|
|
if (tmp != 0) tmp ++ ; // Bump to allow for cursor character
|
|
MarginVW[MWidth - tmp] = '\0' ; // Calculate padding for input and cursor
|
|
sprintf(outStr,"%sUnknown command!\n", MarginVW) ; // Empty response buffer indicates command has not been recognised
|
|
}
|
|
else strcpy(outStr,ResultStr) ;
|
|
printf(outStr) ; // Update terminal
|
|
outStr[0] = '\0' ; // Clear (reset) the string variable
|
|
SPI_Display_Write(result) ; // Update SPI display
|
|
strcpy(LastCmd, inStr) ; // Preserve last command
|
|
}
|
|
return 0;
|
|
}
|