Use hardware SPI

and save a few us and flash ROM bytes
pull/18/head^2
Thierry Frenkel 4 years ago
parent b59e92da9d
commit 341ebadeb9
  1. 56
      Open_Theremin_V3/OTPinDefs.h
  2. 102
      Open_Theremin_V3/SPImcpDAC.h
  3. 34
      Open_Theremin_V3/application.cpp
  4. 33
      Open_Theremin_V3/ihandlers.cpp
  5. 151
      Open_Theremin_V3/mcpDac.h

@ -1,56 +0,0 @@
/**
* \file
* Pin definitions
*/
#ifndef WavePinDefs_h
#define WavePinDefs_h
//------------------------------------------------------------------------------
// DAC pin definitions
// LDAC may be connected to ground to save a pin
/** Set USE_MCP_DAC_LDAC to 0 if LDAC is grounded. */
#define USE_MCP_DAC_LDAC 1
// use arduino pins 2, 3, 4, 5 for DAC
// pin 2 is DAC chip select
/** Data direction register for DAC chip select. */
#define MCP_DAC_CS_DDR DDRB
#define MCP_DAC2_CS_DDR DDRB
/** Port register for DAC chip select. */
#define MCP_DAC_CS_PORT PORTB
/** Port bit number for DAC chip select. */
#define MCP_DAC_CS_BIT 2
#define MCP_DAC2_CS_BIT 1
// pin 3 is DAC serial clock
/** Data direction register for DAC clock. */
#define MCP_DAC_SCK_DDR DDRB
/** Port register for DAC clock. */
#define MCP_DAC_SCK_PORT PORTB
/** Port bit number for DAC clock. */
#define MCP_DAC_SCK_BIT 5
// pin 4 is DAC serial data in
/** Data direction register for DAC serial in. */
#define MCP_DAC_SDI_DDR DDRB
/** Port register for DAC clock. */
#define MCP_DAC_SDI_PORT PORTB
/** Port bit number for DAC clock. */
#define MCP_DAC_SDI_BIT 3
// pin 5 is LDAC if used
#if USE_MCP_DAC_LDAC
/** Data direction register for Latch DAC Input. */
#define MCP_DAC_LDAC_DDR DDRD
/** Port register for Latch DAC Input. */
#define MCP_DAC_LDAC_PORT PORTD
/** Port bit number for Latch DAC Input. */
#define MCP_DAC_LDAC_BIT 7
#endif // USE_MCP_DAC_LDAC
#endif // WavePinDefs_h

@ -0,0 +1,102 @@
/* Control the mcp 4921/4922 DACs with hardware SPI of the Arduino UNO
* ...without all the overhead of the Arduino SPI lib...
* Just the needed functions in a runtime optimized way by "Theremingenieur" Thierry Frenkel
* This file is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*/
#ifndef SPImcpDac_h
#define SPImcpDac_h
#include <Arduino.h>
// Data direction & Port register & Bit number for DAC Latch:
#define MCP_DAC_LDAC_DDR DDRD
#define MCP_DAC_LDAC_PORT PORTD
#define MCP_DAC_LDAC_BIT 7
// Data direction & Port register & Bit number for DAC CS
#define MCP_DAC_CS_DDR DDRB
#define MCP_DAC_CS_PORT PORTB
#define MCP_DAC_CS_BIT 2
// Data direction & Port register & Bit number for DAC2 CS
#define MCP_DAC2_CS_DDR DDRB
#define MCP_DAC2_CS_PORT PORTB
#define MCP_DAC2_CS_BIT 1
// Data direction & Port registers & Bit numbers for Hardware SPI
#define HW_SPI_DDR DDRB
#define HW_SPI_SCK_BIT 5
#define HW_SPI_MISO_BIT 4 // unused in this configuration
#define HW_SPI_MOSI_BIT 3
static inline void SPImcpDACinit()
{
// initialize the latch pin:
MCP_DAC_LDAC_DDR |= _BV(MCP_DAC_LDAC_BIT);
MCP_DAC_LDAC_PORT |= _BV(MCP_DAC_LDAC_BIT);
// initialize the CS pins:
MCP_DAC_CS_DDR |= _BV(MCP_DAC_CS_BIT);
MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT);
MCP_DAC2_CS_DDR |= _BV(MCP_DAC2_CS_BIT);
MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT);
// initialize the hardware SPI pins:
HW_SPI_DDR |= _BV(HW_SPI_SCK_BIT);
HW_SPI_DDR |= _BV(HW_SPI_MOSI_BIT);
// initialize the hardware SPI registers
SPCR = _BV(SPE) | _BV(MSTR); // no interrupt, SPI enable, MSB first, SPI master, SPI mode 0, clock = f_osc/4 (maximum)
SPSR |= SPI2X; // double the SPI clock, ideally we get 8 MHz, so that a 16bit word goes out in 2us plus only a small overhead
}
static inline void SPImcpDACtransmit(uint16_t data)
{
// Send highbyte and wait for complete
SPDR = highByte(data);
while (!(SPSR && _BV(SPIF)))
;
// Send lowbyte and wait for complete
SPDR = lowByte(data);
while (!(SPSR && _BV(SPIF)))
;
}
static inline void SPImcpDAClatch()
{
MCP_DAC_LDAC_PORT &= ~_BV(MCP_DAC_LDAC_BIT);
MCP_DAC_LDAC_PORT |= _BV(MCP_DAC_LDAC_BIT);
}
static inline void SPImcpDACsend(uint16_t data)
{
MCP_DAC_CS_PORT &= ~_BV(MCP_DAC_CS_BIT);
// Sanitize input data and add DAC config MSBs
data &= 0x0FFF;
data |= 0x7000;
SPImcpDACtransmit(data);
MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT);
// Do not latch immpediately, let's do it at the beginning of the next interrupt to get consistent timing
}
static inline void SPImcpDAC2Asend(uint16_t data)
{
MCP_DAC2_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT);
// Sanitize input data and add DAC config MSBs
data &= 0x0FFF;
data |= 0x7000;
SPImcpDACtransmit(data);
MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT);
SPImcpDAClatch();
}
static inline void SPImcpDAC2Bsend(uint16_t data)
{
MCP_DAC2_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT);
// Sanitize input data and add DAC config MSBs
data &= 0x0FFF;
data |= 0xF000;
SPImcpDACtransmit(data);
MCP_DAC2_CS_PORT |= _BV(MCP_DAC2_CS_BIT);
SPImcpDAClatch();
}
#endif

@ -3,7 +3,7 @@
#include "application.h"
#include "hw.h"
#include "mcpDac.h"
#include "SPImcpDAC.h"
#include "ihandlers.h"
#include "timer.h"
#include "EEPROM.h"
@ -42,13 +42,13 @@ void Application::setup() {
digitalWrite(Application::LED_PIN_1, HIGH); // turn the LED off by making the voltage LOW
mcpDacInit();
SPImcpDACinit();
EEPROM.get(0,pitchDAC);
EEPROM.get(2,volumeDAC);
mcpDac2ASend(pitchDAC);
mcpDac2BSend(volumeDAC);
SPImcpDAC2Asend(pitchDAC);
SPImcpDAC2Bsend(volumeDAC);
initialiseTimer();
@ -327,7 +327,7 @@ static long pitchfn = 0;
InitialisePitchMeasurement();
interrupts();
mcpDacInit();
SPImcpDACinit();
qMeasurement = GetQMeasurement(); // Measure Arudino clock frequency
Serial.print("Arudino Freq: ");
@ -344,13 +344,13 @@ Serial.print("\nPitch Set Frequency: ");
Serial.println(pitchfn);
mcpDac2BSend(1600);
SPImcpDAC2Bsend(1600);
mcpDac2ASend(pitchXn0);
SPImcpDAC2Asend(pitchXn0);
delay(100);
pitchfn0 = GetPitchMeasurement();
mcpDac2ASend(pitchXn1);
SPImcpDAC2Asend(pitchXn1);
delay(100);
pitchfn1 = GetPitchMeasurement();
@ -362,11 +362,11 @@ Serial.println(pitchfn1);
while(abs(pitchfn0-pitchfn1)>CalibrationTolerance){ // max allowed pitch frequency offset
mcpDac2ASend(pitchXn0);
SPImcpDAC2Asend(pitchXn0);
delay(100);
pitchfn0 = GetPitchMeasurement()-pitchfn;
mcpDac2ASend(pitchXn1);
SPImcpDAC2Asend(pitchXn1);
delay(100);
pitchfn1 = GetPitchMeasurement()-pitchfn;
@ -411,7 +411,7 @@ static long volumefn = 0;
InitialiseVolumeMeasurement();
interrupts();
mcpDacInit();
SPImcpDACinit();
volumeXn0 = 0;
@ -424,12 +424,12 @@ Serial.print("\nVolume Set Frequency: ");
Serial.println(volumefn);
mcpDac2BSend(volumeXn0);
SPImcpDAC2Bsend(volumeXn0);
delay_NOP(44316);//44316=100ms
volumefn0 = GetVolumeMeasurement();
mcpDac2BSend(volumeXn1);
SPImcpDAC2Bsend(volumeXn1);
delay_NOP(44316);//44316=100ms
volumefn1 = GetVolumeMeasurement();
@ -443,11 +443,11 @@ Serial.println(volumefn1);
while(abs(volumefn0-volumefn1)>CalibrationTolerance){
mcpDac2BSend(volumeXn0);
SPImcpDAC2Bsend(volumeXn0);
delay_NOP(44316);//44316=100ms
volumefn0 = GetVolumeMeasurement()-volumefn;
mcpDac2BSend(volumeXn1);
SPImcpDAC2Bsend(volumeXn1);
delay_NOP(44316);//44316=100ms
volumefn1 = GetVolumeMeasurement()-volumefn;
@ -474,7 +474,7 @@ EEPROM.put(2,volumeXn0);
HW_LED2_OFF;
HW_LED1_ON;
Serial.println("\nCALIBRATION COMPTLETED\n");
Serial.println("\nCALIBRATION COMPLETED\n");
}
void Application::hzToAddVal(float hz) {
@ -515,4 +515,4 @@ void Application::delay_NOP(unsigned long time) {

@ -1,7 +1,7 @@
#include "Arduino.h"
#include "ihandlers.h"
#include "mcpDac.h"
#include "SPImcpDAC.h"
#include "timer.h"
#include "build.h"
@ -126,12 +126,10 @@ static inline uint32_t mulsu_16_8(uint16_t a, uint8_t b)
/* Externaly generated 31250 Hz Interrupt for WAVE generator (32us) */
ISR (INT1_vect) {
// Interrupt takes up a total of max 25 us
// Added by ThF 20200419
#ifdef TH_DEBUG
HW_LED2_ON;
#endif
// Interrupt takes up a total of 14us plus overhead when interrupted itself.
// Latch previously written DAC value:
SPImcpDAClatch();
disableInt1(); // Disable External Interrupt INT1 to avoid recursive interrupts
// Enable Interrupts to allow counter 1 interrupts
@ -154,9 +152,19 @@ ISR (INT1_vect) {
// multiply 16 bit wave number by 8 bit volume value (11.2us / 5.4us)
scaledSample = MCP_DAC_BASE + (mulsu_16_8(waveSample, vScaledVolume) >> 8);
mcpDacSend(scaledSample); //Send result to Digital to Analogue Converter (audio out) (9.6 us)
// Added by ThF 20200419
#ifdef TH_DEBUG
HW_LED2_ON;
#endif
SPImcpDACsend(scaledSample); //Send result to Digital to Analogue Converter (audio out) (6 us)
// Added by ThF 20200419
#ifdef TH_DEBUG
HW_LED2_OFF;
#endif
pointer = pointer + vPointerIncrement; // increment table pointer (ca. 2us)
pointer = pointer + vPointerIncrement; // increment table pointer (ca. 2us)
#endif //CV play sound
incrementTimer(); // update 32us timer
@ -187,11 +195,6 @@ ISR (INT1_vect) {
noInterrupts();
enableInt1();
// Added by ThF 20200419
#ifdef TH_DEBUG
HW_LED2_OFF;
#endif
}
/* VOLUME read - interrupt service routine for capturing volume counter value */
@ -218,3 +221,5 @@ ISR(TIMER1_OVF_vect)
{
timer_overflow_counter++;
}

@ -1,151 +0,0 @@
/* Arduino WaveHC Library
* Copyright (C) 2009 by William Greiman
*
* This file is part of the Arduino WaveHC Library
*
* This Library is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This Library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with the Arduino WaveHC Library. If not, see
* <http://www.gnu.org/licenses/>.
*/
/**
* Macros and inline functions for MCP4921 DAC
*/
#ifndef mcpDac_h
#define mcpDac_h
#include <avr/io.h>
#include "OTPinDefs.h"
//------------------------------------------------------------------------------
#define mcpDacCsLow() MCP_DAC_CS_PORT &= ~_BV(MCP_DAC_CS_BIT)
#define mcpDacCsHigh() MCP_DAC_CS_PORT |= _BV(MCP_DAC_CS_BIT)
#define mcpDac2CsLow() MCP_DAC_CS_PORT &= ~_BV(MCP_DAC2_CS_BIT)
#define mcpDac2CsHigh() MCP_DAC_CS_PORT |= _BV(MCP_DAC2_CS_BIT)
#define mcpDacSckLow() MCP_DAC_SCK_PORT &= ~_BV(MCP_DAC_SCK_BIT)
#define mcpDacSckHigh() MCP_DAC_SCK_PORT |= _BV(MCP_DAC_SCK_BIT)
#define mcpDacSckPulse() {mcpDacSckHigh();mcpDacSckLow();}
#define mcpDacSdiLow() MCP_DAC_SDI_PORT &= ~_BV(MCP_DAC_SDI_BIT)
#define mcpDacSdiHigh() MCP_DAC_SDI_PORT |= _BV(MCP_DAC_SDI_BIT)
#define mcpDacSdiSet(v) if(v){mcpDacSdiHigh();}else{mcpDacSdiLow();}
// send bit b of d
#define mcpDacSendBit(d, b) {mcpDacSdiSet(d&_BV(b));mcpDacSckPulse();}
//------------------------------------------------------------------------------
// init dac I/O ports
inline void mcpDacInit(void) {
// set all to output mode
MCP_DAC_CS_DDR |= _BV(MCP_DAC_CS_BIT);
MCP_DAC2_CS_DDR |= _BV(MCP_DAC2_CS_BIT);
MCP_DAC_SCK_DDR |= _BV(MCP_DAC_SCK_BIT);
MCP_DAC_SDI_DDR |= _BV(MCP_DAC_SDI_BIT);
// chip select high
mcpDacCsHigh();
mcpDac2CsHigh();
#if USE_MCP_DAC_LDAC
// LDAC low always - use unbuffered mode
MCP_DAC_LDAC_DDR |= _BV(MCP_DAC_LDAC_BIT);
MCP_DAC_LDAC_PORT &= ~_BV(MCP_DAC_LDAC_BIT);
#endif // USE_MCP_DAC_LDAC
}
//------------------------------------------------------------------------------
// send 12 bits to dac
// trusted compiler to optimize and it does
// csLow to csHigh takes 8 - 9 usec on a 16 MHz Arduino
inline void mcpDacSend(uint16_t data) {
mcpDacCsLow();
// send DAC config bits
mcpDacSdiLow();
mcpDacSckPulse(); // DAC A
mcpDacSdiHigh();
mcpDacSckPulse(); // buffered REF
mcpDacSckPulse(); // 1X gain
mcpDacSckPulse(); // no SHDN
// send 12 data bits
mcpDacSendBit(data, 11);
mcpDacSendBit(data, 10);
mcpDacSendBit(data, 9);
mcpDacSendBit(data, 8);
mcpDacSendBit(data, 7);
mcpDacSendBit(data, 6);
mcpDacSendBit(data, 5);
mcpDacSendBit(data, 4);
mcpDacSendBit(data, 3);
mcpDacSendBit(data, 2);
mcpDacSendBit(data, 1);
mcpDacSendBit(data, 0);
mcpDacCsHigh();
}
inline void mcpDac2ASend(uint16_t data) {
mcpDac2CsLow();
// send DAC config bits
mcpDacSdiLow();
mcpDacSckPulse(); // DAC A
mcpDacSdiHigh();
mcpDacSckPulse(); // buffered REF
mcpDacSckPulse(); // 1X gain
mcpDacSckPulse(); // no SHDN
// send 12 data bits
mcpDacSendBit(data, 11);
mcpDacSendBit(data, 10);
mcpDacSendBit(data, 9);
mcpDacSendBit(data, 8);
mcpDacSendBit(data, 7);
mcpDacSendBit(data, 6);
mcpDacSendBit(data, 5);
mcpDacSendBit(data, 4);
mcpDacSendBit(data, 3);
mcpDacSendBit(data, 2);
mcpDacSendBit(data, 1);
mcpDacSendBit(data, 0);
mcpDac2CsHigh();
}
inline void mcpDac2BSend(uint16_t data) {
mcpDac2CsLow();
// send DAC config bits
mcpDacSdiHigh();
mcpDacSckPulse(); // DAC A
mcpDacSdiHigh();
mcpDacSckPulse(); // buffered REF
mcpDacSckPulse(); // 1X gain
mcpDacSckPulse(); // no SHDN
// send 12 data bits
mcpDacSendBit(data, 11);
mcpDacSendBit(data, 10);
mcpDacSendBit(data, 9);
mcpDacSendBit(data, 8);
mcpDacSendBit(data, 7);
mcpDacSendBit(data, 6);
mcpDacSendBit(data, 5);
mcpDacSendBit(data, 4);
mcpDacSendBit(data, 3);
mcpDacSendBit(data, 2);
mcpDacSendBit(data, 1);
mcpDacSendBit(data, 0);
mcpDac2CsHigh();
}
#endif //mcpDac_h
Loading…
Cancel
Save