/*
* KeyerBuddy - morse code practice partner, ATtiny4313 main code
* Copyright (C) 2025 David Montag
*
* 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 3 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, see .
*
* Created: 7/22/2025 1:37:14 PM
* Author: david@montag.se
*/
#ifndef F_CPU
#define F_CPU 8000000UL
#endif
#include
#include
//#include
//#include
#include
#include
#include
#include
// A B C D E F G H I J K L M N O P Q R S T U V W X Y Z 0 1 2 3 4 5 6 7 8 9
/* Format: XXXXXYYY
* XXXXX = dit/dah (1 = dit, 0 = dah)
* YYY = length (1-5 = 1-5, 6-7 = 6)
* When length is 6, the first sound is a dit. When length is 7 the first sound is dah. In both cases the actual length is 6.
*/
const uint8_t char_to_dit_dah[] PROGMEM = {
0b10010, // A
0b0111100, // B
0b0101100, // C
0b011011, // D
0b1001, // E
0b1101100, // F
0b001011, // G
0b1111100, // H
0b11010, // I
0b1000100, // J
0b010011, // K
0b1011100, // L
0b00010, // M
0b01010, // N
0b000011, // O
0b1001100, // P
0b0010100, // Q
0b101011, // R
0b111011, // S
0b0001, // T
0b110011, // U
0b1110100, // V
0b100011, // W
0b0110100, // X
0b0100100, // Y
0b0011100, // Z
0b00000101, // 0
0b10000101, // 1
0b11000101, // 2
0b11100101, // 3
0b11110101, // 4
0b11111101, // 5
0b01111101, // 6
0b00111101, // 7
0b00011101, // 8
0b00001101, // 9
0b10011110, // ? question mark
0b01010110, // . period
0b01100111, // , comma
0b00000000, // _ space
0b01110101, // = BT
0b01001101, // < KN
0b11010110, // ~ SK
0b01101101 // / stroke
};
#define NUM_WORDS 100
const uint8_t words[] PROGMEM =
"CQ FB UR_RST_599 TNX ES "
"VY QTH ANT RIG WX "
"GE GM OM OP RST "
"579 PWR_5W QSO TNX_FER_CALL PSE_QRS "
"CQ_DX CU TU 73 AGN "
"PSE MY_NAME_IS QSB HR SOTA "
"UR DR_OM HW? DIPOLE POTA "
"QRL? CQ_DE GA CU_AGN_SN HPE "
"WX_CLOUDY WX_SUNNY WX_21C OK QTH_NR "
"R_R_R = AGN_PSE QRZ? GUD "
"QRP UR_449_449 R_R_FB_UR_589 TNX_FER_NICE_QSO TU_ES_CU_E_E "
"TNX_QSO_ES_HPE_CU_SN OP_DAVID GUD_CONDX RIG_ICOM_7300 RIG_QRP_4W "
"QTH_NR_STOCKHOLM OK_GE_OM ANT_DIPOLE ANT_6EL_YAGI HR_PWR_IS_500_W "
"OK_FB_DR_JONATHAN UR_FB_55N BEST_DX TU_~_E_E QSL_VIA_BURO "
"HPE_CU_AGN_MY_73 TNX_FER_INFO CIAO TNX_FER_RPRT VY_VY_73 "
"R_R_OK RR_FB_OM HI_UR_RST_5NN HR_TEMP_18C TKS_RPRT "
"ANT_3_BAND_QUAD SA0DMA/P SA0DMA/MM OP_DAVE_=_=_QTH_NR GE_DR_OM "
"SRI_AGN_QSB UR_5NN_=_HW? R_OK_VY_FB GE_TNX_RST_57N_OP_MAL RIG_TRUSDX "
"MY_RIG_IS_YAESU_710 TNX_FER_NICE_QSO UR_RST_44N_QSB ANT_EFHW ANT_VERTICAL "
"RIG_300W_ANT_DIPOLE FB_CPY NAME_IS_WALTER GM_OM_ES_TNX_FER_CALL TEST";
void led_on(void) {
PORTB |= (1<> 3;
if (len == 0) {
var_delay(delay*7);
}
if (len == 6) {
play_dit(delay, flash);
len = 5;
}
if (len == 7) {
play_dah(delay, flash);
len = 5;
}
for(uint8_t i = 0; i < len; i++) {
uint8_t ditdah = bits & (1 << (len-i-1));
if (ditdah) {
play_dit(delay, flash);
} else {
play_dah(delay, flash);
}
}
}
// c is ascii
void play_char(uint8_t c, uint32_t delay, uint8_t flash) {
if (c >= 65 && c <= 90) c = c-65;
else if (c >= 48 && c <= 57) c = c-48+26;
else if (c == '?') c = 36;
else if (c == '.') c = 37;
else if (c == ',') c = 38;
else if (c == '_') c = 39;
else if (c == '=') c = 40;
else if (c == '<') c = 41;
else if (c == '~') c = 42;
else if (c == '/') c = 43;
else return;
play_lut_char(c, delay, flash);
}
void play_word(uint8_t word_num, uint32_t delay, uint8_t flash) {
uint8_t current_word = 0;
uint16_t char_index = 0;
while (current_word < word_num) {
while (pgm_read_byte(&words[char_index]) > 0x20) {
char_index += 1;
}
char_index += 1;
current_word += 1;
}
while (pgm_read_byte(&words[char_index]) > 0x20) {
play_char(pgm_read_byte(&words[char_index]), delay, flash);
var_delay(delay*2); // last character includes one delay already
char_index += 1;
}
}
void play_random_callsign(uint32_t delay, uint8_t flash) {
play_lut_char(random() % 26, delay, flash);
var_delay(delay*3);
play_lut_char(random() % 26, delay, flash);
var_delay(delay*3);
play_lut_char(26+(random() % 10), delay, flash);
var_delay(delay*3);
play_lut_char(random() % 26, delay, flash);
var_delay(delay*3);
play_lut_char(random() % 26, delay, flash);
var_delay(delay*3);
play_lut_char(random() % 26, delay, flash);
if((random() % 10) == 0) {
var_delay(delay*3);
play_char('/', delay, flash);
var_delay(delay*3);
play_char('P', delay, flash);
}
}
void play_random_word(uint32_t delay, uint8_t flash) {
uint8_t word_num = random() % NUM_WORDS;
play_word(word_num, delay, flash);
}
#define MIN_VALUE 10
#define MAX_VALUE 800
#define SLEEP_DELAY_SECS 10
int main(void)
{
hardware_init();
sound_off();
GIMSK |= (1< (1000/POT_CHARGE_DELAY_MS)*SLEEP_DELAY_SECS) {
// pin change interrupt for wakeup
GIMSK |= (1< straight key
// keytype=straight socket=plugged --> callsign generator
// keytype=paddle socket=empty --> callsign generator
// keytype=paddle socket=plugged --> callsign generator
if (KEY_SOCKET_EMPTY && KEY_TYPE_STRAIGHT) {
if (IS_ONBOARD_KEY_DOWN) {
idle_cycle_counter = 0;
sound_on();
led_on();
} else {
sound_off();
led_off();
}
} else if (KEY_TYPE_STRAIGHT) {
if (DIT_IS_PRESSED || DAH_IS_PRESSED) {
idle_cycle_counter = 0;
sound_on();
led_on();
} else {
sound_off();
led_off();
}
}
if (!KEY_TYPE_STRAIGHT || KEY_SOCKET_PLUGGED) {
if (IS_ONBOARD_KEY_DOWN) {
idle_cycle_counter = 0;
_delay_ms(100);
if (!IS_ONBOARD_KEY_DOWN) {
play_random_word(delay, flash_sounds);
} else {
_delay_ms(100);
if (!IS_ONBOARD_KEY_DOWN) {
play_random_word(delay, flash_sounds);
} else {
_delay_ms(100);
if (!IS_ONBOARD_KEY_DOWN) {
play_random_word(delay, flash_sounds);
} else {
_delay_ms(100);
if (!IS_ONBOARD_KEY_DOWN) {
play_random_word(delay, flash_sounds);
} else {
_delay_ms(100);
if (IS_ONBOARD_KEY_DOWN) {
play_random_callsign(delay, flash_sounds);
}
}
}
}
}
}
}
if (!KEY_TYPE_STRAIGHT) {
// PADDLE KEY
if ((DIT_IS_PRESSED || key_triggered & 1) && (DAH_IS_PRESSED || key_triggered & 2)) {
idle_cycle_counter = 0;
if (latest_triggered == 1) {
play_dah(delay, flash_sounds);
latest_triggered = 2;
} else if (latest_triggered == 2) {
play_dit(delay, flash_sounds);
latest_triggered = 1;
}
} else if (DIT_IS_PRESSED || key_triggered & 1) {
idle_cycle_counter = 0;
play_dit(delay, flash_sounds);
latest_triggered = 1;
} else if (DAH_IS_PRESSED || key_triggered & 2) {
idle_cycle_counter = 0;
play_dah(delay, flash_sounds);
latest_triggered = 2;
}
}
}
}