Hardware components | ||||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
| × | 1 | ||||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
![]() |
| × | 1 | |||
Software apps and online services | ||||||
![]() |
| |||||
For this project, I created a simple but essential program: a real-time second counter displayed on an LCD. I wanted to build something that shows how timing, looping, and data display work together inside a microcontroller. It may look like a small feature, but it forms the foundation of many real embedded applications—like timers, clocks, and monitoring systems. That idea inspired me to make it.
To make the counter work, I started by declaring an integer variable and initializing it to zero. Then, I used a loop that runs continuously. Inside that loop, the variable increments every second. Each time the value updates, I convert the integer into a string using sprintf(), making it ready to be shown on the LCD. After that, I simply send the string to the display. As long as the program is running, the number keeps increasing and the LCD updates in real time.
In the end, the project demonstrates how software logic and hardware output can synchronize perfectly, even with something as simple as a second counter. It’s a small step, but it reflects a deeper understanding of embedded systems and real-time programming.
#include "main.h"
#include "stm32f4xx_hal.h"
#include <stdio.h>
#include <string.h>
/* LCD pins (PA0..PA5) */
#define LCD_RS_Port GPIOA
#define LCD_RS_Pin GPIO_PIN_0
#define LCD_EN_Port GPIOA
#define LCD_EN_Pin GPIO_PIN_1
#define LCD_D4_Port GPIOA
#define LCD_D4_Pin GPIO_PIN_2
#define LCD_D5_Port GPIOA
#define LCD_D5_Pin GPIO_PIN_3
#define LCD_D6_Port GPIOA
#define LCD_D6_Pin GPIO_PIN_4
#define LCD_D7_Port GPIOA
#define LCD_D7_Pin GPIO_PIN_5
/* prototypes */
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void lcd_init(void);
static void lcd_send_cmd(uint8_t cmd);
static void lcd_send_data(uint8_t data);
static void lcd_print(const char *s);
static void lcd_set_cursor(uint8_t row, uint8_t col);
static char keypad_get_label(void);
/* small helpers */
static void lcd_delay(uint32_t ms) { HAL_Delay(ms); }
static void lcd_enable_pulse(void)
{
HAL_GPIO_WritePin(LCD_EN_Port, LCD_EN_Pin, GPIO_PIN_SET);
lcd_delay(1);
HAL_GPIO_WritePin(LCD_EN_Port, LCD_EN_Pin, GPIO_PIN_RESET);
lcd_delay(1);
}
static void lcd_write4(uint8_t nibble)
{
HAL_GPIO_WritePin(LCD_D4_Port, LCD_D4_Pin, (nibble & 0x01) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_D5_Port, LCD_D5_Pin, (nibble & 0x02) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_D6_Port, LCD_D6_Pin, (nibble & 0x04) ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LCD_D7_Port, LCD_D7_Pin, (nibble & 0x08) ? GPIO_PIN_SET : GPIO_PIN_RESET);
lcd_enable_pulse();
}
static void lcd_send_cmd(uint8_t cmd)
{
HAL_GPIO_WritePin(LCD_RS_Port, LCD_RS_Pin, GPIO_PIN_RESET);
lcd_write4((cmd >> 4) & 0x0F);
lcd_write4(cmd & 0x0F);
lcd_delay(2);
}
static void lcd_send_data(uint8_t data)
{
HAL_GPIO_WritePin(LCD_RS_Port, LCD_RS_Pin, GPIO_PIN_SET);
lcd_write4((data >> 4) & 0x0F);
lcd_write4(data & 0x0F);
lcd_delay(2);
}
static void lcd_print(const char *s)
{
while (*s) lcd_send_data((uint8_t)*s++);
}
static void lcd_set_cursor(uint8_t row, uint8_t col)
{
uint8_t addr = (row == 0) ? (0x80 + col) : (0xC0 + col);
lcd_send_cmd(addr);
}
static void lcd_init(void)
{
lcd_delay(50);
// wake sequence
lcd_write4(0x03); lcd_delay(5);
lcd_write4(0x03); lcd_delay(5);
lcd_write4(0x03); lcd_delay(1);
lcd_write4(0x02); // 4-bit mode
// config
lcd_send_cmd(0x28); // 4-bit, 2 lines
lcd_send_cmd(0x0C); // display on, cursor off
lcd_send_cmd(0x06); // entry mode
lcd_send_cmd(0x01); // clear
lcd_delay(5);
}
int main(void)
{
HAL_Init(); // Initialize HAL (resets peripherals, SysTick, etc.)
SystemClock_Config(); // Configure the system clock
MX_GPIO_Init(); // Initialize GPIO pins for LCD
lcd_init(); // Initialize LCD in 4-bit mode
lcd_print("Timer (sec):"); // Print static label on Line 1
uint32_t counter = 0; // Counter variable for seconds
char buffer[16]; // Buffer to hold converted string for LCD output
while (1) // Infinite loop
{
lcd_send_cmd(0xC0); // Move cursor to Line 2, position 0
sprintf(buffer, "%lu", counter); // Convert counter value into string
lcd_print(buffer); // Print the counter value on LCD
HAL_Delay(1000); // Delay 1 second
counter++; // Increment counter each second
}
}
/* ---------------- System and GPIO initialization ---------------- */
void SystemClock_Config(void)
{
// Minimal HSI clock config. Replace with CubeMX generated clock if you have it.
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
__HAL_RCC_PWR_CLK_ENABLE();
__HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1);
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSI;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_NONE;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_SYSCLK|RCC_CLOCKTYPE_HCLK|
RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_HSI;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV1;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_0);
}
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* enable clocks */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOE_CLK_ENABLE();
__HAL_RCC_GPIOG_CLK_ENABLE();
/* LCD PA0..PA5 output */
GPIO_InitStruct.Pin = GPIO_PIN_0|GPIO_PIN_1|GPIO_PIN_2|
GPIO_PIN_3|GPIO_PIN_4|GPIO_PIN_5;
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);
HAL_GPIO_WritePin(GPIOA, GPIO_InitStruct.Pin, GPIO_PIN_RESET);
/* Keypad rows: PD10, PD8 (GPIOD) */
GPIO_InitStruct.Pin = GPIO_PIN_10|GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOD, GPIO_PIN_10|GPIO_PIN_8, GPIO_PIN_SET); // idle HIGH
/* Keypad rows: PE14, PE12 (GPIOE) */
GPIO_InitStruct.Pin = GPIO_PIN_14|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
HAL_GPIO_WritePin(GPIOE, GPIO_PIN_14|GPIO_PIN_12, GPIO_PIN_SET); // idle HIGH
/* Keypad cols: PE13, PE11 as inputs with pull-up */
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);
/* Keypad cols: PG6, PG7 as inputs with pull-up */
GPIO_InitStruct.Pin = GPIO_PIN_6|GPIO_PIN_7;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOG, &GPIO_InitStruct);
}
main.h
C/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) 2025 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 "stm32f4xx_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 */
/* USER CODE END Private defines */
#ifdef __cplusplus
}
#endif
#endif /* __MAIN_H */














Comments