Keypad-Based Precision Servo Motor Angle Controller

Control servo angles with high precision using just a keypad—simple, accurate, and powerful!

IntermediateFull instructions provided12 hours192
Keypad-Based Precision Servo Motor Angle Controller

Things used in this project

Hardware components

STM32F401CB
×1
SG90 Micro-servo motor
SG90 Micro-servo motor
×1
RGB Backlight LCD - 16x2
Adafruit RGB Backlight LCD - 16x2
×1
Grove - 12-Channel Capacitive Touch Keypad (ATtiny1616)
Seeed Studio Grove - 12-Channel Capacitive Touch Keypad (ATtiny1616)
×1
Rotary potentiometer (generic)
Rotary potentiometer (generic)
×1

Software apps and online services

PROTEUS
KEIL uVISION

Story

Read more

Schematics

Keypad-Based Precision Servo Motor Angle Controller Schematic

This schematic illustrates the connection between the keypad, LCD, and servo with the STM32 microcontroller, including the pin configurations for input, output, and control signals

Code

Keypad-Based Precision Servo Motor Angle Controller Program

C/C++
This program uses an STM32 microcontroller to control a servo motor angle via a keypad, with feedback shown on an LCD. Users enter an angle (0–180) using the keypad, press # to confirm, or * to clear the entry.
#include "stm32f401xc.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#define LCD_RS_PIN 0
#define LCD_EN_PIN 1
#define LCD_D4_PIN 2
#define LCD_D5_PIN 3
#define LCD_D6_PIN 4
#define LCD_D7_PIN 5

#define SERVO_PIN 8

char input_buffer[4] = {0};
uint8_t input_idx = 0;
int current_servo_angle = 0;

static void delay_ms(uint32_t ms) {
    SysTick->LOAD = (84000 * ms) - 1;
    SysTick->VAL = 0;
    SysTick->CTRL = SysTick_CTRL_CLKSOURCE_Msk | SysTick_CTRL_ENABLE_Msk;
    while(!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk));
    SysTick->CTRL = 0;
}

static void SystemClock_Config(void) {
    RCC->CR |= RCC_CR_HSION;
    while(!(RCC->CR & RCC_CR_HSIRDY));

    RCC->PLLCFGR = (16U<<RCC_PLLCFGR_PLLM_Pos) | (168U<<RCC_PLLCFGR_PLLN_Pos) |
                   (0U<<RCC_PLLCFGR_PLLP_Pos) | RCC_PLLCFGR_PLLSRC_HSI | (7U<<RCC_PLLCFGR_PLLQ_Pos);

    RCC->CR |= RCC_CR_PLLON;
    while(!(RCC->CR & RCC_CR_PLLRDY));

    FLASH->ACR = FLASH_ACR_ICEN | FLASH_ACR_DCEN | FLASH_ACR_PRFTEN | FLASH_ACR_LATENCY_2WS;

    RCC->CFGR |= RCC_CFGR_SW_PLL;
    while((RCC->CFGR & RCC_CFGR_SWS) != RCC_CFGR_SWS_PLL);
}

static void GPIO_Init(void) {
    RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN | RCC_AHB1ENR_GPIOBEN;

    GPIOA->MODER &= ~(0xFFF);
    GPIOA->MODER |= (0x1 << (LCD_RS_PIN * 2)) | (0x1 << (LCD_EN_PIN * 2)) |
                    (0x1 << (LCD_D4_PIN * 2)) | (0x1 << (LCD_D5_PIN * 2)) |
                    (0x1 << (LCD_D6_PIN * 2)) | (0x1 << (LCD_D7_PIN * 2));
    GPIOA->OTYPER &= ~(0x3F);
    GPIOA->OSPEEDR |= 0xFFF;

    GPIOA->MODER &= ~(0x3 << (SERVO_PIN * 2));
    GPIOA->MODER |= (0x2 << (SERVO_PIN * 2));

    GPIOA->AFR[1] = (GPIOA->AFR[1] & ~(0xF << ((SERVO_PIN - 8) * 4))) | (0x1 << ((SERVO_PIN - 8) * 4));

    GPIOB->MODER &= ~(0xFF);
    GPIOB->PUPDR = (GPIOB->PUPDR & ~0xFF) | (0x1 << (0 * 2)) | (0x1 << (1 * 2)) |
                   (0x1 << (2 * 2)) | (0x1 << (3 * 2));

    GPIOB->MODER &= ~(0xFF << (4 * 2));
    GPIOB->MODER |= (0x1 << (4 * 2)) | (0x1 << (5 * 2)) | (0x1 << (6 * 2)) | (0x1 << (7 * 2));
    GPIOB->OTYPER &= ~(0xF << 4);
    GPIOB->OSPEEDR |= (0xFF << (4 * 2));
}

static void lcd_pulse(void) {
    GPIOA->BSRR = (1U << (LCD_EN_PIN + 16));
    delay_ms(1);
    GPIOA->BSRR = (1U << LCD_EN_PIN);
    delay_ms(1);
    GPIOA->BSRR = (1U << (LCD_EN_PIN + 16));
    delay_ms(2);
}

static void lcd_nibble(uint8_t n) {
    GPIOA->ODR = (GPIOA->ODR & ~0x3C) | ((n & 0xF) << LCD_D4_PIN);
    lcd_pulse();
}

static void lcd_send(uint8_t v, uint8_t rs) {
    if(rs) GPIOA->BSRR = (1U << LCD_RS_PIN);
    else   GPIOA->BSRR = (1U << (LCD_RS_PIN + 16));

    lcd_nibble(v >> 4);
    lcd_nibble(v & 0xF);
}

static void lcd_init(void) {
    delay_ms(40);
    lcd_nibble(0x3); delay_ms(5);
    lcd_nibble(0x3); delay_ms(1);
    lcd_nibble(0x3);
    lcd_nibble(0x2);
    lcd_send(0x28, 0);
    lcd_send(0x08, 0);
    lcd_send(0x01, 0);
    delay_ms(2);
    lcd_send(0x06, 0);
    lcd_send(0x0C, 0);
}

static void lcd_clear(void) {
    lcd_send(0x01, 0);
    delay_ms(2);
}

static void lcd_set_cursor(uint8_t col, uint8_t row) {
    uint8_t address;
    if (row == 0) {
        address = 0x00 + col;
    } else {
        address = 0x40 + col;
    }
    lcd_send(0x80 | address, 0);
}

static void lcd_puts(const char *s) {
    while(*s) {
        lcd_send(*s++, 1);
    }
}

static void set_servo_angle(int angle) {
    if (angle < 0) angle = 0;
    if (angle > 180) angle = 180;

    current_servo_angle = angle;

    uint32_t ccr_value = 1000 + ((angle * 1000 + 90)/180);

    TIM1->CCR1 = ccr_value;
    TIM1->EGR |= TIM_EGR_UG;
}

static void TIM1_Init(void) {
    RCC->APB2ENR |= RCC_APB2ENR_TIM1EN;

    TIM1->PSC = 84 - 1;

    TIM1->ARR = 20000 - 1;

    TIM1->CCMR1 = (6U<<TIM_CCMR1_OC1M_Pos) | TIM_CCMR1_OC1PE;

    TIM1->CCER |= TIM_CCER_CC1E;

    TIM1->BDTR |= TIM_BDTR_MOE;

    TIM1->CR1 |= TIM_CR1_ARPE;

    TIM1->EGR |= TIM_EGR_UG;

    TIM1->CR1 |= TIM_CR1_CEN;

    set_servo_angle(0);
}

static const char keypad_map[4][3] = {
    {'7','8','9',},
    {'4','5','6',},
    {'1','2','3',},
    {'*','0','#',}
};

static char scan_keypad(void) {
    for(uint8_t c=0; c<4; c++){
        GPIOB->BSRR = (0xF << 4);
        GPIOB->BSRR = ((1U << (4 + c)) << 16);

        delay_ms(1);

        uint8_t r = GPIOB->IDR & 0xF;

        for(uint8_t i=0; i<4; i++) {
            if(!(r & (1U << i))) {
                delay_ms(5);
                if(!( (GPIOB->IDR & 0xF) & (1U << i) )) {
                    return keypad_map[i][c];
                }
            }
        }
    }
    return 0;
}

int main(void) {
    SystemClock_Config();
    GPIO_Init();
    lcd_init();
    TIM1_Init();

    char last_key = 0;
    char lcd_display_buffer[17];

    lcd_clear();
    lcd_set_cursor(0, 0);
    lcd_puts("Enter Angle:");
    lcd_set_cursor(0, 1);
    sprintf(lcd_display_buffer, "Current: %d deg", current_servo_angle);
    lcd_puts(lcd_display_buffer);

    while(1) {
        char key = scan_keypad();

        if (key && key != last_key) {
            if (key >= '0' && key <= '9') {
                if (input_idx < 3) {
                    input_buffer[input_idx++] = key;
                    input_buffer[input_idx] = '\0';

                    lcd_set_cursor(12, 0);
                    lcd_puts(input_buffer);
                    for(int i = input_idx; i < 3; i++) {
                        lcd_send(' ', 1);
                    }
                }
            } else if (key == '#') {
                if (input_idx > 0) {
                    int new_angle = atoi(input_buffer);
                    set_servo_angle(new_angle);

                    lcd_clear();
                    lcd_set_cursor(0, 0);
                    lcd_puts("Angle Set!");
                    lcd_set_cursor(0, 1);
                    sprintf(lcd_display_buffer, "Current: %d deg", new_angle);
                    lcd_puts(lcd_display_buffer);

                    input_idx = 0;
                    input_buffer[0] = '\0';
                }
            } else if (key == '*') {
                input_idx = 0;
                input_buffer[0] = '\0';
                lcd_clear();
                lcd_set_cursor(0, 0);
                lcd_puts("Enter Angle:");
                lcd_set_cursor(12, 0);
                lcd_puts("   ");
                lcd_set_cursor(0, 1);
                sprintf(lcd_display_buffer, "Current: %d deg", current_servo_angle);
                lcd_puts(lcd_display_buffer);
            }
        }
        last_key = key;
    }
}

Credits

Muhammad Angga Pratama
1 project • 4 followers
Nurul
1 project • 0 followers
Siti Juwaria
1 project • 0 followers
Ihsan Andhika Putra
1 project • 0 followers
Urfan Urfan
1 project • 0 followers
syahrul zufar
1 project • 0 followers
MUHAMMAD MULIA JAYA KUSUMA
1 project • 0 followers
Riski Arnando
1 project • 0 followers
Sheren
1 project • 0 followers

Comments