/**
******************************************************************************
* @file : main.c
* @author : Fernando Hermosillo Reynoso
* @brief : Controlador de Inyección (Versión Osciloscopio)
******************************************************************************
*/
#include <stdint.h>
#include "stm32f103_hal.h"
/* Defines -----------------------------------------------------*/
/* Private Variables ------------------------------ */
/* Private constants -------------------------------*/
/* Prototype Function ------------------------------*/
void exti0_init(void);
void exti1_init(void);
void exti2_init(void);
/* User code -----------------------------------------------------*/
// Funciones de retardo bloqueantes (por software)
void delay_ms(uint16_t ms) {
volatile unsigned long t = 0;
for(uint16_t i = 0; i < ms; i++) {
for(t = 0; t < 800; t++);
}
}
void delay_us(uint16_t us) {
for (volatile unsigned int cycles = 0; cycles < us; cycles++);
}
// Variables globales volátiles (modificadas por interrupciones)
volatile uint32_t dientes_por_vuelta = 0;
volatile uint32_t dientes_acumulados = 0;
volatile int pulsos_por_ventana = 0;
volatile int pulsos_acumulados = 0;
volatile uint32_t retardo_inyeccion_us = 0;
int main(void) {
// 1. Habilitar los relojes de los periféricos
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);
// 2. Configurar Entradas y Salidas (I/Os)
gpio_set_output(GPIOC, GPIO_PIN_13, GPIO_OUTPUT_PP, GPIO_SPEED_2MHZ); // PC13: Bandera para el osciloscopio
gpio_set_output(GPIOB, GPIO_PIN_6, GPIO_OUTPUT_PP, GPIO_SPEED_2MHZ); // PB6: Salida PWM Inyector
gpio_set_input(GPIOA, GPIO_PIN_0, GPIO_INPUT_FLOAT); // PA0: Ref 0 grados (EXTI0 - TDC)
gpio_set_input(GPIOA, GPIO_PIN_1, GPIO_INPUT_FLOAT); // PA1: Dientes (EXTI1)
gpio_set_input(GPIOA, GPIO_PIN_2, GPIO_INPUT_FLOAT); // PA2: Disparo (EXTI2)
gpio_set_input(GPIOA, GPIO_PIN_3, GPIO_INPUT_ANALOG); // PA3: Sensor Temp LM35
// 3. Configurar Convertidor ADC
adc_init(ADC1, ADC_MODE_SINGLE, ADC_TRIG_SOFTWARE, ADC_ALIGN_RIGHT);
adc_set_channel(ADC1, ADC_CHANNEL_3, ADC_SMP_28_5);
// 4. Configurar TIM2 (Base de tiempo para calcular RPM)
timer_init(TIM2, TIMER_MODE_UP, 9999, 799);
timer_set_interrupt(TIM2, TIMER_IRQ_UPDATE, IRQ_ENABLE);
NVIC->ISER[0] = 1 << 28;
timer_start(TIM2, TIMER_CONTINUOUS);
// 5. Configurar TIM3 (Retardo del Inyector)
// Prescaler 799 = 100 microsegundos por tick @ 8MHz.
timer_init(TIM3, TIMER_MODE_UP, 0, 799);
timer_set_interrupt(TIM3, TIMER_IRQ_UPDATE, IRQ_ENABLE);
NVIC->ISER[0] = 1 << 29;
// 6. Configurar TIM4 (Generador PWM a 100kHz, Duty 20%)
timer_init(TIM4, TIMER_MODE_UP, 9, 7); // ARR=9, PSC=7
timer_set_oc_channel(
TIM4, // TIMER4
TIMER_CHANNEL_1, // T4C1 (PB6)
TIMER_OC_PWM2, // OC Mode: PWM1
TIMER_OC_POLARITY_HIGH, // Polarity: Active high
2, // CCR1=2 (Duty cycle 20%)
true);
// 7. Inicializar Interrupciones Externas
exti0_init();
exti1_init();
exti2_init();
// Variables locales del bucle
int rpm_motor = 0;
uint32_t tiempo_vuelta_us = 0;
int temperatura_celsius = 0;
// 8. Bucle infinito de control
while (1) {
// --- Lectura de Temperatura ---
uint16_t adc_value = adc_read(ADC1);
temperatura_celsius = (22 * adc_value) / 180;
// --- SECCIÓN CRÍTICA ---
__disable_irq();
uint32_t dientes_vuelta_seguro = dientes_por_vuelta;
int pulsos_ventana_seguro = pulsos_por_ventana;
__enable_irq();
// --- Cálculo de RPM y Periodo ---
if (dientes_vuelta_seguro > 0) {
rpm_motor = (pulsos_ventana_seguro * 60) / dientes_vuelta_seguro; //(cada que un diente pasa frente al sensor*60/ numeros de dientes que pasaron en 1 segundo)
} else {
rpm_motor = 0;
}
if (rpm_motor > 0) {
tiempo_vuelta_us = 60000000 / rpm_motor;
} else {
tiempo_vuelta_us = 1000000;
}
// --- Cálculo del Retardo de Inyección ---
int32_t factor_temperatura = 1 + (200 * (85 - temperatura_celsius));
if (factor_temperatura < 1) {
factor_temperatura = 1;
}
uint64_t retardo_calculado_us = (uint64_t)(tiempo_vuelta_us / 4) * factor_temperatura;
if (retardo_calculado_us < (tiempo_vuelta_us / 4)) {
retardo_calculado_us = (tiempo_vuelta_us / 4);
}
if (retardo_calculado_us > (tiempo_vuelta_us / 2)) {
retardo_calculado_us = (tiempo_vuelta_us / 2);
}
retardo_inyeccion_us = (uint32_t)retardo_calculado_us;
}
}
/* ========================================================================== *
* RUTINAS DE INICIALIZACIÓN DE INTERRUPCIONES (EXTI) *
* ========================================================================== */
void exti0_init(void) {
// 1. Enciende el reloj del multiplexor AFIO para configurar pines externos
RCC->APB2ENR |= 1 << 0;
// 2. Conecta la interrupción EXTI0 al Pin 0 (ej. PA0) limpiando los 4 bits bajos
AFIO->EXTICR[0] &= ~(0x0F << 0);
// 3. Configura el "gatillo" en el flanco de subida (cuando la señal pasa a 3.3V)
EXTI->RTSR |= (1 << 0);
// 4. Desactiva el flanco de bajada para no tener lecturas duplicadas
EXTI->FTSR &= ~(1 << 0);
// 5. Quita la máscara a EXTI0 para que avise a la CPU
EXTI->IMR |= 1 << 0;
// 6. Asigna prioridad 3 en el NVIC (IRQ 6 corresponde a EXTI0)
NVIC->IP[6] = (uint8_t)(0x03 << 4);
// 7. Enciende globalmente la interrupción 6 (EXTI0) en el procesador
NVIC->ISER[0] = 1 << 6;
}
void EXTI0_IRQHandler(void) {
// Verifica que realmente fue el Pin 0 quien generó la alarma
if (EXTI->PR & (1 << 0)) {
// LIMPIEZA DE BANDERA: Se debe escribir un '1' para apagarla
EXTI->PR = (1 << 0);
// Al detectar el TDC, guardamos los dientes de la vuelta anterior
dientes_por_vuelta = dientes_acumulados;
// Reiniciamos el conteo de dientes para la nueva vuelta
dientes_acumulados = 0;
}
}
void exti1_init(void) {
// Conecta la interrupción EXTI1 al Pin 1 (Limpiando los bits 4 a 7)
AFIO->EXTICR[0] &= ~(0x0F << 4);
// Configura el flanco de subida y desactiva el de bajada
EXTI->RTSR |= (1 << 1);
EXTI->FTSR &= ~(1 << 1);
// Desmutea la línea EXTI1
EXTI->IMR |= 1 << 1;
// Enciende la interrupción 7 en el NVIC (que corresponde a EXTI1)
NVIC->ISER[0] = 1 << 7;
}
void EXTI1_IRQHandler(void) {
// Verifica que fue el Pin 1 (El sensor del engrane / tacómetro)
if (EXTI->PR & (1 << 1)) {
// Baja la bandera de la interrupción 1
EXTI->PR = (1 << 1);
// Suma un diente al contador de la vuelta actual (posición)
dientes_acumulados = dientes_acumulados + 1;
// Suma un pulso al contador temporal (para medir RPM en la ventana del TIM2)
pulsos_acumulados = pulsos_acumulados + 1;
}
}
void exti2_init(void) {
// Conecta la interrupción EXTI2 al Pin 2 (Limpiando los bits 8 a 11)
AFIO->EXTICR[0] &= ~(0x0F << 8);
// Configura flanco de subida y desactiva el de bajada
EXTI->RTSR |= (1 << 2);
EXTI->FTSR &= ~(1 << 2);
// Desmutea la línea EXTI2
EXTI->IMR |= 1 << 2;
// Enciende la interrupción 8 en el NVIC (corresponde a EXTI2)
NVIC->ISER[0] = 1 << 8;
}
void EXTI2_IRQHandler(void) {
// Verifica que fue el Pin 2 (Gatillo para iniciar el retraso de inyección)
if (EXTI->PR & (1 << 2)) {
// Baja la bandera de la interrupción 2
EXTI->PR = (1 << 2);
// Carga el retardo calculado por temperatura en el timer (dividido entre 100 porque cada tick es de 100us)
TIM3->ARR = (retardo_inyeccion_us / 100);
// Arranca el timer 3 en modo One-Shot (contará solo una vez y se detendrá)
timer_start(TIM3, TIMER_ONESHOT);
}
}
/* ========================================================================== *
* RUTINAS DE LOS TEMPORIZADORES (TIMERS) *
* ========================================================================== */
void TIM2_IRQHandler(void) {
// Limpia la bandera UIF (Update Interrupt Flag) del Timer 2
TIM2->SR &= ~TIM_SR_UIF;
// Se cumplió la ventana de tiempo (ej. 100ms). Guardamos cuántos pulsos hubo
pulsos_por_ventana = pulsos_acumulados;
// Reiniciamos el contador de pulsos para la siguiente medición de RPM
pulsos_acumulados = 0;
}
void TIM3_IRQHandler(void) {
// Termina el tiempo de retardo calculado. Limpia bandera UIF del Timer 3
TIM3->SR &= ~TIM_SR_UIF;
// --- PULSO PARA EL OSCILOSCOPIO ---
// Genera un pulso cuadrado en PC13 para marcar exactamente el instante de inyección
GPIOC->ODR |= (1 << 13); // 1. Encender pin PC13 (El trazo sube en el osciloscopio)
delay_ms(1); // 2. Duración del pulso visual (1 milisegundo).
// *NOTA: Si a altas RPM causa conflicto, reducir a delay_us(500);
GPIOC->ODR &= ~(1 << 13); // 3. Apagar pin PC13 (El trazo baja, cerrando el pulso visual)
// --- ACCIÓN FÍSICA ---
// Activa PWM del inyector físico (en PB6 u otro pin configurado).
// Esto inyecta físicamente la gasolina.
timer_start(TIM4, TIMER_ONESHOT);
}