PRINCESS LAURONHEART LAHOYLAHOY
Published

STM32 Calculator: Keypad and I2C LCD Interface

This project demonstrates a basic calculator implemented on a STM32F407ZGT6 microcontroller, utilizing a 4x4 keypad and an I2C LCD.

IntermediateProtip6 hours112
STM32 Calculator: Keypad and I2C LCD Interface

Things used in this project

Hardware components

STM32F407ZGT6
×1
I2C 16x2 Arduino LCD Display Module
DFRobot I2C 16x2 Arduino LCD Display Module
×1
4X4 Keypad
×1

Software apps and online services

STM CUBE IDE

Story

Read more

Schematics

RT-Spark Schematic

Code

MAIN.C CODE

C/C++
#include "main.h"
#include "i2c_lcd.h"
#include "keypad.h"
#include <stdio.h>
#include <string.h>


I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart2;  // Use USART2

void SystemClock_Config(void);
void MX_GPIO_Init(void);
void MX_I2C1_Init(void);
void MX_USART2_UART_Init(void);

#define MAX_EXPR_SIZE 16
char expr[MAX_EXPR_SIZE + 1] = {0};
uint8_t expr_pos = 0;

void clear_expr(void) {
    expr[0] = 0;
    expr_pos = 0;
}

void show_expr(void) {
    LCD_Clear();
    LCD_SendString(expr);
}


int calc_full(const char* s, int* result) {
    int len = strlen(s);
    int value = 0, current = 0;
    char last_op = '+';
    int i = 0;

    while (i <= len) {
        
        if (s[i] >= '0' && s[i] <= '9') {
            current = current * 10 + (s[i] - '0');
            i++;
            continue;
        }

        if (s[i] == '+' || s[i] == '-' || s[i] == '*' || s[i] == '/' || s[i] == 0) {
            
            if (last_op == '+') {
                value += current;
            } else if (last_op == '-') {
                value -= current;
            } else if (last_op == '*') {
                value *= current;
            } else if (last_op == '/') {
                if (current == 0) return 0;
                value /= current;
            }
            
            if (!s[i]) break;
            last_op = s[i];
            current = 0;
            i++;
        } else {
            
            return 0;
        }
    }
    *result = value;
    return 1;
}

int main(void)
{
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_I2C1_Init();
    MX_USART2_UART_Init();

    LCD_Init();
    LCD_Clear();
    LCD_SendString("INPUT!");

    HAL_Delay(3000);

    Keypad_Init();
    LCD_Clear();
    LCD_SendString("A=+ B=- E=* C=/ D== F=Clr");

    clear_expr();

    while (1)
    {
        char key = Keypad_GetKey();
        if (key) {
            if ((key >= '0' && key <= '9')) {
                if (expr_pos < MAX_EXPR_SIZE) {
                    expr[expr_pos++] = key;
                    expr[expr_pos] = 0;
                    show_expr();
                }
            }
            else if (key == 'A') { // plus
                if (expr_pos < MAX_EXPR_SIZE) {
                    expr[expr_pos++] = '+';
                    expr[expr_pos] = 0;
                    show_expr();
                }
            }
            else if (key == 'B') { // minus
                if (expr_pos < MAX_EXPR_SIZE) {
                    expr[expr_pos++] = '-';
                    expr[expr_pos] = 0;
                    show_expr();
                }
            }
            else if (key == 'E') { // multiplication
                if (expr_pos < MAX_EXPR_SIZE) {
                    expr[expr_pos++] = '*';
                    expr[expr_pos] = 0;
                    show_expr();
                }
            }
            else if (key == 'C') { // division
                if (expr_pos < MAX_EXPR_SIZE) {
                    expr[expr_pos++] = '/';
                    expr[expr_pos] = 0;
                    show_expr();
                }
            }
            else if (key == 'D') { // equal
                int result = 0;
                if (calc_full(expr, &result)) {
                    // Show input on top, result on bottom right
                    LCD_Clear();
                    LCD_SetCursor(0,0);
                    LCD_SendString(expr);
                    char result_str[17];
                    snprintf(result_str, sizeof(result_str), "= %d", result);
                    int pad = 16 - strlen(result_str);
                    if(pad < 0) pad = 0;
                    LCD_SetCursor(1, pad);
                    LCD_SendString(result_str);
                    HAL_UART_Transmit(&huart2, (uint8_t*)result_str, strlen(result_str), 100);
                    HAL_Delay(800);
                } else {
                    LCD_Clear();
                    LCD_SendString("Err!");
                    HAL_UART_Transmit(&huart2, (uint8_t*)"Err!", 4, 100);
                    HAL_Delay(800);
                }
                clear_expr();
                show_expr();
            }
            else if (key == 'F') { // clear
                clear_expr();
                LCD_Clear();
                LCD_SendString("Clr");
                show_expr();  // No delay
            }

            HAL_UART_Transmit(&huart2, (uint8_t*)&key, 1, 100);
            HAL_Delay(150);
        }
    }
}


void SystemClock_Config(void)
{
    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_HSE;
    RCC_OscInitStruct.HSEState = RCC_HSE_ON;
    RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
    RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
    RCC_OscInitStruct.PLL.PLLM = 8;
    RCC_OscInitStruct.PLL.PLLN = 336;
    RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
    RCC_OscInitStruct.PLL.PLLQ = 7;
    if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) Error_Handler();

    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_DIV4;
    RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;

    if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) Error_Handler();
}
void MX_I2C1_Init(void)
{
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
    if (HAL_I2C_Init(&hi2c1) != HAL_OK) Error_Handler();
}
void MX_USART2_UART_Init(void)
{
    __HAL_RCC_USART2_CLK_ENABLE();
    __HAL_RCC_GPIOA_CLK_ENABLE();
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    GPIO_InitStruct.Pin = GPIO_PIN_2 | GPIO_PIN_3;  // PA2=TX, PA3=RX
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Alternate = GPIO_AF7_USART2;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    huart2.Instance = USART2;
    huart2.Init.BaudRate = 9600;
    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();
}
void MX_GPIO_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};
    __HAL_RCC_GPIOE_CLK_ENABLE();
    __HAL_RCC_GPIOD_CLK_ENABLE();
    __HAL_RCC_GPIOB_CLK_ENABLE();

    // Keypad LCD related pins
    HAL_GPIO_WritePin(GPIOD, GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_7, GPIO_PIN_RESET);

    GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12|GPIO_PIN_13|GPIO_PIN_14;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(GPIOE, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10|GPIO_PIN_7;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
}
void Error_Handler(void)
{
    __disable_irq();
    while(1) {}
}

I2C_LCD.C

C/C++
#include "i2c_lcd.h"
#include "main.h"

extern I2C_HandleTypeDef hi2c1;

// Commands
#define LCD_CLEAR_DISPLAY   0x01
#define LCD_RETURN_HOME     0x02
#define LCD_ENTRY_MODE_SET  0x06
#define LCD_DISPLAY_ON      0x0C
#define LCD_FUNCTION_SET    0x28
#define LCD_SET_DDRAM_ADDR  0x80

// Control bits
#define LCD_BACKLIGHT 0x08
#define LCD_ENABLE    0x04

// ---------------- Low level ----------------
static void LCD_SendInternal(uint8_t data, uint8_t flags)
{
    HAL_StatusTypeDef res;
    uint8_t up = data & 0xF0;
    uint8_t lo = (data << 4) & 0xF0;
    uint8_t data_arr[4];

    data_arr[0] = up | flags | LCD_BACKLIGHT | LCD_ENABLE;
    data_arr[1] = up | flags | LCD_BACKLIGHT;
    data_arr[2] = lo | flags | LCD_BACKLIGHT | LCD_ENABLE;
    data_arr[3] = lo | flags | LCD_BACKLIGHT;

    res = HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_arr, 4, HAL_MAX_DELAY);
    HAL_Delay(1);
}

static void LCD_SendCommand(uint8_t cmd)
{
    LCD_SendInternal(cmd, 0x00);
}

static void LCD_SendData(uint8_t data)
{
    LCD_SendInternal(data, 0x01);
}


void LCD_Init(void)
{
    HAL_Delay(50); 

   
    LCD_SendCommand(0x30);
    HAL_Delay(5);
    LCD_SendCommand(0x30);
    HAL_Delay(1);
    LCD_SendCommand(0x30);
    HAL_Delay(10);
    LCD_SendCommand(0x20); 
   
    LCD_SendCommand(LCD_FUNCTION_SET);
    LCD_SendCommand(LCD_DISPLAY_ON);
    LCD_SendCommand(LCD_CLEAR_DISPLAY);
    LCD_SendCommand(LCD_ENTRY_MODE_SET);
    HAL_Delay(5);
}

void LCD_Clear(void)
{
    LCD_SendCommand(LCD_CLEAR_DISPLAY);
    HAL_Delay(2);
}

void LCD_SetCursor(uint8_t row, uint8_t col)
{
    uint8_t addr = (row == 0) ? 0x00 + col : 0x40 + col;
    LCD_SendCommand(LCD_SET_DDRAM_ADDR | addr);
}

void LCD_SendChar(char c)
{
    LCD_SendData((uint8_t)c);
}

void LCD_SendString(char *str)
{
    while (*str) {
        LCD_SendChar(*str++);
    }
}

I2C_LCD.H

C/C++
#ifndef I2C_LCD_H
#define I2C_LCD_H

#include "stm32f4xx_hal.h"


#define LCD_ADDR (0x27 << 1)


void LCD_Init(void);
void LCD_Clear(void);
void LCD_SetCursor(uint8_t row, uint8_t col);
void LCD_SendString(char *str);
void LCD_SendChar(char c);

#endif 

KEYPAD.C

C/C++
#include "keypad.h"
#include "i2c_lcd.h"

static const char keymap[ROWS][COLS] = {
    { '1', '2', '3', 'A' },
    { '4', '5', '6', 'B' },
    { '7', '8', '9', 'C' },
    { 'E', '0', 'F', 'D' }
};

void Keypad_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

   
    __HAL_RCC_GPIOD_CLK_ENABLE();
    GPIO_InitStruct.Pin = ROW0_PIN | ROW1_PIN | ROW2_PIN | ROW3_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(ROW_PORT, &GPIO_InitStruct);

    
    __HAL_RCC_GPIOE_CLK_ENABLE();
    GPIO_InitStruct.Pin = COL0_PIN | COL1_PIN | COL2_PIN | COL3_PIN;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    HAL_GPIO_Init(COL_PORT, &GPIO_InitStruct);

    
    HAL_GPIO_WritePin(ROW_PORT, ROW0_PIN | ROW1_PIN | ROW2_PIN | ROW3_PIN, GPIO_PIN_SET);
}

char Keypad_GetKey(void)
{
    uint16_t row_pins[ROWS] = { ROW0_PIN, ROW1_PIN, ROW2_PIN, ROW3_PIN };
    uint16_t col_pins[COLS] = { COL0_PIN, COL1_PIN, COL2_PIN, COL3_PIN };

    for (int row = 0; row < ROWS; row++) {
        
        HAL_GPIO_WritePin(ROW_PORT, ROW0_PIN | ROW1_PIN | ROW2_PIN | ROW3_PIN, GPIO_PIN_SET);

        HAL_GPIO_WritePin(ROW_PORT, row_pins[row], GPIO_PIN_RESET);

        
        for (int col = 0; col < COLS; col++) {
            if (HAL_GPIO_ReadPin(COL_PORT, col_pins[col]) == GPIO_PIN_RESET) {
                HAL_Delay(20); // debounce
                if (HAL_GPIO_ReadPin(COL_PORT, col_pins[col]) == GPIO_PIN_RESET) {
                    return keymap[row][col];
                }
            }
        }
    }

    return 0; 
}

KEYPAD.H

C/C++
#ifndef KEYPAD_H_
#define KEYPAD_H_



#endif 
#ifndef KEYPAD_H
#define KEYPAD_H

#include "stm32f4xx_hal.h"

#define ROWS 4
#define COLS 4


#define ROW_PORT GPIOD
#define COL_PORT GPIOE


#define ROW0_PIN GPIO_PIN_7
#define ROW1_PIN GPIO_PIN_8
#define ROW2_PIN GPIO_PIN_9
#define ROW3_PIN GPIO_PIN_10


#define COL0_PIN GPIO_PIN_11
#define COL1_PIN GPIO_PIN_12
#define COL2_PIN GPIO_PIN_13
#define COL3_PIN GPIO_PIN_14


void Keypad_Init(void);
char Keypad_GetKey(void);

#endif 

Credits

PRINCESS LAURON
1 project • 0 followers
HEART LAHOYLAHOY
1 project • 0 followers

Comments