Wednesday, July 20, 2016

Simple iambic keyer with capacitive touch Key and integrated lcd decoder using atmega328/Arduino to practice sending CW (Morse)

Quick iambic practice keyer with arduino uno and a lcd shield

I like cw a lot and was using a straight key for most of the time. As the wrist is getting bad, started moving to automated keyers and a iambic one looks very attractive and is available in most of the transceivers. So here is a post which can be hooked up in a couple of minutes using an arduino uno (or any atmega328 board), a character lcd, a small speaker and two 470k resistors.

The idea is to use the simple and elegant touch keyer from Dimitris (SV1OBT). The keyer uses the arduino input pins to detect the touch signal. The arduino pins A1 and A2 are connected via 470k resistor to A3. The pin A1 and A2 can be connected to two small pieces of copper cladboard to form the touch key or similar to paddles. Keeping finger on one of these keys or pins produces a series of dots or dashes and moving fingers across them produce the necessary charecters and symbols. It makes it easier to key in with minimal effort. A cool video of the key can be watched on Dimitris blog (here).

I needed some way to show the letters and symbols while it is being keyed with the paddles. So i added some decoding to the keyer and added a smal lcd module to show the text. The easiest way to implemet is to use the prototyping keypad shield and an arduino uno


To generate the audio, a small speaker is connected to pin 11 (and pin 8, 9, 4, 5, 6, 7 is used by keypad shield for lcd).  The keying can be done by touching A1 or A2 pin. If both of the paddles are touched, it will alternate between dots and dashes. It can be used to key a qrp rig by adding a switching transistor /mosfet to pin13 of the arduino.

Arduino Program/Sketch

It is borrowed from dimitris code and parts of Hjalmar Skovholm Hansen OZ1JHM's excellend cw decoder.




/* Iambic keyer with lcd for cw practice
Courtesy SV1OBT and OZ1JHM
*/
#include <LiquidCrystal.h>
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // LCD display pins
const int colums = 16; 
const int rows = 2; 
int lcdindex = 0;
int line1[colums];
int line2[colums];
bool halt=false;
long startttimelow;
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};
//#define DIT_PIN 8
//#define DAH_PIN 10
//#define EXC_PIN 9
#define DIT_PIN A1
#define DAH_PIN A2
#define EXC_PIN A3
#define LED  13
#define BAUD_DURATION  80  //mSec
#define INTERBAUD_DURATION BAUD_DURATION*1
#define INTERLETTER_DURATION  BAUD_DURATION*2 //extra time after a baud
#define DIT_DURATION BAUD_DURATION
#define DAH_DURATION BAUD_DURATION*3
#define TOUCH_THRESHOLD 5 //how long to wait in uSec, before sampling the touch pin.
#define INTERWORD_DURATION BAUD_DURATION*7
enum{
    IDLE,
    DIT,
    DAH,
    PAUSE,
};
int dit,dah;
int state;
char code[20];
void readDit()
{
    digitalWrite(EXC_PIN,HIGH);
    delayMicroseconds(TOUCH_THRESHOLD);
    if(digitalRead(DIT_PIN)) dit=0; else dit=1;
    digitalWrite(EXC_PIN,LOW);
}
void readDah()
{
    digitalWrite(EXC_PIN,HIGH);
    delayMicroseconds(TOUCH_THRESHOLD);
    if(digitalRead(DAH_PIN)) dah=0; else dah=1;
    digitalWrite(EXC_PIN,LOW);
}
void setup()
{
    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();
    lcd.begin(colums, rows);
    for (int index = 0; index < colums; index++){
        line1[index] = 32;
        line2[index] = 32;
    }
    Serial.begin(115200);
    Serial.println("Serial is active");
    pinMode(EXC_PIN,OUTPUT);
    digitalWrite(EXC_PIN,LOW);
    pinMode(LED,OUTPUT);
    digitalWrite(LED,LOW);  
    state = 0;
}
void contact(unsigned char state)
{
    if(state) {
        digitalWrite(LED,HIGH);
        analogWrite(11,127); //pin 11 drives an 8 Ohm speaker
    }
    else{
        digitalWrite(LED,LOW);
        analogWrite(11,0);
    }
}
void loop()
{
    switch(state){
        case IDLE:
        if(halt & (millis() - startttimelow) >INTERWORD_DURATION){
            printascii(32);
            halt=false;
            startttimelow =millis();
        }
        readDit();
        if(dit) {
            state = DIT;
        }
        else{
            delayMicroseconds(30);
            readDah();
            if(dah) {
                state = DAH;
            }
        }
        break;
        case DIT:
        startttimelow =millis();
        contact(1);
        delay(DIT_DURATION);
        contact(0);
        delay(INTERBAUD_DURATION);
        Serial.print(".");
        strcat(code,".");
        //now, if dah is pressed go there, else check for dit
        readDah();
        if(dah){
            state = DAH;
        }
        else{
            //read dit now
            readDit();
            if(dit) {
                state = DIT;
            }
            else {
                delay(INTERLETTER_DURATION);
                Serial.print("|");
                docode();
                code[0] = '\0';
                state = IDLE;
            }
        }
        break;
        case DAH:
        startttimelow =millis();
        contact(1);
        delay(DAH_DURATION);
        contact(0);
        delay(INTERBAUD_DURATION);
        Serial.print("-");
        strcat(code,"-");
        readDit();
        if(dit){
            state = DIT;
        }
        else{
            //read dit now
            readDah();
            if(dah) {
                state = DAH;
            }
            else {
                delay(INTERLETTER_DURATION);
                Serial.print("|");
                docode();
                code[0] = '\0';
                state = IDLE;
            }
        }
        break;
    }//switch
    delay(1);
}
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);
    if (strcmp(code,".-.-") == 0) printascii(3);
    if (strcmp(code,"---.") == 0) printascii(4);
    if (strcmp(code,".--.-") == 0) printascii(6);
    halt=true;
}
void sprintascii(int asciinumber){
    Serial.println(char(asciinumber));
}
void printascii(int asciinumber){
    sprintascii(asciinumber);
    int fail = 0;
    if (rows == 4 and colums == 16)fail = -4; 
    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;
}

Thursday, December 24, 2015

Adding Rotary encoder to arduino projects- quick start

DDS with Rotary encoder and ili9341tft

Rotary encoder is a handy add-on for most of the arduino projects which will serve as a measurement instruments, signal generator, direct digital synthesis (dds) vfo and several other projects.  Adding an encoder to your project is pretty easy. In this post i will take in to it quickly. This tutorial is based on a simple and inexpensive encoder with a click button. The click on the encoder is useful for menu selection etc and the rotation is useful for changing the values. The main purpose of this post is to help the addition of an encoder to my dds cum vswr analyser project -  A simple standalone antenna analyzer based on arduino and ad9850 with ili9341tft

Connecting rotary encoder to arduino projects

The encoder has three pins on one side  and two on the other side. The two pins are similar to a click button and can be used as a simple switch. When we push the shaft of the encoder, it closes the switch. Rotation of the encoder triggers the pins on the other side. There are three pins and the central one is connected to ground and the other two pins goes to D2 and D3 on the arduino. If we use the interrupts on the arduino/atmega328, it is important to connect this to hardware interrupt pins on the arduino board. In other models of arduino board see this link to identify the valid interrupt pins which are usable.

BoardDigital Pins Usable For Interrupts
Uno, Nano, Mini, other 328-based2, 3
Mega, Mega2560, MegaADK2, 3, 18, 19, 20, 21
Micro, Leonardo, other 32u4-based0, 1, 2, 3, 7
Zeroall digital pins, except 4
Dueall digital pins

When we turn the encoder, it triggers the interrupt and the code blocks inside the ISR(PCINT2_vect)
function will be triggerd. See the example code to learn more

A library make things easier and can be downloaded here Rotary Library (download and extract to library folder - instruction )

Click button

As mensioned earlier it is a simple push button. To add more possibilities like single click , double click etc we use a library called onebutton (more info) and Download (install instruction)

Attached below is a code which uses an ili9341 tft with rotary. You can comment out all lines starting with tft and can test the library and encoder with serial output. Setting up and wiring the tft is same as in this post - Quickly test an ILI9341 TFT display with an arduino [quick test]

Source Code


#include <stdint.h>  
#include <TFTv2.h>  
#include <SPI.h>  
#include <OneButton.h>  
#include <Rotary.h>  
 Rotary r = Rotary(3, 2);  
 OneButton button(A0,true);  
 void setup()  
 {  
   Tft.TFTinit();  
   Serial.begin(115200);  
   //rotory interrupt  
   PCICR |= (1 << PCIE2);  
   PCMSK2 |= (1 << PCINT18) | (1 << PCINT19);  
   sei();  
   // click on the encoder  
   button.attachDoubleClick(doubleclick);  
   button.attachClick(singleclick);  
   button.attachPress(longclick);  
   Serial.println("Testing Rotary with ili9341 tft");  
 }  
 void loop()  
 {  
   // keep watching the push button:  
   button.tick();  
   // You can implement other code in here or just wait a while  
 }  
 void doubleclick() {  
   Tft.fillScreen(0, 320, 0, 220,BLUE) ;  
   Tft.drawString("DOUBLE",60,50,4,RED);  
   Serial.println("DOUBLE");  
 }  
 void singleclick() {  
   Tft.fillScreen(0, 320, 0, 220,BLUE) ;  
   Tft.drawString("SINGLE",60,50,4,RED);  
   Serial.println("SINGLE");  
 }  
 void longclick() {  
   Tft.fillScreen(0, 320, 0, 220,BLUE) ;  
   Tft.drawString("LONG",60,50,4,RED);  
   Serial.println("LONG");  
 }  
 ISR(PCINT2_vect) {  
   unsigned char result = r.process();  
   if (result == DIR_NONE) {  
     // do nothing  
   }  
   else if (result == DIR_CW) {  
     Tft.fillScreen(0, 320, 0, 220,BLUE);  
     Tft.drawString("CLOCK",60,50,4,RED);  
     Serial.println("CLOCKWISE");  
   }  
   else if (result == DIR_CCW) {  
     Tft.fillScreen(0, 320, 0, 220,BLUE);  
     Tft.drawString("COUNTERCLOCK",60,50,4,RED);  
     Serial.println("COUNTERCLOCK");  
   }  
 }