Program to transmit strings as Morse code using the CC1200. Useful for amateur radio projects!

Dependencies:   CC1200 SerialStream

This project contains a class (CC1200Morse) to convert text into Morse code that can be transmitted over a CC1200 radio IC, and a test program for this class. This is useful for amateur radio projects which need to broadcast their call sign in order to operate legally, and just a cool demonstration of the flexibility of the CC1200.

How It Works

First, the input string is translated into Morse code using a conversion table. Nearly all ASCII characters contained in the Morse character set are supported. For example, the string "ha" would become " ···· · −". Then, the Morse code is converted into one and zero bits according to the standard Morse timing rules. " ···· · −" then becomes 10101010 00010111 000". Finally, these bits are enqueued into the radio's packet buffer and transmitted. The CC1200 is configured into OOK (On-Off Keying) mode, so a 1 bit is modulated as full transmit power, and a 0 bit is modulated as zero transmit power. The transmission will occur at the speed (specified as the time unit, or the length of a dot) that you configure. Note that the CC1200 does the transmission in the background, so your processor can enqueue up to 128 bytes worth of Morse and then go do other things while it transmits at a human readable speed. You can't enqueue two packets at a time though.

Demo Video

Hardware Setup

This program assumes that a CC1200 radio is connected to your processor's SPI bus. The CC1200's circuit board must be configured for the 900MHz band (though you could also change the frequency in the code to match your boards).

I used a custom circuit board for my testing, but you should also be able to use an Mbed board connected to an CC1200 eval kit to run the program. Make sure to edit the #defines at the top of main.cpp to match the pins that your equipment is plugged into!

Note: License free transmission on the 900MHz band is only legal in Region 2 countries (North and South America). Make sure to follow all local regulations covering radio transmissions!

Files at this revision

API Documentation at this revision

Comitter:
Jamie Smith
Date:
Sat Aug 29 03:06:11 2020 -0700
Commit message:
Initial commit

Changed in this revision

.hgignore Show annotated file Show diff for this revision Revisions of this file
CC1200.lib Show annotated file Show diff for this revision Revisions of this file
CC1200Morse.cpp Show annotated file Show diff for this revision Revisions of this file
CC1200Morse.h Show annotated file Show diff for this revision Revisions of this file
SerialStream.lib Show annotated file Show diff for this revision Revisions of this file
main.cpp Show annotated file Show diff for this revision Revisions of this file
mbed-os.lib Show annotated file Show diff for this revision Revisions of this file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/.hgignore	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,5 @@
+^BUILD$
+^.mbed$
+^mbed-os$
+^CC1200$
+^SerialStream$
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CC1200.lib	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/MultipleMonomials/code/CC1200/#c609cc7c9ea7
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CC1200Morse.cpp	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,177 @@
+//
+// Created by jamie on 8/24/2020.
+//
+
+#include "CC1200Morse.h"
+
+#include <cinttypes>
+
+// Morse code tables.
+// Covers ASCII ranges 0x41-0x5A and 0x61-0x7A
+char const * const alphaMorse[] = {".-","-...","-.-.","-..",".","..-.","--.","....","..",".---",
+					"-.-",".-..","--","-.","---",".--.","--.-",".-.","...","-","..-",
+					"...-",".--","-..-","-.--","--.."};
+
+// Covers ASCII range 0x30-0x39
+char const * const numMorse[] = {"-----",".----","..---","...--","....-",".....","-....","--...","---..","----."};
+
+// covers ASCII range 0x21-0x2F
+char const * const punctuation1Morse[] = {"-.-.--", ".-..-.", nullptr, "...-..-", nullptr, ".-...", ".----.", "-.--.",
+										  "-.--.-", nullptr, ".-.-.", "--..--", nullptr, ".-.-.-", "-..-."};
+
+// covers ASCII range 0x3A-0x40
+char const * const punctuation2Morse[] = {"---...", "-.-.-.", nullptr, "-...-", nullptr, "..--..", ".--.-."};
+
+void CC1200Morse::configure(CC1200::Band band, float radioFrequency, float morseTimePeriod, float transmitPower)
+{
+	radio.setPacketMode(CC1200::PacketMode::FIXED_LENGTH);
+	radio.setCRCEnabled(false);
+
+	// set frequency
+	radio.setSymbolRate(1/morseTimePeriod);
+	radio.setRadioFrequency(band, radioFrequency);
+
+	// disable anything getting sent before the data
+	radio.configureSyncWord(0x0, CC1200::SyncMode::SYNC_NONE, 8);
+	radio.configurePreamble(0, 0);
+
+	// configure OOK modulation
+	radio.setModulationFormat(CC1200::ModFormat::ASK);
+	radio.disablePARamping();
+	radio.setASKPowers(transmitPower, CC1200::ASK_MIN_POWER_OFF);
+}
+
+// helper function for convertToMorse
+static bool appendBits(uint8_t *outputBuffer, size_t bufferLen, uint8_t & nextBit, size_t & currByte, uint8_t toAppend, size_t count)
+{
+	//printf("appendBits(%" PRIu8 ", %zu)\n", toAppend, count);
+	for(size_t counter = 0; counter < count; ++counter)
+	{
+		outputBuffer[currByte] |= toAppend << nextBit;
+
+		if(nextBit == 0)
+		{
+			nextBit = 7;
+			currByte += 1;
+			if(currByte >= bufferLen)
+			{
+				// out of space
+				return false;
+			}
+		}
+		else
+		{
+			nextBit--;
+		}
+	}
+
+	return true;
+}
+
+
+CC1200Morse::EncodedMorse CC1200Morse::convertToMorse(const char *string, uint8_t *outputBuffer, size_t bufferLen)
+{
+	memset(outputBuffer, 0, bufferLen);
+
+	// place in the output buffer where next items will be written
+	uint8_t nextBit = 7;
+	size_t currByte = 0;
+
+	EncodedMorse encoded;
+	encoded.buffer = outputBuffer;
+	encoded.valid = false;
+
+	if(!appendBits(outputBuffer, bufferLen, nextBit, currByte, 0, spaceBefore))
+	{
+		return encoded;
+	}
+
+	size_t stringLength = strlen(string);
+	for(size_t charIndex = 0; charIndex < stringLength; ++charIndex)
+	{
+		char currChar = string[charIndex];
+		char const * morseToAppend = nullptr;
+		if((currChar >= 'A' && currChar <= 'Z'))
+		{
+			morseToAppend = alphaMorse[currChar - 'A'];
+		}
+		else if(currChar >= 'a' && currChar <= 'z')
+		{
+			morseToAppend = alphaMorse[currChar - 'a'];
+		}
+		else if(currChar >= '0' && currChar <= '9')
+		{
+			morseToAppend = numMorse[currChar - '0'];
+		}
+		else if(currChar >= '!' && currChar <= '/')
+		{
+			morseToAppend = punctuation1Morse[currChar - '!'];
+		}
+		else if(currChar >= ':' && currChar <= '@')
+		{
+			morseToAppend = punctuation2Morse[currChar - ':'];
+		}
+		else if(currChar == '_') // underscore is off by itself in the ASCII chart
+		{
+			morseToAppend = "..--.-";
+		}
+
+		// append bit timings
+		//printf("currChar = '%c'\n", currChar);
+		if(currChar == ' ')
+		{
+			// space between words is 7 time units
+			if(!appendBits(outputBuffer, bufferLen, nextBit, currByte, 0, 7))
+			{
+				return encoded;
+			}
+		}
+		else if(morseToAppend != nullptr)
+		{
+			size_t morseLength = strlen(morseToAppend);
+			for(size_t morseIndex = 0; morseIndex < morseLength; ++morseIndex)
+			{
+				// dot is 1 time unit, dash is 3 time units
+				if(!appendBits(outputBuffer, bufferLen, nextBit, currByte, 1, morseToAppend[morseIndex] == '-' ? 3 : 1))
+				{
+					return encoded;
+				}
+
+				// space between symbols is 1 time unit
+				if(!appendBits(outputBuffer, bufferLen, nextBit, currByte, 0, 1))
+				{
+					return encoded;
+				}
+			}
+
+			// extra space between letters is 2 time units
+			if(!appendBits(outputBuffer, bufferLen, nextBit, currByte, 0, 2))
+			{
+				return encoded;
+			}
+		}
+	}
+
+	if(!appendBits(outputBuffer, bufferLen, nextBit, currByte, 0, spaceAfter))
+	{
+		return encoded;
+	}
+
+	encoded.valid = true;
+	encoded.byteLen = currByte;
+	encoded.bitLen = 7 - nextBit;
+	encoded.totalLength = currByte + (encoded.bitLen > 0 ? 1 : 0);
+
+	return encoded;
+}
+
+void CC1200Morse::transmit(const CC1200Morse::EncodedMorse &morse)
+{
+	if(morse.totalLength > 128)
+	{
+		// too large to send in one packet
+		return;
+	}
+	radio.setPacketLength(morse.byteLen, morse.bitLen);
+	radio.enqueuePacket(reinterpret_cast<const char *>(morse.buffer), morse.totalLength);
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/CC1200Morse.h	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,75 @@
+//
+// Class allowing you to transmit Morse code using the CC1200 radio
+//
+
+#ifndef LIGHTSPEEDRANGEFINDER_CC1200MORSE_H
+#define LIGHTSPEEDRANGEFINDER_CC1200MORSE_H
+
+#include <CC1200.h>
+
+class CC1200Morse
+{
+	CC1200 & radio;
+
+	// time units at the start and end of the morse message.
+	const size_t spaceBefore = 3;
+	const size_t spaceAfter = 3;
+
+public:
+
+	CC1200Morse(CC1200 & _radio):
+	radio(_radio){}
+
+	/**
+	 * Configure the CC1200 to transmit morse code.
+	 *
+	 * @param band Radio band containing the frequency.
+	 * @param radioFrequency Frequency to transmit on.
+	 * @param morseTimePeriod Time unit in seconds to use when transmitting.
+	 * @param transmitPower Power in dBm to transmit at.  Must be in the CC1200 allowed range.
+	 * Dots are one time unit, dashes are three.
+	 * 50ms is about the fastest a human can understand, while 125ms is a more reasonable speed.
+	 * Specifics are here: http://www.codebug.org.uk/learn/step/541/morse-code-timing-rules/#:~:text=The%20space%20between%20symbols%20(dots,words%20is%207%20time%20units.
+	 */
+	void configure(CC1200::Band band, float radioFrequency, float morseTimePeriod, float transmitPower);
+
+	struct EncodedMorse
+	{
+		/// Whether this morse data is valid.. If false, it couldn't be encoded due to an error.
+		bool valid;
+
+		/// Buffer storing morse data.  Uses memory passed to configure()
+		uint8_t const * buffer;
+
+		/// Number of complete bytes and bits used in the data
+		size_t byteLen;
+		uint8_t bitLen;
+
+		// Number of bytes in the buffer that are at least partially filled.
+		size_t totalLength;
+	};
+
+	/**
+	 * Convert ASCII text into characters suitable to be sent over the radio.
+	 * Some ASCII characters do not have a morse equivalent, these will be removed.
+	 *
+	 * @param string ASCII string to convert.
+	 * @param outputBuffer Buffer to write morse into.  Will be zeroed.
+	 * @return Encoded morse data, if there was insufficient space valid will be false.
+	 */
+	EncodedMorse convertToMorse(const char* string, uint8_t * outputBuffer, size_t bufferLen);
+
+	/**
+	 * Queue this morse code to be transmitted over the radio.
+	 * Packets with a total length larger than 128 bytes cannot be transmitted (though this limit
+	 * could be worked around).
+	 *
+	 * The packet will be transmitted as soon as the radio is switched to TX state.
+	 * If it's already in TX then it will be transmitted immediately.
+	 * @param morse
+	 */
+	void transmit(EncodedMorse const & morse);
+};
+
+
+#endif //LIGHTSPEEDRANGEFINDER_CC1200MORSE_H
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SerialStream.lib	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,1 @@
+https://os.mbed.com/users/MultipleMonomials/code/SerialStream/#9437ea7d0799b269bfaa18d584dec7e17287a623
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/main.cpp	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,160 @@
+//
+// Test program for the CC1200 morse code example
+//
+
+#include <mbed.h>
+#include <SerialStream.h>
+
+#include <CC1200.h>
+#include <cinttypes>
+
+#include "CC1200Morse.h"
+
+#define PIN_SPI_MOSI P0_9
+#define PIN_SPI_MISO P0_8
+#define PIN_SPI_SCLK P0_7
+
+#define PIN_CC1200_CS P0_6
+#define PIN_CC1200_RST P0_29
+
+BufferedSerial serial(USBTX, USBRX, 115200);
+SerialStream<BufferedSerial> pc(serial);
+
+CC1200 radio(PIN_SPI_MOSI, PIN_SPI_MISO, PIN_SPI_SCLK, PIN_CC1200_CS, PIN_CC1200_RST, &pc);
+
+void testMorseCodeConversion()
+{
+	radio.begin();
+	CC1200Morse morseConverter(radio);
+
+	const size_t bufferLen = 64;
+	uint8_t outputBuffer[bufferLen];
+
+	char const * testString = "eternal silence@54!";
+
+	CC1200Morse::EncodedMorse morse = morseConverter.convertToMorse(testString, outputBuffer, bufferLen);
+
+	if(!morse.valid)
+	{
+		pc.printf("Morse is invalid\n");
+	}
+	else
+	{
+		pc.printf("Output %zu bytes:", morse.totalLength);
+		for(size_t index = 0; index < morse.totalLength; ++index)
+		{
+			pc.printf(" %" PRIx8, outputBuffer[index]);
+		}
+		pc.printf("\n");
+	}
+}
+
+/**
+ * Test sending some arbitrary bytes as OOK modulated data.
+ */
+void testMorseByteTransmission()
+{
+	const float timeUnit = 0.1f;
+
+	radio.begin();
+	CC1200Morse morseConverter(radio);
+	morseConverter.configure(CC1200::Band::BAND_820_960MHz, 915e6f, timeUnit, 14.5);
+
+	const char testString[] = "\xFF\x0E\xFF";
+	const size_t testStringLength = 3;
+
+	// manually create morse code data
+	CC1200Morse::EncodedMorse morse;
+	morse.valid = true;
+	morse.buffer = reinterpret_cast<uint8_t const *>(testString);
+	morse.byteLen = testStringLength;
+	morse.bitLen = 0;
+	morse.totalLength = testStringLength;
+
+	morseConverter.transmit(morse);
+
+	Timer messageTimer;
+	messageTimer.start();
+
+	radio.setOnTransmitState(CC1200::State::IDLE);
+	radio.startTX();
+
+	// wait until all bytes have been transmitted.
+	// Note: the FIFO length is 0 when the last byte is being sent, so we can't just check the FIFO length
+	while(radio.getTXFIFOLen() > 0 || radio.getState() != CC1200::State::IDLE)
+	{
+		pc.printf("TX FIFO size: %zu\n", radio.getTXFIFOLen());
+		ThisThread::sleep_for(std::chrono::milliseconds(static_cast<uint32_t>(timeUnit * 1000)));
+		//ThisThread::sleep_for(1ms);
+	}
+
+	float timeSeconds = std::chrono::duration_cast<std::chrono::duration<float>>(messageTimer.elapsed_time()).count();
+	size_t numBits = ((morse.byteLen * 8) + morse.bitLen);
+	float effectiveBitrate = numBits / timeSeconds;
+
+	pc.printf("Sent %zu bits in %.03f s, effective bitrate = %.03f sps\n", numBits, timeSeconds, effectiveBitrate);
+
+}
+
+void testMorseCodeTransmission()
+{
+	const float timeUnit = 0.1f;
+
+	radio.begin();
+	CC1200Morse morseConverter(radio);
+	morseConverter.configure(CC1200::Band::BAND_820_960MHz, 915e6f, timeUnit, 0);
+
+	const size_t bufferLen = 64;
+	uint8_t outputBuffer[bufferLen];
+
+	char const * testString = "eternal silence@54!";
+
+	CC1200Morse::EncodedMorse morse = morseConverter.convertToMorse(testString, outputBuffer, bufferLen);
+
+	if(!morse.valid)
+	{
+		pc.printf("Morse is invalid\n");
+	}
+
+	morseConverter.transmit(morse);
+
+	radio.setOnTransmitState(CC1200::State::IDLE);
+	radio.startTX();
+
+	// wait until all bytes have been transmitted.
+	// Note: the FIFO length is 0 when the last byte is being sent, so we can't just check the FIFO length
+	while(radio.getTXFIFOLen() > 0 || radio.getState() != CC1200::State::IDLE)
+	{
+		pc.printf("TX FIFO size: %zu\n", radio.getTXFIFOLen());
+		ThisThread::sleep_for(std::chrono::milliseconds(static_cast<uint32_t>(timeUnit * 1000)));
+		//ThisThread::sleep_for(1ms);
+	}
+}
+
+int main()
+{
+	pc.printf("\nCC1200 Morse Test Suite:\n");
+
+	while(1){
+		int test=-1;
+		//MENU. ADD AN OPTION FOR EACH TEST.
+		pc.printf("Select a test: \n");
+		pc.printf("1.  Test converting to Morse code\n");
+		pc.printf("2.  Test transmitting bytes as Morse\n");
+		pc.printf("3.  Test transmitting string of Morse\n");
+
+		pc.scanf("%d", &test);
+		printf("Running test %d:\n\n", test);
+		//SWITCH. ADD A CASE FOR EACH TEST.
+		switch(test) {
+			case 1:			testMorseCodeConversion();			break;
+			case 2:			testMorseByteTransmission();		break;
+			case 3:			testMorseCodeTransmission();		break;
+			default:        pc.printf("Invalid test number. Please run again.\n"); continue;
+		}
+		pc.printf("done.\r\n");
+	}
+
+	return 0;
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/mbed-os.lib	Sat Aug 29 03:06:11 2020 -0700
@@ -0,0 +1,1 @@
+https://github.com/ARMmbed/mbed-os.git#a2ada74770f043aff3e61e29d164a8e78274fcd4
\ No newline at end of file