Saturday 26 June 2021

Arduino Clock






 Initial code using a 120x120 TFT display and a realtime clock module.






/*****************************************************************************
   The ST7789 IPS SPI display.is a 3.3v Device, do not use with a 5v Arduino
    unless you level shift.

    Display          |    Arduino
    -------------------------------------------
    VCC              |    3.3v
    GND              |    Gnd
    SCL              |    Pin 13 (SCK)
    SDA              |    Pin 11 (MOSI)
    RES              |    Pin 9
    DC               |    Pin 8
    BLK              |    Leave disconnected
 *****************************************************************************/

#include <Adafruit_GFX.h>
#include <Arduino_ST7789.h>
#include <SPI.h>
#include "RTClib.h"

#define TFT_DC    8
#define TFT_RST   9
#define TFT_MOSI  11
#define TFT_SCLK  13
#define DAYLIGHT_SAVING_PIN  5

// Clock Variables
int _Day, _OldDay = 0;
int _Hours, _OldHours = 0;
int _Minutes, _OldMinutes = 0;
int _DaylightSaving = 0;
int _xH, _yH, _OldxH, _OldyH = 0;
int _xM, _yM, _OldxM, _OldyM = 0;

Arduino_ST7789 tft = Arduino_ST7789(TFT_DC, TFT_RST, TFT_MOSI, TFT_SCLK);   //No CS pin
RTC_DS3231 rtc;

void setup(void)
{
  pinMode(DAYLIGHT_SAVING_PIN, INPUT);                            // Setup daylight saving sw port
  digitalWrite(DAYLIGHT_SAVING_PIN, HIGH);

  // initialize the realtime clock
  rtc.begin();

  // initialize the display
  tft.init(240, 240);
  tft.setRotation(3);
  tft.fillScreen(BLACK);
  tft.setTextSize(2);
  DrawClockFace();
  DrawClockNumbers();
}

void loop()
{
  DateTime now = rtc.now();
  _Day = now.day();
  _Hours = now.hour();
  _Minutes = now.minute();

  if (digitalRead(DAYLIGHT_SAVING_PIN) == 0)
    delay(50);
  if (digitalRead(DAYLIGHT_SAVING_PIN) == 0)
    _DaylightSaving = !_DaylightSaving;

  if (_DaylightSaving == 1)
    _Hours++;

  if ((_Hours != _OldHours) || (_Minutes != _OldMinutes))
  {
    UpdateHands();
    DrawClockNumbers();
    _OldHours = _Hours;
    _OldMinutes = _Minutes;
  }

  if (_Day != _OldDay)
  {
    UpdateDate();
    _OldDay = _Day;
  }

  delay(1000);
}

void UpdateHands()
{
  //x = x0 + r * Math.Cos(theta * Math.PI / 180);
  //y = y0 + r * Math.Sin(theta * Math.PI / 180);

  //Hours hands (erase old hours hand then redraw new one)
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _OldxH, _OldyH, BLACK);

  _xH = (tft.width() / 2) + 70 * cos((((30 * _Hours) + (0.5 * _Minutes)) - 90) * PI / 180);
  _yH = (tft.height() / 2) + 70 * sin((((30 * _Hours) + (0.5 * _Minutes)) - 90) * PI / 180);
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _xH, _yH, WHITE);
  _OldyH = _yH;
  _OldxH = _xH;

  //Minutes hands (erase old minutes hand then redraw new one)
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _OldxM, _OldyM, BLACK);

  _xM = (tft.width() / 2) + 95 * cos(((6 * _Minutes) - 90) * PI / 180);
  _yM = (tft.height() / 2) + 95 * sin(((6 * _Minutes) - 90) * PI / 180);
  tft.drawLine((tft.width() / 2), (tft.height() / 2), _xM, _yM, WHITE);
  _OldxM = _xM;
  _OldyM = _yM;

  tft.fillCircle((tft.width() / 2), (tft.height() / 2), 3, WHITE);
}

void UpdateDate()
{
  tft.setTextColor(BLACK);
  tft.setCursor(210, 220);
  tft.print(_OldDay);

  tft.setTextColor(GREEN);
  tft.setCursor(210, 220);
  tft.print(_Day);

  tft.setTextColor(WHITE);
}

void DrawClockFace()
{
  int _x1, _y1 = 0;

  //x = x0 + r * Math.Cos(theta * Math.PI / 180);
  //y = y0 + r * Math.Sin(theta * Math.PI / 180);

  tft.drawCircle((tft.width() / 2), (tft.height() / 2), 119, WHITE);
  tft.drawCircle((tft.width() / 2), (tft.height() / 2), 118, WHITE);

  // Minute marks
  for (int x = 0; x <= 60; x++)
  {
    _x1 = (tft.width() / 2) + 118 * cos(((6 * x) - 90) * PI / 180);
    _y1 = (tft.height() / 2) + 118 * sin(((6 * x) - 90) * PI / 180);
    tft.drawLine((tft.width() / 2), (tft.height() / 2), _y1, _x1, WHITE);
  }
  tft.fillCircle((tft.width() / 2), (tft.height() / 2), 110, BLACK);

  // 5 minute marks
  for (int x = 0; x <= 12; x++)
  {
    _x1 = (tft.width() / 2) + 118 * cos(((30 * x) - 90) * PI / 180);
    _y1 = (tft.height() / 2) + 118 * sin(((30 * x) - 90) * PI / 180);
    tft.drawLine((tft.width() / 2), (tft.height() / 2), _y1, _x1, WHITE);
  }
  tft.fillCircle((tft.width() / 2), (tft.height() / 2), 105, BLACK);
}

void DrawClockNumbers()
{
  tft.setTextColor(WHITE);
  tft.setCursor(163, 32);
  tft.print("1");

  tft.setCursor(196, 65);
  tft.print("2");

  tft.setCursor(210, 113);
  tft.print("3");

  tft.setCursor(199, 160);
  tft.print("4");

  tft.setCursor(164, 193);
  tft.print("5");

  tft.setCursor(115, 207);
  tft.print("6");

  tft.setCursor(70, 195);
  tft.print("7");

  tft.setCursor(35, 160);
  tft.print("8");

  tft.setCursor(20, 113);
  tft.print("9");

  tft.setCursor(33, 66);
  tft.print("10");

  tft.setCursor(66, 34);
  tft.print("11");

  tft.setCursor(109, 20);
  tft.print("12");
}

Thursday 14 January 2021

Simple SSB Transceiver

 Please see the YouTube channel for details: https://www.youtube.com/channel/UCSNPW3_gzuMJcX_ErBZTv2g




AF Amp






IF Amps










Mixers







BPF




Mic Amp





AGC/Limiter


Relays




RF PA Experiments

1st iteration





2nd iteration






****************************************************************
Final Arduino code

#include <LiquidCrystal_I2C.h>
#include <si5351.h>

static const long bandStart = 3500000;          // start of VFO range
static const long bandEnd =   3900000;          // end of VFO range
static const long bandInit =  3690000;          // where to initially set the frequency
volatile long freq = 3690000;                   // the current freq
volatile long oldfreq = 0;                      // the previous freq
volatile long rx_LSB_BFO_freq = 8997200;           // High side injection thus SB inversion.
volatile long rx_USB_BFO_freq = 8998700;           // High side injection thus SB inversion.
volatile long tx_LSB_BFO_freq = 8996300;           // High side injection thus SB inversion.
volatile long tx_USB_BFO_freq = 8999800;           // High side injection thus SB inversion.
volatile long radix = 1000;                     // How much to change the frequency by, clicking the Up Down switches
volatile long oldradix = 0;                     // the previous radix
volatile int mode = 0;                          // the current mode (0=LSB, 1=USB)
volatile int oldmode = 0;                       // the previous mode (0=LSB, 1=USB)
volatile int TX = 0;                            // 0=RX, 1=TX
volatile int oldTX = 1;                         // the old TX
unsigned int encoderA, encoderB, encoderC = 1;  // rotary encoder variables

// Rotary encoder pins and other inputs
static const int rotAPin = 2;
static const int rotBPin = 3;
static const int radixPin = 4;
static const int modePin = 5;
static const int PTTPin = 6;

// Instantiate the Objects
LiquidCrystal_I2C lcd(0x3F, 16, 2);              // 3F the address of the LCD
Si5351 si5351;


void setup()
{
  // Set up I/O pins
  pinMode(rotAPin, INPUT);
  digitalWrite(rotAPin, HIGH);                    // internal pull-up enabled
  pinMode(rotBPin, INPUT);
  digitalWrite(rotBPin, HIGH);                    // internal pull-up enabled
  pinMode(radixPin, INPUT);
  digitalWrite(radixPin, HIGH);                   // internal pull-up enabled
  pinMode(modePin, INPUT);
  digitalWrite(modePin, HIGH);                    // internal pull-up enabled
  pinMode(PTTPin, INPUT);
  digitalWrite(PTTPin, LOW);                      // internal pull-up disabled

  // Initialize the display
  lcd.begin();
  lcd.backlight();
  lcd.noCursor();

  // Initialize the DDS
  si5351.init(SI5351_CRYSTAL_LOAD_8PF, 0, 62100);         // 62100 is the specific calibration factor for this Si5351 board
  si5351.drive_strength(SI5351_CLK0, SI5351_DRIVE_2MA);   // 2 mA for HB mixers
  si5351.drive_strength(SI5351_CLK2, SI5351_DRIVE_2MA);   // 2 mA for HB mixers
}

void loop()
{
  CheckEncoder();
  CheckRadixSwitch();
  CheckModeSwitch();
  CheckPTTPin();
}

void CheckEncoder()
{
  byte encoderA = digitalRead(rotAPin);
  byte encoderB = digitalRead(rotBPin);

  if ((encoderA == HIGH) && (encoderC == LOW))
  {
    if (encoderB == HIGH)
      // Decrease frequency
      freq = constrain(freq - radix, bandStart, bandEnd);
    else
      // Increase frequency
      freq = constrain(freq + radix, bandStart, bandEnd);
  }
  encoderC = encoderA;

  if (freq != oldfreq)
  {
    UpdateDisplay();
    SendFrequency();
    oldfreq = freq;
  }
}

void CheckRadixSwitch()
{
  if (digitalRead(radixPin) == 0)
  {
    radix = radix / 10;
    if (radix < 1)
      radix = 1000;
    delay(200);
  }

  if (radix != oldradix)
  {
    UpdateRadixDisplay();
    oldradix = radix;
  }
}

void CheckModeSwitch()
{
  if (digitalRead(modePin) == 0)
    mode = 0;                                 // 0=LSB
  if (digitalRead(modePin) == 1)
    mode = 1;                                 // 1=USB

  if (mode != oldmode)
  {
    UpdateDisplay();
    SendFrequency();
    oldmode = mode;
  }
}

void CheckPTTPin()
{
  TX = digitalRead(PTTPin);
  if (TX != oldTX)
  {
    UpdateDisplay();
    SendFrequency();
    oldTX = TX;
  }
}

void UpdateDisplay()
{
  // freq
  lcd.setCursor(0, 0);
  lcd.print(freq);
  // mode
  lcd.setCursor(0, 1);
  if (mode == 0)
    lcd.print("LSB");
  else
    lcd.print("USB");
  lcd.setCursor(10, 1);
  // PTT
  lcd.setCursor(4, 1);
  if (TX == 1)
    lcd.print("TX");
  if (TX == 0)
    lcd.print("RX");
  // callsign
  lcd.setCursor(10, 1);
  lcd.print("ZL2CTM");
}

void UpdateRadixDisplay()
{
  // radix
  if (radix == 1000)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(9, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
  if (radix == 100)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(10, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
  if (radix == 10)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(11, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
  if (radix == 1)
  {
    lcd.setCursor(9, 0);
    lcd.print("      ");
    lcd.setCursor(12, 0);
    lcd.print(radix);
    lcd.setCursor(14, 0);
    lcd.print("Hz");
  }
}

void SendFrequency()
{
  if (mode == 0)                                                          // LSB
  {
    if (TX == 1)                                                          // Transmit
    {
      si5351.set_freq(((tx_LSB_BFO_freq + freq) * 100ULL), SI5351_CLK2);     // VFO
      si5351.set_freq((tx_LSB_BFO_freq * 100ULL), SI5351_CLK0);              // BFO
    }
    else                                                                  // Receive
    {
      si5351.set_freq(((rx_LSB_BFO_freq + freq) * 100ULL), SI5351_CLK0);     // VFO
      si5351.set_freq((rx_LSB_BFO_freq * 100ULL), SI5351_CLK2);              // BFO
    }
  }

  if (mode == 1)                                                          // USB
  {
    if (TX == 1)                                                          // Transmit
    {
      si5351.set_freq(((tx_USB_BFO_freq + freq) * 100ULL), SI5351_CLK2);     // VFO
      si5351.set_freq((tx_USB_BFO_freq * 100ULL), SI5351_CLK0);              // BFO
    }
    else                                                                  // Receive
    {
      si5351.set_freq(((rx_USB_BFO_freq + freq) * 100ULL), SI5351_CLK0);     // VFO
      si5351.set_freq((rx_USB_BFO_freq * 100ULL), SI5351_CLK2);              // BFO
    }
  }
}

Monday 11 January 2021