// Librería estándar de C para tipos de dato con tamaño fijo
// como uint8_t (entero sin signo de 8 bits), uint16_t, etc.
#include <stdint.h>
#include "stm32f103xb.h"
#include "lcd.h"
// ── delay_ms ─────────────────────────────────────────────────
// Espera aproximada en milisegundos.
// El loop interno (~800 iteraciones) consume ~1ms a 8MHz.
void delay_ms(uint16_t t) {
for (uint16_t i = 0; i < t; i++) // repite t veces
for (volatile unsigned long l = 0; l < 800; l++); // ~1ms cada vez
}
// "volatile" evita que el compilador elimine el loop por optimización
// ── delay_us ─────────────────────────────────────────────────
// Espera aproximada en microsegundos usando instrucción NOP.
// __NOP() es una instrucción vacía que consume 1 ciclo de reloj.
void delay_us(uint32_t us) {
for (uint32_t i = 0; i < us * 8; i++) __NOP(); // ~8 ciclos por µs a 8MHz
}
// ── GPIO_Init ────────────────────────────────────────────────
void GPIO_Init(void) {
// Habilitar el reloj de GPIOA (bit 2) y GPIOB (bit 3)
// Sin esto los periféricos no funcionan en STM32
RCC->APB2ENR |= (1 << 2) | (1 << 3);
// ── PB0 y PB1 como entradas con pull-up ──────────────────
// CRL controla los pines 0-7 de GPIOB
// Cada pin ocupa 4 bits: [CNF1|CNF0|MODE1|MODE0]
// Para entrada pull-up: CNF=10 (0b10), MODE=00 → 0x8 por pin
GPIOB->CRL &= ~(0xFF << 0); // limpiar los 8 bits de PB0 y PB1
GPIOB->CRL |= (0x88 << 0); // PB0=0b1000, PB1=0b1000 → entrada pull-up/down
// Poner ODR en 1 activa el pull-up interno (si fuera 0 sería pull-down)
GPIOB->ODR |= (1 << 0) | (1 << 1);
// ── PB8 y PB9 como salidas push-pull 2MHz ────────────────
// CRH controla los pines 8-15 de GPIOB
// Para salida push-pull 2MHz: CNF=00, MODE=10 → 0x2 por pin
GPIOB->CRH &= ~(0xFF << 0); // limpiar los 8 bits de PB8 y PB9
GPIOB->CRH |= (0x22 << 0); // PB8=0b0010, PB9=0b0010 → salida 2MHz
// Inicializar motor apagado (PB8=0, PB9=0)
GPIOB->ODR &= ~((1 << 8) | (1 << 9));
}
// ── BTN_Pressed ──────────────────────────────────────────────
// Detecta si el botón en "pin" de GPIOB fue presionado.
// Los botones van a GND, por eso reposan en HIGH y bajan a LOW al presionar.
uint8_t BTN_Pressed(uint8_t pin) {
if (!(GPIOB->IDR & (1 << pin))) { // ¿el pin está en LOW? (botón presionado)
delay_ms(20); // esperar 20ms para filtrar el rebote
if (!(GPIOB->IDR & (1 << pin))) { // confirmar que sigue en LOW
while (!(GPIOB->IDR & (1 << pin))); // esperar a que lo suelte
return 1; // botón válido detectado
}
}
return 0; // no fue presionado
}
// ── Motor_Update ─────────────────────────────────────────────
// Actualiza las salidas PB8 (IN1) y PB9 (IN2) del H-bridge.
// state: 0=Stop, 1=Run
// dir: 0=Left, 1=Right
void Motor_Update(uint8_t state, uint8_t dir) {
if (state == 0) {
// Freno: ambas entradas del H-bridge en 0
GPIOB->ODR &= ~((1 << 8) | (1 << 9));
} else {
if (dir == 1) {
// Right: IN1=1, IN2=0
GPIOB->ODR |= (1 << 8);
GPIOB->ODR &= ~(1 << 9);
} else {
// Left: IN1=0, IN2=1
GPIOB->ODR &= ~(1 << 8);
GPIOB->ODR |= (1 << 9);
}
}
}
// ── Menu_Render ──────────────────────────────────────────────
// Dibuja el menú completo en el LCD.
// cursor: 0=apunta a Status, 1=apunta a Dir
// state: 0=Stop, 1=Run
// dir: 0=Left, 1=Right
void Menu_Render(uint8_t cursor, uint8_t state, uint8_t dir) {
// ── Línea 0: Status ──────────────────────────────────────
LCD_SetCursor(0, 0); // ir a columna 0, fila 0
if (cursor == 0)
LCD_Print("> Status: "); // cursor apunta aquí
else
LCD_Print(" Status: "); // cursor NO apunta aquí
// Operador ternario: si state==1 imprime "Run ", si no "Stop"
LCD_Print(state == 1 ? "Run " : "Stop");
// ── Línea 1: Dir ─────────────────────────────────────────
LCD_SetCursor(0, 1); // ir a columna 0, fila 1
if (cursor == 1)
LCD_Print("> Dir: ");
else
LCD_Print(" Dir: ");
LCD_Print(dir == 1 ? "Right" : "Left ");
// El espacio al final de "Left " borra el carácter sobrante
// cuando se cambia de "Right" (5 chars) a "Left" (4 chars)
}
// ── main ─────────────────────────────────────────────────────
int main(void) {
uint8_t cursor = 0; // posición del cursor: 0=Status, 1=Dir
uint8_t motorState = 0; // estado del motor: 0=Stop, 1=Run
uint8_t motorDir = 0; // dirección: 0=Left, 1=Right
GPIO_Init(); // configurar pines
LCD_Init(); // inicializar LCD
Menu_Render(cursor, motorState, motorDir); // dibujar menú inicial
while (1) { // loop infinito
// ── Botón PB0: avanzar cursor ────────────────────────
if (BTN_Pressed(0)) {
cursor = (cursor + 1) % 2; // alterna entre 0 y 1
Menu_Render(cursor, motorState, motorDir);
}
// ── Botón PB1: seleccionar / toggle ──────────────────
if (BTN_Pressed(1)) {
if (cursor == 0)
motorState = !motorState; // toggle Stop↔Run
else
motorDir = !motorDir; // toggle Left↔Right
Motor_Update(motorState, motorDir); // aplicar al hardware
Menu_Render(cursor, motorState, motorDir); // actualizar LCD
}
}
}