ARDUINO shield for CW Decoder
Refr.: OZ1JHM (se OZ Juni 2014 s.609)
Som erstatning for et ’fumlebrædt’ til at forbinde et 4x20 LCD til CW decoderen har OZ9VQ fremstillet et ’Shield’ der klarer den del af konstruktionen.
Fordelen er at man så kan samle ARDUINO og LCD som en samlet enhed, omkring dette print.
(Hvis der benyttes et 4x16 LCD skal der i ARDUINO-koden ændres på de to(2) tilsvarende værdier)
Efter at have samlet delene og uploaded programmet skulle der gerne på LCD stå ’WPM’ øverst.
Hvis dette ikke er tilfældet skal der justeres på potmetret (næsten ned til stel) for at WPM kommer frem. Der justeres til bedst mulig kontrast.
Shielded kan godt anvendes til andre konstruktioner med et andet passende program (modificeres) såfremt indgangen lander på ARDUINO ben A1.
Printet er udlagt i CAD-programmet ExpressPCB.
Display : QC2004A, 4x20 LCD , eller tilsvarende.
Arduino uno
Komponentliste : 1 stk. 7805, 5volt regulator, TO-220 hus
2 stk. 10k Ohm, 0805
1 stk. 10NF , X7R, 0805
1 stk. 220 Ohm, 0805
1 stk. Potmeter, 20k Ohm, printmont. (se printudlæg)
1 stk. Jack fatn., stereo, 3mm, printmont. (se printudlæg)
1 stk stift-sokkel, h=10mm, 16 pinde Display
1 stk. stift-liste, h=10mm, 16 pinde -
1 stk. stift-liste, h=10mm, 10 pinde ARDUINO
2 stk. stift-liste, h=10mm, 8 pinde -
1 stk. stift-liste, h=10mm, 6 pinde -
1 stk. kølefinne for regulator 7805
1 stk. isolations-folie for regulator
1 stk. Print OZ9VQ :
./. File: ARDUINO_LCD_4x20 CW Decoder_ modded.pcb
Monterings tegning:
Komponentplacering på oversiden UDEN at man kan se kobberet.
Bemærk dog at Spændingsregulatoren , 7805, skal monteres på bagsiden
___________________________________________________________________________
Kobberet på overside af print, uden at komponenterne kan ses
___________________________________________________________________________
Kobberet på undersiden af printet, uden at komponenterne kan ses
____________________________________________________________________________
Samlet tegning af montering, overside og underside
_____________________________________________________________________
Det skal bemærkes at printet er udført således at stifter og sokler iloddes printet fra den side hvor der KUN er øer. Det vil sige at der på den side hvor der er forbindelser IKKE skal der loddes !!!
Således vil konnekteringen til ARDUINO og LCD passe.
Tjek ARDUINO’en med henblik på at få konnecteringen til at passe inden ilodning
Sæt evt. konnektorerne på ARDUINO’en først, inden ilodning !!
Under 7805’eren skal der være en isolationsplade (på undersiden hvor den monteres) hvis man monterer 7805'eren. Printet kan med fordel forsynes fra en 7,5V eller 9V ’klods’ som Ikke udvikler nær så megen varme fra 7805’eren.
Til fastgørelse af delene anvendes standard stag.
OZ9VQ
13.01.2015
[divider]
programkode:
///////////////////////////////////////////////////////////////////////
// CW Decoder made by Hjalmar Skovholm Hansen OZ1JHM VER 1.01 //
// Feel free to change, copy or what ever you like but respect //
// that license is http://www.gnu.org/copyleft/gpl.html //
// Discuss and give great ideas on //
// https://groups.yahoo.com/neo/groups/oz1jhm/conversations/messages //
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////
// Read more here http://en.wikipedia.org/wiki/Goertzel_algorithm //
// if you want to know about FFT the http://www.dspguide.com/pdfbook.htm //
///////////////////////////////////////////////////////////////////////////
#include <LiquidCrystal.h>
///////////////////////////////////////////////
// select the pins used on the LCD panel /
///////////////////////////////////////////////
// LiquidCrystal lcd(RS, E, D4, D5, D6, D7) //
///////////////////////////////////////////////
LiquidCrystal lcd(12, 11, 5, 4, 3, 2);
const int colums = 20; /// have to be 16 or 20
const int rows = 4; /// have to be 2 or 4
int lcdindex = 0;
int line1[colums];
int line2[colums];
////////////////////////////////
// Define 8 specials letters //
////////////////////////////////
byte U_umlaut[8] = {B01010,B00000,B10001,B10001,B10001,B10001,B01110,B00000}; // 'Ü'
byte O_umlaut[8] = {B01010,B00000,B01110,B10001,B10001,B10001,B01110,B00000}; // 'Ö'
byte A_umlaut[8] = {B01010,B00000,B01110,B10001,B11111,B10001,B10001,B00000}; // 'Ä'
byte AE_capital[8] = {B01111,B10100,B10100,B11110,B10100,B10100,B10111,B00000}; // 'Æ'
byte OE_capital[8] = {B00001,B01110,B10011,B10101,B11001,B01110,B10000,B00000}; // 'Ø'
byte fullblock[8] = {B11111,B11111,B11111,B11111,B11111,B11111,B11111,B11111};
byte AA_capital[8] = {B00100,B00000,B01110,B10001,B11111,B10001,B10001,B00000}; // 'Å'
byte emtyblock[8] = {B00000,B00000,B00000,B00000,B00000,B00000,B00000,B00000};
int audioInPin = A1;
int audioOutPin = 10;
int ledPin = 13;
float magnitude ;
int magnitudelimit = 100;
int magnitudelimit_low = 100;
int realstate = LOW;
int realstatebefore = LOW;
int filteredstate = LOW;
int filteredstatebefore = LOW;
///////////////////////////////////////////////////////////
// The sampling frq will be 8928 on a 16 mhz //
// without any prescaler etc //
// because we need the tone in the center of the bins //
// you can set the tone to 496, 558, 744 or 992 //
// then n the number of samples which give the bandwidth //
// can be (8928 / tone) * 1 or 2 or 3 or 4 etc //
// init is 8928/558 = 16 *4 = 64 samples //
// try to take n = 96 or 128 ;o) //
// 48 will give you a bandwidth around 186 hz //
// 64 will give you a bandwidth around 140 hz //
// 96 will give you a bandwidth around 94 hz //
// 128 will give you a bandwidth around 70 hz //
// BUT remember that high n take a lot of time //
// so you have to find the compromice - i use 48 //
///////////////////////////////////////////////////////////
float coeff;
float Q1 = 0;
float Q2 = 0;
float sine;
float cosine;
float sampling_freq=8928.0;
float target_freq=558.0; /// adjust for your needs see above
float n=48.0; //// if you change her please change next line also
int testData[48];
//////////////////////////////
// Noise Blanker time which //
// shall be computed so //
// this is initial //
//////////////////////////////
int nbtime = 6; /// ms noise blanker
long starttimehigh;
long highduration;
long lasthighduration;
long hightimesavg;
long lowtimesavg;
long startttimelow;
long lowduration;
long laststarttime = 0;
char code[20];
int stop = LOW;
int wpm;
////////////////
// init setup //
////////////////
void setup() {
////////////////////////////////////
// The basic goertzel calculation //
////////////////////////////////////
int k;
float omega;
k = (int) (0.5 + ((n * target_freq) / sampling_freq));
omega = (2.0 * PI * k) / n;
sine = sin(omega);
cosine = cos(omega);
coeff = 2.0 * cosine;
///////////////////////////////
// define special characters //
///////////////////////////////
lcd.createChar(0, U_umlaut); // German
lcd.createChar(1, O_umlaut); // German, Swedish
lcd.createChar(2, A_umlaut); // German, Swedish
lcd.createChar(3, AE_capital); // Danish, Norwegian
lcd.createChar(4, OE_capital); // Danish, Norwegian
lcd.createChar(5, fullblock);
lcd.createChar(6, AA_capital); // Danish, Norwegian, Swedish
lcd.createChar(7, emtyblock);
lcd.clear();
Serial.begin(115200);
pinMode(ledPin, OUTPUT);
lcd.begin(colums, rows);
for (int index = 0; index < colums; index++){
line1[index] = 32;
line2[index] = 32;
}
}
///////////////
// main loop //
///////////////
void loop() {
/////////////////////////////////////
// The basic where we get the tone //
/////////////////////////////////////
for (char index = 0; index < n; index++)
{
testData[index] = analogRead(audioInPin);
}
for (char index = 0; index < n; index++){
float Q0;
Q0 = coeff * Q1 - Q2 + (float) testData[index];
Q2 = Q1;
Q1 = Q0;
}
float magnitudeSquared = (Q1*Q1)+(Q2*Q2)-Q1*Q2*coeff; // we do only need the real part //
magnitude = sqrt(magnitudeSquared);
Q2 = 0;
Q1 = 0;
//Serial.print(magnitude); Serial.println(); //// here you can measure magnitude for setup..
///////////////////////////////////////////////////////////
// here we will try to set the magnitude limit automatic //
///////////////////////////////////////////////////////////
if (magnitude > magnitudelimit_low){
magnitudelimit = (magnitudelimit +((magnitude - magnitudelimit)/6)); /// moving average filter
}
if (magnitudelimit < magnitudelimit_low)
magnitudelimit = magnitudelimit_low;
////////////////////////////////////
// now we check for the magnitude //
////////////////////////////////////
if(magnitude > magnitudelimit*0.6) // just to have some space up
realstate = HIGH;
else
realstate = LOW;
/////////////////////////////////////////////////////
// here we clean up the state with a noise blanker //
/////////////////////////////////////////////////////
if (realstate != realstatebefore){
laststarttime = millis();
}
if ((millis()-laststarttime)> nbtime){
if (realstate != filteredstate){
filteredstate = realstate;
}
}
////////////////////////////////////////////////////////////
// Then we do want to have some durations on high and low //
////////////////////////////////////////////////////////////
if (filteredstate != filteredstatebefore){
if (filteredstate == HIGH){
starttimehigh = millis();
lowduration = (millis() - startttimelow);
}
if (filteredstate == LOW){
startttimelow = millis();
highduration = (millis() - starttimehigh);
if (highduration < (2*hightimesavg) || hightimesavg == 0){
hightimesavg = (highduration+hightimesavg+hightimesavg)/3; // now we know avg dit time ( rolling 3 avg)
}
if (highduration > (5*hightimesavg) ){
hightimesavg = highduration+hightimesavg; // if speed decrease fast ..
}
}
}
///////////////////////////////////////////////////////////////
// now we will check which kind of baud we have - dit or dah //
// and what kind of pause we do have 1 - 3 or 7 pause //
// we think that hightimeavg = 1 bit //
///////////////////////////////////////////////////////////////
if (filteredstate != filteredstatebefore){
stop = LOW;
if (filteredstate == LOW){ //// we did end a HIGH
if (highduration < (hightimesavg*2) && highduration > (hightimesavg*0.6)){ /// 0.6 filter out false dits
strcat(code,".");
Serial.print(".");
}
if (highduration > (hightimesavg*2) && highduration < (hightimesavg*6)){
strcat(code,"-");
Serial.print("-");
wpm = (wpm + (1200/((highduration)/3)))/2; //// the most precise we can do ;o)
}
}
if (filteredstate == HIGH){ //// we did end a LOW
float lacktime = 1;
if(wpm > 25)lacktime=1.0; /// when high speeds we have to have a little more pause before new letter or new word
if(wpm > 30)lacktime=1.2;
if(wpm > 35)lacktime=1.5;
if (lowduration > (hightimesavg*(2*lacktime)) && lowduration < hightimesavg*(5*lacktime)){ // letter space
docode();
code[0] = '';
Serial.print("/");
}
if (lowduration >= hightimesavg*(5*lacktime)){ // word space
docode();
code[0] = '';
printascii(32);
Serial.println();
}
}
}
//////////////////////////////
// write if no more letters //
//////////////////////////////
if ((millis() - startttimelow) > (highduration * 6) && stop == LOW){
docode();
code[0] = '';
stop = HIGH;
}
/////////////////////////////////////
// we will turn on and off the LED //
// and the speaker //
/////////////////////////////////////
if(filteredstate == HIGH){
digitalWrite(ledPin, HIGH);
tone(audioOutPin,target_freq);
}
else{
digitalWrite(ledPin, LOW);
noTone(audioOutPin);
}
//////////////////////////////////
// the end of main loop clean up//
/////////////////////////////////
updateinfolinelcd();
realstatebefore = realstate;
lasthighduration = highduration;
filteredstatebefore = filteredstate;
}
////////////////////////////////
// translate cw code to ascii //
////////////////////////////////
void docode(){
if (strcmp(code,".-") == 0) printascii(65);
if (strcmp(code,"-...") == 0) printascii(66);
if (strcmp(code,"-.-.") == 0) printascii(67);
if (strcmp(code,"-..") == 0) printascii(68);
if (strcmp(code,".") == 0) printascii(69);
if (strcmp(code,"..-.") == 0) printascii(70);
if (strcmp(code,"--.") == 0) printascii(71);
if (strcmp(code,"....") == 0) printascii(72);
if (strcmp(code,"..") == 0) printascii(73);
if (strcmp(code,".---") == 0) printascii(74);
if (strcmp(code,"-.-") == 0) printascii(75);
if (strcmp(code,".-..") == 0) printascii(76);
if (strcmp(code,"--") == 0) printascii(77);
if (strcmp(code,"-.") == 0) printascii(78);
if (strcmp(code,"---") == 0) printascii(79);
if (strcmp(code,".--.") == 0) printascii(80);
if (strcmp(code,"--.-") == 0) printascii(81);
if (strcmp(code,".-.") == 0) printascii(82);
if (strcmp(code,"...") == 0) printascii(83);
if (strcmp(code,"-") == 0) printascii(84);
if (strcmp(code,"..-") == 0) printascii(85);
if (strcmp(code,"...-") == 0) printascii(86);
if (strcmp(code,".--") == 0) printascii(87);
if (strcmp(code,"-..-") == 0) printascii(88);
if (strcmp(code,"-.--") == 0) printascii(89);
if (strcmp(code,"--..") == 0) printascii(90);
if (strcmp(code,".----") == 0) printascii(49);
if (strcmp(code,"..---") == 0) printascii(50);
if (strcmp(code,"...--") == 0) printascii(51);
if (strcmp(code,"....-") == 0) printascii(52);
if (strcmp(code,".....") == 0) printascii(53);
if (strcmp(code,"-....") == 0) printascii(54);
if (strcmp(code,"--...") == 0) printascii(55);
if (strcmp(code,"---..") == 0) printascii(56);
if (strcmp(code,"----.") == 0) printascii(57);
if (strcmp(code,"-----") == 0) printascii(48);
if (strcmp(code,"..--..") == 0) printascii(63);
if (strcmp(code,".-.-.-") == 0) printascii(46);
if (strcmp(code,"--..--") == 0) printascii(44);
if (strcmp(code,"-.-.--") == 0) printascii(33);
if (strcmp(code,".--.-.") == 0) printascii(64);
if (strcmp(code,"---...") == 0) printascii(58);
if (strcmp(code,"-....-") == 0) printascii(45);
if (strcmp(code,"-..-.") == 0) printascii(47);
if (strcmp(code,"-.--.") == 0) printascii(40);
if (strcmp(code,"-.--.-") == 0) printascii(41);
if (strcmp(code,".-...") == 0) printascii(95);
if (strcmp(code,"...-..-") == 0) printascii(36);
if (strcmp(code,"...-.-") == 0) printascii(62);
if (strcmp(code,".-.-.") == 0) printascii(60);
if (strcmp(code,"...-.") == 0) printascii(126);
//////////////////
// The specials //
//////////////////
if (strcmp(code,".-.-") == 0) printascii(3);
if (strcmp(code,"---.") == 0) printascii(4);
if (strcmp(code,".--.-") == 0) printascii(6);
}
/////////////////////////////////////
// print the ascii code to the lcd //
// one a time so we can generate //
// special letters //
/////////////////////////////////////
void printascii(int asciinumber){
int fail = 0;
if (rows == 4 and colums == 16)fail = -4; /// to fix the library problem with 4*16 display http://forum.arduino.cc/index.php/topic,14604.0.html
if (lcdindex > colums-1){
lcdindex = 0;
if (rows==4){
for (int i = 0; i <= colums-1 ; i++){
lcd.setCursor(i,rows-3);
lcd.write(line2[i]);
line2[i]=line1[i];
}
}
for (int i = 0; i <= colums-1 ; i++){
lcd.setCursor(i+fail,rows-2);
lcd.write(line1[i]);
lcd.setCursor(i+fail,rows-1);
lcd.write(32);
}
}
line1[lcdindex]=asciinumber;
lcd.setCursor(lcdindex+fail,rows-1);
lcd.write(asciinumber);
lcdindex += 1;
}
void updateinfolinelcd(){
/////////////////////////////////////
// here we update the upper line //
// with the speed. //
/////////////////////////////////////
int place;
if (rows == 4){
place = colums/2;}
else{
place = 2;
}
if (wpm<10){
lcd.setCursor((place)-2,0);
lcd.print("0");
lcd.setCursor((place)-1,0);
lcd.print(wpm);
lcd.setCursor((place),0);
lcd.print(" WPM");
}
else{
lcd.setCursor((place)-2,0);
lcd.print(wpm);
lcd.setCursor((place),0);
lcd.print(" WPM ");
}
}
One thought on “CW dekoder – Erik Clausen / OZ9VQ”
Hej…
Min version af “CW to ASCII” er væsentlig hurtigere, altså den del af programmet der converterer CW-tegn til ASCII.
Min software er kompatibel med Hjalmars kode, og kan hentes her: http://planker.dk/Projects/Arduino/Decoder/NYdecoderProjectVer1.3.txt