Just for fun and to use the Pandemia holiday I thought will be interesting to have a little remote for the Voice and CW memory.
While the Voice memory callback is relatively simple with CI-V protocol, for CW memory there are no commands provided by ICOM.
Therefore, the keyer will store the preset messages (10 messages) and send them using a 4 x 4 keypad.
The uC of choice is an Arduino with Atmega 328 on UNO platform.
The keypad is also used to Power On and OFF the radio and TUNE the ATU (I use an AH-4).
The 4 x 4 keypad is able to send: -In CW, 10 preprogrammed CW messages, each of 128 bytes; -in SSB, AM, FM, 8 prerecorded voice messages -"A" is Power ON -"B" is Power OFF -"C" is TUNE
Here is a small video on youtube about this
Here is the code
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
CI-V Voice/Morse keyer | |
Version 1.1 | |
https://yo3hjv.blogspot.com/2020/04/cw-and-voice-memory-keyer-for-icom-ic.html | |
This is a CW memory keyer for ICOM CI-V radios. | |
It was tested on IC-7300 and it is working well. | |
It was tested on IC-703 and was find not compatible. | |
Many thanks for inspiring the structure to KEN - KC9UMR. | |
The 4 x 4 keypad is able to send: | |
-In CW, 10 preprogrammed CW messages, each of 128 bytes; | |
-in SSB, AM, FM, 8 prerecorded voice messages | |
-"A" is Power ON | |
-"B" is Power OFF | |
-"C" is TUNE | |
The rest will be defined later on other versions. | |
This was a good opportunity for me to learn to use Arrays and Switch/case | |
Many thanks for the whole community from where I learned! | |
Thanks also to our wise government for keeping us at home... | |
de Adrian Florescu, YO3HJV. April, 2020 | |
This code is released under the EUPL v1.2. | |
*/ | |
#include <SoftwareSerial.h> | |
#include <Keypad.h> | |
const byte numRows = 4; | |
const byte numCols = 4; | |
byte keymap[numRows][numCols] = | |
{ {'1', '2', '3', 'A'}, | |
{'4', '5', '6', 'B'}, | |
{'7', '8', '9', 'C'}, | |
{'*', '0', '#', 'D'} | |
}; | |
byte rowPins[numRows] = {9, 8, 7, 6}; //Rows 0 to 3 | |
byte colPins[numCols] = {5, 4, 3, 2}; //Columns 0 to 3 | |
//initializes an instance of the Keypad class | |
Keypad myKeypad = Keypad(makeKeymap(keymap), rowPins, colPins, numRows, numCols); | |
/* | |
When sending the power ON commandCW (18 01), you need | |
to repeatedly send “FE” before the standard format. The | |
following values for pON has to be observed. | |
••115200 bps: 150 “FE”s | |
••57600 bps: 75 “FE”s | |
••38400 bps: 50 “FE”s | |
••19200 bps: 25 “FE”s | |
••9600 bps: 13 “FE”s | |
••4800 bps: 7 “FE”s | |
*/ | |
#define START_BYTE 0xFE // Start byte | |
#define STOP_BYTE 0xFD // Stop byte | |
#define SEND_CW 0x17 | |
#define VOICE_MEM 0x28 | |
#define ZERO byte(0x00) | |
#define CONTROLLER_ADDRESS 0xE3 // The hex adress of the Arduino controller | |
char toSend; | |
byte asciiCW; | |
byte i; | |
byte j; | |
byte n = 1; // variable. how many times the message is repeated. TBD | |
int RPTint; // Memory repeat interval. TBD | |
byte p; | |
byte pON = 180; | |
char key; | |
byte radio; | |
byte voiceMem1 = 0x01; | |
byte voiceMem2 = 0x02; | |
byte voiceMem3 = 0x03; | |
byte voiceMem4 = 0x04; | |
byte voiceMem5 = 0x05; | |
byte voiceMem6 = 0x06; | |
byte voiceMem7 = 0x07; | |
byte voiceMem8 = 0x08; | |
byte vMem; | |
/* | |
* The following are USER-DEFINED values. | |
* The values are to be modified according to the settings on the radio | |
*/ | |
#define RADIO_ADDRESS 0x94 // ic7300 has default address 0x94 | |
#define CAT_BAUDRATE 19200 // Arduino to radio Baudrate on CI-V interface | |
#define RX 12 // CAT RX on Arduino UNO for software serial | |
#define TX 13 // CAT TX on Arduini UNO for software serial | |
SoftwareSerial CAT(RX, TX); // RX, TX | |
// THIS ARE THE PRESET CW MESSAGES TO BE SENT | |
// both Upper-case or Lower-case letters can be used and the following symbols | |
// * . , ! ? / = ( ) [SPACE] _ - | |
char msg_1[] = "CQ CQ DE YO3HJV yo3hjv"; // max lenght 128 | |
char msg_2[] = "YO3HJV"; // max lenght 128 | |
char msg_3[] = "5NN 5NN TU 73 DE YO3HJV KN"; // max lenght 128 | |
char msg_4[] = "QRL? QRL? DE YO3HJV K"; // max lenght 128 | |
char msg_5[] = "R R = OP HR ADI ADI BK"; // max lenght 128 | |
char msg_6[] = "NAME ADI ADI ADI BTU"; // max lenght 128 | |
char msg_7[] = "CQ"; // max lenght 128 | |
char msg_8[] = "= TNX FER QSO = GL HP CU AGN 73 DE YO3HJV"; // max lenght 128 | |
char msg_9[] = "5NN 5NN TU 73 K"; // max lenght 128 | |
char msg_0[] = "V"; | |
void setup() { | |
Serial.begin(19200); | |
CAT.begin(CAT_BAUDRATE); // set the data rate for the Software Serial port | |
delay(2); | |
CAT.flush(); | |
} | |
void loop() { | |
checkPad(); | |
} | |
void checkPad() { | |
// This is where the magic is happening | |
char keypressed = myKeypad.getKey(); | |
if (keypressed != NO_KEY) { | |
key = keypressed; | |
switch (key) { | |
/////////// NUMBERS /////////// | |
case '1': | |
for (byte j = 0; j < n; j++) { | |
sendMem1(); | |
vMem = voiceMem1; | |
sendVoice(); | |
} | |
break; | |
case '2': | |
for (byte j = 0; j < n; j++) { | |
sendMem2(); | |
vMem = voiceMem2; | |
sendVoice(); | |
} | |
break; | |
case '3': | |
for (byte j = 0; j < n; j++) { | |
sendMem3(); | |
vMem = voiceMem3; | |
sendVoice(); | |
} | |
break; | |
case '4': | |
for (byte j = 0; j < n; j++) { | |
sendMem4(); | |
vMem = voiceMem4; | |
sendVoice(); | |
} | |
break; | |
case '5': | |
for (byte j = 0; j < n; j++) { | |
sendMem5(); | |
vMem = voiceMem5; | |
sendVoice(); | |
} | |
break; | |
case '6': | |
for (byte j = 0; j < n; j++) { | |
sendMem6(); | |
vMem = voiceMem6; | |
sendVoice(); | |
} | |
break; | |
case '7': | |
for (byte j = 0; j < n; j++) { | |
sendMem7(); | |
vMem = voiceMem7; | |
sendVoice(); | |
} | |
break; | |
case '8': | |
for (byte j = 0; j < n; j++) { | |
sendMem8(); | |
vMem = voiceMem8; | |
sendVoice(); | |
} | |
break; | |
case '9': | |
for (byte j = 0; j < n; j++) { | |
sendMem9(); | |
} | |
break; | |
case '0': | |
for (byte j = 0; j < n; j++) { | |
sendMem0(); | |
} | |
//////////////////// LETTERS ////////////////////// | |
break; | |
case 'A': | |
for (byte j = 0; j < n; j++) { | |
powerON(); | |
} | |
break; | |
case 'B': | |
for (byte j = 0; j < n; j++) { | |
powerOFF(); | |
} | |
break; | |
case 'C': | |
for (byte j = 0; j < n; j++) { | |
ATUne(); | |
} | |
break; | |
case 'D': | |
for (byte j = 0; j < n; j++) { | |
// something ; | |
} | |
break; | |
case '*': | |
for (byte j = 0; j < n; j++) { | |
// something(); | |
} | |
break; | |
case '#': | |
for (byte j = 0; j < n; j++) { | |
// something(); | |
} | |
break; | |
}// End of SWITCH | |
} | |
} | |
void sendVoice(){ | |
send_preamble(); | |
commandVOICE(); | |
send_post(); | |
} | |
void sendMem0() { | |
for (byte i = 0; i < sizeof(msg_0) - 1; i++) { | |
toSend = msg_0[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem1() { | |
for (byte i = 0; i < sizeof(msg_1) - 1; i++) { | |
toSend = msg_1[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem2() { | |
for (byte i = 0; i < sizeof(msg_2) - 1; i++) { | |
toSend = msg_2[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem3() { | |
for (byte i = 0; i < sizeof(msg_3) - 1; i++) { | |
toSend = msg_3[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem4() { | |
for (byte i = 0; i < sizeof(msg_4) - 1; i++) { | |
toSend = msg_4[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem5() { | |
for (byte i = 0; i < sizeof(msg_5) - 1; i++) { | |
toSend = msg_5[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem6() { | |
for (byte i = 0; i < sizeof(msg_6) - 1; i++) { | |
toSend = msg_6[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem7() { | |
for (byte i = 0; i < sizeof(msg_7) - 1; i++) { | |
toSend = msg_7[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem8() { | |
for (byte i = 0; i < sizeof(msg_8) - 1; i++) { | |
toSend = msg_8[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void sendMem9() { | |
for (byte i = 0; i < sizeof(msg_9) - 1; i++) { | |
toSend = msg_9[i]; | |
send_preamble(); | |
commandCW(); | |
send_CW(); | |
send_post(); | |
} | |
} | |
void ATUne(){ | |
send_preamble(); | |
CAT.write(0x1C); | |
CAT.write(01); | |
CAT.write(02); | |
send_post(); | |
} | |
void commandCW() { | |
CAT.write(SEND_CW); | |
} | |
void commandVOICE(){ | |
CAT.write(VOICE_MEM); | |
CAT.write(ZERO); | |
CAT.write(vMem); | |
} | |
void send_CW() { | |
ASCIItoHEX(); | |
CAT.write(asciiCW); | |
} | |
void send_preamble() | |
{ | |
CAT.write(0xFE); | |
CAT.write(0xFE); | |
CAT.write(RADIO_ADDRESS); | |
CAT.write(CONTROLLER_ADDRESS); | |
} | |
void send_post() | |
{ | |
CAT.write(0xFD); | |
CAT.flush(); | |
} | |
////////////////////// Here is ASCII to ICOM HEX ASCII /////////////////////////////// | |
void ASCIItoHEX () { | |
switch (toSend) { | |
////////////////////////////// NUMBERS /////////////////////// | |
case '0': | |
asciiCW = 0x30; | |
break; | |
case '1': | |
asciiCW = 0x31; | |
break; | |
case '2': | |
asciiCW = 0x32; | |
break; | |
case '3': | |
asciiCW = 0x33; | |
break; | |
case '4': | |
asciiCW = 0x34; | |
break; | |
case '5': | |
asciiCW = 0x35; | |
break; | |
case '6': | |
asciiCW = 0x36; | |
break; | |
case '7': | |
asciiCW = 0x37; | |
break; | |
case '8': | |
asciiCW = 0x38; | |
break; | |
case '9': | |
asciiCW = 0x39; | |
break; | |
////////////////////////////// SYMBOLS //////////////////////// | |
case ' ' : | |
asciiCW = 0x20; | |
break; | |
case '*': // This is to insert CONTEST NUMBER | |
asciiCW = 0x2A; | |
break; | |
case '/': | |
asciiCW = 0x2F; | |
break; | |
case '-': | |
asciiCW = 0x2D; | |
break; | |
case '?': | |
asciiCW = 0x3F; | |
break; | |
case ',': | |
asciiCW = 0x2C; | |
break; | |
case '.': | |
asciiCW = 0x2E; | |
break; | |
case '@': | |
asciiCW = 0x40; | |
break; | |
case '=': | |
asciiCW = 0x3D; | |
break; | |
case '!': | |
asciiCW = 0x21; | |
break; | |
case '(': | |
asciiCW = 0x28; | |
break; | |
case ')': | |
asciiCW = 0x29; | |
break; | |
case '"': | |
asciiCW = 0x22; | |
break; | |
case '#': | |
asciiCW = 0x23; | |
break; | |
case '_': | |
asciiCW = 0x5F; | |
break; | |
////////////////////// UPPER-CASE LETTERS ///////////////////////////// | |
case 'A': | |
asciiCW = 0x41; | |
break; | |
case 'B': | |
asciiCW = 0x42; | |
break; | |
case 'C': | |
asciiCW = 0x43; | |
break; | |
case 'D': | |
asciiCW = 0x44; | |
break; | |
case 'E': | |
asciiCW = 0x45; | |
break; | |
case 'F': | |
asciiCW = 0x46; | |
break; | |
case 'G': | |
asciiCW = 0x47; | |
break; | |
case 'H': | |
asciiCW = 0x48; | |
break; | |
case 'I': | |
asciiCW = 0x49; | |
break; | |
case 'J': | |
asciiCW = 0x4A; | |
break; | |
case 'K': | |
asciiCW = 0x4B; | |
break; | |
case 'L': | |
asciiCW = 0x4C; | |
break; | |
case 'M': | |
asciiCW = 0x4D; | |
break; | |
case 'N': | |
asciiCW = 0x4E; | |
break; | |
case 'O': | |
asciiCW = 0x4F; | |
break; | |
case 'P': | |
asciiCW = 0x50; | |
break; | |
case 'Q': | |
asciiCW = 0x51; | |
break; | |
case 'R': | |
asciiCW = 0x52; | |
break; | |
case 'S': | |
asciiCW = 0x53; | |
break; | |
case 'T': | |
asciiCW = 0x54; | |
break; | |
case 'U': | |
asciiCW = 0x55; | |
break; | |
case 'V': | |
asciiCW = 0x56; | |
break; | |
case 'W': | |
asciiCW = 0x57; | |
break; | |
case 'X': | |
asciiCW = 0x58; | |
break; | |
case 'Y': | |
asciiCW = 0x59; | |
break; | |
case 'Z': | |
asciiCW = 0x5A; | |
break; | |
///////////////////// LOWER CASE LETTERS //////////////////////////////// | |
case 'a': | |
asciiCW = 0x41; | |
break; | |
case 'b': | |
asciiCW = 0x42; | |
break; | |
case 'c': | |
asciiCW = 0x43; | |
break; | |
case 'd': | |
asciiCW = 0x44; | |
break; | |
case 'e': | |
asciiCW = 0x45; | |
break; | |
case 'f': | |
asciiCW = 0x46; | |
break; | |
case 'g': | |
asciiCW = 0x47; | |
break; | |
case 'h': | |
asciiCW = 0x48; | |
break; | |
case 'i': | |
asciiCW = 0x49; | |
break; | |
case 'j': | |
asciiCW = 0x4A; | |
break; | |
case 'k': | |
asciiCW = 0x4B; | |
break; | |
case 'l': | |
asciiCW = 0x4C; | |
break; | |
case 'm': | |
asciiCW = 0x4D; | |
break; | |
case 'n': | |
asciiCW = 0x4E; | |
break; | |
case 'o': | |
asciiCW = 0x4F; | |
break; | |
case 'p': | |
asciiCW = 0x50; | |
break; | |
case 'q': | |
asciiCW = 0x51; | |
break; | |
case 'r': | |
asciiCW = 0x52; | |
break; | |
case 's': | |
asciiCW = 0x53; | |
break; | |
case 't': | |
asciiCW = 0x54; | |
break; | |
case 'u': | |
asciiCW = 0x55; | |
break; | |
case 'v': | |
asciiCW = 0x56; | |
break; | |
case 'w': | |
asciiCW = 0x57; | |
break; | |
case 'x': | |
asciiCW = 0x58; | |
break; | |
case 'y': | |
asciiCW = 0x59; | |
break; | |
case 'z': | |
asciiCW = 0x5A; | |
break; | |
} | |
delay(1); // For stability | |
} | |
void powerON() { | |
for (byte p = 0; p <= pON; p++) { | |
CAT.write(0xFE); // preamble is sent multiple times | |
} | |
send_preamble(); | |
CAT.write(0x18); | |
CAT.write(0x01); | |
send_post(); | |
} | |
void powerOFF() { | |
send_preamble(); | |
CAT.write(0x18); | |
CAT.write(ZERO); | |
send_post(); | |
} |
Un comentariu:
Arduino:1.8.13 (Windows 10), Plošča:"Arduino Nano, ATmega328P"
Dear friend
I have a problem encoding your program:
CI-VMemory_Keyer_R_1_1.
I am using an Arduino 1.8.13.
what is wrong. can you help me?
73 Franc S52RF franc.s52rf@gmail.com
C:\Users\rf\AppData\Local\Temp\ccTza5kG.ltrans0.ltrans.o: In function `global constructors keyed to 65535_0_CI_VMemory_Keyer_R_1_1.ino.cpp.o.2180':
:(.text.startup+0x8c): undefined reference to `Key::Key()'
:(.text.startup+0xa6): undefined reference to `Key::Key()'
collect2.exe: error: ld returned 1 exit status
exit status 1
Error compiling for board Arduino Nano.
This report would have more information with
"Show verbose output during compilation"
option enabled in File -> Preferences.
Trimiteți un comentariu