表示器にoledを使った50MHz AM VFOである。50MHz AMを運用すると、周波数の連続可変は殆ど必要ない。そこで、チャンネル機能を含めたVFOを作った。特徴は、VFO/チャンネルの2モードで、共にRIT運用が可能。VFO/チャンネルはEEPROMに保存しいてる。電源off直前のVFO/チャンネルを保存してるので、電源on時の煩雑さは無い。VFO/チャンネルの切替は、STEPスイッチの長押しで行う。また、チャンネル周波数は、EEPROMに保存してるので、書き換え可能。
チャンネルモードの表示である。チャンネル切替は、エンコーダ操作。チャンネル周波数の初期値以外への変更は、RITスイッチを長押しし、VFOモードに移行して変更。
回路図
フォント
今回、追加したAMフォントである。labels.hを変更修正。
スケッチ
//////////////////////////////////////////////////////////////////////
// si5351a oled(128x32) VFO program ver.1.0
// Copyright(C)2019.JA2GQP.All rights reserved.
//
// 2021/7/24
// JA2GQP
//--------------------------------------------------------------------
// Function
// 1.VFO/Channel mode
// 2.STEP(10k,1k,50)
// 3.Automatic memory
// 4.Protection Operation At The Time Of Transmission
// 5.Automatic mode switching
// 6.S-Meter
//////////////////////////////////////////////////////////////////////
#include "src/Rotary.h" // http://www.buxtronix.net/2011/10/rotary-encoders-done-properly.html
#include "src/si5351.h" // https://github.com/etherkit/Si5351Arduino, v2.1.0
#include "src/SSD1306AsciiAvrI2c.h" // https://github.com/greiman/SSD1306Ascii
#include <EEPROM.h>
////////////////////////////////
// Set Device
////////////////////////////////
Rotary r = Rotary(2, 3);
Si5351 si5351(0x60); // Si5351 I2C address
SSD1306AsciiAvrI2c oled;
////////////////////////////////
// I/O Port
////////////////////////////////
const byte SW_RIT = A0; // RIT SW
const byte SW_STEP = A1; // STEP SW
const byte SW_TX = A2; // TX/RX
const byte AD_SM = A7; // S-meter AD
////////////////////////////////
// EEPROM Memory Address
////////////////////////////////
const byte Eep_Init = 0x00; // Eep Init(1byte*1)
const byte Eep_Band = 0x01; // Band(1byte*1)
const byte Eep_Ch = 0x02; // Channel(1byte*1)
const byte Eep_Chan = 0x03; // VFO-Channel Mode(1byte*1)
const byte Eep_Freq = 0x10; // Frequency(4byte*5)
const byte Eep_Step = 0x30; // STEP(4byte*5)
const long Eep_Chfreq = 0x50; // Channel(4byte*5)
////////////////////////////////
// frquency data
////////////////////////////////
const unsigned long FRQ_TBL[5] = {
// DEF_F CW_F SSB_F AM_F HI_F
50600000 ,50000000 ,50200000 ,50500000 ,51000000
};
const unsigned long CH_TBL[] = { // Initial frequency value
50500000 ,50550000 ,50600000 ,50620000
};
const byte Max_Chan = sizeof(CH_TBL) / sizeof(CH_TBL[0]); // Max Channel
//---- data offset -----
const byte DEF_F = 0;
const byte CW_F = 1;
const byte SSB_F = 2;
const byte AM_F = 3;
const byte HI_F = 4;
//---- IF offset -------
const unsigned long RX_IF = 0; // RX_IF offset
const unsigned long TX_IF = 0; // TX_IF offset
////////////////////////////////
// Encorder STEP
////////////////////////////////
const int STEP_50 = 50; // STEP 50Hz
const int STEP_1k = 1000; // 1k
const int STEP_10k = 10000; // 10k
////////////////////////////////
// etc
////////////////////////////////
const byte Int_End = 73; // Initial end code
const char Call[9] = "JA2GQP"; // Display Call sign
////////////////////////////////
// Memory Assign
////////////////////////////////
unsigned long Vfo_Dat; // VFO Data
unsigned long Ch_Dat; // Channel Data
int Rit_Dat; // RIT Data
unsigned long Enc_Step; // STEP
char Enc_Dir = 0; // -1 DIR_CCW, 0 DIR_NONE, 1 DIR_CCW
byte Ch_Index = 0; // Channel TBL index
unsigned int Val_Smeter = 0;
unsigned int Val_Smeterb = 1;
long Time_Passd; // int to hold the arduino miilis since startup
long Time_Mode; // Mode change Time
byte Flg_eepWT = 0; // EEP Write Flag
byte Flg_Mode;
byte Flg_Over;
byte Flg_Vfo = 0;
byte Flg_Tx = 0;
byte Flg_Chan = 0; // Channel Flag 0:VFO, 1:Cannel
byte Disp_Over = 1;
byte Disp_Freq = 1;
byte Disp_Tx = 0;
byte Disp_Rit = 0;
byte Flg_Rit = 0; // RIT Flag
//---------- setup ----------------------------------------------------
void setup() {
pinMode(SW_STEP, INPUT_PULLUP);
pinMode(SW_TX, INPUT_PULLUP);
pinMode(SW_RIT, INPUT_PULLUP);
pinMode(12, OUTPUT); // pin12,pin13
pinMode(13, OUTPUT); // 0,0=CW 1,0=LSB 0,1=USB 1,1=AM
attachInterrupt(0, rotary_encoder, CHANGE);
attachInterrupt(1, rotary_encoder, CHANGE);
si5351.init(SI5351_CRYSTAL_LOAD_8PF,0,0); // crystal 25.000 MHz, correction 0
si5351.drive_strength(SI5351_CLK0,SI5351_DRIVE_4MA);//Drive lebel 4mA set
oled.begin(&Adafruit128x32, 0x3C);
if(EEPROM.read(Eep_Init) != Int_End){ // Eep initialaz
delay(10);
eep_init();
}
else{
if(digitalRead(SW_STEP) == LOW){
delay(10);
eep_init();
fover_disp();
while (digitalRead(SW_STEP) == LOW);
}
}
eep_rdata();
mode_disp();
step_disp();
}
//---------- main ----------------------------------------------------
void loop() {
if(Flg_Chan == 0){
vfo_mode();
}
if(Flg_Chan == 1){
ch_mode();
}
}
//---------- Channel mode ------------------------------------------------
void ch_mode(){
////////////////////////
// receive
////////////////////////
if(digitalRead(SW_TX) == HIGH){
Flg_Tx = 0;
si5351.output_enable(SI5351_CLK0, 1); // VFO enable
if (Flg_Rit == 0){
if(Disp_Freq == 1){
if((Ch_Index == 0) && (Enc_Dir == -1))
Ch_Index = Max_Chan-1;
else if((Ch_Index == Max_Chan-1) && (Enc_Dir == 1))
Ch_Index = 0;
else
Ch_Index += Enc_Dir;
Enc_Dir = 0;
Ch_Dat = eep_read4(Eep_Chfreq+Ch_Index*4); // Channel data read
band_check(Ch_Dat); // range check
si5351_set_freq(Ch_Dat + RX_IF); // frequency out
ch_disp(Ch_Dat); // Channel display
Disp_Freq = 0;
Flg_Vfo = 1;
mode_disp();
Rit_Dat = 0;
Time_Passd = millis();
Flg_eepWT = 1;
}
}
else{
if (Disp_Rit == 1){
Rit_Dat += Enc_Dir * Enc_Step;
Enc_Dir = 0;
Rit_Dat = constrain(Rit_Dat, -5000, 5000);
si5351_set_freq(Ch_Dat + RX_IF + Rit_Dat);
rit_disp(Rit_Dat);
mode_disp();
Disp_Rit = 0;
Flg_Vfo = 1;
}
}
////////////////////////
// STEP
////////////////////////
if (digitalRead(SW_STEP) == LOW) { // increment events
Time_Mode = millis();
while (digitalRead(SW_STEP) == LOW){
if(Time_Mode+1000 < millis()){
if(Flg_Chan == 0){ // VFO mode?
Flg_Chan = 1; // yes,Channel mode set
break;
}
else{ // no,VFO mode
Flg_Chan = 0;
freq_disp(Vfo_Dat); // frequency display
si5351_set_freq(Vfo_Dat + RX_IF); // fequency data output
Flg_Rit = 0; // RIT reset
Disp_Freq = 0;
break;
}
}
}
while (digitalRead(SW_STEP) == LOW);
if(Flg_Chan == 1){
enc_step();
step_disp();
}
}
////////////////////////
// RIT
////////////////////////
if (digitalRead(SW_RIT) == LOW) {
Time_Mode = millis();
while (digitalRead(SW_RIT) == LOW){
if(Time_Mode+1000 < millis()){
if(Flg_Chan == 1){
Flg_Chan = 0;
Vfo_Dat = Ch_Dat;
freq_disp(Vfo_Dat);
si5351_set_freq(Vfo_Dat + RX_IF);
Flg_Rit = 0;
break;
}
}
}
while (digitalRead(SW_RIT) == LOW);
if(Flg_Chan == 1){
rit_disp(Rit_Dat);
Fnc_Rit();
}
}
Disp_Tx = 1;
}
////////////////////////
// send
////////////////////////
else{
Flg_Tx = 1;
Disp_Over = 1;
if(Flg_Over == 0){ // Within transmittable range?
freq_disp(Ch_Dat); // frequency display
si5351.output_enable(SI5351_CLK0, 1); // VFO enable
if(Flg_Vfo == 1){
si5351_set_freq(Ch_Dat + TX_IF);
Flg_Vfo = 0;
}
Disp_Freq = 1;
Disp_Rit = 1;
}
else{ // Out of range
si5351.output_enable(SI5351_CLK0, 0); // VFO disable
if(Disp_Over == 1){
fover_disp(); // Display of over range
Disp_Over = 0;
}
}
if(Disp_Tx == 1){
tx_disp();
Disp_Tx = 0;
}
Disp_Freq = 1;
}
////////////////////////
// common
////////////////////////
Val_Smeter = analogRead(AD_SM);
if ((abs(Val_Smeter - Val_Smeterb)) > 3){ // if needed draw S-meter
sm_disp();
Val_Smeterb = Val_Smeter;
}
if(Flg_eepWT == 1){ // EEPROM auto Write
if(Time_Passd+2000 < millis()){
eep_wdata();
Flg_eepWT = 0;
}
}
}
//---------- VFO mode ------------------------------------------------
void vfo_mode(){
////////////////////////
// receive
////////////////////////
if(digitalRead(SW_TX) == HIGH){
Flg_Tx = 0;
si5351.output_enable(SI5351_CLK0, 1); // VFO enable
if (Flg_Rit == 0){
if(Disp_Freq == 1){
Vfo_Dat += Enc_Dir * Enc_Step;
Enc_Dir = 0;
band_check(Vfo_Dat); // range check
si5351_set_freq(Vfo_Dat + RX_IF);
freq_disp(Vfo_Dat); // frequency display
Disp_Freq = 0;
Flg_Vfo = 1;
mode_disp();
Rit_Dat = 0;
Time_Passd = millis();
Flg_eepWT = 1;
}
}
else{
if (Disp_Rit == 1){
Rit_Dat += Enc_Dir * Enc_Step;
Enc_Dir = 0;
Rit_Dat = constrain(Rit_Dat, -5000, 5000);
si5351_set_freq(Vfo_Dat + RX_IF + Rit_Dat);
rit_disp(Rit_Dat);
mode_disp();
Disp_Rit = 0;
Flg_Vfo = 1;
}
}
////////////////////////
// STEP
////////////////////////
if (digitalRead(SW_STEP) == LOW) { // increment events
Time_Mode = millis();
while (digitalRead(SW_STEP) == LOW){
if(Time_Mode+1000 < millis()){
if(Flg_Chan == 0){
Flg_Chan = 1;
ch_disp(Ch_Dat);
si5351_set_freq(Ch_Dat + RX_IF);
eep_wdata();
Flg_Rit = 0;
break;
}
else{
Flg_Chan = 0;
break;
}
}
}
while (digitalRead(SW_STEP) == LOW);
if(Flg_Chan == 0){
enc_step();
step_disp();
}
}
////////////////////////
// RIT
////////////////////////
if (digitalRead(SW_RIT) == LOW) {
Time_Mode = millis();
while (digitalRead(SW_RIT) == LOW){
if(Time_Mode+1000 < millis()){
if(Flg_Chan == 0){
eep_write4(Vfo_Dat,Eep_Freq);
Ch_Dat = Vfo_Dat;
ch_disp(Ch_Dat);
eep_write4(Ch_Dat,Eep_Chfreq+Ch_Index*4);
si5351_set_freq(Ch_Dat + RX_IF);
Flg_Rit = 0;
Flg_Chan = 1;
break;
}
}
}
while (digitalRead(SW_RIT) == LOW);
if(Flg_Chan == 0){
rit_disp(Rit_Dat);
Fnc_Rit();
}
}
Disp_Tx = 1;
}
////////////////////////
// send
////////////////////////
else{
Flg_Tx = 1;
Disp_Over = 1;
if(Flg_Over == 0){ // Within transmittable range?
freq_disp(Vfo_Dat); // frequency display
si5351.output_enable(SI5351_CLK0, 1); // VFO enable
if(Flg_Vfo == 1){
si5351_set_freq(Vfo_Dat + TX_IF);
Flg_Vfo = 0;
}
Disp_Freq = 1;
Disp_Rit = 1;
}
else{ // Out of range
si5351.output_enable(SI5351_CLK0, 0); // VFO disable
if(Disp_Over == 1){
fover_disp(); // Display of over range
Disp_Over = 0;
}
}
if(Disp_Tx == 1){
tx_disp();
Disp_Tx = 0;
}
Disp_Freq = 1;
}
////////////////////////
// common
////////////////////////
Val_Smeter = analogRead(AD_SM);
if ((abs(Val_Smeter - Val_Smeterb)) > 3){ // if needed draw S-meter
sm_disp();
Val_Smeterb = Val_Smeter;
}
if(Flg_eepWT == 1){ // EEPROM auto Write
if(Time_Passd+2000 < millis()){
eep_wdata();
Flg_eepWT = 0;
}
}
}
//---------- Function Rit --------------------------------------------
void Fnc_Rit(){
if(Flg_Rit == 0){
Rit_Dat = 0;
Flg_Rit = 1;
}
else{
Flg_Rit = 0;
Disp_Freq = 1;
}
}
//---------- RIT Display ---------------------------------------------
void rit_disp(int rit_data) {
unsigned int ri,rit;
oled.setFont(lcdnums14x24);
oled.setCursor(1, 0);
oled.print("::::");
if (rit_data < 0)
oled.print('-');
else
oled.print('+');
rit = abs(rit_data);
ri = rit / 1000;
oled.print(ri);
oled.print('.');
ri = (rit % 1000) / 10;
if (ri < 10)
oled.print('0');
oled.print(ri);
}
//---------- Write EEPROM 4byte --------------------------------------
void eep_write4(long value,int address){
address += 3;
for(int i = 0;i < 4;i++){
byte toSave = value & 0xFF;
if(EEPROM.read(address) != toSave){
EEPROM.write(address,toSave);
}
value = value >> 8;
address--;
}
}
//---------- Read EEPROM 4byte ---------------------------------------
long eep_read4(int address){
long value = 0;
for(int i = 0;i < 4;i++){
value = value | EEPROM.read(address);
if( i < 3){
value = value << 8;
address++;
}
}
return value;
}
//---------- EEPROM Dat Read -----------------------------------------
void eep_rdata(){
Vfo_Dat = eep_read4(Eep_Freq);
Enc_Step = eep_read4(Eep_Step);
Ch_Index = EEPROM.read(Eep_Ch);
Flg_Chan = EEPROM.read(Eep_Chan);
Ch_Dat = eep_read4(Eep_Chfreq+Ch_Index*4);
}
//---------- EEPROM Dat Write ----------------------------------------
void eep_wdata(){
eep_write4(Vfo_Dat,Eep_Freq);
eep_write4(Enc_Step,Eep_Step);
EEPROM.write(Eep_Ch,Ch_Index); // Ch_Index set
EEPROM.write(Eep_Chan,Flg_Chan); // VFO-Channel 0:VFO 1:Channel
eep_write4(Ch_Dat,Eep_Chfreq+Ch_Index*4);
}
//---------- EEPROM Initialization ------------------------------------
void eep_init(){
int i;
for (i=0;i<128;i++) // 0 clear(128byte)
EEPROM.write(i, 0);
eep_write4(FRQ_TBL[DEF_F],Eep_Freq);
eep_write4(STEP_1k,Eep_Step); // Step(1kHz)
EEPROM.write(Eep_Ch,Ch_Index); // Ch_Index set
EEPROM.write(Eep_Chan,Flg_Chan); // Ch_Index set
for(i=0;i<Max_Chan;i++)
eep_write4(CH_TBL[i],Eep_Chfreq+i*4);
EEPROM.write(Eep_Init,Int_End); // Init end set(73)
}
//---------- Rotaly Encorder -----------------------------------------
void rotary_encoder() { // rotary encoder events
uint8_t result = r.process();
if(Flg_Tx == 0){
if (result) {
Disp_Freq = 1;
Disp_Rit = 1;
if (result == DIR_CW)
Enc_Dir = 1;
else
Enc_Dir = -1;
}
}
}
//---------- si5351 PLL Output ---------------------------------------
void si5351_set_freq(uint32_t frequency) {
si5351.set_freq(frequency * SI5351_FREQ_MULT, SI5351_CLK0);
}
//---------- Encorede STEP -------------------------------------------
void enc_step() {
if (Enc_Step == STEP_50) {
Enc_Step = STEP_1k; // 1000 Hz, round to XX.XXX.000
float tmp = round (Vfo_Dat / (STEP_1k * 1.000));
Vfo_Dat = (uint32_t)(tmp * STEP_1k);
Disp_Freq = 1;
}
else if(Enc_Step == STEP_1k)
Enc_Step = STEP_10k; // 10k
else
Enc_Step = STEP_50; // 50 Hz
}
//---------- STEP Display --------------------------------------------
void step_disp() {
oled.setCursor(109, 3);
oled.setFont(labels);
if (Enc_Step == STEP_50)
oled.print("6"); // 50Hz
else if(Enc_Step == STEP_1k)
oled.print("8"); // 1k
else
oled.print("9"); // 10k
}
//---------- Frequency renge over --------------------------------------
void fover_disp() {
oled.setFont(lcdnums14x24);
oled.setCursor(1, 0);
oled.print("--.---.--");
}
//---------- callsign display ------------------------------------------
void call_disp() {
oled.setFont(Arial_bold_14);
oled.setCursor(56, 6);
oled.print(Call);
}
//---------- Frequency Display ---------------------------------------
void freq_disp(uint32_t sf_rx) {
uint16_t fr;
oled.setFont(lcdnums14x24);
oled.setCursor(1, 0);
fr = sf_rx / 1000000;
if (fr < 10)
oled.print(':'); // ':' is changed to ' ' in lcdnums14x24.h
oled.print(fr);
oled.print('.');
fr = (sf_rx % 1000000) / 1000;
if (fr < 100)
oled.print('0');
if (fr < 10)
oled.print('0');
oled.print(fr);
oled.print('.');
fr = (sf_rx % 1000) / 10;
if (fr < 10)
oled.print('0');
oled.print(fr);
}
//---------- Channel Display -----------------------------------------
void ch_disp(uint32_t sf_rx) {
uint16_t fr;
oled.setFont(lcdnums14x24);
oled.setCursor(1, 0);
oled.print(Ch_Index);
oled.print("::"); // ':' is changed to ' ' in lcdnums14x24.h
fr = sf_rx / 1000000;
if (fr < 10)
oled.print(':'); // ':' is changed to ' ' in lcdnums14x24.h
oled.print(fr);
oled.print('.');
fr = (sf_rx % 1000000) / 1000;
if (fr < 100)
oled.print('0');
if (fr < 10)
oled.print('0');
oled.print(fr);
}
//---------- TX display ------------------------------------------------
void tx_disp() {
oled.setCursor(2, 3);
oled.setFont(labels);
oled.print("1"); // "1" is "TX" in labels.h
}
//---------- Mode display ----------------------------------------------
void mode_disp() {
oled.setCursor(2, 3);
oled.setFont(labels);
if (Flg_Mode == 0){
oled.print("2"); // "2" is "CW" in labels.h
// oled.print("/"); // "/" is "AM" in labels.h
digitalWrite(12,LOW);
digitalWrite(13,LOW);
}
else if(Flg_Mode == 1){
oled.print("3"); // "3" is "LSB"
digitalWrite(12,HIGH);
digitalWrite(13,LOW);
}
else if(Flg_Mode == 2){
oled.print("4"); // "4" is "USB"
digitalWrite(12,LOW);
digitalWrite(13,HIGH);
}
else if(Flg_Mode == 3){
oled.print("/"); // "4" is "USB"
digitalWrite(12,HIGH);
digitalWrite(13,HIGH);
}
}
//---------- S-Meter Display -----------------------------------------
void sm_disp() {
uint8_t a = 0;
uint8_t m = 0;
a = (Val_Smeter + 3) / 113; // 1024 / 9 characters for S = 1,3,5,7,8,9,+10,+20,+30
oled.setFont(pixels);
oled.setCursor(25, 3);
for (m = 0; m < a; m++)
if (m < 6)
oled.print('7'); // '5' - hollow rectangle, 6px
else
oled.print('8'); // '6' - filled rectangle, 6px
for (m = a; m < 9; m++)
oled.print('.'); // '.' 1px
}
//---------- band chek -----------------------------------------------
void band_check(unsigned long freq){
if((freq >= FRQ_TBL[CW_F])
&& (freq < FRQ_TBL[SSB_F])){
Flg_Mode = 0; // CW(0=CW, 1=LSB, 2=USB, 3=AM)
Flg_Over = 0;
}
else if((freq >= FRQ_TBL[SSB_F])
&& (freq < FRQ_TBL[AM_F])){
if(freq >= 10000000) // greate than 10MHz
Flg_Mode = 2; // USB(0=CW, 1=LSB, 2=USB, 3=AM)
else
Flg_Mode = 1; // LSB(0=CW, 1=LSB, 2=USB, 3=AM)
Flg_Over = 0;
}
else if((freq >= FRQ_TBL[AM_F])
&& (freq < FRQ_TBL[HI_F])){
Flg_Mode = 3; // AM(0=CW, 1=LSB, 2=USB, 3=AM)
Flg_Over = 0;
}
else
Flg_Over = 1;
}