RP2040-code/Function Generator/FunctionGenerator.cpp

937 wiersze
66 KiB
C++
Czysty Zwykły widok Historia

2023-03-27 15:58:28 +00:00
#include <stdio.h>
#include <string.h>
2023-08-27 18:46:27 +00:00
#include <ctype.h>
#include <math.h>
2023-03-27 15:58:28 +00:00
#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"
2023-04-29 15:08:21 +00:00
#include "DAC.pio.h"
2022-06-20 19:13:48 +00:00
2023-08-27 18:46:27 +00:00
#include "hardware/gpio.h" // Required for manually toggling GPIO pins (clock)
2023-06-05 13:27:01 +00:00
//////////////////////////////////////
// Define GPIO connections for Pico...
//////////////////////////////////////
2022-08-17 13:22:46 +00:00
2023-08-27 18:46:27 +00:00
// 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
// ┌──────────┬───────────────┬─────────────┐────────────────┐
2023-06-05 13:27:01 +00:00
// │ PGA2040 │ Connection │ MCP41010 │ Display module │
// ├──────────┼───────────────┼─────────────┤────────────────┤
2023-08-27 18:46:27 +00:00
#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) │
2023-06-05 13:27:01 +00:00
// └──────────┴───────────────┴─────────────┘────────────────┘
2023-03-27 15:58:28 +00:00
2023-08-27 18:46:27 +00:00
#define _DAC_A 0 // DAC channel alias
#define _DAC_B 1 // DAC channel alias
2023-04-29 15:08:21 +00:00
#define _Up 1
#define _Down -1
#define _Sine_ 0 // Permited values for variable WaveForm_Type
2023-04-01 17:42:17 +00:00
#define _Square_ 1
#define _Triangle_ 2
2023-08-27 18:46:27 +00:00
#define _Time_ 3
#define _Funct_ 4
#define _Phase_ 5
#define _Freq_ 6
#define _Level_ 7
#define _Duty_ 8
#define _Range_ 9
#define _Harmonic_ 10
2023-04-29 15:08:21 +00:00
#define eof 255 // EOF in stdio.h -is -1, but getchar returns int 255 to avoid blocking
2023-06-05 13:27:01 +00:00
#define CR 13
#define BitMapSize 256 // Match X to Y resolution
2023-08-27 18:46:27 +00:00
#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
2023-04-29 15:08:21 +00:00
2023-08-27 18:46:27 +00:00
// 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
2023-04-29 15:08:21 +00:00
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
2023-08-27 18:46:27 +00:00
const float _2Pi = 6.283; // 2*Pi
2023-06-05 13:27:01 +00:00
int ParmCnt = 0, Parm[4], WaveForm_Type ; // Storage for 4 command line parameters
int SelectedChan, c, i = 0, dirn = 1, result ;
2023-08-27 18:46:27 +00:00
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) ;
2023-06-18 14:00:47 +00:00
2023-06-05 13:27:01 +00:00
class DAC {
public:
PIO pio; // Class wide var to share value with setter function
2023-05-05 16:08:51 +00:00
unsigned short DAC_data[BitMapSize] __attribute__ ((aligned(2048))) ; // Align DAC data (2048d = 0800h)
2023-08-27 18:46:27 +00:00
int Funct, Range, PIOnum ;
int Level, Freq, Phase, DutyC, Harm, RiseT ;
2023-05-05 16:08:51 +00:00
uint StateMachine, ctrl_chan, data_chan, GPIO, SM_WrapBot, SM_WrapTop ; // Variabes used by the getter function...
2023-06-05 13:27:01 +00:00
char name ; // Name of this instance
2023-04-29 15:08:21 +00:00
float DAC_div ;
2022-04-13 20:21:38 +00:00
2023-06-05 13:27:01 +00:00
void StatusString () {
2023-08-27 18:46:27 +00:00
// 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
2023-06-05 13:27:01 +00:00
switch ( Funct ) { // Calculate status sting...
case _Sine_:
2023-08-27 18:46:27 +00:00
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Wave:Sine Harmonic:%d\n", Margin, name, Freq, Str1, Phase, Level, Harm) ;
2023-06-05 13:27:01 +00:00
break;
case _Triangle_:
2023-08-27 18:46:27 +00:00
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) ;
2023-06-05 13:27:01 +00:00
} else {
2023-08-27 18:46:27 +00:00
sprintf(Str2,"%sChannel %c: Freq:%3d%s Phase:%03d Level:%03d Wave:Triangle Rise time:%d%%\n", Margin, name, Freq, Str1, Phase, Level, RiseT) ;
2023-06-05 13:27:01 +00:00
}
2023-08-27 18:46:27 +00:00
break;
2023-06-05 13:27:01 +00:00
case _Square_:
2023-08-27 18:46:27 +00:00
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) ;
2023-06-05 13:27:01 +00:00
}
2023-08-27 18:46:27 +00:00
strcat(ResultStr,Str2) ;
inStr[0] = '\0' ; // Reset input string
MarginCount = 0 ;
2023-06-05 13:27:01 +00:00
}
// Setter functions...
2023-04-06 18:32:06 +00:00
void ReInit () {
2023-04-29 15:08:21 +00:00
// Re-initialises DMA channels to their initial state.
2023-06-05 13:27:01 +00:00
// Note: 1) DMA channels are not restarted, allowing for atomic (simultaneous) restart of both DAC channels later.
2023-04-29 15:08:21 +00:00
// 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);
2023-04-06 18:32:06 +00:00
}
2023-06-05 13:27:01 +00:00
int Set(int _type, int _val) {
2023-08-27 18:46:27 +00:00
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() ;
2023-06-05 13:27:01 +00:00
}
return (_val) ;
}
int Bump(int _type, int _dirn) {
2023-06-18 14:00:47 +00:00
// _type = Frequency / Phase / Level, Duty, _dirn = Up / Down (_Up = 1, _Down = -1)
2023-06-05 13:27:01 +00:00
int val = 0 ;
if (_type == _Freq_) {
2023-08-27 18:46:27 +00:00
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) ; }
}
2023-06-05 13:27:01 +00:00
if (_type == _Phase_) {
Phase += _dirn ;
2023-06-18 14:00:47 +00:00
if (Phase == 360) Phase = 0 ; // Top Endwrap
if (Phase < 0 ) Phase = 359 ; // Bottom Endwrap
2023-06-05 13:27:01 +00:00
val = Phase ;
DataCalc(); } // Update Bitmap data to include new DAC phase
2023-06-18 14:00:47 +00:00
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
2023-08-27 18:46:27 +00:00
if (_type == _Square_) {
2023-06-05 13:27:01 +00:00
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
2023-08-27 18:46:27 +00:00
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_) {
2023-07-08 12:34:54 +00:00
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
2023-06-05 13:27:01 +00:00
return (val) ;
}
2023-04-29 15:08:21 +00:00
2023-04-06 18:32:06 +00:00
void DACspeed (int _frequency) {
2023-06-05 13:27:01 +00:00
// 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.
2023-04-01 17:42:17 +00:00
float DAC_freq = _frequency * BitMapSize; // Target frequency...
2023-04-29 15:08:21 +00:00
DAC_div = 2 * (float)clock_get_hz(clk_sys) / DAC_freq; // ...calculate the PIO clock divider required for the given Target frequency
2023-04-01 17:42:17 +00:00
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 )
2023-04-29 15:08:21 +00:00
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)
2023-06-05 13:27:01 +00:00
// 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...
2023-04-29 15:08:21 +00:00
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 )
2023-04-01 17:42:17 +00:00
DAC_div = DAC_div / 64; // Adjust DAC_div to keep within useable range
DAC_freq = DAC_freq * 64;
2023-04-29 15:08:21 +00:00
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)
2023-06-05 13:27:01 +00:00
// 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.
2023-04-29 15:08:21 +00:00
pio_sm_set_clkdiv(pio, StateMachine, DAC_div); // Set the State Machine clock speed
2023-04-01 17:42:17 +00:00
}
2023-06-18 14:00:47 +00:00
StatusString () ; // Update the terminal session
2023-04-01 17:42:17 +00:00
}
2023-04-06 18:32:06 +00:00
2023-04-01 17:42:17 +00:00
void DataCalc () {
2023-08-27 18:46:27 +00:00
int i, j, v_offset = BitMapSize/2 - 1; // Shift sine waves up above X axis
2023-04-01 17:42:17 +00:00
int _phase;
float a,b,x1,x2,g1,g2;
// Scale the phase shift to match data size...
2023-07-08 12:34:54 +00:00
_phase = Phase * BitMapSize / 360 ; // Input range: 0 -> 360 (degrees)
2023-04-01 17:42:17 +00:00
// Output range: 0 -> 255 (bytes)
switch (Funct) {
case _Sine_:
2023-07-08 12:34:54 +00:00
Harm = Harm % 10; // Sine harmonics cycles after 7
2023-04-01 17:42:17 +00:00
for (i=0; i<BitMapSize; i++) {
2023-06-05 13:27:01 +00:00
// Add the phase offset and wrap data beyond buffer end back to the buffer start...
2023-04-06 18:32:06 +00:00
j = ( i + _phase ) % BitMapSize; // Horizontal index
2023-04-01 17:42:17 +00:00
a = v_offset * sin((float)_2Pi*i / (float)BitMapSize); // Fundamental frequency...
2023-07-08 12:34:54 +00:00
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
2023-04-01 17:42:17 +00:00
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++) {
2023-04-06 18:32:06 +00:00
j = ( i + _phase ) % BitMapSize; // Horizontal index
if (b <= i) { DAC_data[j] = 0; } // First section low
else { DAC_data[j] = 255; } // Second section high
2023-04-01 17:42:17 +00:00
}
break;
case _Triangle_:
2023-08-27 18:46:27 +00:00
x1 = (RiseT * BitMapSize / 100) -1; // Number of data points to peak
2023-04-01 17:42:17 +00:00
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++) {
2023-04-06 18:32:06 +00:00
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
2023-04-01 17:42:17 +00:00
}
2023-08-27 18:46:27 +00:00
break ;
2023-04-01 17:42:17 +00:00
}
2023-06-05 13:27:01 +00:00
StatusString () ; // Update the terminal session
2023-04-01 17:42:17 +00:00
}
2022-04-20 12:32:41 +00:00
2022-06-07 18:53:53 +00:00
public:
2023-04-29 15:08:21 +00:00
// Each DAC channel consists of...
2023-06-05 13:27:01 +00:00
// BitMap data => DMA => FIFO => State Machine => GPIO pins => R-2-R module
2023-04-29 15:08:21 +00:00
// 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...
2023-06-05 13:27:01 +00:00
// _name = Name of this DAC channel instance
// _pio = Required PIO channel
// _GPIO = Port connecting to the MSB of the R-2-R resistor network.
2023-04-29 15:08:21 +00:00
// Constructor
2023-06-05 13:27:01 +00:00
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
2023-08-27 18:46:27 +00:00
Funct = _Sine_, Freq = 100, Level = 50 ; // Start-up default values...
Range = 1, Harm = 0, DutyC = 50, RiseT = 50 ;
2023-07-08 12:34:54 +00:00
name == 'A' ? Phase=0 : Phase=180 ; // Set Phase difference between channels
2022-06-07 18:53:53 +00:00
int _offset;
2023-04-29 15:08:21 +00:00
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);
2022-06-07 18:53:53 +00:00
// 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
2022-06-20 19:13:48 +00:00
channel_config_set_read_increment(&fc, true); // increment the read adddress
channel_config_set_write_increment(&fc, false); // don't increment write address
2023-04-29 15:08:21 +00:00
channel_config_set_dreq(&fc, pio_get_dreq(_pio, StateMachine, true)); // Transfer when PIO SM TX FIFO has space
2022-06-07 18:53:53 +00:00
channel_config_set_chain_to(&fc, ctrl_chan); // chain to the controller DMA channel
2022-06-20 19:13:48 +00:00
channel_config_set_ring(&fc, false, 9); // 8 bit DAC 1<<9 byte boundary on read ptr. This is why we needed alignment!
2022-06-07 18:53:53 +00:00
dma_channel_configure(
data_chan, // Channel to be configured
&fc, // The configuration we just created
2023-04-29 15:08:21 +00:00
&_pio->txf[StateMachine], // Write to FIFO
2023-04-01 17:42:17 +00:00
DAC_data, // The initial read address (AT NATURAL ALIGNMENT POINT)
2022-06-20 19:13:48 +00:00
BitMapSize, // Number of transfers; in this case each is 2 byte.
2023-03-27 15:58:28 +00:00
false // Don't start immediately. All 4 control channels need to start simultaneously
// to ensure the correct phase shift is applied.
2022-06-07 18:53:53 +00:00
);
2023-04-29 15:08:21 +00:00
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.
2023-06-05 13:27:01 +00:00
DataCalc() ; // Populate bitmap data.
DACspeed(Freq * Range) ; // Initialise State MAchine clock speed.
2022-06-07 18:53:53 +00:00
2023-04-29 15:08:21 +00:00
return(StateMachine);
2022-08-17 13:22:46 +00:00
}
2022-06-07 18:53:53 +00:00
};
2023-03-27 15:58:28 +00:00
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:
2023-06-05 13:27:01 +00:00
uint pioNum, StateMachine, Freq, _offset ;
2023-04-01 17:42:17 +00:00
blink_forever(PIO _pio) {
2023-03-27 15:58:28 +00:00
pio = _pio; // transfer parameter to class wide var
2023-04-01 17:42:17 +00:00
pioNum = pio_get_index(_pio);
2023-03-27 15:58:28 +00:00
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);
2023-06-05 13:27:01 +00:00
blink_program_init(_pio, StateMachine, _offset, PICO_DEFAULT_LED_PIN );
2023-03-27 15:58:28 +00:00
pio_sm_set_enabled(_pio, StateMachine, true);
}
2022-04-13 16:49:54 +00:00
2023-04-01 17:42:17 +00:00
// Setter function...
2023-03-27 15:58:28 +00:00
void Set_Frequency(int _frequency){
2023-04-01 17:42:17 +00:00
Freq = _frequency; // Copy parm to class var
2023-04-29 15:08:21 +00:00
// Frequency scaled by 2000 as blink.pio requires this number of cycles to complete...
2023-03-27 15:58:28 +00:00
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
2022-05-03 18:34:04 +00:00
}
2023-04-01 17:42:17 +00:00
};
2023-03-27 15:58:28 +00:00
2023-08-27 18:46:27 +00:00
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.
2022-05-16 18:51:47 +00:00
}
2023-06-18 14:00:47 +00:00
static inline void cs_select(int _gpio) {
2022-05-28 12:58:58 +00:00
asm volatile("nop \n nop \n nop");
2023-06-18 14:00:47 +00:00
gpio_put(_gpio, 0); // Active low
2022-05-28 12:58:58 +00:00
asm volatile("nop \n nop \n nop");
}
2023-06-18 14:00:47 +00:00
static inline void cs_deselect(int _gpio) {
2022-05-28 12:58:58 +00:00
asm volatile("nop \n nop \n nop");
2023-06-18 14:00:47 +00:00
gpio_put(_gpio, 1);
2022-05-28 12:58:58 +00:00
asm volatile("nop \n nop \n nop");
}
2023-06-05 13:27:01 +00:00
static void SPI_Display_Write(int _data) {
2022-05-28 12:58:58 +00:00
uint8_t buff[2];
2023-03-27 15:58:28 +00:00
buff[0] = _data / 256; // MSB data
buff[1] = _data % 256; // LSB data
2023-06-18 14:00:47 +00:00
cs_select(Display_CS);
2022-05-28 12:58:58 +00:00
spi_write_blocking(SPI_PORT, buff, 2);
2023-06-18 14:00:47 +00:00
cs_deselect(Display_CS);
2022-05-28 12:58:58 +00:00
}
2023-06-18 14:00:47 +00:00
static void MCP41020_Write (uint8_t _ctrl, uint8_t _data) {
2023-08-27 18:46:27 +00:00
// 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.
2023-06-18 14:00:47 +00:00
uint8_t buff[2];
2023-08-27 18:46:27 +00:00
// 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
2023-06-18 14:00:47 +00:00
spi_write_blocking(SPI_PORT, buff, 2) ;
cs_deselect(Level_CS) ;
}
2023-06-05 13:27:01 +00:00
static void getLine() {
char *pPos = (char *)inStr ; // Pointer to start of Global input string
2023-08-27 18:46:27 +00:00
int count = 0 ;
2023-03-27 15:58:28 +00:00
while(1) {
2023-06-05 13:27:01 +00:00
c = getchar();
if (c == eof || c == '\n' || c == '\r') break ; // Non blocking exit
putchar(c); // FullDuplex echo
*pPos++ = c ; // Bump pointer, store character
2023-08-27 18:46:27 +00:00
count ++ ;
}
2023-08-27 18:46:27 +00:00
*pPos = '\0' ;
MarginCount += count ; // Track number of characters on current line
2023-06-05 13:27:01 +00:00
return ;
}
2023-08-27 18:46:27 +00:00
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 ;
}
2023-08-27 18:46:27 +00:00
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.
2023-03-27 15:58:28 +00:00
gpio_set_function(PIN_CLK, GPIO_FUNC_SPI);
gpio_set_function(PIN_TX, GPIO_FUNC_SPI);
2022-04-20 12:32:41 +00:00
2023-03-27 15:58:28 +00:00
// Chip select is active-low, so initialise to a driven-high state...
2023-06-05 13:27:01 +00:00
gpio_init(Display_CS);
gpio_set_dir(Display_CS, GPIO_OUT);
gpio_put(Display_CS, 1);
2023-06-18 14:00:47 +00:00
gpio_init(Level_CS);
gpio_set_dir(Level_CS, GPIO_OUT);
gpio_put(Level_CS, 1);
2023-08-27 18:46:27 +00:00
// Setting Max slew rate and gpio drive strength keeps output linear at high frequencies...
for (int i=0; i<16; i++) {
2023-06-18 14:00:47 +00:00
gpio_set_slew_rate(i, GPIO_SLEW_RATE_FAST);
gpio_set_drive_strength(i, GPIO_DRIVE_STRENGTH_12MA);
2023-07-08 12:34:54 +00:00
}
2023-03-27 15:58:28 +00:00
// Initialise remaining SPI connections...
gpio_set_dir(PIN_CLK, GPIO_OUT);
gpio_set_dir(PIN_TX, GPIO_OUT);
2023-08-27 18:46:27 +00:00
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
2023-03-27 15:58:28 +00:00
2023-06-05 13:27:01 +00:00
// 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.
2023-08-27 18:46:27 +00:00
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
2023-07-08 12:34:54 +00:00
blink_forever LED_blinky(pio0); // Onboard LED blinky object
2023-03-27 15:58:28 +00:00
2023-07-08 12:34:54 +00:00
strcpy(LastCmd,"?") ; // Hitting return will give 'Help'
2023-04-29 15:08:21 +00:00
2023-08-27 18:46:27 +00:00
SPI_Display_Write(SysClock) ; // Pico system clock speed (in MHz)
2023-07-08 12:34:54 +00:00
MCP41020_Write(0x3, 50) ; // Both channels -> 50% output level
2023-04-29 15:08:21 +00:00
2023-07-08 12:34:54 +00:00
LED_blinky.Set_Frequency(1); // Flash LED at 1Hz- waiting for USB connection
2023-03-27 15:58:28 +00:00
2023-07-08 12:34:54 +00:00
while (!stdio_usb_connected()) { sleep_ms(100); } // Wait for USB connection...
2023-03-27 15:58:28 +00:00
2023-07-08 12:34:54 +00:00
LED_blinky.Set_Frequency(10); // Flash LED at 10Hz - USB connected.
2023-08-27 18:46:27 +00:00
SPI_Display_Write(DACobj[_DAC_A].Freq) ; // Frequency => SPI display
2023-03-27 15:58:28 +00:00
2023-07-08 12:34:54 +00:00
// Send (optional) start-up messages to terminal...
2023-08-27 18:46:27 +00:00
VerText() ; // Version text
printf(ResultStr) ; // Update terminal
2023-03-27 15:58:28 +00:00
2023-06-05 13:27:01 +00:00
// Atomic Restart - starting all 4 DMA channels simultaneously ensures phase sync between both DAC channels
2023-03-27 15:58:28 +00:00
dma_start_channel_mask(DAC_channel_mask);
while(1) {
2023-08-27 18:46:27 +00:00
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
2023-06-05 13:27:01 +00:00
printf(">") ; // Command prompt
2023-08-27 18:46:27 +00:00
MarginCount = 1 ; // Reset count and bump for command prompt
2023-05-05 16:08:51 +00:00
2023-08-27 18:46:27 +00:00
getLine() ; // Fetch command line
2023-05-05 16:08:51 +00:00
// Zero length string = 'CR' pressed...
2023-06-05 13:27:01 +00:00
if (strlen(inStr) == 0) { strcpy(inStr,LastCmd) ; // Repeat last command
printf("%s", inStr) ; }
// One character commands...
if (strlen(inStr) == 1) {
2023-08-27 18:46:27 +00:00
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 ??
2023-06-05 13:27:01 +00:00
}
// For all remaining commands, the first character selects DAC channel A or B...
2023-08-27 18:46:27 +00:00
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
2023-06-05 13:27:01 +00:00
2023-06-18 14:00:47 +00:00
// ...and if we aren't bumping a value, there will be one or more numeric parameters...
2023-08-27 18:46:27 +00:00
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
2023-05-05 16:08:51 +00:00
}
}
2023-06-05 13:27:01 +00:00
2023-08-27 18:46:27 +00:00
// 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
2023-06-05 13:27:01 +00:00
}
2023-08-27 18:46:27 +00:00
// 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)
2023-06-05 13:27:01 +00:00
2023-08-27 18:46:27 +00:00
if ((inStr[1]=='t')&(inStr[2]=='i')) { // Time display...
DACobj[_DAC_A].Set(_Phase_, 0) ; // Phase lock
DACobj[_DAC_B].Set(_Phase_, 0) ;
2023-06-18 14:00:47 +00:00
2023-08-27 18:46:27 +00:00
for (int gpio = 0; gpio < 16; gpio++) { // Grabs the GPIO back from the State machines
gpio_init(gpio);
gpio_set_dir(gpio, GPIO_OUT);
2023-07-08 12:34:54 +00:00
}
2023-08-27 18:46:27 +00:00
// 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
}
2023-06-05 13:27:01 +00:00
2023-08-27 18:46:27 +00:00
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 ????
}
}
}
2022-05-12 19:04:40 +00:00
}
2023-06-05 13:27:01 +00:00
2023-08-27 18:46:27 +00:00
// The final command is a continual loop creating the sweep function...
if ((inStr[1] == 's') & (inStr[2] == 'w')) { // Sweep
2023-06-05 13:27:01 +00:00
// Parm[0]=Low frequency, Parm[1]=High frequency, Parm[2]=Scan speed, Parm[3]=Low/High pause
i = Parm[0];
for (;;) {
2023-08-27 18:46:27 +00:00
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
2023-06-05 13:27:01 +00:00
dma_start_channel_mask(DAC_channel_mask); // Atomic restart all 4 DMA channels...
2023-08-27 18:46:27 +00:00
printf(ResultStr) ; // Update terminal
ResultStr[0] = '\0' ; // Reset the string variable
2023-06-05 13:27:01 +00:00
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
2023-08-27 18:46:27 +00:00
if ((c>=32) & (c<=126)) {
strcpy(ResultStr," Exit sweep mode\n") ; // Prevents error message
break; } // exit on keypress
2023-06-05 13:27:01 +00:00
sleep_ms(Parm[2]); // Speed of scan
}
}
2023-08-27 18:46:27 +00:00
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) ;
2023-06-05 13:27:01 +00:00
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
2022-04-13 16:49:54 +00:00
}
2023-03-27 15:58:28 +00:00
return 0;
2022-05-03 18:34:04 +00:00
}