#include "stm32f103_hal.h"
#include <stdbool.h>
/*
* ============================================================================
* SISTEMA DE INYECCIÓN ELECTRÓNICA - EXAMEN PARCIAL 2
* ============================================================================
* Descripción:
* Simula el control de un inyector electrónico usando:
* - Encoder óptico (180 pulsos/rev) para medir RPM
* - Sensor Hall para detectar posición de referencia (0°)
* - Sensor LM35 para temperatura del motor
* - PWM one-shot (100kHz, 20% DC) para activar el inyector
*
* Timers:
* TIM1: Input Capture para encoder (medición de RPM)
* TIM2: Retardo Δt calculado
* TIM3: Retardo de 90° hasta TDC
* TIM4: PWM one-shot 100kHz, 20% DC
*
* Restricciones:
* - Sin delays bloqueantes
* - Uso de interrupciones y timers
* ============================================================================
*/
// ============================================================================
// DEFINICIONES Y CONSTANTES
// ============================================================================
#define ENCODER_PULSES_PER_REV 180 // Pulsos por revolución del encoder
#define PWM_FREQUENCY_HZ 100000 // 100kHz
#define PWM_DUTY_CYCLE 20 // 20%
#define ADC_VREF 3.3f // Voltaje de referencia del ADC
#define LM35_MV_PER_DEGREE 10.0f // LM35: 10mV/°C
// ============================================================================
// VARIABLES GLOBALES
// ============================================================================
volatile uint32_t last_capture_value = 0;
volatile uint32_t capture_diff = 0;
volatile float current_rpm = 0.0f;
volatile float current_temperature = 0.0f;
volatile float delta_t_ms = 0.0f;
volatile bool tdc_event_pending = false;
// ============================================================================
// PROTOTIPOS DE FUNCIONES
// ============================================================================
void system_init(void);
void clock_config(void);
void gpio_config(void);
void tim1_encoder_config(void);
void tim2_delay_config(void);
void tim3_tdc_delay_config(void);
void tim4_pwm_config(void);
void adc_config(void);
void exti_hall_config(void);
void calculate_rpm(void);
void read_temperature(void);
void calculate_delta_t(void);
void start_tdc_sequence(void);
// ============================================================================
// FUNCIÓN PRINCIPAL
// ============================================================================
int main(void)
{
// Inicializar sistema
system_init();
// Loop principal
while(1)
{
// Leer temperatura periódicamente
read_temperature();
// Calcular Δt basado en RPM y temperatura
calculate_delta_t();
// El resto se maneja por interrupciones
}
}
// ============================================================================
// INICIALIZACIÓN DEL SISTEMA
// ============================================================================
void system_init(void)
{
clock_config();
gpio_config();
tim1_encoder_config();
tim2_delay_config();
tim3_tdc_delay_config();
tim4_pwm_config();
adc_config();
exti_hall_config();
// Habilitar interrupciones globales
nvic_enable();
}
// ============================================================================
// CONFIGURACIÓN DE RELOJ
// ============================================================================
void clock_config(void)
{
// Configurar sistema a 72MHz usando PLL con HSE
rcc_set_clock(RCC_SYSCLK_PLL_HSE_DIV_1, RCC_PLL_X9); // 8MHz * 9 = 72MHz
}
// ============================================================================
// CONFIGURACIÓN DE GPIO
// ============================================================================
void gpio_config(void)
{
// Habilitar relojes de GPIOs
rcc_clock_enable(RCC_GPIOA);
rcc_clock_enable(RCC_GPIOB);
// PA0: ADC (LM35) - Entrada analógica
gpio_set_input(GPIOA, GPIO_PIN_0, GPIO_INPUT_ANALOG);
// PA8: TIM1_CH1 (Encoder) - Entrada flotante
gpio_set_input(GPIOA, GPIO_PIN_8, GPIO_INPUT_FLOAT);
// PB5: Sensor Hall (EXTI) - Entrada con pull-down
gpio_set_input(GPIOB, GPIO_PIN_5, GPIO_INPUT_PD);
// PB6: TIM4_CH1 (PWM Inyector) - Salida AF push-pull
gpio_set_output(GPIOB, GPIO_PIN_6, GPIO_OUTPUT_AF_PP, GPIO_SPEED_50MHZ);
}
// ============================================================================
// CONFIGURACIÓN TIM1: INPUT CAPTURE PARA ENCODER
// ============================================================================
void tim1_encoder_config(void)
{
rcc_clock_enable(RCC_TIM1);
// Configurar TIM1 en modo Input Capture
// Frecuencia del timer: 72MHz / (prescaler + 1) = 1MHz (1μs por tick)
timer_init(TIM1, TIMER_MODE_UP, 0xFFFF, 71); // Prescaler = 71 → 1MHz
// Configurar CH1 para Input Capture
timer_set_ic_channel(
TIM1,
TIMER_CHANNEL_1,
TIMER_IC_DIRECT, // Captura directa desde TI1
TIMER_IC_RISING, // Flanco de subida
TIMER_IC_DIV_1, // Sin prescaler
TIMER_IC_FILTER_CKDTS_NONE // Sin filtro
);
// Habilitar interrupción de captura
timer_set_interrupt(TIM1, TIMER_IRQ_CC1, IRQ_ENABLE);
nvic_enable_irq(TIM1_CC_IRQn);
// Iniciar timer
timer_start(TIM1, TIMER_CONTINUOUS);
}
// ============================================================================
// CONFIGURACIÓN TIM2: RETARDO Δt
// ============================================================================
void tim2_delay_config(void)
{
rcc_clock_enable(RCC_TIM2);
// Prescaler 7199 -> 10kHz (0.1ms por cada tick)
timer_init(TIM2, TIMER_MODE_UP, 1000, 7199);
timer_set_interrupt(TIM2, TIMER_IRQ_UPDATE, IRQ_ENABLE);
nvic_enable_irq(TIM2_IRQn);
}
// ============================================================================
// CONFIGURACIÓN TIM3: RETARDO 90° (TDC)
// ============================================================================
void tim3_tdc_delay_config(void)
{
rcc_clock_enable(RCC_TIM3);
// Configurar TIM3 para calcular retardo de 90°
// El periodo se ajustará dinámicamente según RPM
timer_init(TIM3, TIMER_MODE_UP, 1000, 71999); // Configuración base
// Habilitar interrupción de actualización
timer_set_interrupt(TIM3, TIMER_IRQ_UPDATE, IRQ_ENABLE);
nvic_enable_irq(TIM3_IRQn);
// No iniciar aún
}
// ============================================================================
// CONFIGURACIÓN TIM4: PWM 100kHz ONE-SHOT
// ============================================================================
void tim4_pwm_config(void)
{
rcc_clock_enable(RCC_TIM4);
// Configuramos un pulso de 50ms (Súper visible)
// 72MHz / 7200 = 10kHz (0.1ms por tick).
// ARR = 500 ticks = 50ms de duración total.
timer_init(TIM4, TIMER_MODE_UP, 500, 7199);
// Modo One-Pulse: El timer se detiene solo al llegar a ARR
TIM4->CR1 |= (1 << 3);
timer_set_oc_channel(
TIM4,
TIMER_CHANNEL_1,
TIMER_OC_PWM1,
TIMER_OC_POLARITY_LOW,
100, // Pulso de 10ms (20% de 50ms)
true
);
}
// ============================================================================
// CONFIGURACIÓN ADC: SENSOR LM35
// ============================================================================
void adc_config(void)
{
rcc_clock_enable(RCC_ADC1);
// Inicializar ADC en modo single
adc_init(ADC1, ADC_MODE_SINGLE, ADC_TRIG_SOFTWARE, ADC_ALIGN_RIGHT);
// Configurar canal 0 (PA0) con sample time largo para mejor precisión
adc_set_channel(ADC1, ADC_CHANNEL_0, ADC_SMP_239_5);
// Iniciar ADC
adc_start(ADC1);
}
// ============================================================================
// CONFIGURACIÓN EXTI: SENSOR HALL (REFERENCIA 0°)
// ============================================================================
void exti_hall_config(void)
{
rcc_clock_enable(RCC_EXTI);
// Configurar PB5 como fuente de interrupción externa
exti_set_interrupt(PB5, EXTI_IRQ_RISING);
// Habilitar interrupción EXTI9_5
nvic_enable_irq(EXTI9_5_IRQn);
}
// ============================================================================
// CÁLCULO DE RPM
// ============================================================================
void calculate_rpm(void)
{
if(capture_diff > 0)
{
// Tiempo entre pulsos en microsegundos
float time_per_pulse_us = (float)capture_diff;
// Tiempo por revolución = tiempo_por_pulso * pulsos_por_rev
float time_per_rev_us = time_per_pulse_us * ENCODER_PULSES_PER_REV;
// RPM = (60 * 1,000,000) / tiempo_por_rev_us
current_rpm = 60000000.0f / time_per_rev_us;
// ¡CANDADOS CORREGIDOS AQUÍ!
if(current_rpm > 10000.0f) current_rpm = 10000.0f; // Ahora simplemente lo topa al máximo
if(current_rpm < 5.0f) current_rpm = 0.0f; // Bajamos el límite a 5 RPM para evitar que se congele en la simulación
}
}
// ============================================================================
// LECTURA DE TEMPERATURA
// ============================================================================
void read_temperature(void)
{
// Leer ADC
uint16_t adc_value = adc_read(ADC1);
// Convertir a voltaje
float voltage = (adc_value / 4095.0f) * ADC_VREF;
// Convertir a temperatura (LM35: 10mV/°C)
current_temperature = voltage * 100.0f; // V * 1000mV/V / 10mV/°C = °C
// Limitar temperatura a rango válido
if(current_temperature < 0.0f) current_temperature = 0.0f;
if(current_temperature > 150.0f) current_temperature = 150.0f;
}
// ============================================================================
// CÁLCULO DE Δt
// ============================================================================
void calculate_delta_t(void)
{
float T = current_temperature;
// Multiplicamos por 10 porque ahora el Timer 2 corre a 0.1ms por tick
// Queremos que el retardo varíe entre 5ms y 50ms para que sea MUY visible
if(current_rpm < 1000.0f)
{
// A 0°C -> 50ms de retardo. A 150°C -> 5ms de retardo.
delta_t_ms = (50.0f - (T / 3.3f)) * 10.0f;
}
else
{
delta_t_ms = (25.0f - (T / 6.6f)) * 10.0f;
}
if(delta_t_ms < 10.0f) delta_t_ms = 10.0f; // Mínimo 1ms
}
// ============================================================================
// INICIO DE SECUENCIA TDC
// ============================================================================
void start_tdc_sequence(void)
{
// Si current_rpm es 0, usamos 60 como valor por defecto para que el examen funcione
float rpm_para_calculo = (current_rpm < 10.0f) ? 60.0f : current_rpm;
float time_90deg_ms = (15.0f / rpm_para_calculo) * 1000.0f;
uint32_t ticks_90deg = (uint32_t)time_90deg_ms;
if(ticks_90deg > 0 && ticks_90deg < 65535)
{
TIM3->ARR = ticks_90deg;
timer_reset(TIM3);
timer_start(TIM3, TIMER_ONESHOT);
}
}
// ============================================================================
// MANEJADORES DE INTERRUPCIÓN
// ============================================================================
/**
* @brief TIM1 Capture/Compare IRQ Handler
* Captura flancos del encoder para calcular RPM
*/
void TIM1_CC_IRQHandler(void)
{
if(TIM1->SR & TIM_SR_CC1IF)
{
// Leer valor capturado
uint32_t current_capture = timer_get_capture_value(TIM1, TIMER_CHANNEL_1);
// Calcular diferencia
if(current_capture >= last_capture_value)
{
capture_diff = current_capture - last_capture_value;
}
else
{
// Overflow del contador
capture_diff = (0xFFFF - last_capture_value) + current_capture + 1;
}
last_capture_value = current_capture;
// Calcular RPM
calculate_rpm();
// Limpiar flag
TIM1->SR &= ~TIM_SR_CC1IF;
}
}
/**
* @brief EXTI9_5 IRQ Handler
* Detecta el sensor Hall (posición 0°) e inicia secuencia TDC
*/
void EXTI9_5_IRQHandler(void)
{
if(EXTI->PR & (1 << 5)) // PB5
{
// Iniciar secuencia TDC (retardo de 90°)
start_tdc_sequence();
// Limpiar pending bit
EXTI->PR = (1 << 5);
}
}
/**
* @brief TIM3 IRQ Handler
* Termina el retardo de 90° e inicia retardo Δt
*/
void TIM3_IRQHandler(void)
{
if(TIM3->SR & TIM_SR_UIF)
{
timer_stop(TIM3);
uint32_t ticks_delta = (uint32_t)delta_t_ms;
if(ticks_delta > 0 && ticks_delta < 65535)
{
TIM2->ARR = ticks_delta; // Aquí se aplica el cambio de temperatura
TIM2->CNT = 0; // Reiniciar contador
timer_start(TIM2, TIMER_ONESHOT);
}
else
{
// Si falla, dispara el inyector (TIM4) de una vez
TIM4->CNT = 0;
TIM4->CR1 |= (1 << 0);
}
TIM3->SR &= ~TIM_SR_UIF;
}
}
/**
* @brief TIM2 IRQ Handler
* Termina el retardo Δt y activa el inyector con PWM
*/
/**
* @brief TIM2 IRQ Handler
* Termina el retardo Δt y activa el inyector con PWM
*/
void TIM2_IRQHandler(void)
{
if(TIM2->SR & TIM_SR_UIF)
{
timer_stop(TIM2);
// --- TERMINA EL RETARDO: Apagamos la señal del TDC con tu librería ---
gpio_bitwise_clear(GPIOB, 1 << GPIO_PIN_7);
// --- DISPARO LIMPIO DEL INYECTOR ---
TIM4->CR1 &= ~(1 << 0); // Forzar apagado previo por seguridad
TIM4->CNT = 0; // Reiniciar contador al inicio
TIM4->SR &= ~TIM_SR_UIF; // Limpiar banderas pasadas
TIM4->CR1 |= (1 << 0); // ¡FUEGO! (Encender)
TIM2->SR &= ~TIM_SR_UIF;
}
}