Ao olhar para esboços que outras pessoas escreveram, ocasionalmente encontro um código parecido com o seguinte:
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 34286;
TCCR1B |= (1 << CS12);
TIMSK1 |= (1 << TOIE1);
Tudo o que sei é que isso tem algo a ver com o tempo / temporizadores (eu acho). Como posso decifrar - e criar - códigos como este? Quais são TCCR1A
, TCCR1B
, TCNT1
, CS12
, TIMSK1
, e TOIE1
?
timers
programming
O cara com o chapéu
fonte
fonte
Respostas:
Isso não é estranho. É como o código MCU normal realmente se parece.
O que você tem aqui é um exemplo do conceito de periféricos mapeados na memória . Basicamente, o hardware do MCU possui locais especiais no espaço de endereço SRAM do MCU atribuído a ele. Se você escreve nesses endereços, os bits do byte gravados no endereço n controlam o comportamento do m periférico .
Basicamente, certos bancos de memória literalmente têm poucos fios que vão da célula SRAM para o hardware. Se você escrever um "1" nesse bit nesse byte, ele definirá essa célula SRAM para um nível lógico alto, que ativará uma parte do hardware.
Se você procurar nos cabeçalhos do MCU, existem grandes tabelas de mapeamentos de endereços de palavras-chave <->. É assim que coisas como
TCCR1B
etc ... são resolvidas em tempo de compilação.Esse mecanismo de mapeamento de memória é extremamente amplamente usado em MCUs. O ATmega MCU do arduino o usa, assim como as séries PIC, ARM, MSP430, STM32 e STM8 MCU, bem como muitas MCUs com as quais não estou familiarizado imediatamente.
O código do Arduino é estranho, com funções que acessam os registros de controle do MCU indiretamente. Embora isso pareça um pouco "mais agradável", também é muito mais lento e usa muito mais espaço do programa.
As constantes misteriosas são todas descritas detalhadamente na folha de dados do ATmega328P , que você realmente deve ler se estiver interessado em fazer algo mais do que alternar pinos localmente em um arduino.
Selecione trechos da folha de dados vinculada acima:
Assim, por exemplo,
TIMSK1 |= (1 << TOIE1);
define o bitTOIE1
noTIMSK1
. Isso é obtido deslocando o binário 1 (0b00000001
) para a esquerda pelosTOIE1
bits,TOIE1
sendo definido em um arquivo de cabeçalho como 0. Esse valor é OR bit a bit no valor atual deTIMSK1
, que efetivamente define esse bit um pouco alto.Olhando para a documentação do bit 0 de
TIMSK1
, podemos ver que é descrito comoTodas as outras linhas devem ser interpretadas da mesma maneira.
Algumas notas:
Você também pode ver coisas como
TIMSK1 |= _BV(TOIE1);
._BV()
é uma macro comumente usada originalmente na implementação libc do AVR ._BV(TOIE1)
é funcionalmente idêntico a(1 << TOIE1)
, com o benefício de melhor legibilidade.Além disso, você também pode ver linhas como:
TIMSK1 &= ~(1 << TOIE1);
ouTIMSK1 &= ~_BV(TOIE1);
. Isso tem a função oposta deTIMSK1 |= _BV(TOIE1);
, na medida em que desativa o bitTOIE1
inTIMSK1
. Isso é obtido usando a máscara de bit produzida por_BV(TOIE1)
, executando uma operação NOT bit a bit nela (~
) e depoisTIMSK1
ANDing por esse valor NOTed (que é 0b11111110).Observe que em todos esses casos, o valor de coisas como
(1 << TOIE1)
ou_BV(TOIE1)
são totalmente resolvidas no tempo de compilação , portanto, elas se reduzem funcionalmente a uma constante simples e, portanto, não levam tempo de execução para computar no tempo de execução.O código corretamente escrito geralmente possui comentários alinhados com o código que detalha o que os registros que estão sendo atribuídos devem fazer. Aqui está uma rotina bastante simples de SPI que escrevi recentemente:
PORTC
é o registro que controla o valor dos pinos de saída noPORTC
ATmega328P.PINC
é o registro no qual os valores de entrada dePORTC
estão disponíveis. Fundamentalmente, coisas assim são o que acontece internamente quando você usa as funçõesdigitalWrite
oudigitalRead
. No entanto, há uma operação de pesquisa que converte os "números de pinos" do arduino em números de pinos de hardware reais, o que leva a algo em torno de 50 ciclos de clock. Como você provavelmente pode adivinhar, se você está tentando ir rápido, desperdiçar 50 ciclos de relógio em uma operação que deve exigir apenas 1 é um pouco ridículo.A função acima provavelmente leva algum lugar no domínio de 100-200 ciclos de clock para transferir 8 bits. Isso implica 24 gravações de pinos e 8 leituras. Isso é muitas, muitas vezes mais rápido que o uso das
digital{stuff}
funções.fonte
TCCR1A
é timer / contador 1 controle de registro ATCCR1B
é o temporizador / contador 1 registo de controlo BTCNT1
é o valor do contador / contador 1CS12
é o terceiro bit de seleção do relógio para o temporizador / contador 1TIMSK1
é o registro da máscara de interrupção do contador / contador 1TOIE1
é a interrupção de estouro do timer / contador 1 habilitadaPortanto, o código habilita o timer / contador 1 em 62,5 kHz e define o valor para 34286. Em seguida, habilita a interrupção de estouro para que, quando atingir 65535, ative a função de interrupção, provavelmente rotulada como
ISR(timer0_overflow_vect)
fonte
CS12 tem um valor 2, pois representa o bit 2 do registro TCCR1B.
(1 << CS12) pega o valor 1 (0b00000001) e o desloca para a esquerda 2 vezes para obter (0b00000100). A ordem das operações determina que as coisas em () ocorram primeiro, portanto, isso é feito antes da avaliação "| =".
(1 << CS10) pega o valor 1 (0b00000001) e o desloca para a esquerda 0 vezes para obter (0b00000001). A ordem das operações determina que as coisas em () ocorram primeiro, portanto, isso é feito antes da avaliação "| =".
Então agora temos TCCR1B | = 0b00000101, que é o mesmo que TCCR1B = TCCR1B | 0b00000101.
Desde "|" é "OR", todos os bits que não sejam CS12 no TCCR1B não são afetados.
fonte