Pergunta

Esta questão é sobre a programação de microcontroladores pequenos sem um sistema operacional. Em particular, eu estou interessado em PICs no momento, mas a questão é geral.

Eu já vi várias vezes o seguinte padrão para o tempo de manutenção:

código de interrupção Timer (dizem que o temporizador dispara a cada segundo):

...
if (sec_counter > 0)
  sec_counter--;
...

código Mainline (sem interrupção):

sec_counter = 500; // 500 seconds

while (sec_counter)
{
  // .. do stuff
}

O código de linha principal pode repetir, definir o contador para vários valores (não apenas segundos) e assim por diante.

Parece-me que há uma condição de corrida aqui quando a atribuição para sec_counter no código linha principal não é atômica. Por exemplo, em PIC18 a atribuição é traduzido a 4 demonstrações ASM (carregamento de cada byte no momento e escolhendo os bytes desde o banco de memória que antes). Se o código de interrupção vem em meio a isso, o valor final pode ser corrompido.

Curiosamente, se o valor atribuído é inferior a 256, a atribuição é atômica, por isso não há problema.

Am I certo sobre este problema? Que padrões você usar para implementar tal comportamento corretamente? Vejo várias opções:

  • interrupções Desactivar antes de cada tarefa para sec_counter e permitem depois - isso não é bonito
  • Não use uma interrupção, mas um temporizador separado que é iniciado e, em seguida, recolhido. Este é limpo, mas usa até um temporizador inteiro (no caso anterior, o temporizador de disparo de 1 seg pode ser usado para outros fins também).

Quaisquer outras ideias?

Foi útil?

Solução

A arquitetura PIC é tão atômica quanto ele ganha. Ele garante que todas as operações de leitura-modificar-escrever para um arquivo de memória são 'atômica'. Embora leva 4-relógios para executar toda a read-modify-write, todos os 4-relógios são consumidos em uma única instrução ea próxima instrução usa o próximo ciclo de 4-relógio. É a maneira que o gasoduto funciona. Em 8-relógios, duas instruções estão na calha.

Se o valor for maior do que 8 bits, torna-se um problema como o PIC é uma máquina de 8 bits e operandos maiores são tratadas em várias instruções. Isso irá introduzir questões atômicas.

Outras dicas

Escrever o valor, em seguida, verificar se é o valor necessário parece ser a alternativa mais simples.

do {
 sec_counter = value;
} while (sec_counter != value);

BTW você deve fazer a variável volátil se estiver usando C.

Se você precisa ler o valor, então você pode lê-lo duas vezes.

do {
    value = sec_counter;
} while (value != sec_counter);

Você definitivamente precisa desativar a interrupção antes de definir o contador. Feio que seja, é necessário. É uma boa prática para SEMPRE desativar a interrupção antes de configurar registros de hardware ou variáveis ??de software que afetam o método ISR. Se você estiver escrevendo em C, você deve considerar todas as operações como não atômica. Se você achar que você tem que olhar para os gerados montagem muitas vezes, então ele pode ser melhor abandonar C e programa na montagem. Na minha experiência, isso raramente é o caso.

Em relação à questão discutida, este é o que eu sugiro:

ISR:
if (countDownFlag)
{
   sec_counter--;
}

e definindo o contador:

// make sure the countdown isn't running
sec_counter = 500;
countDownFlag = true;

...
// Countdown finished
countDownFlag = false;

Você precisa de uma variável extra e é melhor para embrulhar tudo em uma função:

void startCountDown(int startValue)
{
    sec_counter = 500;
    countDownFlag = true;
}

Desta forma, você abstrair o método de arranque (e se esconder, se necessário feiúra). Por exemplo, você pode facilmente alterá-lo para iniciar um temporizador hardware sem afetar os chamadores do método.

Porque acessos à variável sec_counter não são atômicas, não há realmente nenhuma maneira de evitar a desativação interrupções antes de acessar esta variável em seu código linha principal e restaurar estado de interrupção após o acesso, se você quer um comportamento determinista. Este seria provavelmente uma escolha melhor do que dedicar um temporizador HW para esta tarefa (a menos que você tem um excedente de temporizadores, caso em que você pode também usar um).

Se você baixar TCP livre da Microchip / IP Stack existem rotinas de lá que usam um estouro de temporizador para controlar o tempo decorrido. Especificamente "tick.c" e "tick.h". Basta copiar esses arquivos para seu projeto.

Dentro desses arquivos você pode ver como eles fazem isso.

Não é tão curioso sobre os menos de 256 movimentos sendo atômica - movendo um valor de 8 bit é um código de operação de modo que é tão atômica como você começa.

A melhor solução em tais microcontrolador um como o PIC é desativar as interrupções antes de alterar o valor do temporizador. Você pode até mesmo verificar o valor do flag de interrupção quando você alterar a variável no circuito principal e lidar com isso se você quiser. Torná-lo uma função que altera o valor da variável e você ainda pode chamá-lo a partir da ISR também.

Bem, o que faz o código visual montagem comparação como?

Levado para conta que ele conta para baixo, e que é apenas um zero comparar, ele deve ser seguro se ele primeiro verifica o MSB, então a LSB. Não poderia haver corrupção, mas isso realmente não importa se se trata no meio entre 0x100 e 0xff eo valor corrompido comparar é 0x1ff.

A maneira como você está usando seu temporizador agora, não vai contar segundos inteiros de qualquer maneira, porque você pode alterá-lo no meio de um ciclo. Então, se você não se preocupam com isso. A melhor maneira, na minha opinião, seria para ler o valor e, em seguida, basta comparar a diferença. É preciso um par de OPs mais, mas não tem problemas de multi-threading. (Desde o temporizador tem prioridade)

Se você é mais rigoroso sobre o valor tempo, gostaria de desativar automaticamente o temporizador, uma vez que faz a contagem regressiva para 0, e limpar o contador interno do temporizador e ativar uma vez que você precisar dele.

Mover a parte do código que estaria no main () para uma função adequada, e tê-lo condicionalmente chamado pelo ISR.

Além disso, para evitar qualquer tipo de atraso ou carrapatos faltando, escolha esta ISR temporizador para ser uma interrupção de alta-prio (o PIC18 tem dois níveis).

Uma abordagem é ter uma interrupção manter uma variável byte, e tem outra coisa que é chamado pelo menos uma vez a cada 256 vezes que o contador é atingido; fazer algo como:

// ub==unsigned char; ui==unsigned int; ul==unsigned long
ub now_ctr; // This one is hit by the interrupt
ub prev_ctr;
ul big_ctr;

void poll_counter(void)
{
  ub delta_ctr;

  delta_ctr = (ub)(now_ctr-prev_ctr);
  big_ctr += delta_ctr;
  prev_ctr += delta_ctr;
}

Uma leve variação, se você não se importa forçando balcão da interrupção para ficar em sincronia com o LSB do seu grande balcão:

ul big_ctr;
void poll_counter(void)
{
  big_ctr += (ub)(now_ctr - big_ctr);
}

Ninguém abordou a questão da leitura registros de hardware de vários bytes (por exemplo, um timer. O temporizador pode rolar e incrementar o seu segundo byte, enquanto você está lendo.

dizer que é 0x0001ffff e você lê-lo. Você pode obter 0x0010ffff, ou 0x00010000.

A 16 bits registo periférica é volátil em seu código.

Para qualquer voláteis "variáveis", eu uso a técnica de leitura dupla.

do {
       t = timer;
 } while (t != timer);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top