STM8 FM Radio
Since there’s not many projects on the internet about the STM8, even though our school has used it for atleast the last 2 years of the curriculum and will use it for many more, I decided to write up a “tutorial” on how to use a FM reciever on the STM8S208RB.
Requirements - What I’m going to use:
- STM8S208RB Dev kit
- STVD
- FM reciever - TEA5767 + TDA1308 - The one with the 2 jacks for the antenna + amplified output
- Project template from elektromys
- I2C Library for the STM8
- (Optional) HD44780 1602 LCD 16x2
- (Optional) HD44780 LCD library
- (Optional) Button(s) to control the device
There are better ways to develop for the STM8, but since I don’t know how to port the I2C libraries there, I will show you how to do it the “official” way with STVD. If you don’t know how to create a new project follow this tutorial (Only in Czech). After creating a new project import and include the libraries for I2C and the LCD.
#include "stm8s.h"
#include "milis.h"
#include "stm8_hd44780.h"
#include "stdio.h"
#include "swi2c.h"
Test out if your project works and compiles.
Starting out
First let’s create a function to initialize the STM8 and our other components:
void initialize (void) {
CLK_HSIPrescalerConfig(CLK_PRESCALER_HSIDIV1); //Required
init_milis(); //Initializing the main timing function
lcd_init(); //Initializing the LCD
swi2c_init(); //Initializing the I2C interface
GPIO_Init(GPIOC,GPIO_PIN_1,GPIO_MODE_IN_PU_NO_IT);
GPIO_Init(GPIOC,GPIO_PIN_4,GPIO_MODE_IN_PU_NO_IT);
GPIO_Init(GPIOC,GPIO_PIN_2,GPIO_MODE_IN_PU_NO_IT); //Green button
GPIO_Init(GPIOG,GPIO_PIN_0,GPIO_MODE_IN_PU_NO_IT); //Red button
}
These functions should intialize all parts. If you need more/less buttons feel free to remove/move their incialization.
FM Module
Before we start playing around with the hardware, we need to calculate the number needed for setting the frequency. The manufacturers provides the following formula in the documentation:
By converting it to a C function we get:
uint16_t calculate_frequency ( uint16_t wanted_frequency) {
uint32_t result;
result = (4 * ((wanted_frequency + 2) * 100000 + 225 * 1000)) / 32768;
if (result <= 16384 && result >= 0) return resutl;
else return 0;
}
You need input the desired frequency in MHz as an argument multiplied by 10 e.g.: 87.9 MHz = 879, 101.5 Mhz = 1015
Setting the frequency
After we have calculated the frequency, we somehow need to send it to the FM module. The module i chose communicates with our microcontroller through the I2C bus. That means that we need to connect it with 2 wires to the STM8 + power (+5V and GND). Define your chosen pins/Ports in the swi2c.h header file. Now let’s look at the message we need to send. The manufacturer has defined it like so:
For now we will look at only the first and second byte of the message. Out of the 16 bits from the first two Bytes, 14 of them are used for the frequency (Marked as PLL0 - 13). The seventh bit of the first byte sets the state of the audio output to either muted(1) or unmuted(0). The sixth bit switches the FM module to a searching mode, set it to 0 for now.
Since the the message/frequency can’t take up the whole 16 bits that the uint16_t variable provides us with, we need to split the frequency into two 8bit variables. First shift the MSB(left part of binary representation of the frequency number) by 8 bits to the right, so we use the binary shift operator in C: >>
and save the result in a different variable than the frequency(frequencyMSB
). After that we need to “wipe” the MSB part that we saved in the frequencyMSB
variable and put the resulting LSB part of the frequency in its own frequencyMSB
variable, so we do a binary product of the frequency with a 0xFF which gives us the LSB part.
Only now, that we have the variables in the right format we can send it to the module. Let’s even combine it with our function for calculating the frequency, using the global variable frequency (Could probably be done with pointers, but whatever). Then we do the operations described above and with the function swi2c_block_write
which sends 5 bytes from the variable message
to the slave adress (SLVADR
). The third parameter means how many bytes we will be sending, in our case that’s 5.
void set_frequency(void) {
converted_frequency = calculate_frequency(frequency);
frequencyMSB = converted_frequency >> 8;
frequencyMSB = converted_frequency & 0xFF;
if (mute_flag == 1){
frequencyMSB & 0xC0;
frequencyMSB += 0b10000000;
}
message[0] = frequencyMSB; //First byte of the message
message[1] = frequencyLSB; //Second byte of the message
swi2c_block_write(SLVADR,message,5);
}
Great! Now we everything we need to talk to the FM module.
Buttons
The code for the buttons is mainly just a modified of this. The main reason this is needed because of the bouciness of the buttons and blah blah. I don’t remember it exactly so let’s just say it works. The white and green buttons decrease and increase the frequency respectively, the red one mutes. The yellow one either changes to the favorite frequency or sets it depending if mute is on or not (Muted == sets it).
void scan_buttons(void) {
static uint16_t last_time=0;
static bool last_button_status=1;
if((milis() - last_time) > SCAN_PERIOD) {
last_time = milis();
if(GPIO_ReadInputPin(GPIOC,GPIO_PIN_1) == RESET && last_button_status==1) {
last_button_status = 0;
frequency++;
if (frequency > 1080) frequency = 875;
refresh_lcd();
}
if(GPIO_ReadInputPin(GPIOC,GPIO_PIN_1)!=RESET){
last_button_status=1;
delay_ms(75);
}
if(GPIO_ReadInputPin(GPIOC,GPIO_PIN_4) == RESET && last_button_status==1){
last_button_status = 0;
frequency--;
if (frequency < 875) frequency = 1080;
refresh_lcd();
}
if(GPIO_ReadInputPin(GPIOC,GPIO_PIN_4)!=RESET) {
last_button_status=1;
delay_ms(75);
}
if(GPIO_ReadInputPin(GPIOG,GPIO_PIN_0) == RESET && last_button_status==1) {
last_button_status = 0;
if (mute_flag){mute_flag = 0;}
else{mute_flag = 1;}
refresh_lcd();
delay_ms(50);
}
if(GPIO_ReadInputPin(GPIOG,GPIO_PIN_0) != RESET) {
last_button_status=1;
delay_ms(75);
}
if(GPIO_ReadInputPin(GPIOC,GPIO_PIN_2) == RESET && last_button_status==1) {
last_button_status = 0;
if (mute_flag == 1) favorite_frequency = frequency;
else frequency = favorite_frequency;
refresh_lcd();
}
if(GPIO_ReadInputPin(GPIOC,GPIO_PIN_2) != RESET) {
last_button_status=1;
delay_ms(75);
}
}
}
Displaying the frequency
Since it would be kind of annoying to operate the device without any feedback let’s use an LCD display to display our current frequency. I have chosen to store the frequency as the original number multiplied by ten for ease of use, so it needs to be “translated back to the orignal state. We need to clear the lcd everytime we want something changed and also move the cursor. After formating the frequency, the data can be displayed.
void refresh_lcd(void) {
uint16_t output_frequency1 = frequency / 10;
uint16_t output_frequency2 = frequency % 10;
set_frequency();
lcd_clear();
lcd_gotoxy(0,0);
lcd_puts(text);
lcd_gotoxy(0,1);
if (mute_flag == 1) sprintf(text2,"Muted %d.%d MHz ",output_frequency1,output_frequency2);
else sprintf(text2," %d.%d MHz ",output_frequency1,output_frequency2);
lcd_puts(text2);
}
The if statement checks for the mute flag and displays
Muted
if the user has muted it.
And that’s it! All we need is just to call the function for buttons in a while loop.
void main(void) {
inicialization();
set_frequency();
favorite_frequency = frequency;
refresh_lcd();
while(1) {
scan_buttons();
}
}
Okay, some thing are missing, but the whole main.c (src/main.c) file will be in the git repo, so you can look at it yourself if you’re interested.
- Project repo on github
- Video of the device