RP2040-code/Function Generator/FunctionGenerator.cpp

938 wiersze
73 KiB
C++

// TBD: 1) SPI read connecton
// 2) Capacitors on op-amps
// 3) Issue with phase lock - red/writes to serial port affecting phase lock at high frequencies
#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 the clock face is generated externally using an Excel spreadsheet...
uint8_t FaceX[] = {
0xfa,0xfb,0xfc,0xfd,0xfe,0xff,0xff,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfd,0xfd,0xfd,0xfc,0xfc,0xfb,0xfb,0xfa,0xfa,0xf9,0xf8,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf3,0xf2,0xf1,0xf0,0xef,0xe9,0xea,0xeb,0xec,0xed,0xed,0xec,0xeb,0xea,0xe9,0xe7,0xe6,0xe5,0xe3,0xe2,0xe1,0xdf,0xde,0xdc,0xdb,0xd9,0xd8,0xd6,0xd4,0xd3,0xd1,0xcf,0xcd,0xcc,0xca,0xc8,0xc6,0xc4,0xc3,0xc1,0xbc,0xbd,0xbd,0xbe,0xbe,0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,0xad,0xab,0xa9,0xa6,0xa4,0xa2,0xa0,0x9e,0x9c,0x9a,0x97,0x95,0x93,0x91,0x8f,0x8c,0x8a,0x88,0x86,0x83,0x81,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7d,0x7b,0x78,0x76,0x74,0x72,0x6f,0x6d,0x6b,0x69,0x67,0x64,0x62,0x60,0x5e,0x5c,0x5a,0x58,0x55,0x53,0x51,0x4f,0x4d,0x4b,0x49,0x47,0x45,0x43,0x41,0x42,0x41,0x41,0x40,0x40,0x3f,0x3d,0x3b,0x3a,0x38,0x36,0x34,0x32,0x31,0x2f,0x2d,0x2b,0x2a,0x28,0x26,0x25,0x23,0x22,0x20,0x1f,0x1d,0x1c,0x1b,0x19,0x18,0x17,0x15,0x14,0x13,0x12,0x15,0x14,0x13,0x12,0x11,0x11,0x0f,0x0e,0x0d,0x0c,0x0b,0x0b,0x0a,0x09,0x08,0x07,0x06,0x06,0x05,0x04,0x04,0x03,0x03,0x02,
0x02,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x04,0x03,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x02,0x02,0x03,0x03,0x04,0x04,0x05,0x06,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0b,0x0c,0x0d,0x0e,0x0f,0x15,0x14,0x13,0x12,0x11,0x11,0x12,0x13,0x14,0x15,0x17,0x18,0x19,0x1b,0x1c,0x1d,0x1f,0x20,0x22,0x23,0x25,0x26,0x28,0x2a,0x2b,0x2d,0x2f,0x31,0x32,0x34,0x36,0x38,0x3a,0x3b,0x3d,0x42,0x41,0x41,0x40,0x40,0x3f,0x41,0x43,0x45,0x47,0x49,0x4b,0x4d,0x4f,0x51,0x53,0x55,0x58,0x5a,0x5c,0x5e,0x60,0x62,0x64,0x67,0x69,0x6b,0x6d,0x6f,0x72,0x74,0x76,0x78,0x7b,0x7d,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x81,0x83,0x86,0x88,0x8a,0x8c,0x8f,0x91,0x93,0x95,0x97,0x9a,0x9c,0x9e,0xa0,0xa2,0xa4,0xa6,0xa9,0xab,0xad,0xaf,0xb1,0xb3,0xb5,0xb7,0xb9,0xbb,0xbd,0xbc,0xbd,0xbd,0xbe,0xbe,0xbf,0xc1,0xc3,0xc4,0xc6,0xc8,0xca,0xcc,0xcd,0xcf,0xd1,0xd3,0xd4,0xd6,0xd8,0xd9,0xdb,0xdc,0xde,0xdf,0xe1,0xe2,0xe3,0xe5,0xe6,0xe7,0xe9,0xea,0xeb,0xec,0xe9,0xea,0xeb,0xec,0xed,0xed,0xef,0xf0,0xf1,0xf2,0xf3,0xf3,0xf4,0xf5,
0xf6,0xf7,0xf8,0xf8,0xf9,0xfa,0xfa,0xfb,0xfb,0xfc,0xfc,0xfd,0xfd,0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x4c,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20,0x1f,0x1e,0x1d,0x1c,0x1c,0x1c,0x1c,0x1c,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x28,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20,0x1f,0x1e,0x1d,0x1c,0x18,0x17,0x16,0x15,0x14,0x13,0x12,0x11,0x10,0x0f,0x0e,0x0d,0x0c,0x0c,0x0c,0x0c,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x0e,0x0d,0x0c,0x0c,0x0c,0x0c,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x27,0x26,0x25,0x24,0x23,0x22,0x21,0x20,0x1f,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x1e,0x26,0x25,0x24,0x23,0x22,0x21,0x20,0x1f,0x1e,0x1d,0x1c,0x1b,0x52,0x51,0x50,0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x46,0x46,0x46,0x46,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,
0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x52,0x51,0x50,0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x80,0x81,0x82,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x83,0x82,0x81,0x80,0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x77,0x77,0x77,0x77,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x80,0x81,0x82,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb6,0xb5,0xb4,0xb3,0xb2,0xb1,0xb0,0xaf,0xaf,0xaf,0xaf,0xaf,0xaf,0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xe0,0xe1,0xe2,0xe3,0xe3,0xe3,0xe3,0xe3,0xe3,0xe2,0xe1,0xe0,0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd7,0xd7,0xd7,0xd7,0xd7,0xd8,0xd9,0xda,0xdb,0xdc,0xdd,0xde,0xdf,0xe1,0xe2,0xe3,0xe3,0xe3,0xe3,0xe2,0xe1,0xe0,0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd7,0xd7,0xd7,0xd8,0xd9,0xf0,0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,
0xeb,0xec,0xed,0xee,0xef,0xf0,0xf0,0xf0,0xf0,0xf0,0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe5,0xe4,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xe2,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd0,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xcf,0xd0,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xdb,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xb5,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x86,0x81,0x80,0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,0x75,0x75,0x75,0x75,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x80,0x81,0x80,0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x75,} ;
uint8_t FaceY[] = {
0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7d,0x7b,0x78,0x76,0x74,0x72,0x6f,0x6d,0x6b,0x69,0x67,0x64,0x62,0x60,0x5e,0x5c,0x5a,0x58,0x55,0x53,0x51,0x4f,0x4d,0x4b,0x49,0x47,0x45,0x43,0x41,0x42,0x41,0x41,0x40,0x40,0x3f,0x3d,0x3b,0x3a,0x38,0x36,0x34,0x32,0x31,0x2f,0x2d,0x2b,0x2a,0x28,0x26,0x25,0x23,0x22,0x20,0x1f,0x1d,0x1c,0x1b,0x19,0x18,0x17,0x15,0x14,0x13,0x12,0x15,0x14,0x13,0x12,0x11,0x11,0x0f,0x0e,0x0d,0x0c,0x0b,0x0b,0x0a,0x09,0x08,0x07,0x06,0x06,0x05,0x04,0x04,0x03,0x03,0x02,0x02,0x01,0x01,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x05,0x04,0x03,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x02,0x02,0x03,0x03,0x04,0x04,0x05,0x06,0x06,0x07,0x08,0x09,0x0a,0x0b,0x0b,0x0c,0x0d,0x0e,0x0f,0x15,0x14,0x13,0x12,0x11,0x11,0x12,0x13,0x14,0x15,0x17,0x18,0x19,0x1b,0x1c,0x1d,0x1f,0x20,0x22,0x23,0x25,0x26,0x28,0x2a,0x2b,0x2d,0x2f,0x31,0x32,0x34,0x36,0x38,0x3a,0x3b,0x3d,0x42,0x41,0x41,0x40,0x40,0x3f,0x41,0x43,0x45,0x47,0x49,0x4b,0x4d,0x4f,0x51,0x53,0x55,0x58,0x5a,0x5c,0x5e,0x60,0x62,0x64,
0x67,0x69,0x6b,0x6d,0x6f,0x72,0x74,0x76,0x78,0x7b,0x7d,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x81,0x83,0x86,0x88,0x8a,0x8c,0x8f,0x91,0x93,0x95,0x97,0x9a,0x9c,0x9e,0xa0,0xa2,0xa4,0xa6,0xa9,0xab,0xad,0xaf,0xb1,0xb3,0xb5,0xb7,0xb9,0xbb,0xbd,0xbc,0xbd,0xbd,0xbe,0xbe,0xbf,0xc1,0xc3,0xc4,0xc6,0xc8,0xca,0xcc,0xcd,0xcf,0xd1,0xd3,0xd4,0xd6,0xd8,0xd9,0xdb,0xdc,0xde,0xdf,0xe1,0xe2,0xe3,0xe5,0xe6,0xe7,0xe9,0xea,0xeb,0xec,0xe9,0xea,0xeb,0xec,0xed,0xed,0xef,0xf0,0xf1,0xf2,0xf3,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf8,0xf9,0xfa,0xfa,0xfb,0xfb,0xfc,0xfc,0xfd,0xfd,0xfd,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfa,0xfb,0xfc,0xfd,0xfe,0xff,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfe,0xfd,0xfd,0xfd,0xfc,0xfc,0xfb,0xfb,0xfa,0xfa,0xf9,0xf8,0xf8,0xf7,0xf6,0xf5,0xf4,0xf3,0xf3,0xf2,0xf1,0xf0,0xef,0xe9,0xea,0xeb,0xec,0xed,0xed,0xec,0xeb,0xea,0xe9,0xe7,0xe6,0xe5,0xe3,0xe2,0xe1,0xdf,0xde,0xdc,0xdb,0xd9,0xd8,0xd6,0xd4,0xd3,0xd1,0xcf,0xcd,0xcc,0xca,0xc8,0xc6,0xc4,0xc3,0xc1,0xbc,0xbd,0xbd,0xbe,0xbe,0xbf,0xbd,0xbb,0xb9,0xb7,0xb5,0xb3,0xb1,0xaf,
0xad,0xab,0xa9,0xa6,0xa4,0xa2,0xa0,0x9e,0x9c,0x9a,0x97,0x95,0x93,0x91,0x8f,0x8c,0x8a,0x88,0x86,0x83,0x81,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x3e,0x3d,0x3c,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x78,0x77,0x76,0x75,0x75,0x75,0x75,0x75,0x75,0x75,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x7f,0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x89,0x89,0x89,0x89,0x89,0x89,0x88,0x87,0x86,0xb3,0xb2,0xb1,0xb0,0xaf,0xae,0xad,0xac,0xab,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xe2,0xe3,0xe4,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0,0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,0xda,
0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xe0,0xdf,0xde,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xdd,0xde,0xdf,0xe0,0xe1,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,0xeb,0xec,0xed,0xee,0xef,0xf0,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf1,0xf0,0xef,0xee,0xed,0xec,0xeb,0xea,0xe9,0xe8,0xe7,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe6,0xe7,0xe8,0xe5,0xe4,0xe3,0xe2,0xe1,0xe0,0xdf,0xde,0xdd,0xdc,0xdb,0xda,0xd9,0xd8,0xd7,0xd6,0xd5,0xd4,0xd3,0xd2,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xd1,0xb3,0xb4,0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xbb,0xbc,0xbd,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbe,0xbd,0xbc,0xbb,0xba,0xb9,0xb8,0xb7,0xb6,0xb5,0xb4,0xb3,0xb3,0xb3,0xb3,0xb3,0xb3,0xb2,0xb1,0xb0,0xaf,0xae,0xad,0xac,0xab,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xaa,0xab,0xac,0xad,0xae,0xaf,0xb0,0xb1,0xb2,0x87,0x88,0x89,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x8a,0x89,0x88,0x87,0x86,0x85,0x84,0x83,0x82,0x81,0x80,0x7f,0x7e,0x7d,0x7c,0x7b,0x7a,0x79,0x78,0x77,0x76,0x76,0x76,0x76,
0x76,0x76,0x76,0x77,0x78,0x79,0x7a,0x7b,0x7c,0x7d,0x7e,0x7f,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x7f,0x7e,0x7d,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x4d,0x4e,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4f,0x4e,0x4d,0x4c,0x4b,0x4a,0x49,0x48,0x47,0x46,0x45,0x44,0x43,0x42,0x41,0x40,0x3f,0x3e,0x3d,0x3c,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3b,0x3c,0x3d,0x3e,0x3f,0x40,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0x4a,0x4b,0x4c,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x0e,0x0d,0x0c,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0b,0x0c,0x0d,0x0e,0x0f,0x10,0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18,0x19,0x1a,0x1b,0x1c,0x1d,0x1e,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,0x1f,} ;
// (Number of pixels: 1000)
// 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, LEDCtr=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 ;
}
int Set(int _type, int _val) {
switch (_type) {
case _Freq_:
Freq = _val ; // Frequency (numeric)
DACspeed(Freq * Range) ; // Update State machine run speed
break ;
case _Phase_:
Phase = _val ; // Phase shift (0->355 degrees)
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);
}
};
bool Repeating_Timer_Callback(struct repeating_timer *t) {
// Routine called 5 times per second...
int i, steps=64, MidX=128, MidY=128 ;
// printf("%d\n",LEDCtr) ; // Debug
LEDCtr -- ;
if (LEDCtr>0) {
// LED off, and no change to the time for 4 out of 5 cycles...
gpio_put(PICO_DEFAULT_LED_PIN, 0); // LED is connected to PICO_DEFAULT_LED_PIN
} else {
// Falls through here once per second.
LEDCtr = 5 ;
gpio_put(PICO_DEFAULT_LED_PIN, 1); // LED is connected to PICO_DEFAULT_LED_PIN
// Bump the clock...
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 increment 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| 19th September 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"
"%s<A/B/C>ti - Time mode (display analog clock)\n"
"%swhere...\n"
"%s<A/B/C> = DAC channel A,B or C=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,
MarginFW ) ;
}
void SysInfo (DAC DACobj[] ) {
// 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| Target board: Pico |\n"
"%s| RP2040 clock frequency: %7.3fMHz |\n"
"%s| Max DAC frequency: %7.3fMHz |\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,
MarginFW, (float)clock_get_hz(clk_sys)/1000000,
MarginFW, MaxDACfreq/1000000,
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];
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, waveform 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]) ;
}
// Disable the Ctrl channels...
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_A].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_B].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
// wait for Busy flag to clear...
// Abort the data channels...
dma_channel_abort(DACobj[_DAC_A].data_chan);
dma_channel_abort(DACobj[_DAC_B].data_chan);
// 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);
// Re-enable the Ctrl channels (doesn't restart data transfer)...
hw_set_bits(&dma_hw->ch[DACobj[_DAC_A].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
hw_set_bits(&dma_hw->ch[DACobj[_DAC_B].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
dma_start_channel_mask(DAC_channel_mask); // Atomic restart both DAC channels
return result ;
}
int main() {
bool InvX=false, InvY=false ; // Clock display 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);
gpio_init(Display_CS) ; // Initailse the required GPIO ports...
gpio_set_dir(Display_CS, GPIO_OUT) ;
gpio_put(Display_CS, 1) ; // Chip select is active-low, so initialise to high state
gpio_init(Level_CS) ;
gpio_set_dir(Level_CS, GPIO_OUT) ;
gpio_put(Level_CS, 1) ; // Chip select is active-low, so initialise to high state
gpio_init(PICO_DEFAULT_LED_PIN) ;
gpio_set_dir(PICO_DEFAULT_LED_PIN, GPIO_OUT) ;
gpio_set_dir(PIN_CLK, GPIO_OUT) ; // Initialise remaining SPI connections...
gpio_set_dir(PIN_TX, GPIO_OUT) ;
for (int i=0; i<16; i++) {
gpio_set_slew_rate(i, GPIO_SLEW_RATE_FAST); // Setting Max slew rate and gpio drive strength keeps output
gpio_set_drive_strength(i, GPIO_DRIVE_STRENGTH_12MA); // linear at high frequencies...
}
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
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
while (!stdio_usb_connected()) { sleep_ms(100); } // Wait for USB connection...
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); // Sets the 'Busy' flag in Ctrl reg
struct repeating_timer timer;
add_repeating_timer_ms(-200, Repeating_Timer_Callback, NULL, &timer) ; // 5 x per second to blink LED
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); // 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')) SetVal(DACobj,_Freq_) ; // Frequency
// 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...
// Disable the Ctrl channels...
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_A].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_B].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
// wait for Busy flag to clear...
// Abort the data channels...
dma_channel_abort(DACobj[_DAC_A].data_chan);
dma_channel_abort(DACobj[_DAC_B].data_chan);
// Re-enable the Ctrl channels (doesn't restart data transfer)...
hw_set_bits(&dma_hw->ch[DACobj[_DAC_A].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
hw_set_bits(&dma_hw->ch[DACobj[_DAC_B].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
pio_sm_set_enabled(pio1,0,false) ; // disable State machine 0 !! HARD CODED !!
pio_sm_set_enabled(pio1,1,false) ; // disable State machine 1
for (uint i=0; i<16; i++) { // Grab the GPIO back from the State machines
gpio_init(i);
gpio_set_dir(i, GPIO_OUT);
}
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 ;
int outX, outY ;
// Draw the clock face...
for (int i=0; i<sizeof(FaceX); i++) {
outX=FaceX[i] * DACobj[_DAC_A].Level / 100 ; // Scale output to match state machine settings
outY=FaceY[i] * DACobj[_DAC_B].Level / 100 ;
if (InvX) { gpio_put_masked(0x00ff,outX) ; } // Write inverted data byte to DAC A
else { gpio_put_masked(0x00ff,255-outX) ; } // Write non-inverted data byte to DAC A
if (InvY) { gpio_put_masked(0xff00,outY<<8) ; } // Write inverted data byte to DAC B
else { gpio_put_masked(0xff00,255-outY<<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
outX=HandsX[i] * DACobj[_DAC_A].Level / 100 ; // Scale output to match state machine settings
outY=HandsY[i] * DACobj[_DAC_B].Level / 100 ;
if (InvX) { gpio_put_masked(0x00ff,outX) ; } // Write inverted data byte to DAC A
else { gpio_put_masked(0x00ff,255-outX) ; } // Write non-inverted data byte to DAC A
if (InvY) { gpio_put_masked(0xff00,outY<<8) ; } // Write inverted data byte to DAC B
else { gpio_put_masked(0xff00,255-outY<<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
LEDCtr=0 ; // Force update and do it now
Repeating_Timer_Callback(&timer) ;
printf("\n%sClock set to %02d:%02d:%02d\n>",MarginFW,Hours,Mins,Secs) ;
}
else if ((c=='q') or (c=='Q')) {
for (uint i=0; i<16; i++) { pio_gpio_init(pio1, i); } // Hand the GPIO's back to the state machines
// 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,true) ; // Re-enable State machine 0 !! HARD CODED !!
pio_sm_set_enabled(pio1,1,true) ; // Re-enable State machine 1
dma_start_channel_mask(DAC_channel_mask); // Atomic restart both DAC channels
strcpy(ResultStr," Quit clock mode\n") ; // Prevents error message
}
}
}
}
// 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]); }
// Disable the Ctrl channels...
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_A].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
hw_clear_bits(&dma_hw->ch[DACobj[_DAC_B].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
// wait for Busy flag to clear...
// Abort the data channels...
dma_channel_abort(DACobj[_DAC_A].data_chan);
dma_channel_abort(DACobj[_DAC_B].data_chan);
// Re-enable the Ctrl channels (doesn't restart data transfer)...
hw_set_bits(&dma_hw->ch[DACobj[_DAC_A].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
hw_set_bits(&dma_hw->ch[DACobj[_DAC_B].ctrl_chan].al1_ctrl, DMA_CH0_CTRL_TRIG_EN_BITS);
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;
}