John Bradnam
Published © GPL3+

Improved AD9833 Function Generator

Build a function generator with an ATtiny3216 micro, a AD9833 Programmable Waveform Generator and digitally controlled amplitude.

IntermediateFull instructions provided8 hours2,964
Improved AD9833 Function Generator

Things used in this project

Hardware components

Microchip ATtiny3216 Microprocessor
×1
74HC14 Hex Schmitt Trigger Inverter
SOIC 14 variant
×1
MIC5219-3.3 LDO Regulator
SOT23-5 variant
×1
Passive Components
6 x 10k 0805 resistors, 6 x 2.2k 0805 resistors, 6 x 22nF 0805 capacitors, 3 x 0.1uF 0805 capacitors, 1 x 470pF 0805 capacitor, 1 x 2.2uF 0805 capacitor,
×1
Rotary Encoder with Push-Button
Rotary Encoder with Push-Button
20mm spline shaft
×2
OPEN-SMART 1.8 Inch 128x64 SPI Monochrome LCD
×1
AD9833 Programmable Waveform Generator Module
Green board
×1
Jumper Wires
DIYables Jumper Wires
10cm female-female jumper cable
×1
TP4056 5V 1A 18650 Lithium Battery Charger Board
Type C USB port
×1
18650 Battery Holder
Search for "ABS 18650 Power Bank Cases 1X 2X 3X 4X 18650 DIY Battery Holder Storage Box Case 1 2 3 4 Slot Batteries Container with Hard Pin"
×1
18650 3.7 lithium battery
×1
Rocker Switch, Non Illuminated
Rocker Switch, Non Illuminated
14mm x 9mm
×1

Software apps and online services

Arduino IDE
Arduino IDE

Hand tools and fabrication machines

3D Printer (generic)
3D Printer (generic)
Soldering iron (generic)
Soldering iron (generic)

Story

Read more

Custom parts and enclosures

STL Files

Files for 3D printing

Schematics

Schematic

PCB

Eagle files

Schematic and PCB in Eagle format

Code

SignalGeneratorV4.ino

Arduino
/*---------------------------------------------------------------------
 * AD9833 Function Generator V4
 * By John Bradnam
 * 
 * 2023-06-27 V4 John Bradnam
 *  - Changed processor to ATtiny3216
 *  - Changed screen to ST7567 128x64 LCD
 *  - Added output level tables to set waveforms to the same amplitude
 * ---------------------------------------
 * ATtiny3226 Pins mapped to Ardunio Pins
 *                           _____
 *                   VDD   1|*    |20  GND
 *  (nSS)  (AIN4) PA4  0~  2|     |19  16~ PA3 (AIN3)(SCK)(EXTCLK)
 *         (AIN5) PA5  1~  3|     |18  15  PA2 (AIN2)(MISO)
 *         (AIN6) PA6  2   4|     |17  14  PA1 (AIN1)(MOSI)
 *         (AIN7) PA7  3   5|     |16  17  PA0 (AIN0/nRESET/UPDI)
 *         (AIN8) PB5  4   6|     |15  13  PC3
 *         (AIN9) PB4  5   7|     |14  12  PC2
 *  (RXD) (TOSC1) PB3  6   8|     |13  11~ PC1 (PWM only on 1-series)
 *  (TXD) (TOSC2) PB2  7~  9|     |12  10~ PC0 (PWM only on 1-series)
 *  (SDA) (AIN10) PB1  8~ 10|_____|11   9~ PB0 (AIN11)(SCL)
 *             
 *             
 * BOARD: ATtiny3226/3216/1626/1616/1606/826/816/806/426/416...
 * Chip: ATtiny3216
 * Clock Speed: 20MHz interval
 * millis()/micros(): "Enabled (default there)"
 * Programmer: jtag2updi (megaTinyCore)
 * ----------------------------------------
 */

#include "AD9833_MCP41010.h"
#include "LCD_ST7567.h"
#include "c64enh_font.h"
#include <SPI.h>
#include <EEPROM.h>

//Uncomment next line to calibrate output voltage and fill in table
//#define CALIBRATE
//                    0.1,0.2,0.3,0.4,0.5,0.6,0.7,0.8,0.9,1.0
uint8_t sinTable[] = {5,  12, 19, 26, 34, 41, 49, 56, 64, 71};
uint8_t triTable[] = {4,  12, 20, 27, 35, 42, 50, 57, 65, 72};
uint8_t sqrTable[] = {0,  1,  3,  4,  5,  7,  8,  9,  10, 12};
uint8_t rawAmp = 0;
 
//Pins
#define LCD_BACKLIGHT  16 //PA3
#define LCD_CS         13 //PC3
#define LCD_DC         9  //PB0
#define LCD_RST        15 //PA2
#define LCD_SCK        10 //PC0
#define LCD_MISO       11 //PC1
#define LCD_SDI        12 //PC2

#define FRQ_A          3  //PA7
#define FRQ_B          4  //PB5
#define FRQ_S          5  //PB4
#define AMP_A          7  //PB2
#define AMP_B          8  //PB1
#define AMP_S          6  //PB3

#define OSC_FSY        2  //PA6
#define OSC_CS         1  //PA5

LCD_ST7567 lcd(LCD_DC, LCD_RST, LCD_CS, LCD_SDI, LCD_SCK);
AD9833_MCP41010 osc(OSC_FSY, OSC_CS);

int8_t volatile frqDirection = 0;
bool volatile lastFrqA = false;
bool volatile lastFrqB = false;

int8_t volatile ampDirection = 0;
bool volatile lastAmpA = false;
bool volatile lastAmpB = false;

long frequency = 0;
long amplitude = 0;
bool frqDown = false;
bool ampDown = false;

enum MenuEnum { X1, X10, X100, X1K, X10K, X100K, X1M, WAVEFORM, BACKLIGHT };
enum WaveEnum { SINE, TRIANGLE, SQUARE };
#define MENU_TEXT_LEN 8
const String menuText[] = {"x1Hz", "x10Hz", "x100Hz","x1kHz", "x10kHz", "x100kHz", "x1MHz", "Waveform:", "Backlight:"};
const String waveText[] = {"SINE", "TRIANGLE", "SQUARE"};

//EEPROM handling
//Uncomment next line to clear out EEPROM and reset
//#define RESET_EEPROM
#define EEPROM_ADDRESS 0
#define EEPROM_MAGIC 0x0BAD0DAD
typedef struct {
  uint32_t magic;
  MenuEnum menuState;
  WaveEnum waveState;
  long frequency;
  uint8_t amplitude;
  uint8_t backlight;
} EEPROM_DATA;

EEPROM_DATA EepromData;      //Current EEPROM settings

#define EEPROM_UPDATE_TIME 60000  //Check for eprom update every minute
unsigned long eepromTimeout = 0;
bool eepromUpdate = false;

#define FREQ_MAX 14000000
#define MAX_VOLTAGE 10      //Voltage x 10

bool outputState = false;   //True to output waveform
char buffer[32];

//--------------------------------------------------------------------
// Setup hardware
//--------------------------------------------------------------------

void setup() 
{
  //Get last settings
  readEepromData();

  pinMode(FRQ_A,INPUT);
  pinMode(FRQ_B,INPUT);
  pinMode(FRQ_S,INPUT);
  pinMode(AMP_A,INPUT);
  pinMode(AMP_B,INPUT);
  pinMode(AMP_S,INPUT);
  pinMode(LCD_BACKLIGHT, OUTPUT);

  SPI.pins(LCD_SDI, LCD_MISO, LCD_SCK, OSC_CS);
  
  //Use alternative set of pins for LCD
  analogWrite(LCD_BACKLIGHT,map(EepromData.backlight,0,100,255,0));
  lcd.init();
  lcd.setFont(c64enh);
  
  //Interrupt handers for rotary encoder
  attachInterrupt(FRQ_A, frqRotaryInterrupt, CHANGE);
  attachInterrupt(AMP_A, ampRotaryInterrupt, CHANGE);

  // Initialise the LCD, start the backlight and print a "bootup" message for two seconds
  lcd.cls();
  lcd.printStr(ALIGN_CENTER, 24, (char*)"AD9833");
  lcd.printStr(ALIGN_CENTER, 32, (char*)"Signal Generator");
  lcd.display();
  delay(2000);

  // Display initial set values
  updateDisplay();

  // Initialise the AD9833
  osc.Begin();
  osc.SetPhase(REG0, 0);
  osc.SetFrequency(REG0, EepromData.frequency);
  switch(EepromData.waveState)
  {
    case SINE: osc.SetWaveform(REG0, SINE_WAVE); break;
    case TRIANGLE: osc.SetWaveform(REG0, TRIANGLE_WAVE); break;
    case SQUARE: osc.SetWaveform(REG0, SQUARE_WAVE); break;
  }
  osc.SetOutputSource(REG0);
  osc.EnableOutput(outputState);
  setAmplitude(EepromData.amplitude, EepromData.waveState);

  eepromTimeout = millis() + EEPROM_UPDATE_TIME;
}

//--------------------------------------------------------------------
// Main loop
//--------------------------------------------------------------------

void loop() 
{
  // If frequency button is pressed, change the menu
  if (testButton(FRQ_S,true)) 
  {
    EepromData.menuState = (EepromData.menuState == BACKLIGHT) ? X1 : (MenuEnum)((int)EepromData.menuState + 1);
    eepromUpdate = true;
    updateDisplay();
  }

  // Test for change in frequency rotary encoder
  if (frqDirection != 0)
  {
    // Change frequency or waveform based on menu selection
    switch (EepromData.menuState) 
    {
      case X1: updateFrequency(frqDirection,1); break;
      case X10: updateFrequency(frqDirection,10); break;
      case X100: updateFrequency(frqDirection,100); break;
      case X1K: updateFrequency(frqDirection,1000); break;
      case X10K: updateFrequency(frqDirection,10000); break;
      case X100K: updateFrequency(frqDirection,100000); break;
      case X1M: updateFrequency(frqDirection,1000000); break;
      case WAVEFORM: updateWaveform(frqDirection); break;
      case BACKLIGHT: updateBacklight(frqDirection); break;
    }
    frqDirection = 0;
  }

  //Test if amplitude button is pressed
  if (testButton(AMP_S,true)) 
  {
    outputState = !outputState;
    osc.EnableOutput(outputState);
    updateDisplay();
  }
  
  // Test for change in frequency rotary encoder
  if (ampDirection != 0)
  {
    updateAmplitude(ampDirection);
    ampDirection = 0;
  }
  
  //Update EEPROM if settings changed
  if (millis() > eepromTimeout)
  {
    if (eepromUpdate)
    {
      writeEepromData();
      eepromUpdate = false;
    }
    eepromTimeout = millis() + EEPROM_UPDATE_TIME;
  }
}

//--------------------------------------------------------------------
// Test if button has been pressed
//    pin - Pin switch is on
//    waitForRelease - True to wait until button gos up
//    Returns true if button is pressed
//    Note - Switches are via a Schmitt trigger inverter
//--------------------------------------------------------------------

bool testButton(int pin, bool waitForRelease) 
{
  bool pressed = false;
  if (digitalRead(pin) == HIGH) 
  {
    pressed = true;
    while (waitForRelease && digitalRead(pin) == HIGH)
    {
      yield();
    }
  }
  return pressed;
}

//--------------------------------------------------------------------
//  Change the current frequency based on menu and stepValue
//      value - Either -1 or 1
//      stepValue - Current amount to change frequency by
//--------------------------------------------------------------------

void updateFrequency(int8_t value, long stepValue)
{
  long old = EepromData.frequency;
  if (value == 1)
  {
    EepromData.frequency = min(EepromData.frequency + stepValue,FREQ_MAX);
  }
  else
  {
    EepromData.frequency = max(EepromData.frequency - stepValue,0);
  }
  if (old != EepromData.frequency)
  {
    osc.SetFrequency(REG0, EepromData.frequency);
    eepromUpdate = true;
    updateDisplay();
  }
}

//--------------------------------------------------------------------
//  Change the current waveform
//      value - Either -1 or 1
//--------------------------------------------------------------------

void updateWaveform(int8_t value)
{
  if (value == 1)
  {
    EepromData.waveState = (EepromData.waveState == SQUARE) ? SINE : (WaveEnum)((int)EepromData.waveState + 1);
  }
  else
  {
    EepromData.waveState = (EepromData.waveState == SINE) ? SQUARE : (WaveEnum)((int)EepromData.waveState - 1);
  }
  switch(EepromData.waveState)
  {
    case SINE: osc.SetWaveform(REG0, SINE_WAVE); break;
    case TRIANGLE: osc.SetWaveform(REG0, TRIANGLE_WAVE); break;
    case SQUARE: osc.SetWaveform(REG0, SQUARE_WAVE); break;
  }
  setAmplitude(EepromData.amplitude, EepromData.waveState);
  
  eepromUpdate = true;
  updateDisplay();
}

//--------------------------------------------------------------------
//  Change the current waveform
//      value - Either -1 or 1
//--------------------------------------------------------------------

void updateBacklight(int8_t value)
{
  if (value == 1)
  {
    EepromData.backlight = min(EepromData.backlight + 1,100);
  }
  else
  {
    EepromData.backlight = max(EepromData.backlight - 1,0);
  }
  analogWrite(LCD_BACKLIGHT,map(EepromData.backlight,0,100,255,0));
  eepromUpdate = true;
  updateDisplay();
}

//--------------------------------------------------------------------
//  Change the current amplitude
//      value - Either -1 or 1
//--------------------------------------------------------------------

void updateAmplitude(int8_t value)
{
#ifdef CALIBRATE

  uint8_t old = rawAmp;
  if (value == 1)
  {
    rawAmp = min(rawAmp + 1,255);
  }
  else
  {
    rawAmp = max(rawAmp - 1,0);
  }
  if (old != rawAmp)
  {
    osc.SetWiper(rawAmp);
    updateDisplay();
  }
  
#else  

  uint8_t old = EepromData.amplitude;
  if (value == 1)
  {
    EepromData.amplitude = min(EepromData.amplitude + 1,MAX_VOLTAGE);
  }
  else
  {
    EepromData.amplitude = max(EepromData.amplitude - 1,0);
  }
  if (old != EepromData.amplitude)
  {
    setAmplitude(EepromData.amplitude, EepromData.waveState);
    eepromUpdate = true;
    updateDisplay();
  }

#endif  
}

//--------------------------------------------------------------------
// Use calibration tables to se amplitude
//    amplitude - 0 to 10 representing 0.1V per step
//    waveform - waveform being shown
//--------------------------------------------------------------------

void setAmplitude(uint8_t amplitude,  WaveEnum waveform)
{
  uint8_t* p;
  
  switch(waveform)
  {
    case SQUARE: p = sqrTable; break;
    case SINE: p = sinTable; break;
    case TRIANGLE: p = triTable; break;
  }
  if (amplitude == 0)
  {
    osc.SetWiper(0);
  }
  else
  {
    osc.SetWiper(p[min(EepromData.amplitude,10)-1]);
  }
}

//--------------------------------------------------------------------
// Update the display with lastest info
//--------------------------------------------------------------------

void updateDisplay()
{
  #define LEFT_MARGIN 2
  #define VALUE_MARGIN 64
  #define LINE_1 8
  #define LINE_2 24
  #define LINE_3 40
  #define LINE_4 56

  lcd.cls();
  lcd.printStr(LEFT_MARGIN, LINE_1, (char*)"Freq:");
  formatFrequency(EepromData.frequency);
  lcd.printStr(ALIGN_RIGHT, LINE_1, buffer);
  
  lcd.printStr(LEFT_MARGIN, LINE_2, (char*)"Amp:");
#ifdef CALIBRATE
  sprintf(buffer,"%d",rawAmp);
#else
  formatAmplitude(EepromData.amplitude);
#endif  
  lcd.printStr(ALIGN_RIGHT, LINE_2, buffer);

  lcd.printStr(LEFT_MARGIN, LINE_3, (char*)"Output:");
  if (!outputState)
  {
    lcd.printStr(ALIGN_RIGHT, LINE_3, (char*)"OFF");
  }
  else
  {
    lcd.setSpacing(0);
    switch (EepromData.waveState)
    {
      case SINE: lcd.printStr(ALIGN_RIGHT, LINE_3, (char*)"_`_`"); break;
      case TRIANGLE: lcd.printStr(ALIGN_RIGHT, LINE_3, (char*)"]^]^"); break;
      case SQUARE: lcd.printStr(ALIGN_RIGHT, LINE_3, (char*)"[\\[\\"); break;
    }
    lcd.setSpacing(1);
  }
  menuText[(int)EepromData.menuState].toCharArray(buffer,sizeof(buffer));
  lcd.printStr(LEFT_MARGIN, LINE_4, buffer);
  if (EepromData.menuState == WAVEFORM)
  {
    waveText[(int)EepromData.waveState].toCharArray(buffer,sizeof(buffer));
    lcd.printStr(ALIGN_RIGHT, LINE_4, buffer);
  }
  else if (EepromData.menuState == BACKLIGHT)
  {
    sprintf(buffer,"%d%%",EepromData.backlight);
    lcd.printStr(ALIGN_RIGHT, LINE_4, buffer);
  }
  
  lcd.display();
}

//-----------------------------------------------------------------------------------
// Format the frequency with comma seperators and put in global buffer
//    number - frequency to format
//-----------------------------------------------------------------------------------

void formatFrequency(long number)
{
  String s = "";
  bool space = true;
  for (uint8_t i = 0; i < 8; i++)
  {
    if ((i==3 || i==6) && !space && number > 0)
    {
      s = String(',') + s;
    }
    if (number > 0 || i == 0)
    {
      s = String((char)((number % 10) + 48)) + s;
      space = false;
    }
    else
    {
      space = true;
    }
    number = number / 10;
  }
  s += "Hz";
  s.toCharArray(buffer,sizeof(buffer));
}

//-----------------------------------------------------------------------------------
// Format the amplitude with decimal place and put in global buffer
//    number - amplitude to format (value x 10)
//-----------------------------------------------------------------------------------

void formatAmplitude(uint8_t number)
{
  sprintf(buffer,"%d.%dV",number / 10,number % 10);
}

//---------------------------------------------------------------------
// Interrupt Handler: Frequency Rotary encoder has moved
//---------------------------------------------------------------------

void frqRotaryInterrupt()
{
  bool a = (digitalRead(FRQ_A) == HIGH);
  bool b = (digitalRead(FRQ_B) == HIGH);
  if (a != lastFrqA) 
  {
    lastFrqA = a;
    if (b != lastFrqB) 
    {
      lastFrqB = b;
      if (b)
      {
        frqDirection = (a == b) ? 1 : -1;
      }
    }
  }
}

//---------------------------------------------------------------------
// Interrupt Handler: Amplitude Rotary encoder has moved
//---------------------------------------------------------------------

void ampRotaryInterrupt()
{
  bool a = (digitalRead(AMP_A) == HIGH);
  bool b = (digitalRead(AMP_B) == HIGH);
  if (a != lastAmpA) 
  {
    lastAmpA = a;
    if (b != lastAmpB) 
    {
      lastAmpB = b;
      if (b)
      {
        ampDirection = (a == b) ? 1 : -1;
      }
    }
  }
}

//--------------------------------------------------------------------
// Write the EepromData structure to EEPROM
//--------------------------------------------------------------------

void writeEepromData(void)
{
  //This function uses EEPROM.update() to perform the write, so does not rewrites the value if it didn't change.
  EEPROM.put(EEPROM_ADDRESS,EepromData);
}

//--------------------------------------------------------------------
// Read the EepromData structure from EEPROM, initialise if necessary
//--------------------------------------------------------------------

void readEepromData(void)
{
#ifndef RESET_EEPROM
  EEPROM.get(EEPROM_ADDRESS,EepromData);
  if (EepromData.magic != EEPROM_MAGIC)
  {
#endif  
    EepromData.magic = EEPROM_MAGIC;
    EepromData.menuState = WAVEFORM;
    EepromData.waveState = SINE;
    EepromData.frequency = 1000;
    EepromData.amplitude = 10;
    EepromData.backlight = 160;
    writeEepromData();
#ifndef RESET_EEPROM
  }
#endif  
}

AD9833_MCP41010.h

C Header File
/*
 * AD9833.h
 * 
 * Copyright 2016 Bill Williams <wlwilliams1952@gmail.com, github/BillWilliams1952>
 * 
 * This program 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 * 
 */

#ifndef __AD9833_MCP41010__

#define __AD9833_MCP41010__

#include <Arduino.h>
#include <SPI.h>

//#define FNC_PIN 4			// Define FNC_PIN for fast digital writes

#define WRITE_FNCPIN(Val) digitalWrite(FNCpin,(Val))
#define WRITE_CSPIN(Val) digitalWrite(CSpin,(Val))

#define pow2_28				268435456L	// 2^28 used in frequency word calculation
#define BITS_PER_DEG		11.3777777777778	// 4096 / 360

#define RESET_CMD			0x0100		// Reset enabled (also CMD RESET)
/*		Sleep mode
 * D7	1 = internal clock is disabled
 * D6	1 = put DAC to sleep
 */
#define SLEEP_MODE			0x00C0		// Both DAC and Internal Clock
#define DISABLE_DAC			0x0040
#define	DISABLE_INT_CLK		0x0080

#define PHASE_WRITE_CMD		0xC000		// Setup for Phase write
#define PHASE1_WRITE_REG	0x2000		// Which phase register
#define FREQ0_WRITE_REG		0x4000		// 
#define FREQ1_WRITE_REG		0x8000
#define PHASE1_OUTPUT_REG	0x0400		// Output is based off REG0/REG1
#define FREQ1_OUTPUT_REG	0x0800		// ditto

typedef enum { SINE_WAVE = 0x2000, TRIANGLE_WAVE = 0x2002,
			   SQUARE_WAVE = 0x2028, HALF_SQUARE_WAVE = 0x2020 } WaveformType;
			   
typedef enum { REG0, REG1, SAME_AS_REG0 } Registers;

#define PIN_NOT_USED 255

class AD9833_MCP41010 {

public:
	
	AD9833_MCP41010 ( uint8_t FNCpin, uint8_t CSpin, uint32_t referenceFrequency = 25000000UL );

	// Must be the first command after creating the AD9833 object.
	void Begin ( void );

	// Setup and apply a signal. Note that any calls to EnableOut,
	// SleepMode, DisableDAC, or DisableInternalClock remain in effect
	void ApplySignal ( WaveformType waveType, Registers freqReg,
		float frequencyInHz,
		Registers phaseReg = SAME_AS_REG0, float phaseInDeg = 0.0 );

	// Resets internal registers to 0, which corresponds to an output of
	// midscale - digital output at 0. See EnableOutput function
	void Reset ( void );

	// Update just the frequency in REG0 or REG1
	void SetFrequency ( Registers freqReg, float frequency );

	// Increment the selected frequency register by freqIncHz
	void IncrementFrequency ( Registers freqReg, float freqIncHz );

	// Update just the phase in REG0 or REG1
	void SetPhase ( Registers phaseReg, float phaseInDeg );

	// Increment the selected phase register by phaseIncDeg
	void IncrementPhase ( Registers phaseReg, float phaseIncDeg );

	// Set the output waveform for the selected frequency register
	// SINE_WAVE, TRIANGLE_WAVE, SQUARE_WAVE, HALF_SQUARE_WAVE,
	void SetWaveform ( Registers waveFormReg, WaveformType waveType );

	// Output based on the contents of REG0 or REG1
	void SetOutputSource ( Registers freqReg, Registers phaseReg = SAME_AS_REG0 );

	// Set MCP41010 digital pot value (0 to 255)
	void SetWiper( uint8_t value );

	// Turn ON / OFF output using the RESET command.
	void EnableOutput ( bool enable );

	// Enable/disable Sleep mode.  Internal clock and DAC disabled
	void SleepMode ( bool enable );

	// Enable / Disable DAC
	void DisableDAC ( bool enable );

	// Enable / Disable Internal Clock
	void DisableInternalClock ( bool enable );

	// Return actual frequency programmed in register 
	float GetActualProgrammedFrequency ( Registers reg );

	// Return actual phase programmed in register
	float GetActualProgrammedPhase ( Registers reg );

	// Return frequency resolution 
	float GetResolution ( void );

private:

	void 			WriteFncRegister ( int16_t dat );
  void      WriteMcpRegister ( int8_t msb, int8_t lsb );
	void 			WriteControlRegister ( void );
	uint16_t		waveForm0, waveForm1;
	uint8_t			FNCpin;
	uint8_t         CSpin;
	uint8_t			outputEnabled, DacDisabled, IntClkDisabled;
	uint32_t		refFrequency;
	float			frequency0, frequency1, phase0, phase1;
	Registers		activeFreq, activePhase;
  uint8_t outputLevel;
};

#endif

AD9833_MCP41010.cpp

C/C++
/*
 * AD9833.cpp
 * 
 * Copyright 2016 Bill Williams <wlwilliams1952@gmail.com, github/BillWilliams1952>
 *
 * Thanks to john@vwlowen.co.uk for his work on the AD9833. His web page
 * is: http://www.vwlowen.co.uk/arduino/AD9833-waveform-generator/AD9833-waveform-generator.htm
 * 
 * This program 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 2 of the License, or
 * (at your option) any later version.
 * 
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301, USA.
 * 
 */

#include "AD9833_MCP41010.h"

/*
 * Create an AD9833 object
 */
AD9833_MCP41010 :: AD9833_MCP41010 ( uint8_t FNCpin, uint8_t CSpin, uint32_t referenceFrequency ) {
  // Pin used to enable SPI communication (active LOW)
	this->FNCpin = FNCpin;
	this->CSpin = CSpin;
  WRITE_FNCPIN(HIGH);
	pinMode(FNCpin,OUTPUT);
  if (CSpin != PIN_NOT_USED)
  {
    WRITE_CSPIN(HIGH);
    pinMode(CSpin, OUTPUT);
  }

	/* TODO: The minimum resolution and max frequency are determined by
	 * by referenceFrequency. We should calculate these values and use
	 * them during setFrequency. The problem is if the user programs a
	 * square wave at refFrequency/2, then changes the waveform to sine.
	 * The sine wave will not have enough points?
	 */
	refFrequency = referenceFrequency;
	
	// Setup some defaults
	DacDisabled = false;
	IntClkDisabled = false;
	outputEnabled = false;
	waveForm0 = waveForm1 = SINE_WAVE;
	frequency0 = frequency1 = 1000;		// 1 KHz sine wave to start
	phase0 = phase1 = 0.0;				// 0 phase
	activeFreq = REG0; activePhase = REG0;
  outputLevel = 128;
}

/*
 * This MUST be the first command after declaring the AD9833 object
 * Start SPI and place the AD9833 in the RESET state
 */
void AD9833_MCP41010 :: Begin ( void ) {
	SPI.begin();
	delay(100);
	Reset();	// Hold in RESET until first WriteFncRegister command
}

/*
 * Setup and apply a signal. phaseInDeg defaults to 0.0 if not supplied.
 * phaseReg defaults to value of freqReg if not supplied.
 * Note that any previous calls to EnableOut,
 * SleepMode, DisableDAC, or DisableInternalClock remain in effect.
 */
void AD9833_MCP41010 :: ApplySignal ( WaveformType waveType,
		Registers freqReg, float frequencyInHz,
		Registers phaseReg, float phaseInDeg ) {
	SetFrequency ( freqReg, frequencyInHz );
	SetPhase ( phaseReg, phaseInDeg );
	SetWaveform ( freqReg, waveType );
	SetOutputSource ( freqReg, phaseReg );
  SetWiper(outputLevel);
}

/***********************************************************************
						Control Register
------------------------------------------------------------------------
D15,D14(MSB)	10 = FREQ1 write, 01 = FREQ0 write,
 				11 = PHASE write, 00 = control write
D13	If D15,D14 = 00, 0 = individual LSB and MSB FREQ write,
 					 1 = both LSB and MSB FREQ writes consecutively
	If D15,D14 = 11, 0 = PHASE0 write, 1 = PHASE1 write
D12	0 = writing LSB independently
 	1 = writing MSB independently
D11	0 = output FREQ0,
	1 = output FREQ1
D10	0 = output PHASE0
	1 = output PHASE1
D9	Reserved. Must be 0.
D8	0 = RESET disabled
	1 = RESET enabled
D7	0 = internal clock is enabled
	1 = internal clock is disabled
D6	0 = onboard DAC is active for sine and triangle wave output,
 	1 = put DAC to sleep for square wave output
D5	0 = output depends on D1
	1 = output is a square wave
D4	Reserved. Must be 0.
D3	0 = square wave of half frequency output
	1 = square wave output
D2	Reserved. Must be 0.
D1	If D5 = 1, D1 = 0.
	Otherwise 0 = sine output, 1 = triangle output
D0	Reserved. Must be 0.
***********************************************************************/

/*
 * Hold the AD9833 in RESET state.
 * Resets internal registers to 0, which corresponds to an output of
 * midscale - digital output at 0.
 * 
 * The difference between Reset() and EnableOutput(false) is that
 * EnableOutput(false) keeps the AD9833 in the RESET state until you
 * specifically remove the RESET state using EnableOutput(true).
 * With a call to Reset(), ANY subsequent call to ANY function (other
 * than Reset itself and Set/IncrementPhase) will also remove the RESET
 * state.
 */
void AD9833_MCP41010 :: Reset ( void ) {
	WriteFncRegister(RESET_CMD);
	delay(15);
}

/*
 *  Set the specified frequency register with the frequency (in Hz)
 */
void AD9833_MCP41010 :: SetFrequency ( Registers freqReg, float frequency ) {
	// TODO: calculate max frequency based on refFrequency.
	// Use the calculations for sanity checks on numbers.
	// Sanity check on frequency: Square - refFrequency / 2
	//							  Sine/Triangle - refFrequency / 4
	if ( frequency > 12.5e6 )	// TODO: Fix this based on refFreq
		frequency = 12.5e6;
	if ( frequency < 0.0 ) frequency = 0.0;
	
	// Save frequency for use by IncrementFrequency function
	if ( freqReg == REG0 ) frequency0 = frequency;
	else frequency1 = frequency;
	
	int32_t freqWord = (frequency * pow2_28) / (float)refFrequency;
	int16_t upper14 = (int16_t)((freqWord & 0xFFFC000) >> 14), 
			lower14 = (int16_t)(freqWord & 0x3FFF);

	// Which frequency register are we updating?
	uint16_t reg = freqReg == REG0 ? FREQ0_WRITE_REG : FREQ1_WRITE_REG;
	lower14 |= reg;
	upper14 |= reg;   

	// I do not reset the registers during write. It seems to remove
	// 'glitching' on the outputs.
	WriteControlRegister();
	// Control register has already been setup to accept two frequency
	// writes, one for each 14 bit part of the 28 bit frequency word
	WriteFncRegister(lower14);			// Write lower 14 bits to AD9833
	WriteFncRegister(upper14);			// Write upper 14 bits to AD9833
}

/*
 * Increment the specified frequency register with the frequency (in Hz)
 */
void AD9833_MCP41010 :: IncrementFrequency ( Registers freqReg, float freqIncHz ) {
	// Add/subtract a value from the current frequency programmed in
	// freqReg by the amount given
	float frequency = (freqReg == REG0) ? frequency0 : frequency1;
	SetFrequency(freqReg,frequency+freqIncHz);
}

/*
 * Set the specified phase register with the phase (in degrees)
 * The output signal will be phase shifted by 2/4096 x PHASEREG
 */
void AD9833_MCP41010 :: SetPhase ( Registers phaseReg, float phaseInDeg ) {
	// Sanity checks on input
	phaseInDeg = fmod(phaseInDeg,360);
	if ( phaseInDeg < 0 ) phaseInDeg += 360;
	
	// Phase is in float degrees ( 0.0 - 360.0 )
	// Convert to a number 0 to 4096 where 4096 = 0 by masking
	uint16_t phaseVal = (uint16_t)(BITS_PER_DEG * phaseInDeg) & 0x0FFF;
	phaseVal |= PHASE_WRITE_CMD;
	
	// Save phase for use by IncrementPhase function
	if ( phaseReg == REG0 )	{
		phase0 = phaseInDeg;
	}
	else {
		phase1 = phaseInDeg;
		phaseVal |= PHASE1_WRITE_REG;
	}
	WriteFncRegister(phaseVal);
}

/*
 * Increment the specified phase register by the phase (in degrees)
 */
void AD9833_MCP41010 :: IncrementPhase ( Registers phaseReg, float phaseIncDeg ) {
	// Add/subtract a value from the current phase programmed in
	// phaseReg by the amount given
	float phase = (phaseReg == REG0) ? phase0 : phase1;
	SetPhase(phaseReg,phase + phaseIncDeg);
}

/*
 * Set the type of waveform that is output for a frequency register
 * SINE_WAVE, TRIANGLE_WAVE, SQUARE_WAVE, HALF_SQUARE_WAVE
 */
void AD9833_MCP41010 :: SetWaveform (  Registers waveFormReg, WaveformType waveType ) {
	// TODO: Add more error checking?
	if ( waveFormReg == REG0 )
		waveForm0 = waveType;
	else
		waveForm1 = waveType;
	WriteControlRegister();
}

/*
 * EnableOutput(false) keeps the AD9833 is RESET state until a call to
 * EnableOutput(true). See the Reset function description.
 */
void AD9833_MCP41010 :: EnableOutput ( bool enable ) {
	outputEnabled = enable;
	WriteControlRegister();
}

/*
 * Set which frequency and phase register is being used to output the
 * waveform. If phaseReg is not supplied, it defaults to the same
 * register as freqReg.
 */
void AD9833_MCP41010 :: SetOutputSource ( Registers freqReg, Registers phaseReg ) {
	// TODO: Add more error checking?
	activeFreq = freqReg;
	if ( phaseReg == SAME_AS_REG0 )	activePhase = activeFreq;
	else activePhase = phaseReg;
	WriteControlRegister();
}

/*
 * Set the wiper on the MCP41010 digital pot (0 to 255).
 */
void AD9833_MCP41010 :: SetWiper( uint8_t value )
{
	WriteMcpRegister(B00010001, value);
}


//---------- LOWER LEVEL FUNCTIONS NOT NORMALLY NEEDED -------------

/*
 * Disable/enable both the internal clock and the DAC. Note that square
 * wave outputs are avaiable if using an external Reference.
 * TODO: ?? IS THIS TRUE ??
 */
void AD9833_MCP41010 :: SleepMode ( bool enable ) {
	DacDisabled = enable;
	IntClkDisabled = enable;
	WriteControlRegister();
}

/*
 * Enables / disables the DAC. It will override any previous DAC
 * setting by Waveform type, or via the SleepMode function
 */
void AD9833_MCP41010 :: DisableDAC ( bool enable ) {
	DacDisabled = enable;
	WriteControlRegister();	
}

/*
 * Enables / disables the internal clock. It will override any 
 * previous clock setting by the SleepMode function
 */
void AD9833_MCP41010 :: DisableInternalClock ( bool enable ) { 
	IntClkDisabled = enable;
	WriteControlRegister();	
}

// ------------ STATUS / INFORMATION FUNCTIONS -------------------
/*
 * Return actual frequency programmed
 */
float AD9833_MCP41010 :: GetActualProgrammedFrequency ( Registers reg ) {
	float frequency = reg == REG0 ? frequency0 : frequency1;
	int32_t freqWord = (uint32_t)((frequency * pow2_28) / (float)refFrequency) & 0x0FFFFFFFUL;
	return (float)freqWord * (float)refFrequency / (float)pow2_28;
}

/*
 * Return actual phase programmed
 */
float AD9833_MCP41010 :: GetActualProgrammedPhase ( Registers reg ) {
	float phase = reg == REG0 ? phase0 : phase1;
	uint16_t phaseVal = (uint16_t)(BITS_PER_DEG * phase) & 0x0FFF;
	return (float)phaseVal / BITS_PER_DEG;
}

/*
 * Return frequency resolution
 */
float AD9833_MCP41010 :: GetResolution ( void ) {
	return (float)refFrequency / (float)pow2_28;
}

// --------------------- PRIVATE FUNCTIONS --------------------------

/*
 * Write control register. Setup register based on defined states
 */
void AD9833_MCP41010 :: WriteControlRegister ( void ) {
	uint16_t waveForm;
	// TODO: can speed things up by keeping a writeReg0 and writeReg1
	// that presets all bits during the various setup function calls
	// rather than setting flags. Then we could just call WriteFncRegister
	// directly.
	if ( activeFreq == REG0 ) {
		waveForm = waveForm0;
		waveForm &= ~FREQ1_OUTPUT_REG;
	}
	else {
		waveForm = waveForm1;
		waveForm |= FREQ1_OUTPUT_REG;
	}
	if ( activePhase == REG0 )
		waveForm &= ~PHASE1_OUTPUT_REG;
	else
		waveForm |= PHASE1_OUTPUT_REG;
	if ( outputEnabled )
		waveForm &= ~RESET_CMD;
	else
		waveForm |= RESET_CMD;
	if ( DacDisabled )
		waveForm |= DISABLE_DAC;
	else
		waveForm &= ~DISABLE_DAC;
	if ( IntClkDisabled )
		waveForm |= DISABLE_INT_CLK;
	else
		waveForm &= ~DISABLE_INT_CLK;

	WriteFncRegister ( waveForm );
}

void AD9833_MCP41010 :: WriteFncRegister ( int16_t dat ) {
	/*
	 * We set the mode here, because other hardware may be doing SPI also
	 */
	SPI.setDataMode(SPI_MODE2);

	/* Improve overall switching speed
	 * Note, the times are for this function call, not the write.
	 * digitalWrite(FNCpin)			~ 17.6 usec
	 * digitalWriteFast2(FNC_PIN)	~  8.8 usec
	 */
	WRITE_FNCPIN(LOW);		// FNCpin low to write to AD9833

	//delayMicroseconds(2);	// Some delay may be needed

	// TODO: Are we running at the highest clock rate?
	SPI.transfer(highByte(dat));	// Transmit 16 bits 8 bits at a time
	SPI.transfer(lowByte(dat));

	WRITE_FNCPIN(HIGH);		// Write done
}

void AD9833_MCP41010 :: WriteMcpRegister ( int8_t msb, int8_t lsb ) {
  if (CSpin != PIN_NOT_USED)
  {
  	/*
  	 * We set the mode here, because other hardware may be doing SPI also
  	 */
  	SPI.setDataMode(SPI_MODE2);
  
  	WRITE_CSPIN(LOW);		// FNCpin low to write to AD9833
  
  	//delayMicroseconds(2);	// Some delay may be needed
  
  	SPI.transfer(msb);	// Transmit 16 bits 8 bits at a time
  	SPI.transfer(lsb);
  
  	WRITE_CSPIN(HIGH);		// Write done
  }
}

LCD_ST7567.h

C Header File
// Fast ST7567 128x64 LCD graphics library (with frambuffer)
// (C) 2020 by Pawel A. Hernik

/*
 128x64 ST7567 connections in SPI mode (only 5-6 wires between LCD and MCU):

 #01 LED  -> D6, GND or any pin via resistor
 #02 RST  -> D9 or any pin
 #03 CS   -> D10 or any pin
 #04 DC   -> D8 or any pin
 #05 SCK  -> D13/SCK
 #06 SDI  -> D11/MOSI
 #07 3V3  -> VCC (3.3V)
 #08 GND  -> GND
*/

#ifndef _LCD_ST7567_H
#define _LCD_ST7567_H

// ------------
// remove define for software SPI
#define USE_HW_SPI
// ------------

#include <Arduino.h>
#include <avr/pgmspace.h>

#define SCR_WD  128
#define SCR_HT  64
#define SCR_HT8 8  // SCR_HT/8

#define ALIGN_LEFT    0
#define ALIGN_RIGHT  -1 
#define ALIGN_CENTER -2

#define SET 1
#define CLR 0
#define XOR 2

struct _propFont
{
  const uint8_t* font;
  int8_t xSize;
  uint8_t ySize;
  uint8_t firstCh;
  uint8_t lastCh;
  uint8_t minCharWd;
  uint8_t minDigitWd;
};

// ---------------------------------
class LCD_ST7567 {
public:
  LCD_ST7567(uint8_t dc, uint8_t rst, uint8_t cs);
  LCD_ST7567(uint8_t dc, uint8_t rst, uint8_t cs, uint8_t sdi, uint8_t clk);

  inline void sendSPI(uint8_t v) __attribute__((always_inline)); // costs about 350B of flash
  inline void sendCmd(uint8_t cmd);
  inline void sendData(uint8_t data);
  void init(int contrast=7);
  void begin() { init(); }
  void initCmds();
  void display();
  void copy(uint8_t x, uint8_t y8, uint8_t wd, uint8_t ht8);
  void gotoXY(byte x, byte y);
  void sleep(bool mode=true);
  void setContrast(byte val);
  void setScroll(byte val);
  void displayInvert(bool mode);
  void displayOn(bool mode);
  void displayMode(byte val);
  void setRotation(int mode);

  void cls();
  void clearDisplay() { cls(); }
  void drawPixel(uint8_t x, uint8_t y, uint8_t col);
  void drawLine(int8_t x0, int8_t y0, int8_t x1, int8_t y1, uint8_t col);
  void drawLineH(uint8_t x0, uint8_t x1, uint8_t y, uint8_t col);
  void drawLineV(uint8_t x,  uint8_t y0, uint8_t y1, uint8_t col);
  void drawLineVfast(uint8_t x, uint8_t y0, uint8_t y1, uint8_t col);
  void drawLineVfastD(uint8_t x, uint8_t y0, uint8_t y1, uint8_t col);
  void drawLineHfast(uint8_t x0, uint8_t x1, uint8_t y, uint8_t col);
  void drawLineHfastD(uint8_t x0, uint8_t x1, uint8_t y, uint8_t col);
  void drawRect(uint8_t x0, uint8_t y0, uint8_t w, uint8_t h, uint8_t col);
  void drawRectD(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t col);
  void fillRect(uint8_t x0, uint8_t y0, uint8_t w, uint8_t h, uint8_t col);
  void fillRectD(uint8_t x0, uint8_t y0, uint8_t w, uint8_t h, uint8_t col);
  void drawCircle(uint8_t x0, uint8_t y0, uint8_t radius, uint8_t col);
  void fillCircle(uint8_t x0, uint8_t y0, uint8_t r, uint8_t col);
  void fillCircleD(uint8_t x0, uint8_t y0, uint8_t r, uint8_t col);
  void drawTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color);
  void fillTriangle(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color);
  void fillTriangleD(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t x2, int16_t y2, uint16_t color);
  void setDither(int8_t s);
  int drawBitmap(const uint8_t *bmp, int x, uint8_t y, uint8_t w, uint8_t h);
  int drawBitmap(const uint8_t *bmp, int x, uint8_t y);

  void setFont(const uint8_t* f);
  void setSpacing(int s);
  void setCR(uint8_t _cr) { cr = _cr; }
  void setInvert(uint8_t _inv) { invertCh = _inv; }
  void setFontMinWd(uint8_t wd) { cfont.minCharWd = wd; }
  void setCharMinWd(uint8_t wd) { cfont.minCharWd = wd; }
  void setDigitMinWd(uint8_t wd) { cfont.minDigitWd = wd; }
  int printChar(int xpos, int ypos, unsigned char c);
  int printStr(int xpos, int ypos, char *str);
  int charWidth(uint8_t _ch, bool last=true);
  int fontHeight();
  int strWidth(char *txt);
  unsigned char convertPolish(unsigned char _c);
  static bool isNumber(uint8_t ch);
  static bool isNumberExt(uint8_t ch);
  void setIsNumberFun(bool (*fun)(uint8_t)) { isNumberFun=fun; }
  
public:
  static byte scr[SCR_WD*SCR_HT8];
  byte scrWd = SCR_WD;
  byte scrHt = SCR_HT8;
  uint8_t dcPin, csPin, rstPin;
  uint8_t sdiPin, clkPin;
  int8_t rotation;

  static byte ystab[8];
  static byte yetab[8];
  static byte pattern[4];
  static const byte ditherTab[4*17];

//private:
  bool (*isNumberFun)(uint8_t ch);
  _propFont  cfont;
  uint8_t cr;  // carriage return mode for printStr
  uint8_t dualChar;
  uint8_t invertCh;
  uint8_t spacing = 1;	
};
#endif

LCD_ST7567.cpp

C/C++
No preview (download only).

c64enh_font.h

C Header File
#ifndef c64enh_font_h
#define c64enh_font_h

const uint8_t c64enh[] PROGMEM =
{
-7, 8, 32, '~'+1+18,   // -width, height, firstChar, lastChar
        0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,  //  
        0x02, 0x5F, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00,  // !
        0x06, 0x07, 0x07, 0x00, 0x00, 0x07, 0x07, 0x00,  // "
        0x07, 0x14, 0x7F, 0x7F, 0x14, 0x7F, 0x7F, 0x14,  // #
        0x06, 0x24, 0x2E, 0x6B, 0x6B, 0x3A, 0x12, 0x00,  // $
        0x06, 0x63, 0x73, 0x18, 0x0C, 0x67, 0x63, 0x00,  // %
        0x07, 0x32, 0x7F, 0x4D, 0x4D, 0x77, 0x72, 0x50,  // &
        0x04, 0x06, 0x09, 0x09, 0x06, 0x00, 0x00, 0x00,  // '
        0x04, 0x1C, 0x3E, 0x63, 0x41, 0x00, 0x00, 0x00,  // (
        0x04, 0x41, 0x63, 0x3E, 0x1C, 0x00, 0x00, 0x00,  // )
        0x07, 0x08, 0x2A, 0x3E, 0x1C, 0x3E, 0x2A, 0x08,  // *
        0x06, 0x08, 0x08, 0x3E, 0x3E, 0x08, 0x08, 0x00,  // +
        0x03, 0x80, 0xE0, 0x60, 0x00, 0x00, 0x00, 0x00,  // ,
        0x06, 0x08, 0x08, 0x08, 0x08, 0x08, 0x08, 0x00,  // -
        0x02, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00,  // .
        0x07, 0x40, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x02,  // /
        0x06, 0x3E, 0x7F, 0x49, 0x45, 0x7F, 0x3E, 0x00,  // 0
        0x06, 0x40, 0x44, 0x7F, 0x7F, 0x40, 0x40, 0x00,  // 1
        0x06, 0x62, 0x73, 0x59, 0x49, 0x4F, 0x46, 0x00,  // 2
        0x06, 0x22, 0x63, 0x49, 0x49, 0x7F, 0x36, 0x00,  // 3
        0x07, 0x18, 0x1C, 0x16, 0x13, 0x7F, 0x7F, 0x10,  // 4
        0x06, 0x27, 0x67, 0x45, 0x45, 0x7D, 0x39, 0x00,  // 5
        0x06, 0x3E, 0x7F, 0x49, 0x49, 0x7B, 0x32, 0x00,  // 6
        0x06, 0x01, 0x01, 0x79, 0x7D, 0x07, 0x03, 0x00,  // 7
        0x06, 0x36, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00,  // 8
        0x06, 0x26, 0x6F, 0x49, 0x49, 0x7F, 0x3E, 0x00,  // 9
        0x02, 0x24, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00,  // :
        0x03, 0x80, 0xC4, 0x44, 0x00, 0x00, 0x00, 0x00,  // ;
        0x06, 0x10, 0x30, 0x7F, 0x7F, 0x30, 0x10, 0x00,  // < min
        0x06, 0x14, 0x14, 0x14, 0x14, 0x14, 0x14, 0x00,  // =
        0x06, 0x04, 0x06, 0x7F, 0x7F, 0x06, 0x04, 0x00,  // > max
        0x06, 0x02, 0x03, 0x51, 0x59, 0x0F, 0x06, 0x00,  // ?
        0x06, 0x3E, 0x7F, 0x41, 0x4D, 0x6F, 0x2E, 0x00,  // @
        0x06, 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0x7C, 0x00,  // A
        0x06, 0x7F, 0x7F, 0x49, 0x49, 0x7F, 0x36, 0x00,  // B
        0x06, 0x3E, 0x7F, 0x41, 0x41, 0x63, 0x22, 0x00,  // C
        0x06, 0x7F, 0x7F, 0x41, 0x41, 0x7F, 0x3E, 0x00,  // D
        0x06, 0x7F, 0x7F, 0x49, 0x49, 0x41, 0x41, 0x00,  // E
        0x06, 0x7F, 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00,  // F
        0x06, 0x3E, 0x7F, 0x41, 0x49, 0x7B, 0x3A, 0x00,  // G
        0x06, 0x7F, 0x7F, 0x08, 0x08, 0x7F, 0x7F, 0x00,  // H
        0x04, 0x41, 0x7F, 0x7F, 0x41, 0x00, 0x00, 0x00,  // I
        0x06, 0x20, 0x60, 0x41, 0x7F, 0x3F, 0x01, 0x00,  // J
        0x06, 0x7F, 0x7F, 0x1C, 0x36, 0x63, 0x41, 0x00,  // K
        0x06, 0x7F, 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00,  // L
        0x07, 0x7F, 0x7F, 0x06, 0x0C, 0x06, 0x7F, 0x7F,  // M
        0x06, 0x7F, 0x7F, 0x0C, 0x18, 0x7F, 0x7F, 0x00,  // N
        0x06, 0x3E, 0x7F, 0x41, 0x41, 0x7F, 0x3E, 0x00,  // O
        0x06, 0x7F, 0x7F, 0x09, 0x09, 0x0F, 0x06, 0x00,  // P
        0x06, 0x1E, 0x3F, 0x21, 0x61, 0x7F, 0x5E, 0x00,  // Q
        0x06, 0x7F, 0x7F, 0x19, 0x39, 0x6F, 0x46, 0x00,  // R
        0x06, 0x26, 0x6F, 0x49, 0x49, 0x7B, 0x32, 0x00,  // S
        0x06, 0x01, 0x01, 0x7F, 0x7F, 0x01, 0x01, 0x00,  // T
        0x06, 0x3F, 0x7F, 0x40, 0x40, 0x7F, 0x3F, 0x00,  // U
        0x06, 0x1F, 0x3F, 0x60, 0x60, 0x3F, 0x1F, 0x00,  // V
        0x07, 0x7F, 0x7F, 0x30, 0x18, 0x30, 0x7F, 0x7F,  // W
        0x06, 0x41, 0x63, 0x3E, 0x3E, 0x63, 0x41, 0x00,  // X
        0x06, 0x07, 0x0F, 0x78, 0x78, 0x0F, 0x07, 0x00,  // Y
        0x06, 0x61, 0x71, 0x59, 0x4D, 0x47, 0x43, 0x00,  // Z
        0x07, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xFF, 0xFF,  // [
        0x07, 0x03, 0x03, 0x03, 0x03, 0x03, 0xFF, 0xFF,  // BackSlash
        0x06, 0xC0, 0x60, 0x30, 0x18, 0x0C, 0x06, 0x03,  // ]
        0x06, 0x03, 0x06, 0x0C, 0x18, 0x30, 0x60, 0xC0,  // ^
        0x07, 0xC0, 0x40, 0x70, 0x3C, 0x0E, 0x02, 0x03,  // _
        0x07, 0x03, 0x02, 0x0E, 0x3C, 0x70, 0x40, 0xC0,  // `
        0x06, 0x20, 0x74, 0x54, 0x54, 0x7C, 0x78, 0x00,  // a
        0x06, 0x7F, 0x7F, 0x44, 0x44, 0x7C, 0x38, 0x00,  // b
        0x05, 0x38, 0x7C, 0x44, 0x44, 0x44, 0x00, 0x00,  // c
        0x06, 0x38, 0x7C, 0x44, 0x44, 0x7F, 0x7F, 0x00,  // d
        0x06, 0x38, 0x7C, 0x54, 0x54, 0x5C, 0x18, 0x00,  // e
        0x05, 0x08, 0x7E, 0x7F, 0x09, 0x09, 0x00, 0x00,  // f
        0x06, 0x98, 0xBC, 0xA4, 0xA4, 0xFC, 0x7C, 0x00,  // g
        0x06, 0x7F, 0x7F, 0x04, 0x04, 0x7C, 0x78, 0x00,  // h
        0x04, 0x44, 0x7D, 0x7D, 0x40, 0x00, 0x00, 0x00,  // i
        0x05, 0x80, 0x80, 0x80, 0xFD, 0x7D, 0x00, 0x00,  // j
        0x06, 0x7F, 0x7F, 0x10, 0x38, 0x6C, 0x44, 0x00,  // k
        0x04, 0x41, 0x7F, 0x7F, 0x40, 0x00, 0x00, 0x00,  // l
        0x07, 0x78, 0x7C, 0x0C, 0x38, 0x0C, 0x7C, 0x78,  // m
        0x06, 0x7C, 0x7C, 0x04, 0x04, 0x7C, 0x78, 0x00,  // n
        0x06, 0x38, 0x7C, 0x44, 0x44, 0x7C, 0x38, 0x00,  // o
        0x06, 0xFC, 0xFC, 0x24, 0x24, 0x3C, 0x18, 0x00,  // p
        0x06, 0x18, 0x3C, 0x24, 0x24, 0xFC, 0xFC, 0x00,  // q
        0x06, 0x7C, 0x7C, 0x04, 0x04, 0x0C, 0x08, 0x00,  // r
        0x06, 0x48, 0x5C, 0x54, 0x54, 0x74, 0x24, 0x00,  // s
        0x05, 0x04, 0x3F, 0x7F, 0x44, 0x44, 0x00, 0x00,  // t
        0x06, 0x3C, 0x7C, 0x40, 0x40, 0x7C, 0x7C, 0x00,  // u
        0x06, 0x1C, 0x3C, 0x60, 0x60, 0x3C, 0x1C, 0x00,  // v
        0x07, 0x1C, 0x7C, 0x60, 0x38, 0x60, 0x7C, 0x1C,  // w
        0x06, 0x44, 0x6C, 0x38, 0x38, 0x6C, 0x44, 0x00,  // x
        0x06, 0x9C, 0xBC, 0xA0, 0xE0, 0x7C, 0x3C, 0x00,  // y
        0x06, 0x44, 0x64, 0x74, 0x5C, 0x4C, 0x44, 0x00,  // z
        0x05, 0x08, 0x3E, 0x7F, 0x41, 0x41, 0x00, 0x00,  // {
        0x02, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00,  // |
        0x05, 0x41, 0x41, 0x7F, 0x3E, 0x08, 0x00, 0x00,  // }
        0x05, 0x10, 0x18, 0x18, 0x18, 0x08, 0x00, 0x00,  // ~
        0x02, 0x7E, 0x7E, 0x00, 0x00, 0x00, 0x00, 0x00,   // 

        0x07, 0x7C, 0x7E, 0x13, 0x13, 0x7E, 0xFC, 0x80,  // 
        0x06, 0x3C, 0x7E, 0x46, 0x43, 0x66, 0x24, 0x00,  // !
        0x07, 0x7F, 0x7F, 0x49, 0x49, 0x41, 0xC1, 0x80,  // "
        0x06, 0x7F, 0x7F, 0x48, 0x44, 0x40, 0x40, 0x00,  // #
        0x06, 0x7E, 0x7E, 0x0C, 0x19, 0x7E, 0x7E, 0x00,  // $
        0x06, 0x3C, 0x7E, 0x46, 0x43, 0x7E, 0x3C, 0x00,  // %
        0x06, 0x24, 0x6E, 0x4A, 0x4E, 0x7B, 0x30, 0x00,  // &
        0x06, 0x62, 0x76, 0x5A, 0x4F, 0x46, 0x42, 0x00,  // '
        0x06, 0x69, 0x79, 0x59, 0x4D, 0x4F, 0x4B, 0x00,  // (

        0x07, 0x20, 0x74, 0x54, 0x54, 0x7C, 0xF8, 0x80,  // 
        0x05, 0x38, 0x7C, 0x44, 0x46, 0x45, 0x00, 0x00,  // *
        0x06, 0x38, 0x7C, 0x54, 0x54, 0xDC, 0x98, 0x00,  // +
        0x04, 0x51, 0x7F, 0x7F, 0x44, 0x00, 0x00, 0x00,  // ,
        0x06, 0x7C, 0x7C, 0x04, 0x06, 0x7D, 0x78, 0x00,  // -
        0x06, 0x38, 0x7C, 0x44, 0x46, 0x7D, 0x38, 0x00,  // .
        0x06, 0x48, 0x5C, 0x54, 0x56, 0x75, 0x24, 0x00,  // /
        0x06, 0x44, 0x64, 0x76, 0x5D, 0x4C, 0x44, 0x00,  // 0
        0x06, 0x44, 0x64, 0x75, 0x5D, 0x4C, 0x44, 0x00,  // 1

        0x05, 0x00, 0x7E, 0x43, 0x43, 0x7E, 0x00, 0x00,  // = batt

        0x07, 0x80, 0x80, 0x80, 0xFF, 0x01, 0x01, 0xFF,  // [
        0x07, 0xC0, 0x30, 0x0C, 0x03, 0x0C, 0x30, 0xC0,  // BackSlash
        0x07, 0x80, 0x70, 0x0E, 0x01, 0x0E, 0x70, 0x80,  // ]
};

#endif

Credits

John Bradnam

John Bradnam

145 projects • 177 followers

Comments