Hardware components | ||||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 2 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
| × | 1 | ||||
Software apps and online services | ||||||
| ||||||
Hand tools and fabrication machines | ||||||
| ||||||
|
Recently I built a function generator based around a AD9833 module. In that project I used the blue variant. This is the basic module and just holds the AD9833 and 25MHz crystal clock. At the same time I also bought the green variant but I couldn't find any schematics or code examples on how to use it. It also includes a MCP41010 digital potentiometer and AD8051 high speed, rail-to-rail amplifier.
I had some OPEN-SMART 1.8 Inch 128x64 SPI Monochrome LCD displays that I brought from Ali-Express some time ago. This seemed like the ideal project to figure out how to use both these devices.
One of the issues I had when using my first AD9833 Function Generator was when you changed the waveform, the amplitude would change. So in this version I wanted to control the digital potentiometer in such a way as to keep the peak-to-peak value of the output waveform constant no matter which waveform was being outputted.
To achieve this, there is a table of settings in the software for each 0.1V step of output for each waveform. I limited the output to a maximum of 1V.
SchematicAs the LCD display can only accept voltages of 3.3V, I decided to power the final unit using a single 18650 3.7V to 4.2V lithium battery with a TP4056 charging module. Because the battery voltage can drop down to 3.7V when it discharges, I used a low drop-out voltage (LDO) regulator to supply the unit.
The microprocessor is a ATtiny3216. Two rotary encoders are used. One controls the frequency while the other controls the amplitude of the output. All contacts are hardware de-bounced using a RC network and Schmitt trigger.
3D printingAll 3D printing is done using a 0.2mm layer height. You will need to rotate the parts in your slicer software so they will sit flat on the build plate. No supports are required.
When printing "Box - Text V4.stl", switch to a contrasting color at the start of layer 4. Use double-sided tape or glue to join "Box - Text V4.stl" to "Box - Front Half V4.stl".
LayoutSMD components were used where possible. The two rotary encoders are soldered to the board which is in turn screwed onto the front panel. The LCD display also plugs into the board simplifying any external wiring. The only wire needed is the power wires and the 6 wire cable that goes to the AD9833 board.
The AD9833 board comes with a SMA female connector. You need to solder this onto the board. The board is then fixed to the front panel.
The Eagle files have been included should you wish to have the board commercially made or you can do as I did and make it yourself. I used the Toner method.
AssemblyStart by adding the SMD components. I find it easier to use solder paste rather than use solder from a reel when soldering SMD components. I used my SMD Hot Plate to reflow the solder paste.
Add the links if your board is single sided.
Add the 4 pin and 6 pin straight headers and the KF2510 2 pin male right angle connector to the copper side of the PCB.
Add the two rotary encoders.
Add a 8 pin 5mm straight female header to the PCB. (Note standard headers are usually 8.5mm).
3D print "LCD Support V4.stl" and glue the spacers to the PCB
Plug in the display.
Screw on the AD9833 module to the front panel using two M2.5 4mm screws.
Screw on the main PCB to the front panel using four M2 6mm screws.
Connect the two boards using a 10cm 6 way Dupont cable. I removed the single shrouds and replaced them with six pin shrouds.
Add the power switch. I connected the switch to a two pin male Dupont connector so that it I could unplug the front panel from the back panel.
Hammer in two M2.5 3.5 x 4 brass inserts into the back panel to hold the battery
Screw on the ABS battery holder using two 4mm M2.5 screws.
Solder the battery and output wires to the TP4056 Type C battery charger module and fit into its holder.
Instead of soldering the positive lead to the switch on the front panel, I glued on a two pin straight female Dupont socket so the wires to the switch can be unplugged.
Close the clam-shell case using four 6mm M3 screws.
The ATtiny3216 is part of the new breed of ATtiny microprocessors. Unlike the earlier series such as the ATtiny85, the new breed use the RESET pin to program the CPU. To program it you need a UPDI programmer. I made one using a Arduino Nano. You can find complete build instructions at Create Your Own UPDI Programmer. It also contains the instructions for adding the megaTinyCore boards to your IDE.
Once the board has been installed in the IDE, select it from the Tools menu.
Select board, chip (ATtiny3216), clock speed (20MHz), millis()/micros() timer (TCD0) and the COM port that the Arduino Nano is connected to.
The Programmer needs to be set to jtag2updi (megaTinyCore).
Open the sketch and upload it to the ATtiny3216.
Using the function generatorThe function generator is controlled by two rotary encoders. Each rotary encoder shaft is a push switch.
Frequency rotary encoder
The current mode is shown on the last line. Pushing the knob will switch between the following modes:
- x1Hz - Turning the rotary encoder will increase or decrease the frequency by 1Hz
- x10Hz - Turning the rotary encoder will increase or decrease the frequency by 10Hz
- x100Hz - Turning the rotary encoder will increase or decrease the frequency by 1-100Hz
- x1kHz - Turning the rotary encoder will increase or decrease the frequency by 1, 000Hz
- x10kHz - Turning the rotary encoder will increase or decrease the frequency by 10, 000Hz
- x100kHz - Turning the rotary encoder will increase or decrease the frequency by 100, 000Hz
- x1MHz - Turning the rotary encoder will increase or decrease the frequency by 1, 000, 000Hz
- Waveform - Turning the rotary encoder will switch the waveform between SINE, TRIANGLE and SQUARE
- Backlight - Turning the rotary encoder will increase or decrease the backlight by 1%
Amplitude rotary encoder
Pushing the knob will switch turn on/off the output.
Rotating the rotary encoder will increase or decrease the amplitude by 0.1V
ConclusionHaving a digital potentiometer and high frequency amplifier is a nice addition to the AD9833 board. Unfortunately the resolution of the amplitude was disappointing. Even using 0.1V steps, it is impossible to output the exact voltages. Also it doesn't address the issue of attenuation of the output signal when frequencies exceed around 500kHz. But considering the module costs around $5, it might be asking a bit too much to expect more from it.
All-in-all, it was an interesting build and the interfacing with the AD9833 module and display module was successful. As for a piece of test equipment for the workshop, for audio work it is more than adequate.
/*---------------------------------------------------------------------
* 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.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.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
}
}
// 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
#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
Comments