/**
******************************************************************************
* @file : main.c
* @author : Fernando Hermosillo Reynoso
* @author2 : Eduardo Carmona Aguirre
* @brief : Main program body
******************************************************************************
*/
#include <stdint.h> // Permite usar tipos de tamaño fijo como uint32_t, uint16_t, etc.
#include "stm32f103_hal.h" // Librería HAL que usa tu simulación/proyecto en Wokwi
/* =========================================================
VARIABLES GLOBALES
========================================================= */
/*
teeth_last_rev:
Guarda cuántos dientes se detectaron en la vuelta anterior.
Se actualiza cada vez que llega la señal de ángulo 0°.
*/
volatile uint32_t teeth_last_rev = 0;
/*
teeth_current_rev:
Cuenta cuántos dientes se han detectado en la vuelta actual.
Cada pulso del sensor de dientes incrementa esta variable.
*/
volatile uint32_t teeth_current_rev = 0;
/*
teeth_per_sec:
Guarda cuántos dientes se detectaron en 1 segundo.
Sirve para calcular las RPM.
*/
volatile uint32_t teeth_per_sec = 0;
/*
sec_teeth_counter:
Va contando dientes durante un segundo.
Cuando pasa 1 segundo, su valor se pasa a teeth_per_sec y se reinicia.
*/
volatile uint32_t sec_teeth_counter = 0;
/*
injection_delay_ticks:
Es el retardo calculado antes de disparar la inyección.
Este valor se carga en TIM2.
*/
volatile uint32_t injection_delay_ticks = 0;
/* =========================================================
PROTOTIPOS DE FUNCIONES
========================================================= */
/*
Aquí solo se anuncian las funciones para que el compilador
sepa que existen antes de usarlas en main().
*/
static void gpio_config(void);
static void exti_config(void);
static void adc_config(void);
static void timer_config(void);
static float read_temperature_c(void);
static float calculate_rpm(void);
static void update_injection_delay(float rpm, float temp_c);
/* =========================================================
FUNCIÓN PRINCIPAL
========================================================= */
int main(void)
{
/* -----------------------------------------------------
HABILITAR RELOJES DE LOS PERIFÉRICOS
-----------------------------------------------------
Antes de usar GPIO, timers, ADC, etc., hay que activar
el reloj de cada periférico.
*/
rcc_clock_enable(RCC_GPIOA);
rcc_clock_enable(RCC_GPIOB);
rcc_clock_enable(RCC_GPIOC);
rcc_clock_enable(RCC_TIM2);
rcc_clock_enable(RCC_TIM3);
rcc_clock_enable(RCC_TIM4);
rcc_clock_enable(RCC_ADC1);
rcc_clock_enable(RCC_EXTI);
/* -----------------------------------------------------
CONFIGURACIÓN DE LOS PERIFÉRICOS
----------------------------------------------------- */
gpio_config(); // Configura entradas y salida de debug
exti_config(); // Configura interrupciones externas
adc_config(); // Configura lectura analógica de temperatura
timer_config(); // Configura timers TIM2, TIM3 y TIM4
/* -----------------------------------------------------
VARIABLES LOCALES
----------------------------------------------------- */
float rpm = 0.0f; // Aquí se guardarán las RPM calculadas
float temp_c = 85.0f; // Temperatura inicial de referencia
/* -----------------------------------------------------
BUCLE PRINCIPAL INFINITO
-----------------------------------------------------
El microcontrolador se queda aquí siempre:
1. Lee temperatura
2. Calcula RPM
3. Calcula cuánto debe tardar en inyectar
*/
while (1) {
temp_c = read_temperature_c(); // Lee temperatura desde ADC
rpm = calculate_rpm(); // Calcula RPM del motor
update_injection_delay(rpm, temp_c); // Calcula el retardo de inyección
}
}
/* =========================================================
CONFIGURACIÓN DE GPIO
========================================================= */
static void gpio_config(void)
{
/*
PC13 se usa como salida.
En este código se usa como pin de depuración (debug),
por ejemplo para ver en un LED o en el osciloscopio
cuándo ocurrió la inyección.
*/
gpio_set_output(GPIOC, GPIO_PIN_13, GPIO_OUTPUT_PP, GPIO_SPEED_2MHZ);
/*
Se pone inicialmente en alto.
*/
gpio_bitwise_set(GPIOC, 1 << 13);
/*
PA0 = señal de ángulo 0° (angl0)
Entrada flotante.
*/
gpio_set_input(GPIOA, GPIO_PIN_0, GPIO_INPUT_FLOAT);
/*
PA1 = señal de TDC (Top Dead Center / Punto Muerto Superior)
Entrada flotante.
*/
gpio_set_input(GPIOA, GPIO_PIN_1, GPIO_INPUT_FLOAT);
/*
PA8 = señal de dientes del sensor
Entrada flotante.
*/
gpio_set_input(GPIOA, GPIO_PIN_8, GPIO_INPUT_FLOAT);
/*
PA2 = entrada analógica para la temperatura
Aquí entra el valor del sensor/potenciómetro analógico.
*/
gpio_set_input(GPIOA, GPIO_PIN_2, GPIO_INPUT_ANALOG);
}
/* =========================================================
CONFIGURACIÓN DE INTERRUPCIONES EXTERNAS
========================================================= */
static void exti_config(void)
{
/*
PA0 genera interrupción por flanco de subida.
Cada vez que llegue la señal de ángulo 0°, se ejecutará
EXTI0_IRQHandler().
*/
exti_set_interrupt(PA0, EXTI_IRQ_RISING);
nvic_enable_irq(EXTI0_IRQn);
/*
PA1 genera interrupción por flanco de subida.
Cada vez que llegue la señal de TDC, se ejecutará
EXTI1_IRQHandler().
*/
exti_set_interrupt(PA1, EXTI_IRQ_RISING);
nvic_enable_irq(EXTI1_IRQn);
/*
PA8 genera interrupción por flanco de subida.
Cada pulso de diente ejecutará EXTI9_5_IRQHandler().
PA8 entra en el grupo EXTI de 5 a 9.
*/
exti_set_interrupt(PA8, EXTI_IRQ_RISING);
nvic_enable_irq(EXTI9_5_IRQn);
}
/* =========================================================
CONFIGURACIÓN DEL ADC
========================================================= */
static void adc_config(void)
{
/*
ADC1 se configura en:
- modo de conversión simple
- disparo por software
- alineación a la derecha
*/
adc_init(ADC1, ADC_MODE_SINGLE, ADC_TRIG_SOFTWARE, ADC_ALIGN_RIGHT);
/*
Se selecciona el canal 2 del ADC, que corresponde a PA2.
También se define el tiempo de muestreo.
*/
adc_set_channel(ADC1, ADC_CHANNEL_2, ADC_SMP_239_5);
}
/* =========================================================
CONFIGURACIÓN DE TIMERS
========================================================= */
static void timer_config(void)
{
/*
TIM4 se usa para generar el PWM del inyector.
timer_init(TIM4, TIMER_MODE_UP, 9, 7);
Dependiendo de la librería HAL del proyecto, esto define:
- modo conteo ascendente
- prescaler
- auto-reload
La idea es generar una señal PWM.
*/
timer_init(TIM4, TIMER_MODE_UP, 9, 7);
/*
Se configura el canal 1 de TIM4 como PWM.
Ese PWM representará el pulso del inyector.
*/
timer_set_oc_channel(TIM4, TIMER_CHANNEL_1, TIMER_OC_PWM2,
TIMER_OC_POLARITY_HIGH, 8, true);
/*
TIM2 se usa como temporizador de retardo.
Cuando llega el TDC, no se inyecta inmediatamente:
primero se espera un tiempo calculado.
*/
timer_init(TIM2, TIMER_MODE_UP, 1000, 79);
/*
Se habilita interrupción por actualización en TIM2.
Cuando el timer termine de contar, entra a TIM2_IRQHandler().
*/
timer_set_interrupt(TIM2, TIMER_IRQ_UPDATE, IRQ_ENABLE);
nvic_enable_irq(TIM2_IRQn);
/*
TIM3 se usa para una ventana de 1 segundo.
Cada vez que pase ese segundo, se guarda cuántos dientes
se detectaron para luego calcular RPM.
*/
timer_init(TIM3, TIMER_MODE_UP, 9999, 799);
/*
Se habilita interrupción de TIM3.
*/
timer_set_interrupt(TIM3, TIMER_IRQ_UPDATE, IRQ_ENABLE);
nvic_enable_irq(TIM3_IRQn);
/*
TIM3 empieza a correr continuamente.
*/
timer_start(TIM3, TIMER_CONTINUOUS);
}
/* =========================================================
LEER TEMPERATURA
========================================================= */
static float read_temperature_c(void)
{
/*
Lee el valor del ADC.
Normalmente el ADC da un valor entre 0 y 4095
si es de 12 bits.
*/
uint16_t raw = adc_read(ADC1);
/*
Convierte el valor ADC a una escala de temperatura.
Aquí realmente se está haciendo una conversión lineal.
Si raw = 0 -> temp = 0
Si raw = 4095 -> temp = 330
O sea, se está interpretando como una escala de 0 a 330.
*/
return (raw * 330.0f) / 4095.0f;
}
/* =========================================================
CALCULAR RPM
========================================================= */
static float calculate_rpm(void)
{
/*
Si todavía no hay datos suficientes, regresa 0.
Esto evita divisiones entre cero.
*/
if (teeth_last_rev == 0 || teeth_per_sec == 0) {
return 0.0f;
}
/*
Fórmula usada:
RPM = (dientes por segundo / dientes por vuelta) * 60
porque:
- teeth_per_sec = dientes detectados en 1 segundo
- teeth_last_rev = cuántos dientes tiene una vuelta
- dientes/segundo dividido entre dientes/vuelta = vueltas/segundo
- vueltas/segundo * 60 = vueltas/minuto = RPM
*/
return ((float)teeth_per_sec / (float)teeth_last_rev) * 60.0f;
}
/* =========================================================
CALCULAR RETARDO DE INYECCIÓN
========================================================= */
static void update_injection_delay(float rpm, float temp_c)
{
/*
Si RPM es cero o negativa, no tiene sentido calcular inyección.
*/
if (rpm <= 0.0f) {
injection_delay_ticks = 0;
return;
}
/*
Se calcula el periodo del motor en microsegundos.
rpm = revoluciones por minuto
60/rpm = segundos por revolución
* 1,000,000 = microsegundos por revolución
*/
float motor_period_us = (60.0f / rpm) * 1000000.0f;
/*
Se toma un cuarto del periodo.
Esto puede representar una referencia angular del ciclo.
*/
float quarter_period_us = motor_period_us / 4.0f;
/*
Se calcula el retardo en función de la temperatura.
Mientras más diferente sea la temperatura respecto a 85,
cambia el tiempo de espera.
Ojo: este factor 200.0f hace cambios muy grandes.
Se dejó así para conservar la lógica general que traía
la versión base.
*/
float delay_us = quarter_period_us * (1.0f + 200.0f * (85.0f - temp_c));
/*
Si el retardo sale demasiado pequeño, se limita
al cuarto de periodo.
*/
if (delay_us < quarter_period_us) {
delay_us = quarter_period_us;
}
/*
Si el retardo sale demasiado grande, se limita
a medio periodo.
*/
if (delay_us > (motor_period_us / 2.0f)) {
delay_us = motor_period_us / 2.0f;
}
/*
Se convierte el tiempo en microsegundos a "ticks"
para el timer.
Aquí se divide entre 10.0 porque se está asumiendo
que cada tick del timer equivale aproximadamente
a 10 microsegundos.
*/
injection_delay_ticks = (uint32_t)(delay_us / 10.0f);
}
/* =========================================================
INTERRUPCIÓN EXTI0 -> PA0 -> ANGLE 0
========================================================= */
void EXTI0_IRQHandler(void)
{
/*
Se revisa si realmente la interrupción vino de EXTI0.
*/
if (EXTI->PR & (1 << 0)) {
/*
Se limpia la bandera de interrupción escribiendo 1.
*/
EXTI->PR = (1 << 0);
/*
Al llegar al ángulo 0°, significa que terminó una vuelta.
Entonces:
- guardamos cuántos dientes tuvo la vuelta pasada
- reiniciamos el conteo para comenzar la nueva vuelta
*/
teeth_last_rev = teeth_current_rev;
teeth_current_rev = 0;
}
}
/* =========================================================
INTERRUPCIÓN EXTI1 -> PA1 -> TDC
========================================================= */
void EXTI1_IRQHandler(void)
{
/*
Se revisa si la interrupción fue de EXTI1.
*/
if (EXTI->PR & (1 << 1)) {
/*
Limpiar bandera.
*/
EXTI->PR = (1 << 1);
/*
Si ya hay un retardo calculado válido, se arranca TIM2.
TIM2 contará ese tiempo de espera antes de disparar
la inyección.
*/
if (injection_delay_ticks > 0) {
TIM2->CNT = 0; // Reinicia contador
TIM2->ARR = injection_delay_ticks - 1; // Valor hasta donde contará
timer_start(TIM2, TIMER_ONESHOT); // Arranca una sola vez
}
}
}
/* =========================================================
INTERRUPCIÓN EXTI9_5 -> PA8 -> DIENTES
========================================================= */
void EXTI9_5_IRQHandler(void)
{
/*
Se revisa si la interrupción vino de PA8 / EXTI8.
*/
if (EXTI->PR & (1 << 8)) {
/*
Limpiar bandera.
*/
EXTI->PR = (1 << 8);
/*
Cada vez que entra un pulso de diente:
- aumenta el conteo de dientes de la vuelta actual
- aumenta el conteo de dientes del segundo actual
*/
teeth_current_rev++;
sec_teeth_counter++;
}
}
/* =========================================================
INTERRUPCIÓN TIM3 -> VENTANA DE 1 SEGUNDO
========================================================= */
void TIM3_IRQHandler(void)
{
/*
Se verifica bandera de actualización.
*/
if (TIM3->SR & TIM_SR_UIF) {
/*
Se limpia la bandera.
*/
TIM3->SR &= ~TIM_SR_UIF;
/*
Cada segundo:
- guardamos cuántos dientes hubo en ese segundo
- reiniciamos el contador para el siguiente segundo
*/
teeth_per_sec = sec_teeth_counter;
sec_teeth_counter = 0;
}
}
/* =========================================================
INTERRUPCIÓN TIM2 -> TERMINÓ EL RETARDO
========================================================= */
void TIM2_IRQHandler(void)
{
/*
Se verifica bandera de actualización.
*/
if (TIM2->SR & TIM_SR_UIF) {
/*
Se limpia la bandera.
*/
TIM2->SR &= ~TIM_SR_UIF;
/*
Cuando TIM2 termina, significa que ya pasó el tiempo
de espera calculado.
Entonces ahora sí se activa el PWM del inyector.
*/
timer_start(TIM4, TIMER_ONESHOT);
/*
Se cambia el estado de PC13 como señal de debug.
Esto sirve para ver visualmente o en osciloscopio
que ocurrió el evento.
*/
gpio_bitwise_toggle(GPIOC, 1 << 13);
}
}+90° | | 0° | | TDC(90°) | | Inyect.
Inyector
Azul = Debug