Existe uma boa maneira de implementar a comunicação entre um ISR e o restante do programa para um sistema incorporado que evite variáveis globais?
Parece que o padrão geral é ter uma variável global que seja compartilhada entre o ISR e o restante do programa e usada como sinalizador, mas esse uso de variáveis globais vai contra mim. Incluí um exemplo simples usando ISRs do estilo avr-libc:
volatile uint8_t flag;
int main() {
...
if (flag == 1) {
...
}
...
}
ISR(...) {
...
flag = 1;
...
}
Não consigo desviar o que é essencialmente uma questão de escopo; Quaisquer variáveis acessíveis pelo ISR e pelo restante do programa devem ser inerentemente globais, com certeza? Apesar disso, muitas vezes eu vi pessoas dizerem coisas como "variáveis globais são uma maneira de implementar a comunicação entre ISRs e o resto do programa" (grifo meu), o que parece implicar que existem outros métodos; se existem outros métodos, quais são eles?
Respostas:
Existe uma maneira padrão de fato de fazer isso (assumindo a programação C):
extern
palavras - chave ou simplesmente por engano.static
. Essa variável não é global, mas restrita ao arquivo em que é declarada.volatile
. Nota: isso não fornece acesso atômico ou resolve a reentrada!fonte
inline
esteja se tornando obsoleto, os compiladores ficam cada vez mais inteligentes na otimização do código. Eu diria que se preocupar com a sobrecarga é "otimização antecipada" - na maioria dos casos, a sobrecarga não importa, se é que está presente no código da máquina.Este é o verdadeiro problema. Deixe isso para trás.
Agora, antes que os cotovelos se queixem imediatamente de como isso é impuro, deixe-me qualificar um pouco. Certamente há perigo em usar variáveis globais em excesso. Mas eles também podem aumentar a eficiência, o que às vezes é importante em pequenos sistemas com recursos limitados.
A chave é pensar em quando você pode usá-los razoavelmente e é improvável que se metam em problemas, em comparação com um bug que está esperando para acontecer. Sempre há trocas. Embora geralmente evitar variáveis globais para a comunicação entre código de interrupção e primeiro plano seja uma diretriz undertandable, levá-lo, como a maioria das outras diretrizes, a um extremo religioso é contraproducente.
Alguns exemplos em que às vezes uso variáveis globais para passar informações entre o código de interrupção e o primeiro plano são:
Garanto que os ticks de 1 ms, 10 ms e 100 ms tenham um tamanho de palavra que possa ser lido em uma única operação atômica. Se estiver usando um idioma de alto nível, informe ao compilador que essas variáveis podem mudar de forma assíncrona. Em C, você as declara voláteis externamente , por exemplo. É claro que isso é algo incluído em um arquivo de inclusão enlatada, para que você não precise se lembrar disso em todos os projetos.
Às vezes, faço com que o contador de 1 s marque o tempo total decorrido, de modo que tenha 32 bits de largura. Isso não pode ser lido em uma única operação atômica em muitos dos pequenos micro que eu uso, de modo que não é globalizado. Em vez disso, é fornecida uma rotina que lê o valor de várias palavras, lida com possíveis atualizações entre leituras e retorna o resultado.
É claro que poderia haver rotinas para obter contadores de 1 ms, 10 ms, etc. menores também. No entanto, isso realmente faz muito pouco para você, adiciona muitas instruções no lugar da leitura de uma única palavra e usa outro local da pilha de chamadas.
Qual é a desvantagem? Suponho que alguém possa cometer um erro de digitação que grave acidentalmente em um dos contadores, o que poderia atrapalhar outro momento no sistema. Escrever deliberadamente em um contador não faria sentido, portanto esse tipo de bug precisaria ser algo não intencional como um erro de digitação. Parece muito improvável. Não me lembro disso ter acontecido em mais de 100 pequenos projetos de microcontroladores.
Por exemplo, o A / D pode estar lendo a saída de 0 a 3 V de um divisor de tensão para medir a alimentação de 24 V. As muitas leituras são executadas através de alguma filtragem e, em seguida, redimensionadas para que o valor final seja em milivolts. Se o fornecimento estiver em 24,015 V, o valor final será 24015.
O restante do sistema vê apenas um valor atualizado ao vivo, indicando a tensão de alimentação. Ele não sabe nem precisa se preocupar quando exatamente isso é atualizado, especialmente porque é atualizado com muito mais frequência do que o tempo de estabilização do filtro passa-baixo.
Novamente, uma rotina de interface pode ser usada, mas você obtém muito pouco benefício disso. Apenas usar a variável global sempre que você precisar da tensão da fonte de alimentação é muito mais simples. Lembre-se de que a simplicidade não é apenas para a máquina, mas que mais simples também significa menos chances de erro humano.
fonte
extern int ticks10ms
porinline int getTicks10ms()
não fará absolutamente nenhuma diferença na montagem compilada, enquanto, por outro lado, dificultará a alteração acidental de seu valor em outras partes do programa e também permitirá que você uma maneira de "ligar" a esta chamada (por exemplo, para zombar do tempo durante o teste de unidade, para registrar o acesso a essa variável ou qualquer outra coisa). Mesmo se você argumentar que a chance de um programador de san alterar essa variável para zero, não há custo de um getter em linha.Qualquer interrupção específica será um recurso global. Às vezes, no entanto, pode ser útil ter várias interrupções compartilhando o mesmo código. Por exemplo, um sistema pode ter vários UARTs, todos os quais devem usar lógica de envio / recebimento semelhante.
Uma boa abordagem para lidar com isso é colocar as coisas usadas pelo manipulador de interrupções ou ponteiros para eles em um objeto de estrutura e fazer com que os manipuladores de interrupção de hardware reais sejam algo como:
Os objetos
uart1_info
,uart2_info
etc. seria variáveis globais, mas eles seriam os única variáveis globais utilizadas pelos manipuladores de interrupção. Tudo o mais que os manipuladores vão tocar seria tratado dentro deles.Observe que qualquer coisa acessada pelo manipulador de interrupções e pelo código da linha principal deve ser qualificada
volatile
. Pode ser mais simples declarar comovolatile
tudo o que será usado pelo manipulador de interrupções, mas se o desempenho for importante, pode-se escrever código que copia informações para valores temporários, opera sobre eles e depois as grava novamente. Por exemplo, em vez de escrever:escrever:
A primeira abordagem pode ser mais fácil de ler e entender, mas será menos eficiente que a segunda. Se isso é uma preocupação, depende do aplicativo.
fonte
Aqui estão três idéias:
Declare a variável do sinalizador como estática para limitar o escopo a um único arquivo.
Torne a variável do sinalizador privada e use as funções getter e setter para acessar o valor do sinalizador.
Use um objeto de sinalização, como um semáforo, em vez de uma variável de flag. O ISR configuraria / postaria o semáforo.
fonte
Uma interrupção (ou seja, o vetor que aponta para o seu manipulador) é um recurso global. Portanto, mesmo se você usar alguma variável na pilha ou na pilha:
ou código orientado a objeto com uma função 'virtual':
… A primeira etapa deve envolver uma variável global real (ou pelo menos estática) para alcançar esses outros dados.
Todos esses mecanismos adicionam uma indireção, portanto isso geralmente não é feito se você deseja extrair o último ciclo do manipulador de interrupções.
fonte
Estou codificando para o Cortex M0 / M4 no momento e a abordagem que estamos usando no C ++ (não há tag C ++, portanto, essa resposta pode estar fora do tópico) é a seguinte:
Usamos uma classe
CInterruptVectorTable
que contém todas as rotinas de serviço de interrupção que são armazenadas no vetor de interrupção real do controlador:A classe
CInterruptVectorTable
implementa uma abstração dos vetores de interrupção, para que você possa vincular diferentes funções aos vetores de interrupção durante o tempo de execução.A interface dessa classe fica assim:
Você precisa criar as funções que são armazenadas na tabela de vetores,
static
porque o controlador não pode fornecer umthis
ponteiro, pois a tabela de vetores não é um objeto. Então, para contornar esse problema, temos opThis
ponteiro estático dentro doCInterruptVectorTable
. Ao inserir uma das funções de interrupção estática, ele pode acessar opThis
ponteiro para obter acesso aos membros do único objeto deCInterruptVectorTable
.Agora no programa, você pode usar o
SetIsrCallbackfunction
para fornecer um ponteiro de função para umastatic
função que deve ser chamada quando ocorrer uma interrupção. Os ponteiros são armazenados noInterruptVectorTable_t virtualVectorTable
.E a implementação de uma função de interrupção é assim:
Portanto, isso chamará um
static
método de outra classe (que pode serprivate
), que poderá conter outrostatic
this
-pointer para obter acesso às variáveis-membro desse objeto (apenas uma).Eu acho que você pode criar e interagir como
IInterruptHandler
e armazenar ponteiros para objetos, para não precisar dostatic
this
ponteiro em todas essas classes. (talvez tentemos isso na próxima iteração da nossa arquitetura)A outra abordagem funciona bem para nós, já que os únicos objetos permitidos para implementar um manipulador de interrupções são aqueles dentro da camada de abstração de hardware, e geralmente temos apenas um objeto para cada bloco de hardware, portanto, é bom trabalhar com
static
this
-pointers. E a camada de abstração de hardware fornece outra abstração para interrupções, chamadaICallback
que é então implementada na camada de dispositivo acima do hardware.Você acessa dados globais? Claro que sim, mas você pode
this
privar a maioria dos dados globais necessários, como as funções -pointers e as interrupções.Não é à prova de balas, e acrescenta sobrecarga. Você lutará para implementar uma pilha IO-Link usando essa abordagem. Mas se você não é extremamente rigoroso com os tempos, isso funciona muito bem para obter uma abstração flexível de interrupções e comunicação em módulos sem usar variáveis globais acessíveis em qualquer lugar.
fonte