Estou desenvolvendo um pequeno analisador lógico com 7 entradas. Meu dispositivo de destino é um ATmega168
com uma taxa de clock de 20 MHz. Para detectar alterações lógicas, uso interrupções de alteração de pinos. Agora estou tentando descobrir a menor taxa de amostragem possível de detectar essas alterações de pinos. Eu determinei um valor mínimo de 5,6 µs (178,5 kHz). Não é possível capturar todos os sinais abaixo dessa taxa.
Meu código está escrito em C (avr-gcc). Minha rotina se parece com:
ISR()
{
pinc = PINC; // char
timestamp_ll = TCNT1L; // char
timestamp_lh = TCNT1H; // char
timestamp_h = timerh; // 2 byte integer
stack_counter++;
}
Minha alteração de sinal capturada está localizada em pinc
. Para localizá-lo, tenho um valor de carimbo de data / hora de 4 bytes.
Na folha de dados, li que a rotina de serviço de interrupção leva 5 relógios para saltar e 5 para retornar ao procedimento principal. Estou assumindo que cada comando no meu ISR()
está levando 1 relógio para ser executado; Então, em suma, deve haver uma sobrecarga de 5 + 5 + 5 = 15
relógios. A duração de um relógio deve estar de acordo com a taxa de clock de 20MHz 1/20000000 = 0.00000005 = 50 ns
. A sobrecarga total, em segundos deveria ser, em seguida,: 15 * 50 ns = 750 ns = 0.75 µs
. Agora não entendo por que não consigo capturar nada abaixo de 5,6 µs. Alguém pode explicar o que está acontecendo?
Respostas:
Existem alguns problemas:
AND
é uma instrução de um relógio,MUL
(multiplica) leva dois relógios, enquantoLPM
(carregar memória do programa) é três eCALL
é 4. Portanto, com relação à execução da instrução, isso realmente depende da instrução.RETI
instruções, o compilador adiciona todos os tipos de outros códigos, o que também leva tempo. Por exemplo, você pode precisar de variáveis locais que são criadas na pilha e devem ser salvas etc. A melhor coisa a fazer para ver o que realmente está acontecendo é observar a desmontagem.Se
x
for o tempo necessário para reparar sua interrupção, o sinal B nunca será capturado.Se pegarmos seu código ISR, colá-lo em uma rotina ISR (eu usei
ISR(PCINT0_vect)
), declarar todas as variáveisvolatile
e compilar para ATmega168P, o código desmontado será o seguinte (consulte a resposta da @ jipple para obter mais informações) antes de chegarmos ao código que "faz alguma coisa" ; em outras palavras, o prólogo do seu ISR é o seguinte:então,
PUSH
x 5,in
x 1,clr
x 1. Não é tão ruim quanto os vars de 32 bits do jipple, mas ainda não é nada.Parte disso é necessária (expanda a discussão nos comentários). Obviamente, como a rotina ISR pode ocorrer a qualquer momento, ela deve preservar os registros que usa, a menos que você saiba que nenhum código em que uma interrupção pode ocorrer usa o mesmo registro que sua rotina de interrupção. Por exemplo, a seguinte linha no ISR desmontado:
Existe porque tudo passa
r24
: o seupinc
é carregado lá antes de entrar na memória, etc. Então você deve ter isso primeiro.__SREG__
é carregador0
e pressionado: se isso ocorrerr24
, você poderá salvar umPUSH
Algumas soluções possíveis:
ISR_NAKED
, o gcc não gera código de prólogo / epílogo, e você é responsável por salvar os registros que seu código modifica, bem como chamarreti
(retornar de uma interrupção). Infelizmente, não há nenhuma maneira de usar registros em avr-gcc C diretamente (obviamente você pode na montagem), no entanto, o que você pode fazer é variáveis de ligação aos registos específicos com osregister
+asm
palavras-chave, como este:register uint8_t counter asm("r3");
. Se você fizer isso, para o ISR, saberá o que está registrando no ISR. O problema, então, é que não há como gerarpush
epop
salvar os registros usados sem montagem embutida (ver ponto 1). Para garantir a necessidade de salvar menos registros, você também pode vincular todas as variáveis não ISR a registros específicos, no entanto, você não se depara com um problema que o gcc usa registros para embaralhar dados para e da memória. Isso significa que, a menos que você observe a desmontagem, não saberá o que o seu código principal usa. Portanto, se você estiver pensandoISR_NAKED
, é melhor escrever o ISR na montagem.fonte
Há muitos registros PUSH'ing e POP'ing a serem empilhados antes do início do ISR real, ou seja, os cinco ciclos de relógio mencionados. Dê uma olhada na desmontagem do código gerado.
Dependendo da cadeia de ferramentas usada, a montagem da lista é feita de várias maneiras. Eu trabalho na linha de comando do Linux e este é o comando que eu uso (ele requer o arquivo .elf como entrada):
Dê uma olhada em um sniplet de código que usei recentemente para um ATtiny. É assim que o código C se parece:
E este é o código de montagem gerado para ele:
Para ser sincero, minha rotina C usa mais algumas variáveis que causam todos esses problemas, mas você entendeu.
O carregamento de uma variável de 32 bits se parece com o seguinte:
Aumentar uma variável de 32 bits em 1 se parece com isso:
Armazenar uma variável de 32 bits se parece com isso:
Então é claro que você precisa exibir os valores antigos depois de sair do ISR:
De acordo com o resumo de instruções na folha de dados, a maioria das instruções é de ciclo único, mas PUSH e POP são de ciclo duplo. Você entendeu a origem do atraso?
fonte
avr-objdump -C -d $(src).elf
!avr-objdump
exibidas, elas são explicadas brevemente na folha de dados em Resumo das instruções. Na minha opinião, é uma boa prática familiarizar-se com os mnemônicos, pois isso pode ajudar muito ao depurar seu código C.Makefile
: portanto, sempre que você cria seu projeto, ele é automaticamente desmontado, para que você não precise pensar sobre isso ou lembre-se de fazê-lo manualmente.