In this task, we are implementing an AT command system via UART using a microcontroller and a PC. The system allows users to send predefined AT commands from a PC to control an LED, read ADC values (from a potentiometer), and check system uptime.
AT
→ Basic acknowledgment (OK response).AT+LED=ON
→ Turns LED on.AT+ADC_VALUE?
→ Reads ADC value from a potentiometer.
Interfacing Components
Component | Interface |
UART | Serial Terminal (PC) |
LED | GPIO pin |
Potentiometer | ADC Channel |
So, by connecting and configuring the microcontroller and UART communication, we can implement the task.
Below are the solutions to the given task using different microcontrollers
We’re using an STM32 NUCLEO-F103RB board, which operates at a 3.3V logic level.
Project Setup in STM32CubeIDE
HAL_Init()
→ Initializes HAL and system tick.SystemClock_Config()
→ Configures system clock (HSI + PLL).MX_GPIO_Init()
→ Initializes GPIO ports.MX_USART2_UART_Init()
→ Configures UART2.MX_ADC1_Init()
→ Configures ADC1.ADC Initialization (MX_ADC1_Init)
hadc1.Instance = ADC1;
hadc1.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc1.Init.ContinuousConvMode = DISABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
if (HAL_ADC_Init(&hadc1) != HAL_OK) {
Error_Handler();
}
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_239CYCLES_5;
if (HAL_ADC_ConfigChannel(&hadc1, &sConfig) != HAL_OK) {
Error_Handler();
}
GPIO Initialization (MX_GPIO_Init)
/*Configure GPIO pin : PA8 */
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_SPEED_FREQ_LOW
).
UART Initialization (MX_USART2_UART_Init)
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
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();
}
UART_MODE_TX_RX
).
Header Includes, Defines, and Variables
#include <stdio.h> // Provides sprintf, printf (string formatting and printing)
#include <string.h> // Provides string operations (strcmp, strcpy, strlen etc.)
Circular Buffer Structure
#define RX_BUFFER_SIZE 128 // Maximum size of UART circular buffer
typedef struct {
uint8_t buffer[RX_BUFFER_SIZE]; // Storage array for incoming UART data
volatile uint16_t head; // Write index (producer)
volatile uint16_t tail; // Read index (consumer)
} CircularBuffer;
ADC & Hardware Constants
#define ADC_MAX_VALUE 4095 // 12-bit ADC maximum value
#define ADC_REF_VOLTAGE 3.3 // Reference voltage (Vref+)
#define VOLTAGE_OFFSET 0.05 // Calibration offset for accuracy
#define LED0_PORT GPIOA // LED connected to GPIOA
#define LED0_PIN GPIO_PIN_8 // LED pin is PA8
Global Buffers and Flags
char uartTXBuffer[50]; // UART transmit buffer for formatted messages
uint8_t uartRXByte; // Single received UART byte
CircularBuffer uartRxCB; // Circular buffer for UART reception
char receivedData[30]; // Stores complete received command string
uint8_t strReceivedFlag = 0; // Flag = 1 when a valid command string is ready
Circular Buffer Utility Functions
/* ====== Circular Buffer Utility Functions ====== */
static inline void CB_Init(CircularBuffer *cb) {
cb->head = 0; // Reset head
cb->tail = 0; // Reset tail
}
static inline uint8_t CB_IsEmpty(CircularBuffer *cb) {
return (cb->head == cb->tail); // True if no data
}
static inline uint8_t CB_IsFull(CircularBuffer *cb) {
return ((cb->head + 1) % RX_BUFFER_SIZE == cb->tail); // Next position wraps to tail
}
static inline void CB_Push(CircularBuffer *cb, uint8_t data) {
if (!CB_IsFull(cb)) {
cb->buffer[cb->head] = data; // Store byte
cb->head = (cb->head + 1) % RX_BUFFER_SIZE; // Move head
}
}
static inline uint8_t CB_Pop(CircularBuffer *cb, uint8_t *data) {
if (!CB_IsEmpty(cb)) {
*data = cb->buffer[cb->tail]; // Read byte
cb->tail = (cb->tail + 1) % RX_BUFFER_SIZE; // Move tail
return 1; // Success
}
return 0; // No data available
}
UART Support Functions
static void printOnSerialMonitor(const char *msg) {
HAL_UART_Transmit(&huart2, (uint8_t*) msg, strlen(msg), HAL_MAX_DELAY);
}
static void floatToStr(float val, char *buffer, uint8_t decimals) {
int32_t whole = (int32_t) val;
float fraction = val - whole;
if (val < 0)
fraction *= -1;
int32_t n = whole;
uint8_t i = 0;
char temp[10];
if (n == 0)
buffer[i++] = '0';
else {
if (n < 0) {
buffer[i++] = '-';
n = -n;
}
uint8_t j = 0;
while (n > 0) {
temp[j++] = (n % 10) + '0';
n /= 10;
}
while (j > 0)
buffer[i++] = temp[--j];
}
buffer[i++] = '.';
for (uint8_t d = 0; d < decimals; d++) {
fraction *= 10;
uint8_t digit = (uint8_t) fraction;
buffer[i++] = digit + '0';
fraction -= digit;
}
buffer[i] = '\0';
}
ADC Operations
void readAndPrintPOTADCValue() {
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 20);
sprintf(uartTXBuffer, "+ADC_VALUE: %lu \r\n", HAL_ADC_GetValue(&hadc1));
printOnSerialMonitor(uartTXBuffer);
}
void readAndPrintPOTADCVoltage() {
char potADCVoltageValueStr[10];
HAL_ADC_Start(&hadc1);
HAL_ADC_PollForConversion(&hadc1, 20);
float potADCVoltageValue = ((HAL_ADC_GetValue(&hadc1) * ADC_REF_VOLTAGE) / ADC_MAX_VALUE) + VOLTAGE_OFFSET;
floatToStr(potADCVoltageValue, potADCVoltageValueStr, 3);
sprintf(uartTXBuffer, "+ADC_VOLTAGE: %s \r\n", potADCVoltageValueStr);
printOnSerialMonitor(uartTXBuffer);
}
System Monitoring
void printSysOnTime() {
uint32_t uptime = HAL_GetTick(); // System tick (ms since reset)
uint16_t hours = uptime / 3600000;
uint8_t minutes = (uptime % 3600000) / 60000;
uint8_t seconds = (uptime % 60000) / 1000;
sprintf(uartTXBuffer, "+SYS_ON_TIME: %d:%d:%d \r\n", (int) hours, (int) minutes, (int) seconds);
printOnSerialMonitor(uartTXBuffer);
}
LED Control
void printLEDStatus() {
if (HAL_GPIO_ReadPin(LED0_PORT, LED0_PIN))
sprintf(uartTXBuffer, "+LED_STATUS:\"ON\" \r\n");
else
sprintf(uartTXBuffer, "+LED_STATUS:\"OFF\" \r\n");
printOnSerialMonitor(uartTXBuffer);
}
void printTurnONLED() {
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, 1);
sprintf(uartTXBuffer, "+LED:\"ON\" \r\n");
printOnSerialMonitor(uartTXBuffer);
}
void printTurnOFFLED() {
HAL_GPIO_WritePin(LED0_PORT, LED0_PIN, 0);
sprintf(uartTXBuffer, "+LED:\"OFF\" \r\n");
printOnSerialMonitor(uartTXBuffer);
}
UART Rx Handling
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if (huart->Instance == USART2) {
CB_Push(&uartRxCB, uartRXByte); // Push received byte into circular buffer
uartStartReception(); // Restart UART reception interrupt
}
}
void uartStartReception() {
HAL_UART_Receive_IT(&huart2, &uartRXByte, 1); // Enable interrupt-driven UART reception
}
void trimString(char *str) {
int i = strlen(str) - 1;
while (i >= 0 && (str[i] == '\r' || str[i] == '\n' || str[i] == ' ')) {
str[i] = '\0'; // Remove trailing whitespace/newlines
i--;
}
}
CB_Init(&uartRxCB); // Initialize circular buffer
uartStartReception(); // Start UART reception interrupt
uint8_t rxByte;
static char cmdBuffer[30];
static uint8_t cmdIndex = 0;
while (1) {
// Process circular buffer
while (CB_Pop(&uartRxCB, &rxByte)) {
if (rxByte == '\r' || rxByte == '\n') {
if (cmdIndex > 0) {
cmdBuffer[cmdIndex] = '\0';
strcpy(receivedData, cmdBuffer);
strReceivedFlag = 1;
cmdIndex = 0;
}
} else if (cmdIndex < sizeof(cmdBuffer) - 1) {
cmdBuffer[cmdIndex++] = rxByte;
HAL_UART_Transmit(&huart2, &rxByte, 1, HAL_MAX_DELAY); // Echo received byte back
}
}
if (strReceivedFlag) {
strReceivedFlag = 0;
trimString(receivedData);
sprintf(uartTXBuffer, " Received: %s \r\n", receivedData);
printOnSerialMonitor(uartTXBuffer);
if (strcmp(receivedData, "AT") == 0) {
sprintf(uartTXBuffer, "OK\r\n");
printOnSerialMonitor(uartTXBuffer);
} else if (strcmp(receivedData, "AT+LED=ON") == 0) {
printTurnONLED();
} else if (strcmp(receivedData, "AT+LED=OFF") == 0) {
printTurnOFFLED();
} else if (strcmp(receivedData, "AT+LED_STATUS?") == 0) {
printLEDStatus();
} else if (strcmp(receivedData, "AT+ADC_VALUE?") == 0) {
readAndPrintPOTADCValue();
} else if (strcmp(receivedData, "AT+ADC_VOLTAGE?") == 0) {
readAndPrintPOTADCVoltage();
} else if (strcmp(receivedData, "AT+SYS_ON_TIME?") == 0) {
printSysOnTime();
} else {
sprintf(uartTXBuffer, "Invalid Command! \r\n");
printOnSerialMonitor(uartTXBuffer);
}
}
}
Setup Phase
UART Data Handling
\r/\n
) is detected, → command is considered complete.Command Execution
AT
→ Acknowledgement (OK).AT+LED=ON/OFF
→ LED control.AT+LED_STATUS?
→ LED state feedback.AT+ADC_VALUE?
→ Raw ADC reading.AT+ADC_VOLTAGE?
→ Converted ADC voltage.AT+SYS_ON_TIME?
→ System uptime in ms.
Example Workflow
The complete STM32CubeIDE project (including .ioc configuration, main.c, and HAL files) is available here:
📥 Download Project
We are using the ESP32 DevKit v4 development board and programming it using the Arduino IDE.
On the ESP32 DevKit V4, a USB-to-UART bridge chip (either CP2102 or CH340C) is integrated on the board.
This chip converts USB data from the PC into UART signals, which are sent to UART0 of the ESP32 (TX0 – GPIO1, RX0 – GPIO3).
UART0 is used for programming the ESP32 and for communication with the Serial Monitor.
#define ADC_Value 34 // ADC pin for analog input
#define LED_pin 13 // GPIO pin for LED
String receivedData; // Stores incoming serial data
float volt; // Stores calculated voltage
void setup() {
Serial.begin(115200); // Start UART at 115200 baud
pinMode(LED_pin, OUTPUT); // Configure LED pin as output
}
void loop() {
if (Serial.available() > 0) { // Check if serial data is available
receivedData = Serial.readString();
receivedData.trim(); // Remove whitespace/newline
Serial.print("Received: ");
Serial.println(receivedData); // Echo received command
if (receivedData == "AT") {
Serial.println("OK"); // Basic AT response
} else if (receivedData == "AT+LED=ON") {
digitalWrite(LED_pin, HIGH); // Turn LED ON
Serial.println("+LED:\"ON\"");
} else if (receivedData == "AT+LED=OFF") {
digitalWrite(LED_pin, LOW); // Turn LED OFF
Serial.println("+LED:\"OFF\"");
} else if (receivedData == "AT+LED_STATUS?") {
// Report LED state
if (digitalRead(LED_pin) == HIGH) {
Serial.println("+LED_STATUS:\"ON\"");
} else {
Serial.println("+LED_STATUS:\"OFF\"");
}
} else if (receivedData == "AT+ADC_VALUE?") {
int adcVal = analogRead(ADC_Value); // Read raw ADC value
Serial.print("+ADC_VALUE:");
Serial.println(adcVal);
} else if (receivedData == "AT+ADC_VOLTAGE?") {
int adcVal = analogRead(ADC_Value); // Read ADC value
volt = (3.3 * adcVal) / 4095.0; // Convert to voltage (12-bit, 3.3V ref)
Serial.print("+ADC_VOLTAGE:");
Serial.println(volt, 2); // Print with 2 decimals
} else if (receivedData == "AT+SYS_ON_TIME?") {
unsigned long uptime = millis(); // System uptime (ms)
// Convert ms → HH:MM:SS
unsigned long hours = uptime / 3600000;
unsigned long minutes = (uptime % 3600000) / 60000;
unsigned long seconds = (uptime % 60000) / 1000;
char timeBuffer[9]; // Store formatted time
sprintf(timeBuffer, "%02lu:%02lu:%02lu", hours, minutes, seconds);
Serial.print("+SYS_ON_TIME:");
Serial.println(timeBuffer);
} else {
Serial.println("Invalid Command!"); // Handle wrong command
}
}
}
We are using the Arduino UNO development board and programming it using the Arduino IDE.
The Arduino UNO will be connected to a PC via USB, enabling Serial communication between the Arduino UNO and the Serial Monitor for sending and receiving commands.
In the given task, we will send AT commands from the Serial Monitor, and the Arduino UNO will respond to these commands accordingly.
Setting the Baud Rate
Let’s connect the hardware,
Circuit Diagram
#define ADC_Value A0
#define LED_pin 3
String receivedData;
float volt;
void setup() {
Serial.begin(115200); // Initialize UART communication at 115200 baud rate
pinMode(LED_pin, OUTPUT); // Set LED pin as output
}
void loop() {
if (Serial.available() > 0) { // Check if data is received
receivedData = Serial.readString();
receivedData.trim(); // Remove trailing newline or spaces
Serial.print("Received: ");
Serial.println(receivedData); // Print received data
if (receivedData == "AT") {
Serial.println("OK");
} else if (receivedData == "AT+LED=ON") {
digitalWrite(LED_pin, HIGH);
Serial.println("+LED:\"ON\"");
} else if (receivedData == "AT+LED=OFF") {
digitalWrite(LED_pin, LOW);
Serial.println("+LED:\"OFF\"");
} else if (receivedData == "AT+LED_STATUS?") {
// Check LED status and report
if (digitalRead(LED_pin) == HIGH) {
Serial.println("+LED_STATUS:\"ON\"");
} else {
Serial.println("+LED_STATUS:\"OFF\"");
}
} else if (receivedData == "AT+ADC_VALUE?") {
int adcVal = analogRead(ADC_Value); // Read ADC
Serial.print("+ADC_VALUE:");
Serial.println(adcVal);
} else if (receivedData == "AT+ADC_VOLTAGE?") {
int adcVal = analogRead(ADC_Value);
volt = (5.0 * adcVal) / 1023.0;
Serial.print("+ADC_VOLTAGE:");
Serial.println(volt, 2); // Prints voltage with 2 decimal places
} else if (receivedData == "AT+SYS_ON_TIME?") {
// Get system uptime in milliseconds
unsigned long uptime = millis();
// Convert uptime to hours, minutes, seconds
unsigned long hours = uptime / 3600000; // 1 hour = 3600000 milliseconds
unsigned long minutes = (uptime % 3600000) / 60000; // 1 minute = 60000 milliseconds
unsigned long seconds = (uptime % 60000) / 1000; // 1 second = 1000 milliseconds
// Print uptime in HH:MM:SS format
char timeBuffer[9]; // Buffer for "HH:MM:SS"
sprintf(timeBuffer, "%02lu:%02lu:%02lu", hours, minutes, seconds);
Serial.print("+SYS_ON_TIME:");
Serial.println(timeBuffer);
} else {
Serial.println("Invalid Command!"); // Handles unknown inputs
}
}
}
AT
→ Responds with OK.AT+LED=ON
→ Turns LED ON, responds +LED:"ON"
.AT+LED=OFF
→ Turns LED OFF, responds +LED:"OFF"
.AT+LED_STATUS?
→ Reports LED status: +LED_STATUS:"ON" or "OFF"
.AT+ADC_VALUE?
→ Reads analog pin (A0), responds with +ADC_VALUE:<0-1023>
.AT+ADC_VOLTAGE?
→ Converts ADC reading to voltage (0-5V), responds +ADC_VOLTAGE:<value>
.AT+SYS_ON_TIME?
→ Reports system uptime in HH:MM:SS
.Invalid Command!
".Hardware Setup