Setup and Configure a ZS-040 HC-05 Bluetooth master/slave transceiver using an STM32


How to setup and configure a ZS-040 HC-05 Bluetooth transceiver

Welcome to the thrilling world of Bluetooth device configuration! In this exciting tutorial, we’re embarking on an engaging journey to set up a master and a slave ZS-040 HC-05 Bluetooth transceiver, a project that’s perfect for both novices and seasoned enthusiasts in the MicroControllersTech community! You’ll need two ZS-040 devices, which we will expertly configure to function as a master and a slave. With our easy-to-follow example code, you’ll be guided through an automated process that simplifies the configuration of the slave device, seamlessly retrieving its address for the master setup. This structured approach ensures that by the time we configure the master, you’ll have everything you need to establish a direct connection to the slave using the address obtained earlier. So gear up and get ready to dive into the world of Bluetooth communication—your hands-on learning experience awaits!

This is just the beginning, as we delve into Part 1 of our exciting two-part series. Here, we will unravel the intricacies of configuring your Bluetooth devices, laying the groundwork for seamless communication. But hold onto your hats, because in Part 2, we’ll unveil an example setup that will bring both master and slave devices into harmonious dialogue. It’s a journey of discovery that promises to spark your curiosity and enhance your technical prowess, so stay tuned for what’s to come!

We will be utilizing the interrupt driven function HAL_UART_Receive_IT with HAL_UART_RxCpltCallback routine to trap for incoming bytes.



Materials List


FTDI Pinouts

FTDI to USB Pinout from right to left. The FTDI module is needed to convert the USART RX/TX signals into RS-232/USB such that you can interface with a dumb terminal.

  • Pin 1 – GND
  • Pin 2 – CTS
  • Pin 3 – VCC
  • Pin 4 – TX
  • Pin 5 – RX
  • Pin 6 – DTR
  • USB Mini – Connect to PC via USB cable

Make sure the jumper is set for 5v or 3v which ever is being used to power the FTDI device. If we look below at the schematic, it is showing in this case as 5v. Also, validate that any hardware flow control is turned off.
If the dumb terminal emulator you are using has hardware flow control, you probably will not see the response to the <Enter> key being pressed and therefore the master Bluetooth device will not proceed to get configured.


ZS-040 HC-05 Bluetooth Master/Slave Transceiver

NOTE: It is recommended that you label each Bluetooth device, one as MASTER, and the other as SLAVE. You can use a marker or a sticky label. Whichever you use, should not easily come off.


Schematic

Note: There is a drawing issue with the below schematic. PA2 is TxD and PA3 is RxD which are supposed to connect to RxD and TxD of the HZ-040. PA2(TxD) -> RxD and
PA3(RxD)-> TxD respectively. This will be corrected in a future update of the schematic. Also, the U2 labes are incorrect , they should be a ZS-040 which is an HC-05


Pinouts & Configurations


Exploring the Source Code

main.h

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.h
  * @brief          : Header for main.c file.
  *                   This file contains the common defines of the application.
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */

/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __MAIN_H
#define __MAIN_H

#ifdef __cplusplus
extern "C" {
#endif

/* Includes ------------------------------------------------------------------*/
#include "stm32f1xx_hal.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Exported types ------------------------------------------------------------*/
/* USER CODE BEGIN ET */

/* USER CODE END ET */

/* Exported constants --------------------------------------------------------*/
/* USER CODE BEGIN EC */

/* USER CODE END EC */

/* Exported macro ------------------------------------------------------------*/
/* USER CODE BEGIN EM */

/* USER CODE END EM */

/* Exported functions prototypes ---------------------------------------------*/
void Error_Handler(void);

/* USER CODE BEGIN EFP */

/* USER CODE END EFP */

/* Private defines -----------------------------------------------------------*/

/* USER CODE BEGIN Private defines */

void _Error_Handler(const char *, int);
	
#define Error_Handler() _Error_Handler((const char *)__FILE__, __LINE__)
#define REDIRECT_PRINTF

/* USER CODE END Private defines */

#ifdef __cplusplus
}
#endif

#endif /* __MAIN_H */

main.c

C
/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "stdio.h"
#include "string.h"
#include "stdbool.h"

/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
typedef enum role {
	SLAVE = 0,
	MASTER = 1
} ROLE_t;

typedef enum masterPhases {
	M_AT = 0,
	M_Q_ROLE,
	M_SET_ROLE,
	M_Q_CMODE,
	M_SET_CMODE,
	M_SET_SLAVE_ADDR,
	M_DONE
} E_MPHASE_t;

typedef enum slavePhases {
	S_AT = 0,
	S_Q_ROLE,
	S_SET_ROLE,
	S_Q_SLAVE_ADDR,
	S_DONE
} E_SPHASE_t;
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */

/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */

/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/
UART_HandleTypeDef huart2;
UART_HandleTypeDef huart3;

/* USER CODE BEGIN PV */
uint8_t Rx_byte;
uint8_t Rx_data[128];
uint8_t Rx_indx = 0;
uint8_t keyboardByte;

char RxBuffer[128];
char slaveAddr[80];

bool	commandSent = false;
bool	recvdResponse = false;
bool	processNext = false;

ROLE_t curRole;
E_MPHASE_t masterPhase;
E_MPHASE_t nextMastPhase;
E_SPHASE_t slavePhase;
E_SPHASE_t nextSlavePhase;

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART2_UART_Init(void);
static void MX_USART3_UART_Init(void);
/* USER CODE BEGIN PFP */
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart);
void processMaster(void);
void processSlave(void);
/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

#ifdef REDIRECT_PRINTF
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#endif

#ifdef REDIRECT_PRINTF
/**
  * @brief  Retargets the C library printf function to the USART.
  * @param  None
  * @retval None
  */
PUTCHAR_PROTOTYPE
{
  /* Place your implementation of fputc here */
  /* e.g. write a character to the USART2 and Loop until the end of transmission */
  HAL_UART_Transmit(&huart3, (uint8_t *)&ch, 1, 0xFFFF);

  return ch;
}
#endif

void recvDeviceData() {
	Rx_data[Rx_indx ++] = Rx_byte;    // Add data to Rx_Buffer

	if (Rx_indx >= 4) {
		if (strncmp((char *) &Rx_data[Rx_indx - 4], "OK\r\n", 4) == 0) {	// We wait until we get the OK back

			strcpy(RxBuffer, (char *) Rx_data);
			recvdResponse = true;

			memset(Rx_data, 0x00, sizeof(Rx_data));
			Rx_indx = 0;
		}
	}
}

void recvKeyboardData() {
	if ((char) keyboardByte == '\r') {
		processNext = true;
	}
}

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	HAL_StatusTypeDef halStatus = HAL_OK;

   if (huart->Instance == USART2) {	// Blue Tooth Device
	   recvDeviceData();
	   halStatus = HAL_UART_Receive_IT(&huart2, &Rx_byte, 1);

	   if (halStatus != HAL_OK) {
		   Error_Handler();
	   }
   }  else {
	   if (huart->Instance == USART3) {	// Dumb terminal Device
		   recvKeyboardData();

		   if (!processNext) {
			   halStatus = HAL_UART_Receive_IT(&huart3, &keyboardByte, 1);

			   if (halStatus != HAL_OK) {
				   Error_Handler();
			   }
		   }
	   }
   }
}

void processMaster() {
	HAL_StatusTypeDef halStatus = HAL_OK;
	int len = 0;
	char obuf[128] = {0x00};

	switch (masterPhase) {
		case M_AT: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT\r\n");
				printf("Master TxData: %s", obuf);
				len = strlen(obuf);
				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "OK", 2) == 0) {
						printf("RxBuffer: %s", (char *) RxBuffer);
						recvdResponse = false;
						commandSent = false;
						masterPhase = M_Q_ROLE;
						break;
					}
				}
			}
			break;
		}
		case M_Q_ROLE: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+ROLE?\r\n");
				printf("Master TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "+ROLE:", 6) == 0) {
						recvdResponse = false;
						commandSent = false;
						printf("RxBuffer: %s", (char *) RxBuffer);

						if (RxBuffer[6] != '1') {	// Slave is ROLE=0, Master is ROLE=1
							masterPhase = M_SET_ROLE;
						} else {
							masterPhase = M_SET_CMODE;
						}
						break;
					}
				}
			}
			break;
		}

		case M_SET_ROLE: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+ROLE=1\r\n");		// Slave is ROLE=0, Master is ROLE=1
				printf("Master TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "OK", 2) == 0) {
						printf("RxBuffer: %s", (char *) RxBuffer);
						recvdResponse = false;
						commandSent = false;
						masterPhase = M_SET_CMODE;
						break;
					}
				}
			}
			break;
		}

		case M_SET_CMODE: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+CMODE=0\r\n");
				printf("Master TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "OK", 2) == 0) {
						printf("RxBuffer: %s", (char *) RxBuffer);
						recvdResponse = false;
						commandSent = false;
						masterPhase = M_SET_SLAVE_ADDR;
						break;
					}
				}
			}

			break;
		}

		case M_SET_SLAVE_ADDR: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+BIND=%s\r\n", slaveAddr);
				printf("Master TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "OK", 2) == 0) {
						printf("RxBuffer: %s", (char *) RxBuffer);
						recvdResponse = false;
						commandSent = false;
						masterPhase = M_DONE;
						break;
					}
				}
			}

			break;
		}

		case M_DONE: {
			break;
		}

		default:	{
		   Error_Handler();
		}

	}
}

void processSlave() {
	HAL_StatusTypeDef halStatus = HAL_OK;
	int len = 0;
	char obuf[80] = {0x00};

	switch (slavePhase) {
		case S_AT: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT\r\n");
				printf("Slave TxData: %s", obuf);

				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "OK", 2) == 0) {
						recvdResponse = false;
						commandSent = false;
						slavePhase = S_Q_ROLE;
						printf("RxBuffer: %s", (char *) RxBuffer);
						break;
					}
				}
			}
			break;
		}

		case S_Q_ROLE: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+ROLE?\r\n");
				printf("Slave TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "+ROLE:", 6) == 0) {
						recvdResponse = false;
						commandSent  = false;
						printf("RxBuffer: %s", (char *) RxBuffer);

						if (RxBuffer[6] != '0') {	// Slave is ROLE=0, Master is ROLE=1
							slavePhase = S_SET_ROLE;
						} else {
							slavePhase = S_Q_SLAVE_ADDR;
						}
						break;
					}
				}
			}
			break;
		}

		case S_SET_ROLE: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+ROLE=0\r\n");		// Slave is ROLE=0, Master is ROLE=1
				printf("Slave TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "OK", 2) == 0) {
						printf("RxBuffer: %s", (char *) RxBuffer);
						recvdResponse = false;
						commandSent = false;
						slavePhase = S_Q_SLAVE_ADDR;
						break;
					}
				}
			}
			break;
		}

		case S_Q_SLAVE_ADDR: {
			if (!commandSent) {
				memset(Rx_data, 0x00, sizeof(Rx_data));
				memset(RxBuffer, 0x00, sizeof(RxBuffer));
				sprintf(obuf, "AT+ADDR?\r\n");
				printf("Slave TxData: %s", obuf);
				len = strlen(obuf);

				halStatus = HAL_UART_Transmit(&huart2, (uint8_t *)&obuf[0], len, 0xFFFF);

				if (halStatus != HAL_OK) {
				   Error_Handler();
				}

				commandSent = true;
				recvdResponse = false;
				break;
			} else {
				if (commandSent && recvdResponse) {
					if (strncmp(RxBuffer, "+ADDR:", 6) == 0) {
						printf("RxBuffer: %s", (char *) RxBuffer);
						recvdResponse = false;
						commandSent = false;

						strcpy(slaveAddr, RxBuffer + 6);
						int len = strlen(slaveAddr);

						for (int i = 0; i < len; i ++) {
							if (slaveAddr[i] == '\r') {
								slaveAddr[i] = '\0';
								printf("Saving Slave Addr: %s\r\n", slaveAddr);
								break;
							}
						}

						printf("Changing all the ':' to ',' Colons to Commas in the Slave Address\r\n");

						for (int i = 0; i < len; i ++) {
							if (slaveAddr[i] == ':') {
								slaveAddr[i] = ',';
							}
						}

						slavePhase = S_DONE;
						break;
					}
				}
			}
			break;
		}

		case S_DONE: {
			break;
		}

		default:	{
		   Error_Handler();
		}
	}
}
/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

	curRole = SLAVE;	// Need to process slave first then master.  Get Slave Addr and save it for later.
						// When we are processing master we use the save Slave Addr to configure master.
	masterPhase = M_AT;
	slavePhase = S_AT;

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  HAL_StatusTypeDef halStatus = HAL_OK;

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_USART2_UART_Init();
  MX_USART3_UART_Init();
  /* USER CODE BEGIN 2 */

  printf("\x1b[2J\x1b[H");	// Clear the dumb terminal screen
  printf("Starting up the App!!!\r\n");

  memset(slaveAddr, 0x00, sizeof(slaveAddr));

  memset(Rx_data, 0x00, sizeof(Rx_data));
  Rx_indx = 0;

  HAL_UART_Receive_IT(&huart2, &Rx_byte, 1);
  HAL_UART_Receive_IT(&huart3, &keyboardByte, 1);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */

  if (curRole == MASTER) {
	  printf("Starting the process as MASTER\r\n");
  } else {
	  printf("Starting the process as SLAVE\r\n");
  }

  bool done = false;

  while (!done)
  {
	  switch(curRole) {
		  case MASTER: {
			  processMaster();

			  if (masterPhase == M_DONE) {
				  printf("\r\n");
				  printf("Master Device Setup has been completed.  You may now remove the Device.\r\n");

				  done = true;
			  }

			  break;
		  }

		  case SLAVE: {
			  processSlave();

			  if (slavePhase == S_DONE) {
				  masterPhase = M_AT;
				  curRole = MASTER;

				  printf("\r\n");
				  printf("Remove Slave Device and Insert Master Device.\r\nPress <Enter Key> when ready to continue\r\n");

				  while (!processNext) {
					  HAL_Delay(1000);
				  }

				  printf("Switching to Master\r\n");
				  printf("The Slave Address was previously saved as: %s\r\n", slaveAddr);

				  memset(Rx_data, 0x00, sizeof(Rx_data));
				  Rx_indx = 0;

				  int cntr = 0;

				  while (cntr < 10) {
					  halStatus = HAL_UART_Receive_IT(&huart2, &Rx_byte, 1);

					  if (halStatus == HAL_BUSY) {
						  HAL_Delay(100);
						  MX_USART2_UART_Init();
						  HAL_Delay(1000);

						  cntr ++;
					  } else {
						  break;
					  }
				  }

				  if (halStatus != HAL_OK) {
				     Error_Handler();
				  }
			  }
			  break;
		  }
		  default: {
			  Error_Handler();
		  }
	  }
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  HAL_Delay(10);
  }

  while(1) {
	  HAL_Delay(5000);
  }

  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/**
  * @brief USART2 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART2_UART_Init(void)
{

  /* USER CODE BEGIN USART2_Init 0 */

  /* USER CODE END USART2_Init 0 */

  /* USER CODE BEGIN USART2_Init 1 */

  /* USER CODE END USART2_Init 1 */
  huart2.Instance = USART2;
  huart2.Init.BaudRate = 38400;
  huart2.Init.WordLength = UART_WORDLENGTH_8B;
  huart2.Init.StopBits = UART_STOPBITS_1;
  huart2.Init.Parity = UART_PARITY_NONE;
  huart2.Init.Mode = UART_MODE_TX_RX;
  huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart2.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart2) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART2_Init 2 */

  /* USER CODE END USART2_Init 2 */

}

/**
  * @brief USART3 Initialization Function
  * @param None
  * @retval None
  */
static void MX_USART3_UART_Init(void)
{

  /* USER CODE BEGIN USART3_Init 0 */

  /* USER CODE END USART3_Init 0 */

  /* USER CODE BEGIN USART3_Init 1 */

  /* USER CODE END USART3_Init 1 */
  huart3.Instance = USART3;
  huart3.Init.BaudRate = 19200;
  huart3.Init.WordLength = UART_WORDLENGTH_8B;
  huart3.Init.StopBits = UART_STOPBITS_1;
  huart3.Init.Parity = UART_PARITY_NONE;
  huart3.Init.Mode = UART_MODE_TX_RX;
  huart3.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart3.Init.OverSampling = UART_OVERSAMPLING_16;
  if (HAL_UART_Init(&huart3) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN USART3_Init 2 */

  /* USER CODE END USART3_Init 2 */

}

/**
  * @brief GPIO Initialization Function
  * @param None
  * @retval None
  */
static void MX_GPIO_Init(void)
{
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */

  /* GPIO Ports Clock Enable */
  __HAL_RCC_GPIOD_CLK_ENABLE();
  __HAL_RCC_GPIOA_CLK_ENABLE();
  __HAL_RCC_GPIOB_CLK_ENABLE();

/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}

/* USER CODE BEGIN 4 */


/**
 * @brief  This function is executed in case of error occurrence.
 * @param  file: The file name as string.
 * @param  line: The line in file as a number.
 * @retval None
 */
void _Error_Handler(const char *file, int line)
{
	/* USER CODE BEGIN Error_Handler_Debug */
	  __disable_irq();
#ifdef REDIRECT_PRINTF
	char buf[80];
	sprintf(buf, "Trapped in Error_Handler().  Called from: %s, line: %d\r\n", file, line);
	printf(buf);
#endif
	/* User can add his own implementation to report the HAL error return state */
	while(1)
	{
	}
}

/* USER CODE END 4 */

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Let’s dissect some of the code concepts

We have configured 2 USART devices. USART2 will communicate with the Bluetooth device and USART3 will be associated with a dumb terminal emulator. The USART2 is configured for a baud rate of 38,400 as this is the default for the Bluetooth devices. The USART3 which will be used for input/output to the dumb terminal is configured at 19,200 Baud. You can change the baud rate for the terminal, but suggest that you leave the baud rate at 38,400 for the Bluetooth devices.

This code uses a state machine engine to walk through the steps of communicating with the Bluetooth device to configure and set things up using AT modem commands. The code first configures the slave Bluetooth device, then the master Bluetooth device. The code will prompt the user when to remove the slave and insert the master. When this occurs, simply swap the devices out and hit the <Enter> Key on the keyboard to continue. It is recommended to use a marker to indicate which device is the slave and which is the master.

The schematic shows that the Bluetooth device is hard wired on the EN pin to +3.3v This puts the device into programming mode or the AT Modem setup. The device will slowly blink showing that it is now in this mode. Nothing else needs to happen when plugging in the devices. After the first device is insert into the breadboard, which should be the slave, press the reset button on the STM32 board to initiate the programming steps.

Machine states for setting up the Slave Device

  • First the state machine sends an “AT” command waiting for the device to respond with an “OK”.
  • We query the device for the ROLE. Slave is ROLE=0, Master is ROLE=1
  • If the response is not ROLE=0, then next step is to setup the slave device to take on the ROLE=0 by setting the ROLE
  • The final step for the slave device to to query it’s address. This address is saved into a variable for when we setup the master device. We must replace “:” colon characters in the address string with “,” commas before we use it to program the master.

Machine states for setting up the Master Device

  • After being prompted to replace the Slave device with the Master device and hitting the <Enter> key on the dumb terminal keyboard the Master device will start to be configured.
  • First the state machine sends an “AT” command waiting for the device to respond with an “OK”.
  • We query the device for the ROLE. Slave is ROLE=0, Master is ROLE=1
  • If the response is not ROLE=1, then next step is to setup the master device to take on the ROLE=1 by setting the ROLE
  • Next we hard code the master to a CMODE, “Connect Mode” of 0. CMODE=0 is connect to a single device, whereas CMODE=1 is to allow connection to ANY device. We only want the master to connect to our dedicated slave device.
  • Finally we configure the master device to BIND with the slave device, by issuing the BIND=<saved slave address string>
  • The dumb terminal will display when the setup is completed and that you can then remove the device.

At this point in time both the Master and the Slave Bluetooth devices are configured to talk to each other. In a future tutorial we shall cover a project to have them talk to each other. This will require two STM32 boards, and a duplicate of everything built on the master breadboard to be replicated on the slave breadboard.


HC-05 AT Command List


USART3 Dumb Terminal Output


Logic Analyzer Output

Master Output

Slave Output