Exemplo de experimento de captura de entrada STM32 detalhado

índice

Experiência de captura de entrada STM32

Registre a explicação da peça (tome TIM5_CH1 como exemplo)

A função de TIMx_CCMR1.ICF [3: 0]

A função de TIMx_CCER.CC1P

A função de TIMx_CCMR1.CC1S

A função de TIMx_CCMR1.ICPS

A função de TIMx_CCER.CC1E

Habilidades de programação

Como julgar se é uma borda ascendente ou uma borda descendente?

Como realizar a medição da largura de pulso?

O que devo fazer se o cronômetro estourar quando o nível alto durar muito tempo?

O que devo fazer se o cronômetro estourar muitas vezes?

Princípio de configuração da função de biblioteca

Diagrama de bloco geral

Configuração de pinos multiplexados e GPIO

Configuração do contador

Configuração das propriedades do canal de captura de entrada

Configuração de prioridade de interrupção

Seleção do tipo de interrupção TIM5_CH1

Ativar temporizador

Função de interrupção de serviço para medição de pulso eficaz

Foco: A lógica de escrever funções de serviço de interrupção

Dificuldade: O que devo fazer quando o nível alto dura muito tempo?

Exemplo de código

Main.c

Timer.c

Timer.h

Key.c

Key.h

resultado da operação


Experiência de captura de entrada STM32

Registre a explicação da peça (tome TIM5_CH1 como exemplo)

 

A função de TIMx_CCMR1.ICF [3: 0]

A função do filtro é "coletar amostras para determinar o estado de nível preciso". Vamos tomar ICIF [3: 0] = 0010 como exemplo:

 

 

 

Exemplo de aplicação: assumindo que o sinal de entrada oscila dentro de um máximo de 5 ciclos de clock internos, devemos configurar a largura de banda do filtro para ser maior que 5 ciclos de clock. Portanto, podemos amostrar 8 vezes continuamente (na frequência fDTS) para confirmar a última transição de borda real em TI1, ou seja, escrever IC1F = 0011 no registrador TIMx_CCMR1.

A função de TIMx_CCER.CC1P

Este registro é muito importante, ele determina a "captura de entrada do trigger de borda ascendente / descendente" e, o mais importante, é o único registro usado para configurar a polaridade, o que significa que a polaridade de entrada e a polaridade de saída devem passar por ele. Configuração, portanto, quando chamarmos a função abaixo, você descobrirá que seja uma saída PWM ou uma captura de entrada, alterar a polaridade é a mesma função da configuração.

 

A função de TIMx_CCMR1.CC1S

 

O acima é a configuração do canal. A existência de CC1S [1: 0] é para configurar o "tipo de sinal que dispara o evento / interrupção". Por exemplo, CC1S [1: 0] = 01, então o sinal que dispara a interrupção / evento em TIM5_CH1 é de TI1FP1 .

 

A função de TIMx_CCMR1.ICPS

A função do prescaler é configurar "Quantas alterações de nível válido precisamos para acionar uma interrupção?".

 

A função de TIMx_CCER.CC1E

Habilite o canal de captura de entrada correspondente, de modo que quando a porta IC1PS produzir uma mudança de nível válida, ela possa ser capturada imediatamente para acionar o evento / interrupção correspondente.

Habilidades de programação

Como julgar se é uma borda ascendente ou uma borda descendente?

Borda de pulso + estado do nível no momento anterior = mudança de nível. Por exemplo: o nível no momento anterior estava alto e a borda do pulso foi capturada, então podemos saber "o nível neste momento é baixo e a borda do pulso é um pulso de borda descendente".

Como realizar a medição da largura de pulso?

Sabemos que "o princípio da captura de entrada é: quando uma mudança de pulso válida é capturada, o valor do contador será automaticamente capturado pelo registro de captura / comparação", podemos fazer isso: se quisermos medir a duração de um nível alto, podemos Quando ocorre a borda ascendente, definimos o valor de contagem inicial do contador como 0 e, quando ocorre a borda descendente, carregamos o valor do contador neste momento no contador de captura / comparação para capturar e ler. Finalmente "(valor capturado - valor inicial do contador) * duração da contagem da unidade do contador = duração de alto nível".

O que devo fazer se o cronômetro estourar quando o nível alto durar muito tempo?

Sabemos que cada temporizador tem uma função de serviço de interrupção correspondente, podemos habilitar duas interrupções: interrupção de estouro do contador + interrupção de captura de entrada. Podemos registrar o número de vezes que o temporizador transborda quando o nível de entrada é alto. No cálculo final do tempo, "duração de alto nível = (tempos de estouro * (valor MAX do contador-valor inicial do contador) + (valor de captura-valor inicial do contador)) * tempo de contagem da unidade do contador".

O que devo fazer se o cronômetro estourar muitas vezes?

Quando a duração de alto nível é muito longa e o contador transborda muitas vezes, não precisamos esperar pelo "pulso de queda", definir diretamente o "sinalizador de conclusão de captura" e definir o valor de captura para o que seu tipo de dados pode representar Valor máximo.

Princípio de configuração da função de biblioteca

Diagrama de bloco geral

 

Configuração de pinos multiplexados e GPIO

Habilite o clock de barramento correspondente e a inicialização GPIO:

Eu multiplexo o pino PA0 como TIM5_CH1 para captura de entrada.

Podemos ver no status de KEY_UP que GPIOA.0 deve ser uma entrada pull-down.Somente desta forma, o botão pode ser pressionado para acionar a mudança de nível 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为下拉输入

 

Configuração do contador

O contador é a alma do temporizador. Independentemente de configurarmos a saída PWM ou a captura de entrada, devemos primeiro configurar o temporizador:

A imagem acima é a função de "IM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; // AHB-> APB1 sem divisão de frequência".

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); // 初始化计数器  

Configuração das propriedades do canal de captura de entrada

Configure ainda TIM5_CH1 de acordo com dois modos de entrada / saída 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的输入捕获属性  

 

Configuração de prioridade de interrupção

Configure a prioridade do vetor de interrupção 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嵌入式中断向量优先级  

 

Seleção do tipo de interrupção TIM5_CH1

Além disso, configure o tipo de gatilho de interrupção TIM5_CH1:

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

 

Ativar temporizador

Timer TIM5 habilitado:

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

 

Função de interrupção de serviço para medição de pulso eficaz

Aqui queremos medir o tempo que o botão é pressionado, ou seja, calcular a "duração de alto nível":

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);  
}  

 

Foco: A lógica de escrever funções de serviço de interrupção

 

 

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);  // 清除中断标志
}  

Após os procedimentos a seguir, já podemos julgar: O nível neste momento é válido?

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); // 捕获上升沿  
            }  
        } 

Em seguida, registramos o número de overflows conforme se trata de um nível alto: quando está em um nível alto, ou seja, um nível efetivo, o estouro do contador é considerado válido e registrado.

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
                }  
            }  
        } 

Dificuldade: O que devo fazer quando o nível alto dura muito tempo?

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++;  
        }  
    }  

 

Quando a duração de alto nível é muito longa e o contador transborda muitas vezes, não precisamos esperar pelo "pulso de queda", definir diretamente o "sinalizador de conclusão de captura" e definir o valor de captura para o que seu tipo de dados pode representar Valor máximo.

Exemplo de código

Main.c

#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内的数据以便再次捕获
        }  
    }     
}

  

Timer.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); // 清楚中断标志位  
}  

 

Timer.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 da operação

 

Acho que você gosta

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