This program was an introduction to the SPI communication protocol on the Raspberry Pi Pico. RP2040 has two identical SPI controllers, both based on an ARM Primecell Synchronous Serial Port (SSP). I used an MCP4822 DAC to generate a sine wave of a given frequency. The Pico transmits data to the DAC using SPI. The resources for the project include the C SDK User Guide, the RP2040 Datasheet and Prof. Hunter's website.
/*
* Parth Sarthi Sharma (pss242@cornell.edu)
* Code based on examples from Raspberry Pi Foundation.
* This code is a demonstration of Direct Digital Synthesis (DDS)
* using a digital to analog converter (DAC). The Raspberry Pi Pico
* communicates with the DAC using Serial Peripheral Interface (SPI).
* It takes in the potentiometer input and outputs a sine wave of
* corresponding frequency.
*/
#include <stdio.h> //The standard C library
#include <math.h> //The standard math library
#include "pico/stdlib.h" //Standard library for Pico
#include "hardware/gpio.h" //The hardware GPIO library
#include "hardware/adc.h" //The hardware ADC library
#include "pico/time.h" //The pico time library
#include "hardware/irq.h" //The hardware interrupt library
#include "hardware/spi.h" //The hardware SPI library
#define CS 17 //The chip-select pin
#define MOSI 19 //The MOSI pin
#define MISO 16 //The MISO pin
#define SCK 18 //The clock pin
#define SPI_PORT spi0 //The SPI port to be used
#define SIZE 256 //The sine table size
#define Fs 44000 //The sampling frequency
#define two32divFs 97612.8930909 //(4294967296 / 44000)
#define DAC_config_chan_A 0b0011000000000000 //The DAC configuration bits
uint32_t phaseAccum = 0, phaseInc = 0; //The phase accumulator and the phase incrementer
uint16_t adcIn = 0, dacData; //The ADC input and data to be sent to DAC
int sinTable[SIZE]; //The sin table
static bool repeating_timer_callback(struct repeating_timer *t){ //Callback for a repeating timer
adcIn = adc_read(); //Read the ADC value
phaseInc = adcIn * two32divFs; //Calculate the phase increment
phaseAccum += phaseInc; //Add the incrementer to the accumulator
dacData = sinTable[phaseAccum >> 24]; //We only care about the 8 most significant bits
dacData = DAC_config_chan_A | (dacData & 0x0fff); //Configure DAC data using bitmasks on the data
spi_write16_blocking(SPI_PORT, &dacData, 1); //Send 16 bits to SPI device
return true; //true to continue repeating, false to stop
}
int main(){ //The program running on core 0
stdio_init_all(); //Initialize all of the present standard stdio types that are linked into the binary
struct repeating_timer timer; //The repeating timer object for interrupt
int i; //The counter i
for(i = 0; i < SIZE; i++){ //For the sine table size
sinTable[i] = (int)(2047 * sin((float) i * 6.283 / (float) SIZE) + 2047); //Create the sine table
}
adc_init(); //Initialise the ADC hardware
adc_gpio_init(26); //Initialise the gpio for use as an ADC pin
adc_select_input(0); //Select an ADC input. 0...3 are GPIOs 26...29 respectively
spi_init(SPI_PORT, 20000000); //Initialise SPI instance with the given baud rate
gpio_set_function(MOSI, GPIO_FUNC_SPI); //Select GPIO function on the pin
gpio_set_function(MISO, GPIO_FUNC_SPI); //Select GPIO function on the pin
gpio_set_function(SCK, GPIO_FUNC_SPI); //Select GPIO function on the pin
gpio_set_function(CS, GPIO_FUNC_SPI); //Select GPIO function on the pin
spi_set_format(SPI_PORT, 16, 0, 0, 0); //Configure SPI with number of bits in each transaction, SPI mode (0, 0) and SPI order
add_repeating_timer_us(-23, repeating_timer_callback, NULL, &timer); //Add a repeating timer that is called repeatedly at the specified interval in microseconds. Negative time means it is the time difference between the start times of 2 consecutive callbacks
while(1){ //Empty eternity
}
}
The observation upon which the direct digital synthesis algorithm is based is that a variable overflowing is isomorphic to one rotation of a phasor. A sine wave is generated by projecting a rotating phasor onto the imaginary axis, as shown below. We see the rotating phasor and it's associated angle in red, the projection onto the imaginary axis in green, and the generated sine wave streaming off in orange. Note that, after the phasor has rotated 360 degrees, we have completed one period of the sine wave and the whole thing repeats.
Now, suppose that we represented the angle of that phasor, from the positive x-axis, with a 32-bit number that we'll call the accumulator. A scaled version of the phase angle will be stored in the accumulator. An angle of 0 degrees from the positive x-axis will correspond to an accumulator value of 0 (a 0 in each of the 32 bits). An angle of 360 degrees from the positive x-axis will correspond to an accumulator value of $2^{32}−1$ (a 1 in each of the 32 bits). Then, overflowing the accumulator corresponds to completing a full rotation of the phasor.
We'll see why this is useful in the next section. For now, all that we've done is scaled the range of phasor angles from 0→2π radians to instead 0→$2^{32}−1$ units, and stored that rescaled value in a 32-bit variable that we are calling accumulator.
anim