STM32 L1 Morse Code Beacon

Since I’ve found STM32L uController series to be most amusing to play with I did some experiments that no one, not even the manufacturer himself would dare to call ‘normal’ use cases :). In this post I’m going to present you a way to turn your STM32L uC into fully-functional, transmitting actual RF signals, Morse code beacon. Beacon that could be picked up with nothing more than a standard FM Broadcast receiver (you know, the one that works in 87.5 – 108.0 MHz range).

How to get RF out of “non-RF” chip?

Basically what we call RF is no more no less than signal with alternating voltage. The rate at which this voltage alternates is called frequency. We need to find a way to generate a signal with frequency between 87.5 and 108MHz on one of uC pins. Surprisingly this can be done in a chip that claims to operate only at much lower frequencies. All we need to do is to make proper improper 🙂 use of PLL (Phase Locked Loop).

Hardware

Well, not much going on there, all you need to have soldered is a crystal oscillator, in order to be able to use it as PLL’s source clock (HSE). To be completely honest, you don’t even need that for this chip to transmit, but when internal oscillator is used (HSI) a lot (and I mean: A LOT) of phase noise will be present at the output, and nobody likes phase noise of that magnitude. Believe me, things can get messy when your so called “transmitter” occupies 10% of total FM Broadcast Band, so please, please! use HSE, since it only adds three parts to your bill of materials (2 caps, 1 crystal) . Here’s the schematic:

Morse

Configuration of PLL

The frequency that I chose for operation is equal to 96MHz, which is 16MHz (my crystal oscillator, HSE) times six. STM’s PLL does not support divider that is less than two, so in order to generate 96MHz we have to multiply PLL’s source clock frequency to get 192MHz. This implies multiplying by a factor of 12 (16MHz * 12 = 192MHz). Then we set divider’s value to 2. All is done with following code

	/* start hsi clock */
	RCC_HSECmd(ENABLE);
	/* wait till it stabilizes */
	while (!RCC_GetHSEStatus());

	/* configure pll to give out 96 MHz */
	RCC_PLLConfig(RCC_PLLSOURCE_HSE, RCC_PLLMUL_12, RCC_PLLDIV_2);
	/* start pll */
	RCC_PLLCmd(ENABLE);
	/* stabilize pll */
	while (!RCC_GetPLLStatus());

Plain and simple. Only thing worth noticing is that we do not switch our MCU to operate on that what comes out of PLL. That would surely do us no good, since that frequency definitely exceeds IC’s capabilities.

Routing RF out of chip’s insides

There is a nice feature that comes with STM32L series, called MCO (Microcontroller Clock Output), that can be used to provide MCU’s clock to other system components. The real nice thing about it is that it can source not only MCU’s system clock, but any other clock (like HSI, HSE, and .. PLL!) as well. Brilliant! The only drawback is that only Pin PA8 can be configured to act as MCO. Here’s the code:

/* initialize gpio structure */
GPIO_StructInit(&gpio);
/* use pin 13 */
gpio.pins = GPIO_P8;
/* mode: alternate setting */
gpio.mode = GPIO_ALT;
/* system */
gpio.af = GPIO_AF_SYSTEM;
/* output speed: 40MHz */
gpio.speed = GPIO_OS_40M;
/* apply configuration */
GPIO_Init(GPIOA, &gpio);

/* configure mco pin */
RCC_MCOConfig(RCC_MCOSOURCE_PLLCLK, RCC_MCODIV_1);

As you can see MCO has it’s own divider, but we are not using it as it would require PLL to generate even higher frequencies and I don’t find it possible. Please keep in mind that this output pin needs to be configured for highest output speed possible, which, in case of STM32L is 40MHz.

Last thing to do is to find a way to control the RF emission by enabling/disabling MCO. The best way to do it is to use bit-band mapped to PA8 MODER bit number 1 (not 0). By altering it’s value user can switch between Anlternate Function (which is MCO) and General Output which will just set pin low. Here’s the mapping, done as a global variable:

/* bit that controls rf output */
static bitband_t rf = BITBAND_PERIPH(&GPIOA->MODER, 17);

Now setting rf to 1 (0) will start (stop) generation of RF.

Morse code generation

I’ve written a simple piece of code that generates all the dots and dashes form C string.  It uses a Look-Up Table for character generation. Every character in LUT was encoded on 8 bits in such manner that:

  • bits 0-2 are used to represent number of dots and dashes that represent character. This information is required because Morse letters have variable length.
  • bits 3-7 hold the actual letters representation, 1 represents a ‘dot’, 0 goes for a ‘dash’. Sequence should be processed from bit 7 to bit 3, and not the opposite. I stored it that way because it was easier for me to build up all those bits when looking at Morse Code Chart which is written from left to right, obviously.

Here’s the code of Morse code generation:

/* encoded morse letters from A to Z */
const uint8_t morse_letters[] = {
		0b10000010, 0b01110100, 0b01010100, 0b01100011,
		0b10000001, 0b11010100, 0b00100011, 0b11110100,
		0b11000010, 0b10000100, 0b01000011, 0b10110100,
		0b00000010, 0b01000010, 0b00000011, 0b10010100,
		0b00100100, 0b10100011, 0b11100011, 0b00000001,
		0b00100011, 0b11100100, 0b10000011, 0b01100100,
		0b01000100, 0b00110100
};

/* sends morse letter */
void send_morse(char c)
{
	/* get code */
	uint8_t code = morse_letters[c - 'a'], i = code & 0x7;
	/* process every bit of morse code */
	while (i--) {
		/* enable rf */
		*(rf) = 1;
		/* dot */
		if (code & 0x80) {
			simple_delay(DOT_DURATION);
		/* dash */
		} else {
			simple_delay(DOT_DURATION * 3);
		}
		/* disable rf */
		*(rf) = 0;
		/* dot space */
		simple_delay(DOT_DURATION);
		/* next bit */
		code = code << 1;
	}
}

Since it’s only a demo I didn’t go all the way to have support for numerals, spaces, commas, periods and all that stuff. Of course if you are in need of those, feel free to modify it anyway you like.

Outcome

A video says more than a thousand words:

In this video I am using PCB from one of my projects, but you only need to stick to what was told in “Hardware” section of this article. I’ve added some LED blinks for every dot and dash for convenience.

The software that I am using to monitor RF band is called SDR#, and I definitely recommend you getting one if you are planning to go for RF projects. It performs outstandingly well with my Realtek chip based DVB-T dongle.

Files