Manuseio de interrupção em microcontroladores e exemplo FSM


9

Questão inicial

Eu tenho uma pergunta geral sobre o manuseio de interrupções em microcontroladores. Estou usando o MSP430, mas acho que a pergunta pode ser estendida a outros UCs. Gostaria de saber se é uma boa prática ativar / desativar as interrupções com frequência ao longo do código. Quero dizer, se eu tenho uma parte do código que não será sensível a interrupções (ou, pior ainda, não deve ouvir interrupções, por qualquer motivo), é melhor:

  • Desative as interrupções antes e depois as reative após a seção crítica.
  • Coloque um sinalizador dentro do respectivo ISR e (em vez de desativar a interrupção), defina o sinalizador como false antes da seção crítica e redefina-o para true, logo após. Para impedir que o código do ISR seja executado.
  • Nenhum dos dois, então sugestões são bem-vindas!

Atualização: interrupções e gráficos de estado

Vou fornecer uma situação específica. Vamos supor que queremos implementar um gráfico de estados, composto por 4 blocos:

  1. Transições / Efeito.
  2. Condições de saída.
  3. Atividade de entrada.
  4. Faça atividade.

Isso é o que um professor nos ensinou na universidade. Provavelmente, não é a melhor maneira de fazer isso, seguindo este esquema:

while(true) {

  /* Transitions/Effects */
  //----------------------------------------------------------------------
  next_state = current_state;

  switch (current_state)
  {
    case STATE_A:
      if(EVENT1) {next_state = STATE_C}
      if(d == THRESHOLD) {next_state = STATE_D; a++}
      break;
    case STATE_B:
      // transitions and effects
      break;
    (...)
  }

  /* Exit activity -> only performed if I leave the state for a new one */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(current_state)
    {
      case STATE_A:
        // Exit activity of STATE_A
        break;
      case STATE_B:
        // Exit activity of STATE_B
        break;
        (...)
    }
  }

  /* Entry activity -> only performed the 1st time I enter a state */
  //----------------------------------------------------------------------
  if (next_state != current_state)
  {
    switch(next_state)
    {
      case STATE_A:
        // Entry activity of STATE_A
        break;
      case STATE_B:
        // Entry activity of STATE_B
        break;
      (...)
    }
  }

  current_state = next_state;

  /* Do activity */
  //----------------------------------------------------------------------
  switch (current_state)
  {
    case STATE_A:
      // Do activity of STATE_A
      break;
    case STATE_B:
      // Do activity of STATE_B
      break;
    (...)
  }
}

Suponhamos também que, digamos STATE_A, eu queira ser sensível a uma interrupção vinda de um conjunto de botões (com sistema de desacoplamento, etc. etc.). Quando alguém pressiona um desses botões, uma interrupção é gerada e o sinalizador relacionado à porta de entrada é copiado em uma variável buttonPressed. Se o debounce estiver definido para 200 ms de alguma forma (watchdog timer, timer, counter, ...), temos certeza de que buttonPressednão será possível atualizar com um novo valor antes de 200 ms. É isso que estou perguntando a você (e a mim mesmo :), é claro)

Preciso ativar a interrupção na atividade DO STATE_Ae desativar antes de sair?

/* Do activity */
//-------------------------------------
switch (current_state)
{
  case STATE_A:
    // Do activity of STATE_A
    Enable_ButtonsInterrupt(); // and clear flags before it
    // Do fancy stuff and ...
    // ... wait until a button is pressed (e.g. LPM3 of the MSP430)
    // Here I have my buttonPressed flag ready!
    Disable_ButtonsInterrupt();
    break;
  case STATE_B:
    // Do activity of STATE_B
    break;
  (...)
}

De uma maneira que tenho certeza de que na próxima vez em que executo o bloco 1 (transição / efeitos) na próxima iteração, tenho certeza de que as condições verificadas ao longo das transições não são provenientes de uma interrupção subsequente que substituiu o valor anterior buttonPresseddaquele precisa (embora seja impossível que isso aconteça porque 250 ms devem decorrer).


3
É difícil fazer uma recomendação sem saber mais sobre sua situação. Mas às vezes é necessário desativar interrupções em sistemas embarcados. É preferível manter as interrupções desativadas apenas por curtos períodos de tempo, para que não sejam perdidas. Pode ser possível desativar apenas interrupções específicas em vez de todas as interrupções. Não me lembro de ter usado a técnica de flag-inside-ISR como você descreveu, então estou cético quanto à sua melhor solução.
Krambo #

Respostas:


17

A primeira tática é arquitetar o firmware geral, para que não haja interrupções a qualquer momento. Ter que desativar as interrupções para que o código em primeiro plano possa executar uma sequência atômica deve ser feito com moderação. Muitas vezes existe uma maneira arquitetônica de contornar isso.

No entanto, a máquina está lá para atendê-lo, e não o contrário. As regras gerais são apenas para impedir que programadores ruins escrevam códigos realmente ruins. É muito melhor entender exatamente como a máquina funciona e, em seguida, arquitetar uma boa maneira de aproveitar esses recursos para executar a tarefa desejada.

Lembre-se de que, a menos que você esteja realmente preocupado com ciclos ou locais de memória (isso certamente pode acontecer), você deseja otimizar a clareza e a manutenção do código. Por exemplo, se você tiver uma máquina de 16 bits que atualiza um contador de 32 bits em uma interrupção de tique do relógio, precisará garantir que, quando o código de primeiro plano ler o contador, as duas metades dele sejam consistentes. Uma maneira é desligar as interrupções, ler as duas palavras e ativar novamente as interrupções. Se a latência de interrupção não for crítica, é perfeitamente aceitável.

No caso em que você deve ter latência baixa de interrupção, é possível, por exemplo, ler a palavra mais alta, ler a palavra mais baixa, ler a palavra mais alta novamente e repetir se ela mudou. Isso diminui um pouco o código de primeiro plano, mas não adiciona latência de interrupção. Existem vários pequenos truques. Outro pode ser definir um sinalizador na rotina de interrupção que indique que o contador deve ser incrementado; em seguida, faça isso no loop do evento principal no código de primeiro plano. Isso funciona bem se a taxa de interrupção do contador for lenta o suficiente para que o loop de eventos faça o incremento antes que o sinalizador seja definido novamente.

Ou, em vez de um sinalizador, use um contador de uma palavra. O código de primeiro plano mantém uma variável separada que contém o último contador para o qual o sistema foi atualizado. Ele faz uma subtração não assinada do contador ativo menos o valor salvo para determinar quantos ticks por vez ele deve manipular. Isso permite que o código em primeiro plano perca até 2 eventos N -1 por vez, onde N é o número de bits em uma palavra nativa que a ALU pode manipular atomicamente.

Cada método tem seu próprio conjunto de vantagens e desvantagens. Não existe uma única resposta correta. Mais uma vez, entenda como a máquina funciona, então você não precisará de regras práticas.


7

Se você precisar de uma seção crítica, verifique se a operação que protege sua seção crítica é atômica e não pode ser interrompida.

Assim, desabilitar as interrupções, que geralmente são tratadas por uma única instrução do processador (e chamado usando uma função intrínseca do compilador), é uma das apostas mais seguras que você pode fazer.

Dependendo do seu sistema, pode haver alguns problemas com isso, como uma interrupção pode ser perdida. Alguns microcontroladores definem os sinalizadores, independentemente do estado da ativação global da interrupção, e depois de sair da seção crítica, as interrupções são executadas e são atrasadas. Mas se houver uma interrupção que ocorra a uma taxa alta, você poderá perder uma segunda vez que a interrupção ocorreu se bloquear as interrupções por um período muito longo.

Se sua seção crítica exige que apenas uma interrupção não seja executada, mas outra deve ser executada, a outra abordagem parece viável.

Eu me pego programando as rotinas de serviço de interrupção o mais curto possível. Então, eles apenas definem um sinalizador que é verificado durante as rotinas normais do programa. Mas se você fizer isso, tome cuidado com as condições da corrida enquanto aguarda a definição do sinalizador.

Existem muitas opções e certamente não há uma resposta correta para isso, este é um tópico que requer um design cuidadoso e merece um pouco mais de reflexão do que outras coisas.


5

Se você determinou que uma seção do código deve ser executada ininterruptamente, exceto em circunstâncias incomuns, desative as interrupções pela duração mínima possível para concluir a tarefa e reative-as depois.

Coloque um sinalizador dentro do respectivo ISR e (em vez de desativar a interrupção), defina o sinalizador como false antes da seção crítica e redefina-o para true, logo após. Para impedir que o código do ISR seja executado.

Isso ainda permitiria uma interrupção, um salto de código, uma verificação e um retorno. Se o seu código puder lidar com essa interrupção, provavelmente você deve simplesmente projetar o ISR para definir um sinalizador em vez de executar a verificação - seria mais curto - e manipular o sinalizador na sua rotina normal de código. Parece que alguém colocou muito código na interrupção e está usando a interrupção para executar ações mais longas que devem ocorrer no código regular.

Se você estiver lidando com código em que as interrupções são longas, um sinalizador sugerido poderá resolver o problema, mas ainda será melhor simplesmente desativar as interrupções, se você não puder fatorar novamente o código para eliminar o código excessivo na interrupção. .

O principal problema ao fazer isso da maneira flagrante é que você não executa a interrupção - o que pode ter repercussões posteriormente. A maioria dos microcontroladores rastreia sinalizadores de interrupção mesmo quando as interrupções são desativadas globalmente e executam a interrupção quando você reativar as interrupções:

  • Se nenhuma interrupção ocorreu durante a seção crítica, nenhuma será executada depois.
  • Se uma interrupção ocorrer durante a seção crítica, uma será executada depois.
  • Se várias interrupções ocorrerem durante a seção crítica, somente uma será executada depois.

Se o seu sistema for complexo e precisar rastrear as interrupções mais completamente, você precisará criar um sistema mais complicado para rastrear as interrupções e operar adequadamente.

No entanto, se você sempre projetar suas interrupções para executar o trabalho mínimo necessário para atingir sua função e atrasar todo o resto para o processamento regular, as interrupções raramente afetarão negativamente seu outro código. Faça com que a interrupção capture ou libere dados, se necessário, ou configure / reconfigure saídas, etc., conforme necessário, e faça com que o caminho do código principal preste atenção aos sinalizadores, buffers e variáveis ​​que a interrupção afeta, para que o processamento demorado possa ser feito no loop principal, ao invés da interrupção.

Isso deve eliminar todas as situações, exceto muito, muito poucas, nas quais você pode precisar de uma seção de código ininterrupto.


Eu atualizei o post para explicar melhor a situação que eu estou trabalhando em :)
Umberto D.

1
No seu código de exemplo, consideraria desabilitar a interrupção específica do botão quando não for necessário e habilitá-lo quando necessário. Fazer isso com frequência não é um problema por design. Deixe as interrupções globais ativadas para adicionar outras interrupções ao código posteriormente, se necessário. Como alternativa, basta redefinir o sinalizador ao ir para o estado A e ignorá-lo. Se o botão for pressionado e a bandeira definida, quem se importa? Ignorá-lo até que você voltar ao estado A.
Adam Davis

Sim! Isso pode ser uma solução porque, no design real, eu frequentemente uso o LPM3 (MSP430) com interrupções globais ativadas e saio do LPM3, retomando a execução assim que uma interrupção é detectada. Portanto, uma solução é a que você apresentou e acho que é relatada na segunda parte do código: enable interrompe assim que eu começar a executar a atividade do de um estado que precisa e desabilite antes de ir para o bloco de transições. Outra solução possível pode ser desativar a interrupção antes de sair do "bloco de atividades" e reativar algum tempo (quando?) Depois?
Umberto D.

1

Colocar uma bandeira dentro do ISR como você descreve provavelmente não funcionará, pois você basicamente está ignorando o evento que acionou a interrupção. Desabilitar interrupções globalmente geralmente é uma escolha melhor. Como outros já disseram, você não deve fazer isso com muita frequência. Lembre-se de que qualquer leitura ou gravação feita por meio de uma única instrução não precisa ser protegida, pois a instrução é executada ou não.

Depende muito de que tipo de recursos você está tentando compartilhar. Se você estiver alimentando dados do ISR para o programa principal (ou vice-versa), poderá implementar algo como um buffer FIFO. A única operação atômica seria atualizar os ponteiros de leitura e gravação, o que minimiza o tempo gasto com as interrupções desativadas.


0

Há uma diferença sutil que você precisa levar em consideração. Você pode optar por "atrasar" o tratamento de uma interrupção ou "desconsiderar" e interromper.

Muitas vezes dizemos no código que desativamos a interrupção. O que provavelmente acontecerá, devido às funções do hardware, é que, uma vez que ativamos as interrupções, ele será acionado. Isso de certa forma está atrasando a interrupção. Para que o sistema funcione de maneira confiável, precisamos saber o tempo máximo em que podemos atrasar o tratamento dessas interrupções. E, em seguida, verifique se todos os casos em que as interrupções estão desabilitadas serão concluídos em menos tempo.

Às vezes, queremos desconsiderar interrupções. A melhor maneira pode ser bloquear a interrupção no nível do hardware. Geralmente, existe um controlador de interrupção ou similar, onde podemos dizer quais entradas devem gerar interrupções.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.