Seções críticas em Cortex-M3

10

Estou pensando um pouco sobre a implementação de seções de código crítico em um Cortex-M3, onde exceções não são permitidas devido a restrições de tempo ou problemas de simultaneidade.

No meu caso, estou executando um LPC1758 e tenho um transceptor TI CC2500 a bordo. O CC2500 possui pinos que podem ser usados ​​como linhas de interrupção para dados no buffer RX e espaço livre no buffer TX.

Como exemplo, eu quero ter um buffer TX na SRAM do meu MCU e quando houver espaço livre no buffer TX do transceptor, quero gravar esses dados lá. Mas a rotina que coloca os dados no buffer SRAM obviamente não pode ser interrompida pela interrupção de espaço livre em TX. Então, o que eu quero fazer é desativar temporariamente as interrupções ao executar este procedimento de preenchimento desse buffer, mas executar qualquer interrupção durante esse procedimento após a conclusão.

Como isso é feito melhor no Cortex-M3?

Emil Eriksson
fonte

Respostas:

11

O Cortex M3 suporta um par útil de operações (comum em muitas outras máquinas) chamado "Load-Exclusive" (LDREX) e "Store-Exclusive" (STREX). Conceitualmente, a operação LDREX executa uma carga, também define algum hardware especial para observar se o local que foi carregado pode ser gravado por outra coisa. A execução de um STREX no endereço usado pelo último LDREX fará com que esse endereço seja gravado apenas se nada mais o tiver escrito primeiro . A instrução STREX carregará um registro com 0 se a loja ocorreu ou 1 se foi abortada.

Observe que o STREX geralmente é pessimista. Existem várias situações em que pode decidir não executar a loja, mesmo que o local em questão não tenha sido realmente tocado. Por exemplo, uma interrupção entre um LDREX e STREX fará com que o STREX assuma que o local que está sendo observado pode ter sido atingido. Por esse motivo, geralmente é uma boa ideia minimizar a quantidade de código entre o LDREX e o STREX. Por exemplo, considere algo como o seguinte:

inline void safe_increment (uint32_t * addr)
{
  uint32_t novo_valor;
  Faz
  {
    new_value = __ldrex (endereço) + 1;
  } while (__ strex (novo_valor, endereço));
}

que compila para algo como:

; Suponha que R0 mantenha o endereço em questão; r1 lixeira
lp:
  ldrex r1, [r0]
  adicione r1, r1, # 1
  strex r1, r1, [r0]
  cmp r1, # 0; Teste se diferente de zero
  bne lp
  .. código continua

Na grande maioria das vezes que o código é executado, nada acontece entre o LDREX e o STREX para "perturbá-los"; portanto, o STREX terá êxito sem mais delongas. Se, no entanto, ocorrer uma interrupção imediatamente após a instrução LDREX ou ADD, o STREX não executará o armazenamento, mas o código retornará para ler o valor (possivelmente atualizado) de [r0] e calcular um novo valor incrementado com base nisso.

O uso do LDREX / STREX para formar operações como safe_increment possibilita não apenas gerenciar seções críticas, mas também em muitos casos, para evitar a necessidade delas.

supercat
fonte
Portanto, não há como "bloquear" as interrupções, para que possam ser exibidas novamente quando forem desbloqueadas? Sei que essa é provavelmente uma solução deselegante, mesmo que possível, mas só quero aprender mais sobre o manuseio de interrupção do ARM.
Emil Eriksson
3
É possível desativar interrupções, e no Cortex-M0 geralmente não há alternativa prática para isso. Considero que a abordagem LDREX / STREX é mais limpa do que desabilitar interrupções, embora, em muitos casos, isso realmente não importe (acho que habilitar e desabilitar acabam sendo um ciclo cada, e desabilitar interrupções por cinco ciclos provavelmente não é grande coisa) . Observe que uma abordagem ldrex / strex funcionará se o código for migrado para uma CPU com vários núcleos, enquanto uma abordagem que desativa interrupções não. Além disso, alguns códigos de execução do RTOS com permissões reduzidas que não têm permissão para desativar interrupções.
Supercat
Provavelmente, acabarei indo com o FreeRTOS de qualquer maneira, então não vou fazer isso sozinho, mas gostaria de aprender de qualquer maneira. Que método de desativar interrupções devo usar para bloquear as interrupções, conforme descrito em vez de descartar as interrupções que ocorrem durante o procedimento? Como eu faria isso se eu quiser descartá-los?
Emil Eriksson
A única resposta acima não pode ser confiável porque o código associado está ausente entre parênteses: while(STREXW(new_value, addr); como podemos acreditar que o que você diz está correto se o seu código nem sequer é compilado?
@ Tim: Desculpe, minha digitação não é perfeita; Não tenho o código real que escrevi à mão para comparação; portanto, não me lembro se o sistema que eu estava usando usava STREXW ou __STREXW, mas uma referência ao compilador lista __strex como intrínseco (ao contrário do STREXW, que é limitado a STREX de 32 bits, o uso intrínseco de __strex gera um STREXB, STREXH ou STREX, dependendo do tamanho do ponteiro fornecido)
supercat
4

Parece que você precisa de alguns buffers circulares ou FIFOs no seu software MCU. Ao rastrear dois índices ou ponteiros na matriz para leitura e gravação, é possível que o primeiro plano e o segundo plano acessem o mesmo buffer sem interferência.

O código de primeiro plano pode ser gravado gratuitamente no buffer circular a qualquer momento. Ele insere dados no ponteiro de gravação e depois incrementa o ponteiro de gravação.

O código de plano de fundo (tratamento de interrupção) consome dados do ponteiro de leitura e incrementa o ponteiro de leitura.

Quando os ponteiros de leitura e gravação são iguais, o buffer fica vazio e o processo em segundo plano não envia dados. Quando o buffer está cheio, o processo em primeiro plano se recusa a gravar mais (ou pode sobrescrever dados antigos, dependendo de suas necessidades).

O uso de buffers circulares para dissociar leitores e gravadores deve remover a necessidade de desativar interrupções.

Toby Jaffey
fonte
Sim, obviamente vou usar buffers circulares, mas incremento e decremento não são operações atômicas.
Emil Eriksson
3
@Emil: Eles não precisam ser. Para um buffer circular clássico, com dois ponteiros e um slot "inutilizável", tudo o que é necessário é que as gravações na memória sejam atômicas e aplicadas em ordem. O leitor é dono de um ponteiro, o escritor é dono do outro e, embora ambos possam ler qualquer um dos ponteiros, apenas o proprietário do ponteiro escreve seu ponteiro. Nesse ponto, tudo o que você precisa são gravações atômicas em ordem.
John R. Strohm
2

Não me lembro da localização exata, mas nas bibliotecas que vêm do ARM (não TI, ARM, ele deve estar no CMSIS ou algo assim, eu uso o ST, mas lembro de ler em algum lugar que esse arquivo veio do ARM, então você também deve tê-lo ) existe uma opção de desativação de interrupção global. É uma chamada de função. (Não estou no trabalho, mas amanhã procurarei a função exata). Gostaria de encerrar com um bom nome em seu sistema e desativar as interrupções, fazer suas coisas e ativar novamente. Dito isto, a melhor opção seria implementar um semáforo ou uma estrutura de filas em vez de desativar a interrupção global.

Ktc
fonte