MICROCONTROLADORES ARM
¿Que son los microcontroladores ARM?
Los microcontroladores ARM son dispositivos integrados que
incluyen un procesador basado en la arquitectura
ARM (Advanced RISC Machine), junto con memoria, periféricos y
otros componentes en un solo chip. Son ampliamente utilizados en
sistemas embebidos debido a su alto rendimiento, bajo
consumo de energía y escalabilidad.
Características principales de los microcontroladores
ARM
Arquitectura RISC
Diseño simplificado para mayor eficiencia.
Familia Cortex-M (para microcontroladores)
Cortex-M0/M0+: Bajo costo y consumo (aplicaciones simples).
Cortex-M3/M4: Balance entre rendimiento y consumo (con DSP
y FPU opcional).
Cortex-M7: Alto rendimiento (aplicaciones críticas en tiempo
real).
Aplicaciones comunes
Electrónica embebida: Sensores, actuadores, wearables.
IoT: Dispositivos conectados (Wi-Fi, Bluetooth, LoRa).
Industria: Control de motores, automatización.
Automoción: Sistemas de infoentretenimiento, control de
motor.
Robótica: Procesamiento en tiempo real.
Ventajas frente a otros microcontroladores (como
AVR o PIC)
✅ Mayor rendimiento por MHz.
✅ Mejor relación potencia/consumo.
✅ Más opciones de fabricantes y familias.
✅ Soporte para sistemas operativos en tiempo real (FreeRTOS,
Zephyr).
Ejemplos populares
STM32F4 (Cortex-M4 con FPU).
RP2040 (Doble núcleo Cortex-M0+, usado en Raspberry Pi Pico).
NXP LPC1768 (Cortex-M3 clásico).
En resumen, los microcontroladores ARM dominan el mercado de
sistemas embebidos gracias a su flexibilidad, eficiencia y amplio
ecosistema de herramientas de desarrollo (Keil, GCC, PlatformIO).
Caracteristicas del microcontrolador
STM32F103C8T6
1. Procesador y Arquitectura
Núcleo: ARM Cortex-M3 a 72 MHz.
Conjunto de instrucciones: Thumb-2 (eficiente en código y
rendimiento).
Pipeline de 3 etapas para mayor velocidad.
2. Memoria
Flash: 64 KB (aunque algunos chips tienen 128 KB "ocultos").
SRAM: 20 KB.
EEPROM: No incluida (se puede emular en Flash).
3. Periféricos Integrados
GPIO: 37 pines de E/S de propósito general (5V tolerant en
algunos).
ADC: 2 x ADC de 12 bits (hasta 16 canales, 1 µs de tiempo de
conversión).
Timers:
3 x TIM2, TIM3, TIM4 (16-bit).
1 x TIM1 (16-bit avanzado para PWM).
1 x SysTick (para RTOS).
Comunicación serial:
3 x USART (UART + sincronía opcional).
2 x SPI.
2 x I2C.
1 x USB 2.0 Full-Speed (sin PHY integrado).
1 x CAN 2.0B (para aplicaciones
industriales/automotrices).
Interfaces de depuración: SWD y JTAG (para programación y
debugging).
4. Voltaje y Consumo
Voltaje de operación: 2.0V a 3.6V (típico 3.3V).
Consumo:
~50 µA en modo Sleep.
~20 µA en modo Stop.
~2 µA en modo Standby.
o
5. Paquete y Pines
Paquete: LQFP48 (48 pines).
Pines accesibles: 37 GPIO + alimentación +
comunicación.
RESUMEN DE CONCEPTOS CLAVES
Comunicación Serial
Método para transmitir datos bit a bit (en serie) entre dispositivos.
Tipos comunes:
UART: Asíncrona (2 cables: TX, RX). Ejemplo: Monitor
Serie en Arduino.
SPI: Síncrona (4 cables: CLK, MOSI, MISO, SS). Alta
velocidad.
I2C: Síncrona (2 cables: SDA, SCL). Conexión múltiples
dispositivos.
Memorias
Flash: Almacena el código del programa (no volátil, se
borra por bloques). Ejemplo: 64 KB en STM32F103.
SRAM: Memoria volátil (se pierde al apagar) para
variables y datos temporales. Ejemplo: 20 KB en
STM32F103.
EEPROM: Memoria no volátil para guardar datos (ej:
configuraciones). En STM32 se emula en Flash.
Pines GPIO
(General Purpose Input/Output): Pines programables como:
Entrada: Leer señales (ej: botones, sensores digitales).
Salida: Controlar dispositivos (ej: LEDs, relés).
Alternate Function: UART, SPI, etc.
ADC (Analog-to-Digital Converter)
Convierte señales analógicas (ej: 0-3.3V) a valores digitales (ej: 0-
4095 en 12 bits). Usos: Lectura de sensores (temperatura,
potenciómetros).
Timers: Circuitos para contar tiempo o generar señales:
Base de tiempo: Delays, medición de pulsos.
PWM: Control de motores, LEDs (señal cuadrada
ajustable).
Captura/Compara: Medir frecuencia de señales.
Modos de bajo consumo
Sleep: CPU detenida, periféricos activos. Despierta con
interrupciones.
Stop: CPU y periféricos apagados (excepto SRAM).
Despierta con eventos externos.
Standby: Todo apagado (incluida SRAM). Solo despierta
con reset o pin específico (ej: RTC).
Concept
Función Principal Ejemplo de Uso
o
Comunicación bit a
Serial Enviar datos a PC
bit
Flash Almacenar código Programa del micro
Variables
SRAM Datos en ejecución
temporales
GPIO Entrada/Salida Leer botón, encender LED
Leer señales
ADC Sensor de luz
analógicas
Contar/Generar
Timer PWM para motor
señales
Sleep/ Sensor que despierta cada
Ahorrar energía
Stop hora
CONCEPTOS BASICOS:
Diferencia entre byte y bit
1 bit: Es la unidad más pequeña de datos en
computación. Puede tener dos valores: 0 o 1.
1 byte: Es un grupo de 8 bits. Por ejemplo: 01101011 (8
dígitos binarios).01101011 (8 dígitos binarios).
Ejemplos prácticos
Un archivo de texto de 1 KB (kilobyte) = 1024
bytes = 8192 bits.
Un microcontrolador de 32 bits (como ARM Cortex-M)
procesa 4 bytes a la vez (32 ÷ 8 = 4).
Unida Equivalen
d cia
1 bit 0o1
1 byte 8 bits
1 KB 1024 bytes
1 MB 1024 KB
En redes (ej: Internet), la velocidad se mide en bits por
segundo (Mbps = megabits/s), mientras que el almacenamiento
(ej: disco duro) se mide en bytes (MB, GB).
¿Qué significa exactamente "32 bits"?
Que un microcontrolador (como los ARM Cortex-M) sea de 32
bits significa que su arquitectura interna está diseñada para
procesar datos y direcciones de memoria en bloques de 32 bits (4
bytes) a la vez. Esto tiene implicaciones clave en su rendimiento y
capacidades.
Ejemplo práctico
Micro de 8 bits (AVR): Para sumar dos números de 32 bits,
necesita 4 operaciones (8 bits a la vez).
Micro de 32 bits (ARM Cortex-M): Lo hace en 1 operación,
reduciendo tiempo y energía.
Resumen
Un microcontrolador de 32 bits (como el STM32) es como una
autopista de 4 carriles frente a una calle de 1 carril (8 bits):
mueve más datos en paralelo, acelerando operaciones y
permitiendo aplicaciones más complejas (IoT, robótica, audio
digital).
PROGRAMAS A UTILIZAR
¿Que es STM32CubeIDE?
STM32CubeIDE es el entorno de desarrollo integrado (IDE) oficial
de STMicroelectronics para programar y depurar
microcontroladores de la familia STM32 (basados en ARM Cortex-
M). Es una herramienta gratuita y todo en uno, diseñada para
simplificar el desarrollo de aplicaciones embebidas.
¿Qué es el ST-Link?
El ST-Link es un programador y depurador oficial de
STMicroelectronics diseñado específicamente para
microcontroladores STM8 y STM32. Permite:
Programar el firmware en la memoria Flash del
microcontrolador.
Depurar el código en tiempo real (breakpoints, paso a
paso, visualización de variables, etc.).
Versiones comunes
ST-Link/V2:
La versión más usada (integrada en placas
como Nucleo y Discovery).
Soporta STM32 y STM8.
Conecta vía USB y usa interfaz SWD (Serial Wire Debug)
o JTAG.
ST-Link/V3:
Más rápida y con mayor corriente para alimentar la placa
objetivo.
Incluye interfaz VCP (Virtual COM Port) para
comunicación serial.
ST-Link integrado:
Muchas placas de desarrollo (ej: STM32 Nucleo) lo traen
incluido.
¿Cómo se conecta?
Conexión típica (SWD):
ST-Link → STM32
SWDIO → PA13 (SWDIO)
SWCLK → PA14 (SWCLK)
GND → GND
3.3V → 3.3V (opcional, para alimentar la placa objetivo).
En placas Nucleo/Discovery:
Ya está integrado, solo conecta por USB.
PARTE 1: CREAR PROYECTO EN STM32CUBE IDE
File....STM32Project....Buscar modelo del micro.....Poner
nombre pryecto....next...finish
System Core:
RCC: En High speed clock poner: Cristal/Ceramic resonator
Clock Configuration:
Habilitar HSE....En PLL poner x9.....Seleccionar
PLLCLOCK....En APB1 Prescaler poner /2.
Pinout and Configuration: Configurar pines
Project Manager:
Code generator....Habilitar Generate Peripheral
initation...
Guardar todo
Programar
Conectar a ST-LINK y a la PC
Cambiar posicion jumper y presionar boton de reset:
En Run Last Tool....External Tools Configuration....Run
En Run Last Tool poner 1 ST_LINK Programmer: (Carga
el código al micro)
Una vez subido el programa cambiar posición de jumper y
presionar el botón de reset.
PARTE 2: CONTROL DE PUERTOS GPIO
Normalmente en los microcontroladores se les nombra como GPIO a las entradas y
salidas. GPIO significa entrada/salida de proposito general.
Este es el esquema de cada uno de los pines
PIN GPIO: Se puede configurar como entrada o salida
Si es ENTRADA: Entra una señal de voltaje por el
pin pasa por Schmitt trigger, se almacena y el
micro lo lee.
Si es SALIDA: Se escribe pasa por Multiplexador
y por etapa de transistores y envía 1 o 0 a la
salida.
¿Qué es un Trigger Schmitt?
El Trigger Schmitt (o Schmitt Trigger) es un circuito comparador
con histéresis, diseñado para convertir señales analógicas ruidosas
o lentas en señales digitales limpias (0 o 1). Es ampliamente usado
en microcontroladores (como en las entradas GPIO de los STM32)
para evitar falsos disparos causados por ruido eléctrico.
Funcionamiento básico NMOS y PMOS
¿Qué es un Multiplexador (MUX)?
Un multiplexador (o MUX) es un circuito digital que actúa como
un selector de canales, permitiendo elegir una de varias señales
de entrada y dirigirla a una única salida. Funciona como un
"interruptor controlado digitalmente" y es esencial en sistemas
electrónicos y microcontroladores (como los ARM Cortex-M).
Resistencias Pull-Up y Pull-Down: Usos y
Funcionamiento
Las resistencias pull-up y pull-down se utilizan en circuitos
digitales para definir un estado lógico estable en una línea de
señal cuando no está activamente impulsada (evitando estados
flotantes o indeterminados). Son esenciales en microcontroladores
(como STM32, Arduino) y sistemas digitales.
¿Qué Problema Resuelven?
Cuando un pin de entrada (como un GPIO) no está conectado a una
señal activa (ej: un botón sin pulsar), queda en estado
flotante (alta impedancia), lo que puede causar:
Lecturas erráticas (ruido eléctrico interpretado
como 0 o 1 aleatorio).
Consumo de energía innecesario.
Fallos en la lógica del circuito.
Resistencia Pull-Up
Valor típico:
1kΩ a 10kΩ (depende de la velocidad y consumo requerido).
Aplicaciones:
Botones e interruptores.
Comunicaciones I2C (los buses SDA y SCL requieren pull-ups).
Entradas digitales en microcontroladores.
Resistencia Pull-Down
Valor típico:
1kΩ a 10kΩ (similar a pull-up).
Aplicaciones:
Detección de señales activas en HIGH.
Circuitos donde el estado por defecto debe ser LOW.
Conclusión
Pull-Up: Para estados por defecto HIGH (ej: botones, I2C).
Pull-Down: Para estados por defecto LOW (ej: sensores activos
en HIGH).
Internas vs Externas: Usa las internas para simplificar, y las
externas para mayor precisión.
Push-Pull vs Open-Drain: Diferencias y Usos
Ambos son modos de configuración para salidas digitales en
microcontroladores (como STM32, Arduino) y circuitos integrados,
pero tienen comportamientos eléctricos y aplicaciones distintas.
Salida Push-Pull
Si ENTRADA = 1. Conecta VDD a la salida
Si ENTRADA = 0. Conecta GND a la salida
Características
✅ Ventajas:
Entrega corriente activamente en ambos estados
(HIGH y LOW).
Señales más rápidas (ideal para alta frecuencia).
No requiere resistencia externa.
❌ Desventajas:
No permite conexión en bus (ej: múltiples salidas unidas).
Aplicaciones
Señales digitales estándar (ej: GPIO, PWM, UART).
Control de LEDs, motores, relés.
Salida Open-Drain (o Open-Collector)
Cómo funciona
Solo usa un transistor NMOS conectado a GND.
LOW: El NMOS conduce, conectando la salida a GND.
HIGH: El transistor se apaga, dejando la
salida flotante (requiere una resistencia pull-up externa
para llevarla a VCC).
Características
✅ Ventajas:
Permite conexión en bus (varios dispositivos comparten la
misma línea).
Compatibilidad con diferentes voltajes (ej: 3.3V y 5V).
❌ Desventajas:
Más lento que Push-Pull (depende de la resistencia pull-up).
Requiere resistencia externa para estado HIGH.
Aplicaciones
Comunicaciones I2C (el bus necesita open-drain).
Interfaz de niveles mixtos (ej: 3.3V ↔ 5V).
Control compartido (varios dispositivos en una línea).
Comparación Push-Pull vs Open-Drain
Caracterí
Push-Pull Open-Drain
stica
Estructur
NMOS + PMOS Solo NMOS
a
Estado Flotante (necesita pull-
Conecta a VCC
HIGH up)
Estado
Conecta a GND Conecta a GND
LOW
Velocida
Más rápida Más lenta
d
Uso en
No compatible Ideal (ej: I2C)
buses
Mayor en Menor (solo consume en
Consumo
transiciones LOW)
FUNCIONES PARA CONTROL DE ENTRADAS Y
SALIDAS
1. Función HAL_GPIO_WritePin()
Propósito
Establecer un pin GPIO en estado HIGH (1) o LOW (0).
Sintaxis
HAL_GPIO_WritePin(GPIOx, GPIO_PIN_y, Estado);
GPIOx: Puerto del GPIO (ej: GPIOA, GPIOB, etc.)
GPIO_PIN_y: Número del pin (ej: GPIO_PIN_5).
Estado:
GPIO_PIN_SET → HIGH (3.3V).
GPIO_PIN_RESET → LOW (0V).
Ejemplo:
// Encender un LED conectado a PB0 (HIGH)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_SET);
// Apagar un LED conectado a PB0 (LOW)
HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
2. Función HAL_GPIO_TogglePin()
Propósito
Cambiar el estado actual de un pin (HIGH → LOW o LOW → HIGH). Útil para
parpadear LEDs sin necesidad de leer el estado previo.
Sintaxis
HAL_GPIO_TogglePin(GPIOx, GPIO_PIN_y);
Ejemplos:
// Cambiar el estado de PB0 (si estaba HIGH, pasa a LOW, y
viceversa)
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
// Uso típico en un bucle para parpadear un LED
while (1) {
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
HAL_Delay(500); // Espera 500 ms
}
RETARDO EN MILISEGUNDOS Y
MICROSEGUNDOS
La función HAL_Delay() es una utilidad proporcionada por la capa de abstracción de
hardware (HAL) de STMicroelectronics para generar retardos temporizados en
milisegundos. Es ampliamente usada en proyectos con microcontroladores STM32
para pausar la ejecución del código sin bloquear otras operaciones críticas (como
interrupciones).
Libreri: #include “stm32_delay.h”
Int main(void) {
//inicializa las funciones para el manejo de los retardos
MX_Delay_Init();
HAL_Delay_US(); Permite generar retardos en
microsegundos
HAL_Delay_MS(); Permite generar retardos en
milisegundos
ANTIREBOTE EN PULSADORES
El código mostrado implementa un antirrebote (debounce) por
software para eliminar los falsos contactos mecánicos de pulsadores
o interruptores. Aquí te explico cómo funciona, tanto para
configuraciones Pull-Up como Pull-Down.
Explicación del Código
1. Configuración Pull-Up
if (HAL_GPIO_ReadPin(Port, Pin) == 0) { // Si se detecta LOW
(botón presionado)
HAL_Delay(5); // Espera 5 ms (filtra ruido inicial)
while (HAL_GPIO_ReadPin(Port, Pin) == 0); // Espera hasta que
se suelte el botón
HAL_Delay(5); // Espera 5 ms (filtra ruido al soltar)
// Acciones a ejecutar (ej: encender LED)
}
¿Cómo funciona?
Pull-Up: El pin está en HIGH (1) cuando el botón no se
presiona (gracias a la resistencia pull-up).
Al presionar el botón, el pin va a LOW (0).
Los retardos (HAL_Delay(5)) ignoran los rebotes mecánicos
(señales fluctuantes al presionar/soltar).
2. Configuración Pull-Down
if (HAL_GPIO_ReadPin(Port, Pin) == 1) { // Si se detecta HIGH
(botón presionado)
HAL_Delay(5); // Espera 5 ms (filtra ruido inicial)
while (HAL_GPIO_ReadPin(Port, Pin) == 1); // Espera hasta que
se suelte el botón
HAL_Delay(5); // Espera 5 ms (filtra ruido al soltar)
// Acciones a ejecutar (ej: encender LED)
}
¿Cómo funciona?
Pull-Down: El pin está en LOW (0) cuando el botón no se
presiona (resistencia pull-down).
Al presionar el botón, el pin va a HIGH (1).
Los retardos filtran los rebotes.
¿Por qué 5 ms?
Los rebotes mecánicos suelen durar 1–50 ms.
5 ms es un valor típico para asegurar que los rebotes han
terminado.
Explicación del ADC en el
STM32F103C8T6
El microcontrolador STM32F103C8T6 incluye 2 módulos ADC
(Analog-to-Digital Converter) con las siguientes características
clave:
Características Principales
1. Resolucion de 12 bits
Convierte señales analógicas en valores digitales desde 0
hasta 4095 (porque = 4096 niveles)
Ejemplo:
0V → Valor digital 0.
3.3V → Valor digital 4095.
2. 16 canales analógicos:
Puede leer hasta 16 señales analógicas diferentes
(multiplexadas).
Los pines ADC están etiquetados como PA0 a PA7, PB0, PB1,
PC0 a PC5, etc. (consulta el datasheet para el mapeo exacto).
3. Rango de voltaje de entrada:
0V a 3.3V (voltaje de referencia del ADC).
Precisión: Cada paso (bit) equivale a:
3.3V / 4095 ≈ 0.0008V = 0.8mV por bit.
¿Cómo funciona el ADC?
1. Muestreo:
El ADC captura el voltaje analógico en un pin (ej: PA0).
2. Conversión:
Transforma el voltaje en un valor digital de 12 bits (0–4095).
3. Escalado:
Si mides un sensor de 0–5V, necesitas un divisor de voltaje
para adaptarlo a 0–3.3V.
¿Qué es el ADC?
Un ADC convierte una señal analógica (como el voltaje de un
potenciómetro o un sensor) en un valor digital que tu
microcontrolador puede entender.
Por ejemplo, un valor de voltaje entre 0V y 3.3V se convierte en un
número entre 0 y 4095 si el ADC es de 12 bits (porque =
4096 niveles posibles).
✅ Funciones mostradas en la imagen
1. HAL_ADC_Start();
¿Qué hace?
Inicia la conversión ADC. Es decir, le dice al ADC: "Empieza a
convertir la señal analógica en digital".
2. HAL_ADC_GetValue();
¿Qué hace?
Después de que la conversión ha terminado, esta función obtiene el
valor digital convertido del ADC.
¿Qué valor devuelve?
Devuelve un número entre 0 y 4095, dependiendo del voltaje leído.
EJEMPLO SENSILLO:
Pasos:
1. Configurar reloj
2. Click en System Core....Analog
Aquí se encuentran los dos módulos del ADC del
microcontrolador
3. Utilizar ADC1...INO
Por el momento solo usamos un canal
4. Unico parametro que vamos a modificar
En configuration:
ADC_Settings
Continuous Conver: Enabled
5. Aparece un error en la configuración del reloj:
Esto es porque el ADC no trabaja a esta frecuencia tan alta.
Trabaja a 12MHz
En ADC Prescaler dividir entre 6
CODIGO
Código es para encender y apagar led según la lectura del valor del
potenciometro en stm32f10c8t6 usando la placa blue pill
while (1)
{
HAL_ADC_Start(&hadc1);
int valor_adc = HAL_ADC_GetValue(&hadc1);
float voltaje = (float)((valor_adc*3.3)/4095.0);
if(voltaje > 2.5)
{
HAL_GPIO_WritePin(GPIOB, Led_Pin, GPIO_PIN_SET);
}
else
{
HAL_GPIO_WritePin(GPIOB, Led_Pin, GPIO_PIN_RESET); }
}
Control de brillo de un led con potenciometro
Para leer un potenciómetro con una Blue Pill (STM32F103C8T6) y
controlar el brillo de un LED mediante PWM, usando STM32CubeIDE,
tenés que hacer lo siguiente:
✅ Resumen de lo que necesitás
Leer el potenciómetro por ADC (conversión analógica a digital)
Generar una señal PWM en un pin con Timer
Usar el valor del ADC para variar el duty cycle del PWM
1. Conexiones
Conexión a Blue
Componente
Pill
Potenciómetro
GND
GND
Potenciómetro
3.3V (no usar 5V)
VCC
Potenciómetro
PA0 (canal ADC)
OUT
PA8 (PWM) con
LED ánodo (+)
resistencia
LED cátodo (-) GND
2. Configuración en STM32CubeIDE
Paso a paso:
A. Habilitar ADC
1. En la vista de configuración de pines, hacé clic en PA0.
2. Seleccioná “ADC1_IN0”
3. En la pestaña "Analog", activá ADC1
4. En “Configuration”, activá “Continuous Conversion Mode”
B. Habilitar PWM (Timer)
a. Hacé clic en PA8
b. Seleccioná “TIM1 Channel 1”
c. En la pestaña “Timers”, configurá:
d. Prescaler: 72 - 1 (para 1 MHz si estás usando HCLK de 72 MHz)
e. Counter Period: 999 (para obtener un PWM de 1 kHz)
uint32_t adc_val;
int main(void)
{
HAL_ADC_Start(&hadc1);
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
while (1)
{
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
adc_val = HAL_ADC_GetValue(&hadc1);
uint32_t pwm_val = adc_val * 1000 / 4095;
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,
pwm_val);
}
}
¿Qué hace este código?
1. Inicializa los periféricos: ADC y Timer.
2. Lee el valor del potenciómetro.
3. Convierte ese valor a un duty cycle.
4. Actualiza el PWM para controlar el brillo del LED.
#include "main.h"
#include "adc.h"
#include "tim.h"
Estos incluyen los archivos necesarios para usar el ADC, el Timer y
funciones de la HAL (Hardware Abstraction Layer).
No tenés que declarar hadc1 ni htim1, ya están en esos headers.
uint32_t adc_val;
Variable global que guarda el valor leído desde el potenciómetro (0
a 4095 en resolución de 12 bits).
int main(void)
Función principal que se ejecuta al encender la placa.
HAL_Init();
Inicializa la HAL, necesaria antes de usar cualquier periférico.
SystemClock_Config();
Configura el reloj del sistema (esto lo genera CubeIDE, no lo tocás).
Asegura que todo funcione con la frecuencia correcta (ej: 72 MHz).
1. MX_GPIO_Init();
2. MX_ADC1_Init();
3. MX_TIM1_Init();
Inicializa los periféricos:
a) GPIO: entradas/salidas generales
b) ADC1: para leer el potenciómetro
c) TIM1: para generar la señal PWM
HAL_ADC_Start(&hadc1);
Inicia el ADC. Esto permite comenzar a hacer conversiones
analógicas a digitales.
&hadc1: Significa que active el ADC1
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
Inicia el Timer en modo PWM, en el canal 1 (PA8 por ejemplo).
&htim1: Significa que active TIM1
TIM_CHANNEL_1: Significa que en Channel1 puse PWM
Generation CH1
NOTA: Hay 4 timers, TIM1, TIM2, TIM3 y TIM4. Cada timer
tiene 4 canales, CH1, CH2, CH3 y CH4.
while (1)
Bucle infinito. Todo lo que esté dentro se repite para siempre.
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
Espera hasta que el ADC termine de convertir. HAL_MAX_DELAY
significa que esperará el tiempo que sea necesario.
adc_val = HAL_ADC_GetValue(&hadc1);
Obtiene el valor del ADC. Este valor representa el voltaje del
potenciómetro convertido a digital (0 a 4095).
uint32_t pwm_val = adc_val * 1000 / 4095;
Convierte el valor del potenciómetro en un valor para el PWM:
1. Si el ADC da 0, el PWM es 0 (LED apagado).
2. Si el ADC da 4095, el PWM es 1000 (LED al máximo brillo).
1000 es el valor máximo del período del timer (lo configuraste como
999 en CubeIDE).
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1,
pwm_val);
Cambia el duty cycle del PWM. Esto controla el brillo del LED:
1. Bajo valor → poca energía → LED tenue.
2. Alto valor → más energía → LED brillante.
Resumen:
hadc1 → Es el ADC que está leyendo el voltaje del
potenciómetro.
htim1 → Es el Timer que está generando el PWM para el LED.
TIM_CHANNEL_1 → Es el canal del Timer que estás usando para
el PWM.
Cada timer tiene cuatro canales que pueden ser configurados en
modo PWM. Podemos controlar hasta 16 servomotores en forma
independiente utilizando este microcontrolador.
Ciclo de trabajo
Ejemplo:
Querés un PWM de 1 kHz
El reloj principal del microcontrolador (f_CPU) es de 72 MHz =
72,000,000 Hz. Esto es demasiado rápido para muchas
aplicaciones, como generar PWM para un LED (porque queremos
frecuencias de 1 kHz, no 72 millones).
Un Timer en STM32 cuenta números hacia arriba, y cuando llega a
un valor (el Counter Period), vuelve a 0 y empieza de nuevo.
Esto genera una señal periódica: el PWM.
Querés una frecuencia de timer de 1 MHz (más manejable)
Prescaler = (72MHz / 1MHz) - 1 = 72 - 1 = 71
Period = ( 1MHz / 1KHz ) - 1 = 1000 - 1 = 999
¿Qué pasa si cambiás estos valores?
Si Prescaler es más alto, el PWM es más lento.
Si Period es más alto, tenés más resolución para controlar el
duty (más suavidad en el brillo).
Si hacés ambas cosas muy altas, la frecuencia baja demasiado y
el LED empieza a parpadear.
Control de Servomotor SG90 con
STM32F103C8T6 (Blue Pill) usando
STM32CubeIDE
A continuación te mostraré cómo controlar un servomotor SG90
para que se mueva de 0 a 180 grados usando el STM32F103C8T6
(Blue Pill) con STM32CubeIDE.
Pasos para implementar
1. Configuración en STM32CubeIDE
1. Configura un timer para generar PWM:
a) Ve a la pestaña "Timers"
b) Selecciona un timer (por ejemplo, TIM2 o TIM3)
c) Activa un canal (por ejemplo, CH1) en modo "PWM
Generation"
d) Configura el prescaler y el periodo para obtener una
frecuencia de 50Hz (20ms de periodo):
e) Clock del timer: 72MHz (si usas el reloj por defecto)
f) Prescaler: 71 (72MHz / (71+1) = 1MHz)
g) Counter Period (ARR): 19999 (1MHz / 20000 = 50Hz)
h) Pulse: Valor inicial para 0 grados (aproximadamente 500)
Codigo
#define MIN_PULSE 500 // 0 grados
#define MAX_PULSE 2500 // 180 grados
#define MID_PULSE 1500 // 90 grados
void setServoAngle(uint32_t angle) {
if(angle > 180) angle = 180;
uint32_t pulse = MIN_PULSE + (angle * (MAX_PULSE - MIN_PULSE) /
180);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse);
}
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_TIM2_Init();
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
setServoAngle(0);
HAL_Delay(1000);
while (1) {
for(int angle = 0; angle <= 180; angle += 10) {
setServoAngle(angle);
HAL_Delay(100);
}
HAL_Delay(500);
for(int angle = 180; angle >= 0; angle -= 10) {
setServoAngle(angle);
HAL_Delay(100);
}
HAL_Delay(500);
}
}
Análisis línea por línea completo del
código
1. Includes y definiciones
#include "main.h" // Configuraciones principales generadas
por STM32CubeIDE
#include "tim.h" // Configuración del Timer (PWM)
#include "gpio.h" // Configuración de pines GPIO
Propósito: Incluir archivos de cabecera generados por
STM32CubeIDE con configuraciones de hardware.
#define MIN_PULSE 500 // Ancho de pulso para 0° (500µs)
#define MAX_PULSE 2500 // Ancho de pulso para 180° (2500µs)
#define MID_PULSE 1500 // Ancho de pulso para 90° (1500µs)
Valores clave:
Los servos SG90 usan pulsos de 500µs a 2500µs para moverse
de 0° a 180°.
2. Función setServoAngle
void setServoAngle(uint32_t angle) {
if(angle > 180) angle = 180;
uint32_t pulse = MIN_PULSE + (angle * (MAX_PULSE - MIN_PULSE) /
180);
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pulse);
Esta función mueve el servo al ángulo especificado (de 0° a
180°).
Si el ángulo es mayor a 180°, lo limita a 180.
Calcula el valor del ciclo de trabajo (duty cycle)
correspondiente.
Usa __HAL_TIM_SET_COMPARE para actualizar el PWM en el
canal 1 del temporizador 2.
¿De donde sale?:
uint32_t pulse = MIN_PULSE + (angle * (MAX_PULSE - MIN_PULSE) /
180);
Mapeo:
Es una conversión lineal que mapea ángulos (0° a 180°) a pulsos
PWM (500µs a 2500µs) que entiende el servomotor SG90. Aquí su
procedencia:
Rango del Servomotor
Los servos estándar como el SG90 responden a pulsos PWM con:
500µs (0.5ms): Posición extrema a 0°
1500µs (1.5ms): Posición central a 90°
2500µs (2.5ms): Posición extrema a 180°
Por tanto:
MIN_PULSE = 500 (para 0°)
MAX_PULSE = 2500 (para 180°)
3. Función main()
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1); // Inicia el PWM en
el canal 1 de TIM2
setServoAngle(0); // Mueve el servo a 0°
HAL_Delay(1000); // Espera 1 segundo
4. Bucle infinito (while(1))
while (1)
{
for(int angle = 0; angle <= 180; angle += 10) {
setServoAngle(angle); // Incrementa de 10 en 10 hasta
180°
HAL_Delay(100); // Espera 100 ms entre cada
movimiento
}
HAL_Delay(500); // Espera 500 ms
for(int angle = 180; angle >= 0; angle -= 10) {
setServoAngle(angle); // Decrementa de 10 en 10 hasta
0°
HAL_Delay(100);
}
HAL_Delay(500);
}
}
En el while(1) se hace que el servo:
Suba de 0° a 180° en pasos de 10°.
Baje de 180° a 0° también en pasos de 10°.
Entre cada paso hay una pausa de 100 ms.
Al final de cada subida o bajada, se espera 500 ms.
Mover un servomotor sg90 con un
potenciometro con ChatGPT
1. Configuración en STM32CubeIDE
A. Configurar ADC (para el potenciómetro)
[Link] sugerido: PA0 (canal ADC1_IN0).
[Link] la pestaña "Pinout & Configuration", selecciona PA0 y
elige:
ADC1_IN0
[Link] Configuration > ADC1:
Modo: Single Conversion
Scan: desactivado
Continuous Mode: activado
Data Alignment: Right
Channel: IN0
Sampling Time: 71.5 cycles (puede variar)
B. Configurar PWM (para el servo)
[Link] sugerido: PA8 (Timer1, Channel 1).
[Link] Pinout, selecciona PA8 y ponlo como:
TIM1_CH1
[Link] Configuration > TIM1:
Prescaler: 71
Counter Period (ARR): 19999
(esto da un período de 20ms → 50Hz, frecuencia típica de
servos)
Cálculo:
Timer clock = 72 MHz / (71 + 1) = 1 MHz
1 MHz / 20000 = 50 Hz
4. PWM Mode: PWM Generation CH1
[Link] PWM en Channel 1
2. Conexiones
Componen Conexión a la
te Blue Pill
Potencióme VCC (3.3V), GND,
tro PA0
VCC (5V), GND,
Servo SG90
PA8
3.Código de ejemplo (main.c)
#include "main.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_ADC1_Init();
MX_TIM1_Init();
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1); // Inicia PWM
HAL_ADC_Start(&hadc1); // Inicia ADC
while (1)
{ // Lectura de pin del potenciometro
HAL_ADC_PollForConversion(&hadc1, HAL_MAX_DELAY);
uint32_t adc_value = HAL_ADC_GetValue(&hadc1); // 0 - 4095
// Mapear a rango de servo: 500 - 2500 µs
uint32_t pulse = 500 + ((adc_value * 2000) / 4095);
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, pulse);
HAL_Delay(10); // pequeño retardo
}
}
¿Qué significa uintXX_t?
Son tipos de tamaño fijo, definidos en C (en <stdint.h>) para que
siempre usen exactamente cierta cantidad de bits, sin importar el
compilador o arquitectura.
Formato general:
u = unsigned (sin signo)
int = entero
XX = cantidad de bits: 8, 16, 32 o 64
_t = "type" (solo convención de nombre)
Ejemplo: El ADC de la Blue Pill da un valor entre 0 y 4095 (12
bits):
uint16_t adc_val; // 16 bits, más que suficiente
adc_val = HAL_ADC_GetValue(&hadc1);
✅El ADC es de 12 bits, pero usamos uint16_t (16 bits)
El ADC del STM32F103 da valores entre 0 y 4095 (eso es 2¹² - 1).
El tipo más chico que puede almacenar hasta 4095 es:
uint8_t → 0 a 255 ❌ no sirve
uint16_t → 0 a 65.535 ✅ perfecto
Por eso usamos uint16_t o uint32_t aunque el ADC sea de
solo 12 bits.
¿Te conviene usar uint16_t o uint32_t?
Opció
Ventaja ¿Conviene?
n
uint32
100% compatible con HAL ✅ Sí
_t
uint16 Ahorra un poco de RAM (2 ✅ Si sos muy preciso con
_t bytes) recursos
ADC (Conversor Analógico Digital)
En la STM32F103C8T6 (Blue Pill):
Tiene un ADC de 12 bits
Eso significa: los valores de salida van de 0 a 4095
Los tipos de datos adecuados para almacenarlo:
uint16_t (suficiente)
uint32_t (lo que devuelve HAL)
Timers: En la Blue Pill, los timers tienen diferente
cantidad de bits según cuál uses:
Tim
Bits Notas
er
Avanzado (PWM,
TIM1 16
break, etc)
TIM2 32 ✅ General-purpose
TIM3 16 General-purpose
TIM4 16 General-purpose
TIM2 es de 32 bits
TIM1, TIM3, TIM4 son de 16 bits
Protocolos de comunicación
Los protocolos de comunicación son conjuntos de reglas y
estándares que permiten que dos o más dispositivos electrónicos
intercambien información de manera ordenada y eficiente. Definen
cómo se transmiten, reciben, interpretan y gestionan los datos entre
sistemas (como microcontroladores, sensores, computadoras, etc.).
1. Tipos principales de protocolos de comunicación
Se clasifican en dos categorías según el tipo de señal:
A. Protocolos seriales
Transfieren datos bit a bit por un solo cable (o par de cables).
Ideales para distancias cortas o medias.
I2C: 2 cables (SDA, SCL), multimaestro, bajo consumo. Ejemplo:
LCD1602, sensores.
SPI: 4+ cables (MISO, MOSI, SCK, SS), alta velocidad. Ejemplo:
pantallas TFT, memorias SD.
UART: 2 cables (TX, RX), asíncrono, punto a punto. Ejemplo:
comunicación Arduino-PC vía USB.
1-Wire: 1 cable, bajo costo. Ejemplo: sensores de temperatura
DS18B20.
B. Protocolos paralelos
Transfieren múltiples bits simultáneamente (por varios cables).
Usados en sistemas de alta velocidad pero complejos.
GPIO paralelo: Común en impresoras antiguas o pantallas LCD
de 8 bits.
2. Características clave de un protocolo
Velocidad: Bits por segundo (bps). Ejemplo: I2C (400 kbps), SPI (10+
Mbps).
Sincronización:
Síncrono: Usa un reloj (ej. I2C, SPI).
Asíncrono: Sin reloj, con bits de inicio/parada (ej. UART).
Topología:
Punto a punto: UART.
Bus compartido: I2C, CAN.
Direccionamiento: Identifica dispositivos en la red (ej.
direcciones I2C).
3. Ejemplos de aplicación
Protoco
Uso típico
lo
I2C Sensores (BME280), memorias EEPROM.
SPI Pantallas OLED, módulos WiFi (ESP8266).
Comunicación PC-Arduino, módulos
UART
Bluetooth.
Sistemas automotrices (red de
CAN
sensores).
Conexión de periféricos (teclado,
USB
mouse).
4. ¿Cómo elegir un protocolo?
Velocidad requerida: SPI para alta velocidad, I2C para bajo
consumo.
Número de dispositivos: I2C admite muchos esclavos; UART es 1
a 1.
Complejidad: SPI requiere más pines que I2C.
Distancia: Para largas distancias, usa RS-485 o CAN.
5. Comparativa rápida
Protoco
Ventajas Desventajas
lo
Simple, pocos cables, Lento, direcciones
I2C
multidispositivo. limitadas.
SPI Muy rápido, full-duplex. Requiere más pines.
UART Flexible, sin reloj. Solo punto a punto.
Tanto Arduino como STM32 pueden utilizar el protocolo I2C, pero
hay diferencias clave en su implementación y configuración. Aquí te
lo explico detalladamente:
1. Arduino con I2C
Hardware:
Los modelos clásicos (Uno, Nano, Mega) tienen pines dedicados
para I2C:
SDA: A4 (o SDA en placas modernas).
SCL: A5 (o SCL en placas modernas).
En Arduino Mega, los pines son 20 (SDA) y 21 (SCL).
2. STM32 con I2C
Hardware:
Los STM32 tienen múltiples interfaces I2C (ej. I2C1, I2C2).
Los pines varían según el modelo (consulta el datasheet o
usa STM32CubeMX para asignarlos).
Ejemplo en STM32F103 (Blue Pill):
I2C1: PB6 (SCL), PB7 (SDA).
Software:
Opción 1: Usar HAL Library (con STM32CubeIDE):
3. Diferencias clave
Aspect
Arduino STM32
o
Pines Fijos (A4/A5 o SDA/SCL Configurables (múltiples
I2C dedicados). opciones).
Velocid Hasta 1 MHz (en algunos
Hasta 400 kHz (Fast Mode).
ad modelos).
Librería HAL/LL (más compleja, pero
Wire.h (sencilla).
s potente).
Flexibili Alta (DMA, interrupciones,
Limitada.
dad etc.).
Ejemplo Sencillo para LCD 16x2 con I2C en STM32
Blue Pill
1. En el Pinout & Configuration:
Click en el pin PB6 → elegir I2C1_SCL.
Click en el pin PB7 → elegir I2C1_SDA.
2. En el panel izquierdo, ir a Connectivity → I2C1:
Mode: I2C
I2C Speed Mode: Standard Mode (100 kHz)
Conexiones físicas:
LCD I2C Blue Pill (STM32F103C8)
-------------------------------
GND GND
VCC Fuente lm317 a 4.5V y mover potenciometro modulo I2C.
SDA PB7
SCL PB6
Cuando di a guardar todo se genero este codigo
#include "main.h"
#include "i2c.h"
#include "gpio.h"
void SystemClock_Config(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
while (1)
{
}
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.[Link] = RCC_PLL_ON;
RCC_OscInitStruct.[Link] = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.[Link] = RCC_PLL_MUL9;
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_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
Codigo final
#include "main.h"
#include "i2c.h"
#include "gpio.h"
#include <string.h>
void SystemClock_Config(void);
// Configuración LCD
#define LCD_ADDR (0x27 << 1) // Dirección I2C (0x27 o 0x3F,
ajustar según módulo)
#define LCD_COLS 16
#define LCD_ROWS 2
// Prototipos de funciones LCD
void LCD_SendCmd(uint8_t cmd);
void LCD_SendData(uint8_t data);
void LCD_Init(void);
void LCD_SendString(char *str);
void LCD_SetCursor(uint8_t row, uint8_t col);
void LCD_Clear(void);
int main(void)
{
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_I2C1_Init();
// Inicializar LCD
LCD_Init();
// Escribir mensajes
LCD_SendString("PICHULO");
LCD_SetCursor(1, 0);
LCD_SendString("LCD 16x2 I2C");
while (1)
{
// Puedes agregar más funcionalidad aquí
HAL_Delay(1000);
}
// Función para enviar comandos al LCD
void LCD_SendCmd(uint8_t cmd)
{
uint8_t data_u, data_l;
uint8_t data_t[4];
data_u = (cmd & 0xF0);
data_l = ((cmd << 4) & 0xF0);
data_t[0] = data_u | 0x0C; // en=1, rs=0
data_t[1] = data_u | 0x08; // en=0, rs=0
data_t[2] = data_l | 0x0C; // en=1, rs=0
data_t[3] = data_l | 0x08; // en=0, rs=0
HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_t, 4, 100);
HAL_Delay(1);
}
// Función para enviar datos al LCD
void LCD_SendData(uint8_t data)
{
uint8_t data_u, data_l;
uint8_t data_t[4];
data_u = (data & 0xF0);
data_l = ((data << 4) & 0xF0);
data_t[0] = data_u | 0x0D; // en=1, rs=1
data_t[1] = data_u | 0x09; // en=0, rs=1
data_t[2] = data_l | 0x0D; // en=1, rs=1
data_t[3] = data_l | 0x09; // en=0, rs=1
HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_t, 4, 100);
HAL_Delay(1);
}
// Inicialización del LCD
void LCD_Init(void)
{
HAL_Delay(50); // Espera inicial
// Secuencia de inicialización
LCD_SendCmd(0x03);
HAL_Delay(5);
LCD_SendCmd(0x03);
HAL_Delay(1);
LCD_SendCmd(0x03);
HAL_Delay(10);
LCD_SendCmd(0x02); // Modo 4 bits
// Configuración del display
LCD_SendCmd(0x28); // 2 líneas, fuente 5x8
LCD_SendCmd(0x0C); // Display on, cursor off
LCD_SendCmd(0x06); // Incremento automático del cursor
LCD_Clear(); // Limpiar display
}
// Enviar cadena de texto al LCD
void LCD_SendString(char *str)
{
while(*str) LCD_SendData(*str++);
}
// Posicionar cursor
void LCD_SetCursor(uint8_t row, uint8_t col)
{
uint8_t address;
col &= 0x0F; // Máximo 15 columnas
if(row == 0) address = 0x80 + col;
else address = 0xC0 + col;
LCD_SendCmd(address);
}
// Limpiar display
void LCD_Clear(void)
{
LCD_SendCmd(0x01);
HAL_Delay(2);
}
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.[Link] = RCC_PLL_ON;
RCC_OscInitStruct.[Link] = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.[Link] = RCC_PLL_MUL9;
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_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) !
= HAL_OK)
{
Error_Handler();
}
}
void Error_Handler(void)
{
__disable_irq();
while (1)
{
}
#ifdef USE_FULL_ASSERT
void assert_failed(uint8_t *file, uint32_t line)
{
}
#endif
Codigo explicado
1. Estructura General
El código controla un LCD 16x2 con interfaz I2C desde una placa
STM32 Blue Pill (STM32F103C8). Usa el hardware I2C de la STM32 y
el protocolo HD44780 (estándar para LCDs alfanuméricos).
2. Headers e Inicialización
#include "main.h" // Configuración generada por
STM32CubeIDE
#include "i2c.h" // Driver I2C de HAL
#include "gpio.h" // Configuración de GPIO
#include <string.h> // Para manejo de cadenas
main.h: Contiene configuraciones de hardware generadas por
STM32CubeIDE.
i2c.h/gpio.h: Drivers para I2C y GPIO.
3. Configuración del LCD
#define LCD_ADDR (0x27 << 1) // Dirección I2C (típica para
PCF8574)
#define LCD_COLS 16 // Columnas del LCD
#define LCD_ROWS 2 // Filas del LCD
LCD_ADDR: La dirección I2C del módulo (normalmente 0x27 o
0x3F). Se desplaza << 1 porque la HAL de STM32 maneja
direcciones de 7 bits.
LCD_COLS/ROWS: Dimensiones del display.
4. Función Principal (main())
int main(void) {
HAL_Init(); // Inicializa HAL (Hardware Abstraction Layer)
SystemClock_Config(); // Configura el reloj a 72 MHz (típico en
Blue Pill)
MX_GPIO_Init(); // Inicializa GPIOs
MX_I2C1_Init(); // Inicializa I2C1 (PB6=SCL, PB7=SDA)
LCD_Init(); // Inicializa el LCD
LCD_SendString("PICHULO"); // Escribe en la primera línea
LCD_SetCursor(1, 0); // Mueve cursor a la segunda línea
LCD_SendString("LCD 16x2 I2C"); // Escribe en la segunda línea
while (1) {
HAL_Delay(1000); // Bucle infinito
}
}
Secuencia de inicio: Inicializa hardware, configura el LCD, y muestra
un mensaje.
while(1): Mantiene el programa activo.
5. Funciones Clave del LCD
a) LCD_SendCmd(uint8_t cmd)
Envía un comando al LCD (ej: limpiar pantalla, mover cursor).
void LCD_SendCmd(uint8_t cmd) {
uint8_t data_u, data_l;
uint8_t data_t[4];
data_u = (cmd & 0xF0); // Parte alta del nibble (4 bits)
data_l = ((cmd << 4) & 0xF0); // Parte baja del nibble
// Secuencia de enable (EN) para enviar el comando en 4 bits:
data_t[0] = data_u | 0x0C; // EN=1, RS=0 (RS=0 indica
comando)
data_t[1] = data_u | 0x08; // EN=0, RS=0
data_t[2] = data_l | 0x0C; // EN=1, RS=0
data_t[3] = data_l | 0x08; // EN=0, RS=0
HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_t, 4, 100);
HAL_Delay(1);
}
Protocolo de 4 bits: El LCD HD44780 usa 2 nibbles (4 bits) para
enviar 1 byte.
RS=0: Indica que es un comando (no datos).
b) LCD_SendData(uint8_t data)
Envía datos (caracteres) al LCD.
void LCD_SendData(uint8_t data) {
// Similar a LCD_SendCmd, pero con RS=1 (para datos)
data_t[0] = data_u | 0x0D; // EN=1, RS=1 (RS=1 indica dato)
data_t[1] = data_u | 0x09; // EN=0, RS=1
// ... (resto igual que LCD_SendCmd)
}
RS=1: Indica que es un dato (carácter).
c) LCD_Init()
Inicializa el LCD en modo 4 bits.
void LCD_Init(void) {
HAL_Delay(50); // Espera estabilización
// Secuencia de inicialización (obligatoria para modo 4 bits)
LCD_SendCmd(0x03); HAL_Delay(5);
LCD_SendCmd(0x03); HAL_Delay(1);
LCD_SendCmd(0x03); HAL_Delay(10);
LCD_SendCmd(0x02); // Modo 4 bits
// Configuración final:
LCD_SendCmd(0x28); // 2 líneas, matriz 5x8
LCD_SendCmd(0x0C); // Display ON, cursor OFF
LCD_SendCmd(0x06); // Auto-incremento del cursor
LCD_Clear(); // Limpia pantalla
}
Secuencia 0x03 repetida: Obligatoria para sincronizar el LCD en
modo 4 bits.
0x28: Configura el LCD para 2 líneas y caracteres 5x8 píxeles.
0x0C: Enciende el display sin cursor.
d) Otras Funciones
LCD_SendString(char *str): Envía una cadena de caracteres.
LCD_SetCursor(row, col): Mueve el cursor a una posición específica.
LCD_Clear(): Limpia la pantalla (0x01).
6. Configuración del Reloj (SystemClock_Config)
void SystemClock_Config(void) {
// Configura el HSE (oscilador externo) y PLL para 72 MHz
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.[Link] = RCC_PLL_MUL9; // 8 MHz * 9 =
72 MHz
// ...
}
HSE: Cristal externo de 8 MHz (típico en Blue Pill).
PLL: Multiplica la frecuencia para lograr 72 MHz (máximo para
STM32F103).
Aquí te detallo qué partes corresponden a
librerías/frameworks y qué partes son código
personalizado:
Partes que dependen de librerías/frameworks:
[Link]ías HAL de STM32CubeIDE (manejo de hardware):
#include "main.h" // Configuraciones generadas por CubeIDE
#include "i2c.h" // Driver I2C de STM32 HAL
#include "gpio.h" // Driver GPIO de STM32 HAL
Estas son proporcionadas por STM32CubeIDE y manejan el
hardware a bajo nivel.
[Link]ía estándar de C:
#include <string.h> // Para funciones de manejo de cadenas
(aunque no se usa directamente)
Partes de código personalizado (implementación específica para
el LCD):
Todas estas funciones fueron escritas manualmente para controlar
el LCD:
[Link]ón del LCD:
#define LCD_ADDR (0x27 << 1) // Dirección I2C
#define LCD_COLS 16 // Columnas
#define LCD_ROWS 2 // Filas
[Link] principales del LCD:
void LCD_SendCmd(uint8_t cmd); // Envía comandos al LCD
void LCD_SendData(uint8_t data); // Envía datos (caracteres)
void LCD_Init(void); // Inicialización
void LCD_SendString(char *str); // Escribe una cadena
void LCD_SetCursor(uint8_t row, uint8_t col); // Posiciona
cursor
void LCD_Clear(void); // Limpia la pantalla
[Link] de inicialización (en LCD_Init()):
LCD_SendCmd(0x03); HAL_Delay(5); // Reset en modo 8 bits
LCD_SendCmd(0x03); HAL_Delay(1);
LCD_SendCmd(0x03); HAL_Delay(10);
LCD_SendCmd(0x02); // Cambia a modo 4 bits
Partes mixtas (HAL + lógica personalizada):
[Link]ón I2C (usa HAL pero con lógica personalizada):
HAL_I2C_Master_Transmit(&hi2c1, LCD_ADDR, data_t, 4,
100);
La función es de HAL, pero la implementación de data_t y los
nibbles es personalizada.
2.SystemClock_Config():
Generada automáticamente por STM32CubeIDE, pero ajustada para
el reloj de 72 MHz.