We posted the video above of the FlexRadio 6400 streaming it's SmartSDR output to a Tesla on our Twitter feed and had several ham radio operators ask; "How did you do that"? Well, it's a lot easier than you may think.
-----
Most hams never throw anything away and will find the materials needed to pull this off laying around their ham shack:
After you have gathered the materials, it's easy. Just go to the Dead Simple Screen Sharing site on the PC, click the button for the share link, then type that link into the browser on the Tesla. The Dead Simple Screen Sharing site really is 'dead simple'. I wouldn't suggest it for on-line banking, but for less critical applications it fits the bill.
-----
The stand for the FlexRadio Maestro is bulky and doesn't travel well. FlexRadio does make folding feet to take care of this, but that solution is a hundred bucks. We didn't need a less bulky travel stand all the time and we didn't want to part with the cash. So.... we printed one.
-----
Works great! Design file on Thingiverse.
-----
We had originally printed this Morse Code key to operate in "Cootie" mode. You can read about that here. It turns out using a Cootie key is fun and easy on the wrist, but..... learning to operate a Cootie key does take some practice. That said, the automotive spark plug feeler gauge gave such an effortless feel we decided to make a small modification and create a Single Lever Paddle from the original design.
-----
This mod allows usage with an electronic keyer in three terminal mode. The two terminals on the left can be shorted to quickly switch into Cootie mode.
Hardware parts list:
Qty 1: 0.81mm / 0.032 in feeler gauge (hack saw blade also works)
----- CW or Morse Code is a way ham radio operators send messages with a simple tone of two durations. It was a
must before the days of wireless voice communications and still remains
popular because it is a fun challenge.
There are many online tools that will convert text into Morse Code. However, a Google AIY Voice Bonnet came into our possession and we thought we could take that another step by creating a "Speech to Morse" translator that would actually send code over the air on the ham radio bands from spoken word.
To use the rig, you don't need a ham radio license unless you connect to an actually ham radio transmitter; which is kinda the whole point. My quick ham radio advertisement: Get a FCC Amateur Radio license; it's not hard and is an extremely interesting hobby. BTW, learning Morse Code is no longer a requirement.
-----
When we were given the AIY Voice Bonnet we really didn't even know what it did. We knew it needed a Raspberry PI and it could act somewhat like a Google Voice Assistant. We had a Raspberry PI Zero handy and after some Google search engineering we found a disk image file to get us started.
Like a lot of internet projects APIs can change and documentation can be conflicting, we finally got the RasPI and Voice Bonnet to talk to the web after some effort. A full blown Google Voice Assistant this thing is NOT; if you want one of those then BUY, don't BUILD. After leaning into the AIY GitHub for knowledge we were finally rewarded with success.
-----
The connections are easy and just follow the AIY GitHub to wire the ACTIVATE BUTTON. We used IO Pin 16 on the RasPI for the relay control because it wasn't used by the AIY Voice Bonnet. The AIY Voice Bonnet has four GPIO pins on board, but we could never get them to work. Here's the summary:
-----
And here is the rig in action. The Python code to make it run is below.
Demo at 20 WPM:
Demo at 12 WPM:
-----
Thanks and 73! Here is our Python source code:
#!/usr/bin/env python3
#
# July 2020
# Voice to Morse Code Translator
# WhiskeyTangoHotel.Com
#
# RaspberryPI Zero and AIY Voice Bonnet.
#
# Use Google Voice AIY to convert speech to text.
# Read that text file created and convert the text.
# Turn that text into to Morse Code "." and "-".
# Close a relay to transmit it over the air with a ham radio
#
#
# BTW, CW is fun to learn.
# * --... / ...-- // --... / ...--
# Some program set up and Define some variables
import os # needed to delete the text file after reading
import time # needed for delays
from datetime import datetime # need to calc run time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM) # Use BCM I/O board pin numbers
SendingLine = 0 # this is the text file line we will send
StartCW = 0 # Char position to start sending from the parsed line
EndCW = 0 # Char position to stop sending from the parsed line
keyer = 16 # Pin where the keyer relay is connected
GPIO.setup(keyer, GPIO.OUT)
GPIO.output(keyer, GPIO.LOW) # make certain we are not keyed down
# Load the CW database 'dits' and 'dahs' for each Alphanumeric. Boring....
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 = "--.."
period = ".-.-.-"
zero = "-----"
one = ".----"
two = "..---"
three = "...--"
four = "....-"
five = "....."
six = "-...."
seven = "--..."
eight = "---.."
nine = "----."
# WPM spacing calculated using the "Paris Standard"
# Per Morse code convention: 1 Dah = 3 Dits, LETTERS are spaced by one Dit, WORDS are spaced by one Dah.
WPM = 20.0 # Don't leave off the ".0" or WPM will be an integer (we want it to float)
Dit = 1.200 / WPM
Dah = Dit * 3.0
between_ditdah_spacing = Dit
between_character_spacing = Dit * 2
between_word_spacing = Dit * 3
"""Calls to Google Assistant GRPC recognizer."""
# We use AIY in the cloud to do the speech to text processing. The RaspberryPI just can't do it as good.
# Copyright 2017 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import argparse
import locale
import logging
import signal
import sys
from aiy.assistant.grpc import AssistantServiceClientWithLed
from aiy.board import Board
def volume(string):
value = int(string)
if value < 0 or value > 100:
raise argparse.ArgumentTypeError('Volume must be in [0...100] range.')
return value
def locale_language():
language, _ = locale.getdefaultlocale()
return language
with Board() as board:
assistant = AssistantServiceClientWithLed(board=board,
volume_percentage=args.volume,
language_code=args.language)
while True:
logging.info('Press button to start conversation...')
board.button.wait_for_press()
logging.info('Conversation started!')
assistant.conversation()
# Voice to CW routine starts here are the dim RED button is pressed
# Button will turn bright RED while the rig is listening
# Button will dim blink RED when ready for next speech input
# Read the text file created by the speech to text and get it ready to send as CW
# Look for "Trigger Lines" in the text file. Parse and send the line above it
Trig1 = "Recording stopped."
Trig2 = "End of audio request detected"
Trig3 = "Updating conversation state"
i = -1
Get_file = "keywhat.txt" # this is the text file created from AIY that we are going to process
mylines = []
with open (Get_file, 'rt') as myfile: #'rt' means 'read text data'
for line in myfile:
i = i + 1
mylines.append(line)
# If we find one to the Trigger lines, we have passed the voice text by one line in the file
if mylines[i].find(Trig1) and mylines[i].find(Trig2) and mylines[i].find(Trig3)!= -1 :
SendingLine = i-1
# Strip out only the text we want to send over the air
StartCW = int((mylines[SendingLine].find('"')) + 1)
EndCW = int(len(mylines[SendingLine]) - 3)
# print StartCW, EndCW # used for debug
SendasCW = Send_text[StartCW:EndCW] # this is the text sent as CW
#SendasCW = "test" # used for debug
SendasCW = (SendasCW.upper()) # and we always make certain it is all caps
# We have the text. Now parse it and send each letter as CW
print ("Going to send: " + SendasCW + " at " + str(int(WPM)) + " WPM.")
print ("-------------------------------------------")
L = len(SendasCW)
t0 = datetime.now()
for x in range(0,L): # Start character converstion to dit and dahs
Text_substring = SendasCW[x:x+1]
print ("Sending: " + Text_substring) # for debug
if Text_substring == "0" : Text_substring = zero
if Text_substring == "1" : Text_substring = one
if Text_substring == "2" : Text_substring = two
if Text_substring == "3" : Text_substring = three
if Text_substring == "4" : Text_substring = four
if Text_substring == "5" : Text_substring = five
if Text_substring == "6" : Text_substring = six
if Text_substring == "7" : Text_substring = seven
if Text_substring == "8" : Text_substring = eight
if Text_substring == "9" : Text_substring = nine
if Text_substring == "." : Text_substring = period
if Text_substring == "A" : Text_substring = A
if Text_substring == "B" : Text_substring = B
if Text_substring == "C" : Text_substring = C
if Text_substring == "D" : Text_substring = D
if Text_substring == "E" : Text_substring = E
if Text_substring == "F" : Text_substring = F
if Text_substring == "G" : Text_substring = G
if Text_substring == "H" : Text_substring = H
if Text_substring == "I" : Text_substring = I
if Text_substring == "J" : Text_substring = J
if Text_substring == "K" : Text_substring = K
if Text_substring == "L" : Text_substring = L
if Text_substring == "M" : Text_substring = M
if Text_substring == "N" : Text_substring = N
if Text_substring == "O" : Text_substring = O
if Text_substring == "P" : Text_substring = P
if Text_substring == "Q" : Text_substring = Q
if Text_substring == "R" : Text_substring = R
if Text_substring == "S" : Text_substring = S
if Text_substring == "T" : Text_substring = T
if Text_substring == "U" : Text_substring = U
if Text_substring == "V" : Text_substring = V
if Text_substring == "W" : Text_substring = W
if Text_substring == "X" : Text_substring = X
if Text_substring == "Y" : Text_substring = Y
if Text_substring == "Z" : Text_substring = Z
# Now send the dit/dahs for the Self Test Callsign Character
CW_length = int(len(Text_substring))
time.sleep(between_character_spacing) # a delay between characters
for j in range(0, CW_length): # Send tones and blink LED in CW
Dit_or_Dah = Text_substring[j:j+1]
# HIGH closes a relay connect to the ham radio. LOW opens the relay
# This relay closure is the same as pushing down on a straight key
if (Dit_or_Dah) == "." : # process a Dit
print (Dit_or_Dah)
GPIO.output(keyer, GPIO.HIGH)
time.sleep(Dit) # a delay of "1 Dit" between dit/dahs in a character
GPIO.output(keyer, GPIO.LOW) #make certain we are not keyed down
time.sleep(between_ditdah_spacing) # a delay bewteen characters
# end if process a Dit
if (Dit_or_Dah) == "-" : # process a Dah
print (Dit_or_Dah)
GPIO.output(keyer, GPIO.HIGH)
time.sleep(Dah) # a delay of "1 Dit" between dit/dahs in a character
GPIO.output(keyer, GPIO.LOW) #make certain we are not keyed down
time.sleep(between_ditdah_spacing) # a delay bewteen characters
# end if process a Dah
if (Dit_or_Dah) == " " : # process a SPACE (delay between words)
print ("<SPACE>")
GPIO.output(keyer, GPIO.LOW) #make double certain we are not keyed down
time.sleep(between_word_spacing) # a delay of "3 Dit" between words
# end if process a SPACE
GPIO.output(keyer, GPIO.LOW) #make absolutely certain we are not keyed down
t1 = datetime.now() - t0 # how long did it take to send?
print ("-------------------------------------------")
print ("Completed in " + str(t1) + " seconds at " + str(int(WPM)) + " WPM to send:")
print (SendasCW)
# close and delete voice to text cloud file to get ready for the next voice decode
time.sleep(1) # file handling delay, probably not needed
myfile.close()
os.remove(Get_file)
time.sleep(1) # file handling delay, probably not needed
myfile = open(Get_file, "w")
----- FlexRadio makes controlling the ON/OFF state of their ham radio rigs simple. Basically a "short" across the RCA jack on the back labeled REM ON will start the rig and an "open" will power the rig down.
-----
Here's how we did it for the FlexRadio 6400 (video below).
- From SmartSMR go to Setting / Radio Setup.... (I wish SmartSDR would just use CTRL R for this and similar for other menu commands). In the screen make "Remote on: Enabled":
- Now you need some hardware. Most hams will have plenty of the wire, wall warts, and connectors gathering dust that are in the diagram below. However, two items may need to be purchased. Don't worry; they're cheap:
- This 120VAC outlet that can be controlled via your smart phone. Cost from Amazon ~$10.
- A simple relay to "short/open" the REM ON labeled RCA jack on the back of the rig. Cost for three was ~$8 from Amazon.
-----
You will need to download an app for your smart phone to control the AC outlet. Then hook things up like this (click to enlarge image):
-----
Here's vid of the whole enchilada in action:
-----
It took us more time to document the project than to actually do it. It's simple. Hope to catch you on the air. dit dit.
----- May 2020 Update: This key was gifted to KK5PJ who was extremely patient with me in my very first CW QSO and the many practice sessions that followed. THX.
----- CW or Morse Code is a way ham radio operators send messages with a simple tone of two durations. It was a must before the days of wireless voice communications and still remains popular because it is a fun challenge. Also, due to its efficiency messages can get through with limited power or in poor conditions.
-----
There are no shortage of morse code key designs, but in general they fall into a few categories that are discussed here. We decided to DIY a "cootie" or "sideswiper" key and used our 3D printer to produce this design from Thingiverse.
Hardware parts list:
Qty 1: 0.81mm / 0.032 in feeler gauge (hack saw blade also works)
-----
The Icom IC7300 is a fantastic and very popular ham radio HF rig. One thing that we didn't care for is if you utilize the transmit memory functions for CW, phone, etc. half of the touch screen was dedicated to the memory location buttons.
The IC7300 has other features that are a much better use of the touch screen real estate than displaying the memory location buttons. A few examples are...
Making the Band Scope larger:
Displaying the Audio Scope:
Or getting more information on how the rig is behaving:
-----
In a RTFM moment, I discovered that Icom had a simple circuit that allows the operator to use an external switch keypad to control the top four memory locations.
-----
A search of our spare parts bin uncovered that all of the materials needed were "in stock". We set the 3D printer on a mission to print a project box while the soldering iron connected everything up. Soldering took about 10 minutes; the 3D print a few hours. The end result is a nicely done external keypad for the memories. We like it; we like it a lot!
-----
On-line shopping has changed the way goods are acquired. For most packages it is not necessary to be around for the dropoff. But... for the important stuff it is. Those packages always seem to show up right when you are in the backyard for mere seconds causing that frustrating "Personal Signature Required for Delivery" note on the door. This is our DIY effort to solve that problem.
-----
Needed:
- ESP8266 (microcontroller with WiFi)
- 1M ohm resistor
- reed switch
- IFTTT account (using the WebHook applet)
- wire and stuff
- fundamental knowledge on Arduino sketches (simple code edits).
-----
The connections are simple:
----
And the rig will look something like this:
-----
A 1M ohm resister connected from 3.3V to the A0 [ADC] input on the ESP8266 keeps the A0 reading at maximum value. The normally open reed switch is placed right on top of the coils that activate the doorbell. These coils act like that electromagnet you build in grade school to ring the doorbell and cause the reed switch to close. This drops the impedance to the 1M resistor and changes the ADC value on the A0 pin for detection of the doorbell. Then the SMS is sent to your phone and a Google Drive Sheet is updated.
-----
-----
The sketch you need to upload to the the ESP8266 looks like this:
```````````````````````````````````````````````````````````````````````````````
/*
* FEB2020
* Door Bell Monitoring
* WhiskeyTangoHotel.Com
*
* Build details at:
* http://www.whiskeytangohotel.com/2020/02/esp8266-doorbell-sends-sms-and-updates.html
*
* On Doorbell ring:
* Logs to Google Drive (as an appending spreadsheet)
* Sends SMS to cell phone
*
* ESP8266 uC
*/
// For the Wireless
#include <ESP8266WiFi.h>
// WiFi Connection Information
const char* ssid = "yourssid"; // PRIVATE: Enter your personal setup information.
const char* password = "yourwifipassword"; // PRIVATE: Enter your personal setup information.
// IFTTT Information for WebHook widget
String MAKER_SECRET_KEY = "yourIFTTTprivatekey"; // PRIVATE: Enter your personal setup information. Your IFTTT Webhook key here
String TRIGGER_NAME_google_drive = "googlebell"; // this is the Maker IFTTT trigger name for google drive spreadsheet logging
String TRIGGER_NAME_SMS = "doorbell"; // this is the Maker IFTTT trigger name to send SMS.
const char* host = "maker.ifttt.com";
String url_SMS; // url that gets built for the IFTTT Webhook sending SMS
String url_google_drive; // url that gets built for the IFTTT Webhook logging to google drive spreadsheet
String Status ="**_Starting_on:"; // Status payload for Google Sheet. We log all starts and reboots
// Define and set up some variables
int sensorValue = 0; // reading from A0. This pin detects the doorbell (ADC reading between 0-1024)
// Define pins
const int led = 2; // Blue on board LED is on PIN2. Active LOW. Blinks it between reads
const int bell = A0; // analog input for the doorbell transducer. 1MOhm on board in // with 1MOhm from the reed switch pickup
// Program control variables
int Seconds_dwell_after_detect = 8; // Prevents logging flood and debounce. Sensor reads, LED flashing, etc.
int logging = 1; // If 1 then SMS and log to cloud. Any other value (0) turns it off.
void setup(){ // This is run once.
pinMode(led, OUTPUT); // set up the onbaord LED pin as an output.
Serial.begin(115200); // turn on the serial monitor for debug
// wait until serial port opens for native USB devices
while (! Serial) {
delay(10);
}
// Is the WiFi working?
WiFi.begin(ssid, password);
Serial.println("");
// Wait for connection
while (WiFi.status() != WL_CONNECTED) {
Serial.print("Trying to connect to ");
Serial.print(ssid);
Serial.print(" on ");
Serial.println(WiFi.localIP());
for (int x = 0; x < 20; x++) { //
digitalWrite(led, !digitalRead(led)); // toggle state of the on board blue LED. Shows program is trying to WiFi connect
//Serial.println("Server Start blink loop....");
delay(5); // Delay so short the LED looks like it is always on
} // endfor WiFi blink connect
}
Serial.print("Connected to ");
Serial.println(ssid);
Serial.println(WiFi.localIP());
for (int x = 0; x < 10; x++) { // 5 slow LED blinks to slow WIFI Connected.
digitalWrite(led, !digitalRead(led)); // toggle state of the on board blue LED.
Serial.println("WIFI is Connected....");
delay(500); } // endif for WIFI Connected blink
// Use WiFiClient class to create TCP connections for WiFi logging
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
Serial.println("connection failed"); // Boo!!!
return;
}
// Trigger the IFTTT Webhook Channel to update a Google sheet with the activity of the server starting/restarting
// This can help log power outs, etc. For first run we defined String Status for identify a startup condition.
// Create the request for IFTTT google drive
url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
Serial.println (url_google_drive);
Serial.println(" ");
// This sends the request to the IFTTT server
client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
delay(500); // Delay for web traffic; maybe not required.
// Read all the lines of the reply from server and print them to Serial
while(client.available()){
String line = client.readStringUntil('\r'); }
String Status = "Payload_String_for_Google_Sheet";
}
void loop(){ // Loop forever or until the Dallas Cowboys win a Super Bowl
// Read the A0 pin and post based on the delay values set above.
// The blue onboard LED will fast blink while polling
sensorValue = analogRead(bell);
//Serial.println (sensorValue); //debug only
// Fast Blink Blue LED while waiting for doorbell press
digitalWrite(led, !digitalRead(led)); // toggle state of the on board blue LED. Shows program is running
Serial.println("Waiting for DoorBell....");
delay(50);
// Use WiFiClient class to create TCP connections for IFTT
WiFiClient client;
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
return;
}
if (sensorValue < 200 || sensorValue > 300) { // A0 values between 200-300 are normal. Increases (apprx doubles) when DB is pressed
Serial.println("----------Doorbell Dectected----------");
if (logging == 1) { // is logging turned on? Non "1" is for debug... Typically would be set = 1
Serial.println("***** Logging is ON *****");
// This must be run before any IFTTT webhook
const int httpPort = 80;
if (!client.connect(host, httpPort)) {
Serial.println("connection failed");
return;
}
// Set up IFTTT Webhook Channel to update a Google sheet with the activity.
Status ="__Doorbell_on:";
url_google_drive = "https://maker.ifttt.com/trigger/" + TRIGGER_NAME_google_drive + "/with/key/" + MAKER_SECRET_KEY + "?value1=" + String(Status);
client.print(String("POST ") + url_google_drive + " HTTP/1.1\r\n" +
"Host: " + host + "\r\n" +
"Connection: close\r\n\r\n");
Serial.println(" ");
Serial.println("IFTTT url payload to Google Drive is:");
Serial.println(url_google_drive);
Serial.println(" ");
} else { // We are not sending to IFTTT. Debug mode
Serial.println("Logging is OFF.");
Serial.println(" ");
} // endif/else logging
for (int x = 0; x < Seconds_dwell_after_detect*2; x++) { // DoorBell dectected. Pause and LED flash for visual acknowledgement
digitalWrite(led, !digitalRead(led)); // toggle state of the on board blue LED
Serial.println("Doorbell dectected, Now in BLINK Loop....");
delay(500); } // endfor DoorBell Dwell loop
} // end doorbell A0 pressed
} // end of endless loop
-----
That's it and thanks for stopping by!!!
-----
Note: As presented this project requires a FCC Amateur Radio license. Amateur Radio is about experimentation. Even still you must be versed in the band plan,
stay away from local repeater stations, stay in accordance to FCC
Amateur Radio rules in FCC Section 97, and probably a few other things. That said, let's continue....
-----
There was talk between the CW operators at the club to have some CW practice sessions or a CW Net on 2M FM. We will probable never do that (for several reasons) but the idea of converting a ham radio HT into a CW transceiver was an interesting thought project.
-----
Really not much explanation needed; just connect as shown above. This project uses an electronic keyer, but a simple straight key/oscillator combo could also be used. Oh... and you will also need to know Morse Code.
-----
Here's the rig in action:
-----
Note: As presented this project requires a FCC Amateur Radio license. Amateur Radio is about experimentation. Even still you must be versed in the band plan,
stay away from local repeater stations, stay in accordance to FCC
Amateur Radio rules in FCC Section 97, and probably a few other things. Also, you will be a guest on a remote ham radio station owned by someone that is nice enough to let you enjoy their (often very impressive) rig. Adhere to the guidelines they post when you log into their station. It's their station; they make the rules. Be a good guest and everyone has fun! That said, let's continue....
-----
We have been wanting to experiment with remote ham radio operation for awhile now. This weekend band conditions were poor so I was dreaming of having a more expensive and better amp/antenna setup. This was a great excuse to give RemoteHams.Com a try. After signing up and installing the Remote Client software I was presented with 100s of rigs to choose from all over the World. I was amazed.
-----
I somewhat randomly selected the rig of N4GYN. This was great because I was greeted by Ray and after a brief online chat he verified my license and gave me permission to transmit!
-----
Ray's station lets you operate phone using a headset plugged into your PC's USB port. The headset part for phone is easy; most any USB headset will do. However, we only had an interest in operating CW (Morse code). Ray's rig lets you do that from your PC keyboard by just typing in what you wanted to send. But, we wanted to use a traditional key.
-----
Here's how we did it: You can set this up for paddle or straight key operation. Since we had a cheap straight key not being used we went with that. What you see in the image at the top of the page is the straight key wired into a USB to RS232 serial port adapter like so:
with the Remote Rig setting as:
-----
On our very first CQ we had our very first remote station manual CW key QSO with NA5N who was 3W QRP from Colorado!!! If you are wishing you had a better station or you have QTH restrictions consider giving RemoteHams.Com a try.
-----
73!
-----
This is primarily a variant of the "Four Letter Word Clock" that we modified after getting tired of Daylight Savings Time making us push a few buttons twice a year to correct the time. Actually, the project is more entertaining now.
The "Four Letter Word Clock" project page provides the BOM and schematics. We left the Real Time Clock (RTC) implementation in the source code below, but it is not needed as a RTC is not used.
We were able to put 1,002 four letter words into the EPROM, Since we display two four letter words selected at random that is over a million possible combinations. The PICAXE random number generator is seeded by doing an analog read on an ADC open pin; basically we read 'noise' and use that for the seed. It's pretty random, but I don't expect the method to be used in Vegas slot machines.
-----
It's interesting to see the combinations produced by the rig. Bored? Watch this 4 minute demo:
It's a fun build.
-----
The PICAXE source code is:
#rem
*******************************
***** www.WhiskeyTangoHotel.Com *****
*******************************
Project Name: 4Letter Word Clock
Start Date: August 2012
Program Rev History:
March 2019. Now called "4Letter Word by 4Letter"
Tired of the simple two time/year DST change
so we changed to format to display to random 4 letter
words side by side. The words are now random and not sequintial.
#picaxe 18m2
#no_data 'do not read internal 18M2 EEPROM
dirsc = 010111 ;c0, c1, c2, c4 as output
symbol clock = c.0 ;Clock output pin
symbol dio = c.1 ;Data input output pin
symbol strobe = c.2 ;Strobe output pin
' s1 thru s8 are the tact swithes under the single RED/Green LEDs
symbol s1 = bit16 ;b2 'to set hours
symbol s2 = bit17 ;b2 ' to set minutes - both to set seconds
symbol s3 = bit18 ;b2
symbol s4 = bit19 ;b2
symbol s5 = bit20 ;b2
symbol s6 = bit21 ;b2
symbol s7 = bit22 ;b2
symbol s8 = bit23 ;b2 'toggle to turn on and off the ticker relay
symbol dataio = b0 ;w0 and bit 0 to bit 7
symbol pad = b1 ;w0 and bit 8 to bit 15
symbol iobuf = w0 ;b0, b1
symbol keys = b2 ;bit16 to bit 23
symbol fixaddr = b3 ;start address for DE display
symbol Segment4LEFT = b4 ;Rightmost 7 seg, LEFT Side
symbol Segment4RIGHT = b5 ;Rightmost 7 seg, RIGHT Side
symbol Segment2LEFT = b6 ;Leftmiddle 7 seg, LEFT Side
symbol Segment3LEFT = b7 ;Rightmiddle 7 seg, LEFT Side
symbol Segment2RIGHT = b8 ;Leftmiddle 7 seg, Right Side
symbol Segment3RIGHT = b9 ;Rightmiddle 7 seg, Right Side
symbol Segment1LEFT = b10 ;Leftmost 7 seg, LEFT Side
symbol Segment1RIGHT = b11 ;Leftmost 7 seg, Right Side
symbol char = b12
symbol bank = b13
symbol tmpry = b14
symbol dispbrit = b15
symbol autoaddr = b16
symbol readmode = b17
symbol tmpry2 = b18
symbol EEPROMChar = b19
'w10 (b20/21) = used to read var from EEPROM
symbol LEDTicker = b22
symbol seconds = b23 ' vars for RTC
symbol minutes = b24
symbol hours = b25
symbol blinky = b26 'for RTC 010000 would Enable output at 1Hz blink rate. 000000 is no blink
symbol junkread = b27 'used to read/write RTC day, month, year, date. Also as a temp var in time set adjust routines
fixaddr = $c0
dispbrit = $88 '$88 (136DEC) min bright. $8F (143DEC) max bright
autoaddr = $40
readmode = $42
init:
high strobe ;Ensure strobe is initially high
gosub clearchars ;Clear all characters
blinky = 010000 ' 010000 would Enable output at 1Hz blink rate, start w/ relay click ON.. 000000 is no blink.
' Set the time on the DS1307 RTC
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
pause 50
'\/ \/ \/ \/ Un_REM THESE LINES (BELOW) IF SETTING UP A NEW RTC \/ \/ \/ \/
#rem
' Set the RTC chip time
; write time and date e.g. to 11:59:00 on Thurs 25/12/03
'; would be "writei2c 0,($00, $59, $11, $03, $25, $12, $03, 010000)"
' readi2c 0, (b0,b1,b2,b3,b4,b5,b6,b7) reads back the data
let hours = $19 ; 01-12 Note all BCD format
let minutes = $11 ; 00-59 Note all BCD format
let seconds = $10 ; 00-59 Note all BCD format
; program does not use for we use seconds. Set manually in the write statement
' for SQ Wave out on RTC. Last val: 010000 would Enable output at 1Hz blink rate. 000000 is no blink
;Segment Values 0-9 = ( 0 , 1, 2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
' 10-19 = A , b , C , d , E , F , g, H, i, J,
' 20-29 = K, L, M, N, o, P, q, r, S, T,
' 30-35 = U, V, W, X, y, Z ,
' 36-44 = segA, segB, segC, segD, segE, segF, segG, dp, off)
'the 'gosub display' routine expect 8 values; SegmentxLEFT and SEGMENTxRIGHT coded as
'lookup values shown in the rem above.
' At Startup turn the clicky relay on. S8 button will turn it off
blinky = 010000
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
main:
if s1 = 1 or s2 = 1 or s8 = 1 then 'setting the clock time or relay ticker
if s1 = 1 and s2 = 0 then 'setting hours
junkread = junkread + 1
if junkread > 23 then
junkread = 0
end if
lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23), hours
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 10
endif ' s1 = 1, setting hours
if s2 = 1 and s1 = 0 then 'setting minutes
junkread = junkread + 1
if junkread > 59 then
junkread = 0
end if
lookup junkread, ($00,$01,$02,$03,$04,$05,$06,$07,$08,$09,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59), minutes
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 10
endif 's2 = 1, setting minute
if s1 = 1 and s2 = 1 then 'reset seconds to 00
seconds = $00
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 10
endif 'settin seconds to zero
if s8 = 1 then ' turn on/off the clicking relay
'read the RTC to dected the seconds for the write to RTC below keeps them accurate
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
pause 10
if blinky = 010000 then 'blinky from RTC is ON and clinking the relay. turn it OFF
blinky = 000000
else 'blinky from RTC is OFF and NOT clinking the relay. turn it ON
blinky = 010000
end if
i2cslave %11010000, i2cslow, i2cbyte
writei2c 0, (seconds, minutes, hours, 01, 01, 01, 01, blinky)
pause 500
end if
else ' not settign the clock, check for brightness adjust and run as normal; so read a new 4letter word
if s7 = 1 then 'increase brightness
dispbrit = 140 'other values cause random LED7 behavior
end if
if s6 = 1 then 'decrease brightness
dispbrit = 136 ' 136 is min bright
end if
sertxd (#dispbrit, 13,10)
'Get SegmentxLEFT values for clock by reading the RTC
i2cslave %11010000, i2cslow, i2cbyte ; set PICAXE as master and DS1307 slave address
readi2c 0,(seconds, minutes, hours, junkread, junkread, junkread, junkread, blinky)
pause 10
gosub ReadEEPROM ' read the four letter word. These are loaded into SegmentxRIGHT vars
gosub Ticker 'ticks thru the R/G LEDs to show seconds
endif
' THIS IS WHERE SEGMENT LEFT IS LOADED WITH THE TIME.
' CHANGE IT TO A WORD
'
'Segment1LEFT = hours & %11110000 / 16 'BCD so shift upper 4 bits to lower 4 bits
'Segment2LEFT = hours & 001111
dataio = dataio + fixaddr ;LEDs are at odd addresses 1 to 15
junkread = dataio 'used to turn off LED later in this sub
low strobe
gosub sendchar
LEDTicker = LEDTicker + 1
if LEDTicker = 2 then
LEDTicker = 1
end if
dataio = LEDTicker 'Light the LEDs. 1 = RED. 2 = GREEN. 3 = R/G
gosub sendchar
high strobe
'Turn off LED here
dataio = junkread
low strobe
gosub sendchar
dataio = 0 '0 turns off the currently selected LED
gosub sendchar
high strobe;
dataio = dispbrit ;Display control on, brightness level
low strobe ;Strobe low
gosub sendchar
high strobe ;Strobe high
return 'Ticker
ReadEEPROM:
'24LC256 EEPROM is loaded with 987 four letters words (3948 characters)
'Each character is an address from 0 to 3947
'readi2c addrs, (charvalue)
i2cslave %10100000, i2cslow, i2cword ; set PICAXE as master and DS1307 slave address
'Read and Translate the char read from the EEPROM for the lookup(.,...), dataio command.
'Read the EEPROM letter then subtract 87 from that ASCII value for the "lookupchar" sub. Examples:
'ASCII value for a = 97; Lookup in this program value is 10. So, 97 - 87 = 10
'ASCII value for j = 106; Lookup in this program value is 19. So, 106 - 87 = 19
'ASCII value for k = 122; Lookup in this program value is 35. So, 122 - 87 = 35
'Last word in EEPROM is YURT and starts at Location 4008
touch16 B.7, b20 'lower bits w10
touch16 B.7, b21 'w10 upper
'RANDOM number for w10
w12 = w10 // 1003 ; scale it to 0-1002 (4 * 4008 = 4008)
w10 = w12 * 4 ; max is YURT at 4008 start
display: ;Displays data on the 7 seg displays, using 2 blocks of 4 digits
bank = 0 ;LEFT Side: First block of digits
dataio = fixaddr + bank + 0 ;Set Leftmost 7 seg, LEFT Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment1LEFT ;Leftmost 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, LEFT Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment2LEFT ;Leftmiddle 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ; End of data - Strobe high
dataio = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, LEFT Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment3LEFT ;Rightmiddle 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 6 ;Set Rightmost 7 seg, LEFT Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment4LEFT ;Rightmost 7 seg, LEFT Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
'RIGHT BANK
bank = 8 ;RIGHT Side: Second block of 4 digits
dataio = fixaddr + bank + 0 ;Set Leftmost 7 seg, Right Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment1RIGHT ;Leftmost 7 seg, Right Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 2 ;Set Leftmiddle 7 seg, Right Side write address
low strobe ;Strobe low
gosub sendchar
char = Segment2RIGHT ;Leftmiddle 7 seg, Right Side
gosub lookupchar
gosub sendchar
high strobe ;End of data - Strobe high
dataio = fixaddr + bank + 4 ;Set Rightmiddle 7 seg, Right Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment3RIGHT ;Rightmiddle 7 seg, Right Side
gosub lookupchar
gosub sendchar
high strobe ; End of data - Strobe high
dataio = fixaddr + bank + 6 ;Set Rightmost 7 seg, RIGHT Side write address
low strobe ; Strobe low
gosub sendchar
char = Segment4RIGHT ;Rightmost 7 seg, RIGHT Side
gosub lookupchar
gosub sendchar
'-----------------
'must refresh dispbrit each time
dataio = dispbrit ;Display brightness level. $88 (136DEC) min bright. $8F (143DEC) max bright
low strobe ; Strobe low
gosub sendchar
high strobe ; Strobe high
clearchars: ;Clear LEDs and 7 seg displays. ALL LEDS OFF. Segs and LEDs
dataio = autoaddr ; Data mode auto increment
low strobe ; Strobe low
gosub sendchar
high strobe ; Strobe high
;
low strobe ; Strobe low
dataio = fixaddr ; Set start address
gosub sendchar
for tmpry = 1 to $0f ;$0F = 15, so loop runs 16 times. 7 LEDs and 7 seg displays
dataio = 0 ;Zero blanks the display
gosub sendchar
next
high strobe ;Strobe high, keep low to end of data
return
sendchar: ;Routine to send all characters to LKM1638 module serially
pad = $ff ;$FF = 255. Set counter
high clock ;Ensure clock is high for pulseout
do
pinc.1 = bit0 ;Make c.1 the value in bit0
iobuf = iobuf/2 ;Shift right
pulsout clock,1 '10us clock pulse
loop Until pad = 0 'excecute 256 times
return
getkeys: ;Reads the input tact buttons in and places them in bits16 to bits23
dataio = readmode ; Data mode read
low strobe
gosub sendchar
input c.1 ;set c.1 as input
high clock ;Ensure clock is high for pulseout
for tmpry = 1 to 16 ;Read in bits 0-15
bit0 = pinc.1 ;Make bit0 the value on c.1. Need to use c.1 as it is both in & out
iobuf = iobuf*2 ;Shift bit left
pulsout clock,1 ;10us clock pulse, read next bit
next
s6 = bit3 ;Move 1st word switch values out of buffer
s2 = bit7
s5 = bit11
s1 = bit15
for tmpry = 1 to 16 ;Read in bits 16-31
bit0 = pinc.1 ;Make bit0 the value on b.0. Need to use c.1 as it is both in & out
iobuf = iobuf*2 ;Shift bit left
pulsout clock,1 ;10us clock pulse, read next bit
next
s8 = bit3 ;Move 2nd word switch values out of buffer
s4 = bit7
s7 = bit11
s3 = bit15
output c.1 ;Return c.1 to output
high strobe
return
lookupchar: ;Looks up the code to display the digit in 'char' on the 7 seg display
;character 0-9 = ( 0 , 1, 2 , 3 , 4 , 5 , 6 , 7, 8 , 9,
' 10-19 = A , b , C , d , E , F , g, H, i, J,
' 20-29 = K, L, M, N, o, P, q, r, S, T,
' 30-35 = U, V, W, X, y, Z ,
' 36-44 = segA, segB, segC, segD, segE, segF, segG, dp, off)