Ejemplo detallado del experimento de captura de entrada STM32

Tabla de contenido

Experimento de captura de entrada STM32

Registrar la explicación de la pieza (tomar TIM5_CH1 como ejemplo)

El rol de TIMx_CCMR1.ICF [3: 0]

El papel de TIMx_CCER.CC1P

El papel de TIMx_CCMR1.CC1S

El papel de TIMx_CCMR1.ICPS

El papel de TIMx_CCER.CC1E

Habilidades en programación

¿Cómo juzgar si se trata de un flanco ascendente o descendente?

¿Cómo realizar la medición del ancho de pulso?

¿Qué debo hacer si el temporizador se desborda cuando el nivel alto dura demasiado?

¿Qué debo hacer si el temporizador se desborda demasiadas veces?

Principio de configuración de la función de biblioteca

Diagrama de bloques general

Configuración de pines multiplexados y GPIO

Configuración del contador

Configuración de las propiedades del canal de captura de entrada

Interrupción de la configuración de prioridad

Selección del tipo de interrupción TIM5_CH1

Activar temporizador

Función de servicio de interrupción para medir el pulso efectivo

Enfoque: la lógica de escribir funciones de servicio de interrupción

Dificultad: ¿Qué debo hacer cuando el nivel alto dura demasiado?

Ejemplo de código

C Principal

Temporizador.c

Temporizador.h

Key.c

Key.h

resultado de la operación


Experimento de captura de entrada STM32

Registrar la explicación de la pieza (tomar TIM5_CH1 como ejemplo)

 

El rol de TIMx_CCMR1.ICF [3: 0]

La función del filtro es "recolectar muestras para determinar el estado de nivel exacto". Tomemos ICIF [3: 0] = 0010 como ejemplo:

 

 

 

Aplicación de ejemplo: asumiendo que la señal de entrada tiembla dentro de un máximo de 5 ciclos de reloj interno, debemos configurar el ancho de banda del filtro para que sea más largo que 5 ciclos de reloj. Por lo tanto, podemos muestrear 8 veces continuamente (a la frecuencia fDTS) para confirmar la última transición de borde real en TI1, es decir, escribir IC1F = 0011 en el registro TIMx_CCMR1.

El papel de TIMx_CCER.CC1P

Este registro es muy importante, determina la "captura de entrada del disparador de flanco ascendente / flanco descendente" y, lo más importante, es el único registro utilizado para configurar la polaridad, lo que significa que la polaridad de entrada y la polaridad de salida deben pasar por él. Configuración, por lo que cuando llamamos a la función a continuación, encontrará que si es una salida PWM o una captura de entrada, cambiar la polaridad es la misma función de configuración.

 

El papel de TIMx_CCMR1.CC1S

 

Lo anterior es la configuración del canal. La existencia de CC1S [1: 0] es para configurar el "tipo de señal que dispara el evento / interrupción". Por ejemplo, CC1S [1: 0] = 01, entonces la señal que dispara la interrupción / evento en TIM5_CH1 es de TI1FP1 .

 

El papel de TIMx_CCMR1.ICPS

La función del preescaler es configurar "¿Cuántos cambios de nivel válidos necesitamos para activar una interrupción?".

 

El papel de TIMx_CCER.CC1E

Habilite el canal de captura de entrada correspondiente, de modo que cuando el puerto IC1PS emita un cambio de nivel válido, pueda capturarse inmediatamente para activar el evento / interrupción correspondiente.

Habilidades en programación

¿Cómo juzgar si se trata de un flanco ascendente o descendente?

Flanco de pulso + estado de nivel en el momento anterior = cambio de nivel. Por ejemplo: el nivel en el momento anterior era alto y el borde del pulso fue capturado, entonces podemos saber "el nivel en este momento es bajo y el borde del pulso es un pulso de borde descendente".

¿Cómo realizar la medición del ancho de pulso?

Sabemos que "el principio de captura de entrada es: cuando se captura un cambio de pulso válido, entonces el valor del contador será capturado automáticamente por el registro de captura / comparación", podemos hacer esto: si queremos medir la duración de un nivel alto, podemos Cuando ocurre el borde ascendente, establecemos el valor de conteo inicial del contador en 0, y cuando ocurre el borde descendente, cargamos el valor del contador en este momento en el contador de captura / comparación para capturar y leer. Finalmente "(valor capturado-valor inicial del contador) * unidad de duración del contador = duración de alto nivel".

¿Qué debo hacer si el temporizador se desborda cuando el nivel alto dura demasiado?

Sabemos que cada temporizador tiene una función de servicio de interrupción correspondiente, podemos habilitar dos interrupciones: interrupción de desbordamiento del contador + interrupción de captura de entrada. Podemos registrar el número de veces que el temporizador se desborda cuando el nivel de entrada es alto. En el cálculo final del tiempo, "duración de nivel alto = (tiempos de desbordamiento * (valor MAX del contador-valor inicial del contador) + (valor de captura-valor inicial del contador)) * tiempo de recuento de la unidad de contador".

¿Qué debo hacer si el temporizador se desborda demasiadas veces?

Cuando la duración del nivel alto es demasiado larga y el contador se desborda demasiadas veces, no es necesario esperar el "pulso de borde descendente", establecer directamente el "indicador de finalización de captura" y establecer el valor de captura según el tipo de datos que pueda representar. Valor máximo.

Principio de configuración de la función de biblioteca

Diagrama de bloques general

 

Configuración de pines multiplexados y GPIO

Habilite el reloj de bus correspondiente y la inicialización de GPIO:

Multiplexo el pin PA0 como TIM5_CH1 para la captura de entrada.

Podemos ver en el estado de KEY_UP que GPIOA.0 debe ser una entrada desplegable, solo de esta manera se puede presionar el botón para activar el cambio de nivel de entrada.

GPIO_InitTypeDef GPIO_InitStructure;  
      
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 使能GPIOA与TIM5的时钟进行引脚的复用
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);  
      
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA0为下拉输入

 

Configuración del contador

El contador es el alma del temporizador. Ya sea que configuremos la salida PWM o la captura de entrada, primero debemos configurar el temporizador:

La imagen de arriba es la función de "IM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB-> APB1 sin división de frecuencia".

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;    
IM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB->APB1不分频  
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式  
TIM_TimeBaseInitStructure.TIM_Period = ARR;  
TIM_TimeBaseInitStructure.TIM_Prescaler = PR;  
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStructure); // 初始化计数器  

Configuración de las propiedades del canal de captura de entrada

Configure adicionalmente TIM5_CH1 de acuerdo con dos modos de entrada / salida diferentes:

TIM_ICInitTypeDef TIM_ICInitStructure;    
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;   // 使用TIM5_CH1进行输入捕获实验
TIM_ICInitStructure.TIM_ICFilter = 0;   // 不进行采集滤波
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 上升沿触发
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  // 一个有效上升沿触发一次输入捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;  // 选择通道1的信号作为触发输入捕获的信号
TIM_ICInit(TIM5, &TIM_ICInitStructure); // 配置TIM5_CH1的输入捕获属性  

 

Interrupción de la configuración de prioridad

Configure la prioridad del vector de interrupción NVIC:

NVIC_InitTypeDef NVIC_InitStructure;      
VIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;   // TIM5中断使能
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;   // 抢占优先级 = 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;  // 响应优先级 = 1
NVIC_Init(&NVIC_InitStructure); // 配置NVIC嵌入式中断向量优先级  

 

Selección del tipo de interrupción TIM5_CH1

Configure adicionalmente el tipo de disparo de interrupción TIM5_CH1:

TIM_ITConfig(TIM5, TIM_IT_CC1|TIM_IT_Update, ENABLE); // 使能TIM5_CH1的中断  

 

Activar temporizador

Habilitación del temporizador TIM5:

TIM_Cmd(TIM5, ENABLE); // TIM5定时器使能  

 

Función de servicio de interrupción para medir el pulso efectivo

Aquí queremos medir el tiempo que se presiona el botón, es decir, calcular la "duración del nivel alto":

u8 TIM_CAP_STA = 0;  
u16 TIM_CAP_VAL = 0;  
  
void TIM5_IRQHandler()  
{  
    if((TIM_CAP_STA&0x80) == 0) // 捕获未完成  
    {  
        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  
        {  
            if((TIM_CAP_STA&0x40) == 0) // 上升沿  
            {  
                TIM_SetCounter(TIM5, 0); // 开始计数  
                TIM_CAP_VAL = 0;  
                TIM_CAP_STA = 0;  
                TIM_CAP_STA |= 0x40; // 高电平  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 捕获下降沿  
            }  
            else // 下降沿  
            {  
                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获当前值  
                TIM_CAP_STA |= 0x80; // 完成捕获  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 捕获上升沿  
            }  
        }  
        if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  
        {  
            if(TIM_CAP_STA&0x40)  
            {  
                if((TIM_CAP_STA&0x3F) == 0x3F)  
                {  
                    TIM_CAP_VAL = 0xFFFF;  
                    TIM_CAP_STA |= 0x80;  
                }  
                else  
                {  
                    TIM_CAP_STA++;  
                }  
            }  
        }  
    }  
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);  
}  

 

Enfoque: la lógica de escribir funciones de servicio de interrupción

 

 

u8 TIM_CAP_STA = 0;  
u16 TIM_CAP_VAL = 0;  
  
void TIM5_IRQHandler()  
{  
    if((TIM_CAP_STA&0x80) == 0) // 捕获未完成  
    {  
        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  // TIM5_CH1捕获有效的脉冲沿
        {  
            if((TIM_CAP_STA&0x40) == 0) // 前一刻为低电平——脉冲沿为上升沿  
            {  
                TIM_SetCounter(TIM5, 0); // 开始计数  
                TIM_CAP_VAL = 0;  
                TIM_CAP_STA = 0;  
                TIM_CAP_STA |= 0x40; // 更新电平状态  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 捕获下降沿  
            }  
            else // 前一刻为高电平——脉冲沿为下降沿  
            {  
                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获当前值  
                TIM_CAP_STA |= 0x80; // 完成捕获  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 捕获上升沿  
            }  
        }  
        if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  // 计数器溢出
        {  
            if(TIM_CAP_STA&0x40)  // 如果为高电平,计数器溢出则会被记录
            {  
                if((TIM_CAP_STA&0x3F) == 0x3F)  // 如果计数器溢出次数过多
                {  
                    TIM_CAP_VAL = 0xFFFF;  // 此时的捕获值设为MAX
                    TIM_CAP_STA |= 0x80;  // 已经完成高电平的捕获
                }  
                else  
                {  
                    TIM_CAP_STA++;  // 计数器溢出次数+1
                }  
            }  
        }  
    }  
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update);  // 清除中断标志
}  

Después de los siguientes procedimientos, ya podemos juzgar: ¿Es válido el nivel en este momento?

if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  // TIM5_CH1捕获有效的脉冲沿
        {  
            if((TIM_CAP_STA&0x40) == 0) // 前一刻为低电平——脉冲沿为上升沿  
            {  
                TIM_SetCounter(TIM5, 0); // 开始计数  
                TIM_CAP_VAL = 0;  
                TIM_CAP_STA = 0;  
                TIM_CAP_STA |= 0x40; // 更新电平状态  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 捕获下降沿  
            }  
            else // 前一刻为高电平——脉冲沿为下降沿  
            {  
                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获当前值  
                TIM_CAP_STA |= 0x80; // 完成捕获  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 捕获上升沿  
            }  
        } 

Luego, registramos el número de desbordamientos según si es un nivel alto: cuando está en un nivel alto, es decir, un nivel efectivo, el desbordamiento del contador se considera válido y se registra.

if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  // 计数器溢出
        {  
            if(TIM_CAP_STA&0x40)  // 如果为高电平,计数器溢出则会被记录
            {  
                if((TIM_CAP_STA&0x3F) == 0x3F)  // 如果计数器溢出次数过多
                {  
                    TIM_CAP_VAL = 0xFFFF;  // 此时的捕获值设为MAX
                    TIM_CAP_STA |= 0x80;  // 已经完成高电平的捕获
                }  
                else  
                {  
                    TIM_CAP_STA++;  // 计数器溢出次数+1
                }  
            }  
        } 

Dificultad: ¿Qué debo hacer cuando el nivel alto dura demasiado?

if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET)  // 计数器是否溢出
{  
    if(TIM_CAP_STA&0x40)  // 是否为有效电平(高电平)
    {  
        if((TIM_CAP_STA&0x3F) == 0x3F)  // 捕获次数已经为MAX
        {  
            TIM_CAP_VAL = 0xFFFF;  // 捕获值为MAX
            TIM_CAP_STA |= 0x80;  // 直接标记:已经捕获完成
        }  
        else  
        {  
            TIM_CAP_STA++;  
        }  
    }  
} 

 

Cuando la duración del nivel alto es demasiado larga y el contador se desborda demasiadas veces, no es necesario esperar el "pulso de borde descendente", establecer directamente el "indicador de finalización de captura" y establecer el valor de captura según el tipo de datos que pueda representar. Valor máximo.

Ejemplo de código

C Principal

#include "key.h"  
#include "timer.h"  
#include "usart.h"  
#include "led.h"  
#include "delay.h"  
  
int main()  
{  
    extern u8 TIM_CAP_STA;  
    extern u16 TIM_CAP_VAL;  
    u32 temp = 0;  
      
    delay_init(); // 初始化systick时钟  
    KEY_InitConfig(); // 初始化KEY_WAKEUP  
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); // 设置中断优先级分组  
    TIM_CAPTURE_InitConfig(0xFFFF, 72-1); // 周期为65.535s  
    uart_init(115200); // 初始化串口  
      
    while(1)  
    {  
        delay_ms(10);  
        if((TIM_CAP_STA&0x80) == 0x80)  // 完成有效电平的捕获
        {  
            temp = TIM_CAP_VAL + (TIM_CAP_STA&0x3F) * 0xFFFF; // 计算脉冲持续时间  
            printf("%d\r\n", temp); // 串口打印  
            TIM_CAP_STA = 0; // 清空TIM_CAP_STA内的数据以便再次捕获
        }  
    }     
}

  

Temporizador.c

#include "timer.h"  
#include "stm32f10x.h"  
#include "sys.h"  
#include "led.h"  
  
void TIM_CAPTURE_InitConfig(u16 ARR, u16 PSC)  
{  
    GPIO_InitTypeDef GPIO_InitStructure;  
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;  
    TIM_ICInitTypeDef TIM_ICInitStructure;  
    NVIC_InitTypeDef NVIC_InitStructure;  
      
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 使能APB1总线的TIM5时钟  
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能APB2总线的GPIOA时钟  
      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA, &GPIO_InitStructure); // PA0初始化为下拉输入  
      
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB = APB1  
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数模式  
    TIM_TimeBaseInitStructure.TIM_Period = ARR;  
    TIM_TimeBaseInitStructure.TIM_Prescaler = PSC;  
    TIM_TimeBaseInit(TIM5, &TIM_TimeBaseInitStructure); // 配置计数器的属性  
      
    TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;  
    TIM_ICInitStructure.TIM_ICFilter = 0;  
    TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  
    TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;  
    TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;  
    TIM_ICInit(TIM5, &TIM_ICInitStructure); // 配置TIM5的输入属性  
      
    NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;  
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  
    NVIC_Init(&NVIC_InitStructure); // 配置TIM5的中断优先级  
      
    TIM_ITConfig(TIM5, TIM_IT_CC1|TIM_IT_Update, ENABLE); // 使能TIM5_CH1的中断  
      
    TIM_Cmd(TIM5, ENABLE); // 使能TIM5  
}  
  
u8 TIM_CAP_STA = 0;  
u16 TIM_CAP_VAL = 0;  
  
void TIM5_IRQHandler()  
{  
    if((TIM_CAP_STA&0x80) == 0)  
    {  
        if(TIM_GetITStatus(TIM5, TIM_IT_CC1) == SET)  
        {  
            if((TIM_CAP_STA&0x40) == 0x00) // 上升沿  
            {  
                TIM_CAP_STA = 0; // 初始化  
                TIM_CAP_VAL = 0; // 初始化  
                TIM_SetCounter(TIM5, 0); // 计数器开始计数  
                TIM_CAP_STA |= 0x40;  // 高电平  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_Low); // 改变极性/改变有效电平——捕获上升沿  
            }  
            else // 下降沿  
            {  
                TIM_CAP_VAL = TIM_GetCapture1(TIM5); // 捕获此时计数器的值  
                TIM_CAP_STA |= 0x80; // 完成捕获  
                TIM_OC1PolarityConfig(TIM5, TIM_OCPolarity_High); // 改变极性/改变有效电平——捕获下降沿  
            }  
        }  
        if(TIM_GetITStatus(TIM5, TIM_IT_Update) == SET) //  计数器溢出  
        {  
            if((TIM_CAP_STA&0x40) == 0x40) // 上升沿  
            {  
                if((TIM_CAP_STA&0x3F) == 0x3F) // 当溢出次数过多,就默认"捕获完成并且此时计数器的值为MAX"  
                {  
                    TIM_CAP_VAL = 0xFFFF;  
                    TIM_CAP_STA |= 0x80; // 完成捕获  
                }  
                else  
                {  
                    TIM_CAP_VAL++; // 溢出次数递增  
                }  
            }  
        }  
    }  
    TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); // 清楚中断标志位  
}  

 

Temporizador.h

#ifndef _TIMER_H  
#define _TIMER_H  
  
#include "sys.h"  
  
void TIM_CAPTURE_InitConfig(u16 ARR, u16 PR);  
  
#endif  

 

Key.c

#include "key.h"  
#include "stm32f10x.h"  
  
void KEY_InitConfig()  
{  
    GPIO_InitTypeDef GPIO_InitStructure;  
      
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 使能总线时钟  
      
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;  
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;  
    GPIO_Init(GPIOA, &GPIO_InitStructure); // 初始化KEY_WAKRUP为下拉输入  
}

  

Key.h

#ifndef _KEY_H  
#define _KEY_H  
  
void KEY_InitConfig();  
  
#endif  

 

resultado de la operación

 

Supongo que te gusta

Origin blog.csdn.net/weixin_45590473/article/details/108164766
Recomendado
Clasificación