Se ligarmos à saída PWM um auto-falante é possível reproduzir sons. Conhecendo a frequência de cada uma das notas musicais e a duração destas é possível reproduzir uma música. Para repro- duzir o tempo com uma precisão melhor podemos utilizar o TIMER0 como unidade de tempo.
Conforme visto na seção 14, o PWM utilizado na placa consegue reproduzir as frequências audíveis a partir de 488,3Hz. Por isso escolhemos começar a escala musical a partir do C5 (Dó Tenor) que possui a frequência de 523 Hz. A segunda escala possui o dobro da frequência (uma oitava acima). Para reproduzir a ausência de som escolhemos a frequência de 125.000 Hz2, que é
inaudível. Isto simplifica o programa. Código 15.4: Reprodução de sons
1 #include "config.h" 2 #include "p18f4520.h" 3 #include "pwm.h" 4 #include "timer.h" 5 6 //frequência das 7 //notas musicais 8 #define C 523 9 #define CS 554 10 #define D 587 11 #define DS 622 12 #define E 659 13 #define F 698 14 #define FS 740 15 #define G 784 16 #define GS 830 17 #define A 880 18 #define AS 932 19 #define B 987 20 21 22 //segunda oitava 23 #define C2 C*2 24 #define C2S CS*2 25 #define D2 D*2 26 #define D2S DS*2 27 #define E2 E*2 28 #define F2 F*2 29 #define F2S FS*2 30 #define G2 G*2 31 #define G2S GS*2 32 #define A2 A*2
33 #define A2S AS*2
34 #define B2 B*2 35 36 #define C3 C2*2 37 38 //sem som 39 #define v 125000 40 //início do programa
41 void main(void)
42 {
43 unsigned char cont=0;
44 unsigned char pos=0;
45 //Imperial March (SW Episode V)
46 unsigned char tempo[] = {50, 10, 50, 10, ←֓
50, 10, 50, 5, 25, 5, 50, 5, 50, 5, ←֓ 25, 5, 50, 50, 50, 10, 50, 10, 50, ←֓ 10, 50, 5, 25, 5, 50, 5, 50, 5, 25, ←֓ 5, 50, 50, 100, 5, 25, 5, 25, 10, ←֓ 100, 5, 50, 5, 25, 2, 10, 2, 10, 2, ←֓ 100, 250};
47 unsigned int notas[] = {G, v, G, v, G, ←֓
v, E, v, B, v, G, v, E, v, B, v, G, ←֓ v, D2S, v, D2S, v, D2S, v, E2, v, ←֓ B, v, FS, v, E, v, B, v, G, v, G2S, ←֓ v, G, v, G, v, G2S, v, G2, v, F2S, ←֓ v, F2, v, E2, v, F2S, v}; 48 InicializaPWM(); 49 timerInit(); 50 SetaFreqPWM(notas[0]);
51 SetaPWM1(50); //garante duty−cycle de 50%
52 for(;;) 53 { 54 AguardaTimer(); 55 timerReset(10000); 56 cont ++; 57 if (cont >= tempo[pos]) 58 { 59 pos++; 60 SetaFreqPWM(notas[pos]); 61 SetaPWM1(50); 62 cont=0; 63 } 64 } 65 }
A seguir o exemplo de outra música codificada utilizando o sistema desenvolvido. Cada nota é representada por sua duração (vetortempo[]) e sua frequência (vetornotas[]). Esta informação pode ser obtida facilmente através da partitura da música.
66 //Super Mario theme
67 unsigned char tempo[] = {
68 15, 5, 15, 7, 30, 15, 30, 30, 30, 30, 69 30, 30, 15, 30, 15, 30, 15, 30, 30, 15, 70 30, 22, 15, 15, 30, 15, 30, 30, 15, 15, 71 30, 15, 30, 15, 30, 15, 30, 15, 30, 30, 72 15, 30, 22, 15, 15, 30, 15, 30, 30, 15, 73 15, 30, 30, 15, 15, 15, 15, 15, 15, 15, 74 15, 15, 15, 15, 15, 15, 15, 30, 15, 15, 75 5, 15, 15, 15, 15, 15, 15, 15, 15, 30, 76 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 77 15, 15, 15, 15, 30, 15, 30, 15, 30, 15};
78 unsigned int notas[] = {
79 E2, v, E2, v, E2, C2, E2, G2, v, G,
80 v, C2, v, G, v, E, v, A, B, AS,
81 A, G, E2, G2, A2, F2, G2, E2, C2, D2,
82 B, v, C2, v, G, v, E, v, A, B,
83 AS, A, G, E2, G2, A2, F2, G2, E2, C2,
84 D2, B, v, G2, F2S, F2, D2S, v, E2, v,
85 G2, A, C2, v, A, C2, D2, v, G2, F2S,
86 F2, D2S, v, E2, v, C3, v, C3, C3, v,
87 G2, F2S, F2, D2S, v, E2, v, GS, A, C2,
88 v, A, C2, D2, v, D2S, v, D2, v, C2};
O último elemento musical não apresentado é a intensidade da nota, ou seja, seu volume. Com os dispositivos apresentados até agora não é possível implementar mais essa característica de modo simples.
C
APÍTULO16
Interrupção
“When I go to a library and I see the librarian at her desk reading, I’m afraid to interrupt her, even though she sits there specifically so that she may be interrupted, even though being interrupted for reasons like this by people like me is her very job.”
Aaron Swartz
Até o momento todos os programas que foram desenvolvidos seguiam um fluxo sequencial sendo alterado apenas por chamadas de funções, estruturas de decisão ou loop. Um dos problemas de se utilizar este tipo de estrutura é que alguns periféricos possuem um tempo muito grande para realizarem sua função como o conversor AD por exemplo. Nesta situação o que fazemos é iniciar a conversão e ficar monitorando uma variável que indicava quando a conversão tinha terminado. Esta técnica é conhecida como pooling.
O problema de se realizar a leitura de algum periférico por pooling é que o processador perde tempo realizando operações desnecessárias checando a variável de controle. Uma alternativa é utilizar um sistema que, quando a operação desejada estivesse finalizada, nos avisasse para que pudéssemos tomar uma providência. Este procedimento é chamado de interrupção.
Alguns dispositivos possuem a possibilidade de operarem com interrupções. Quando a condição do dispositivo for satisfeita (fim da conversão para o AD, chegada de informação na serial, mudança no valor da variável na porta B) ele gera uma interrupção. A interrupção para o programa no ponto em que ele estiver, salva todos os dados atuais e vai para uma função pré-definida. Esta função realiza seu trabalho e assim que terminar volta o programa no mesmo ponto onde estava antes da interrupção.
Dos dispositivos estudados até agora os que geram interrupção são:
• Porta Serial: quando chega alguma informação em RCREG ou quando o buffer de transmissão TXREG estiver disponível.
• Conversor AD: quando o resultado da conversão estiver disponível para leitura. • Porta B: quando algum dos bits configurados como entrada altera seu valor. • Timer 0: quando acontece overflow em seu contador.
Para gerenciar a interrupção, deve-se criar uma rotina que irá verificar qual foi o hardware que ge- rou a interrupção e tomar as providências necessárias. A maneira de declarar que uma determinada função será a responsável pelo tratamento da interrupção depende do compilador.
Para o compilador SDCC basta que coloquemos a expressão “interrupt 1” após o nome da função.
1 void NomeDaFuncao(void) interrupt 1
2 {
3 //código ...
4 }
Para o compilador C18 da Microchip temos que gerar um código em assembler que indicará qual função será a responsável pela interrupção.
1 void NomeDaFuncao(void)
2 {
3 //código ...
4 }
5
6 //Indicar a posição no vetor de interrupções
7 #pragma code high_vector=0x08
8 void interrupt_at_high_vector(void)
9 {
10 _asm GOTO Interrupcao _endasm
11 }
12 #pragma code
13 #pragma interrupt NomeDaFuncao
A função que irá tratar da interrupção não retorna nem recebe nenhum valor.
Existe uma correlação entre o número que vem depois da expressão “interrupt” para o compila- dor SDCC e o número ao final da expressão “#pragma code high_vector” para o C18. Estes números representam a posição para a qual o microcontrolador vai quando acontece uma interrupção. Estas posições estão numa área conhecida como vetor de interrupções.
Para o microcontrolador PIC 18f4520 este vetor possui três posições importantes: 0x00(0), 0x08(1) e 0x18(2). O compilador C18 usa a posição física e o SDCC o número entre parênteses.
A posição 0 (0x00) representa o endereço que o microcontrolador busca quando este acaba de ser ligado. É a posição de reset. Geralmente saímos deste vetor e vamos direto para a função main().
As posições 1 e 2 (0x08,0x18) são reservadas para as interrupções de alta e baixa prioridade, respectivamente. É necessário que o programador escolha quais dispositivos são de alta e quais são de baixa prioridade. Existe ainda um modo de compatibilidade com os microcontroladores mais antigos no qual todos os periféricos são mapeados na primeira interrupção (0x08). Utilizaremos este modo por questão de facilidade.
Como todos os periféricos estão mapeados na mesma interrupção, a função deve ser capaz de diferenciar entre as diversas fontes de requisição. Uma maneira de se realizar esta verificação é através das flags de controle, ou seja, bits que indicam a situação de cada periférico.
O programa 16.1 apresenta uma função que trata de todas as fontes possíveis de interrupção para o PIC 18f4520.
Em geral não é necessário tratar todas as interrupções, apenas aquelas que influenciarão o sis- tema. O programa 16.2 apresenta um exemplo de uma função que trata as interrupções advindas da porta B, do timer 0, da serial e do AD.
Para que a função apresentada no programa 16.2 funcione corretamente devemos inicializar as interrupções de modo adequado, conforme apresentado no programa 16.3.
Código 16.1: Fontes de Interrupção
1 void Interrupcao(void) interrupt 1
2 {
3 // não é necessário utilizar todos os if's, apenas
4 // aqueles das interrupções desejadas
5 if (BitTst(PIR1,0)) { /∗código∗/ } //Flag de overflow do TIMER1
6 if (BitTst(PIR1,1)) { /∗código∗/ } //Flag de comparação do TIMER2 com PR2
7 if (BitTst(PIR1,2)) { /∗código∗/ } //Flag de comparação do CCP1
8 if (BitTst(PIR1,3)) { /∗código∗/ } //Flag de fim de operação na porta paralela
9 if (BitTst(PIR1,4)) { /∗código∗/ } //Flag de fim de transmissão da Serial
10 if (BitTst(PIR1,5)) { /∗código∗/ } //Flag de recepção da Serial
11 if (BitTst(PIR1,6)) { /∗código∗/ } //Flag de fim de conversão do AD
12 if (BitTst(PIR1,7)) { /∗código∗/ } //Flag de leitura/escrita da porta paralela
13 if (BitTst(PIR2,0)) { /∗código∗/ } //Flag de comparação do CCP2
14 if (BitTst(PIR2,1)) { /∗código∗/ } //Flag de overflow do TIMER3
15 if (BitTst(PIR2,2)) { /∗código∗/ } //Flag de condição de Tensão Alta/Baixa
16 if (BitTst(PIR2,3)) { /∗código∗/ } //Flag de detecção de colisão no barramento
17 if (BitTst(PIR2,4)) { /∗código∗/ } //Flag de fim escrita na memória flash
18 if (BitTst(PIR2,5)) { /∗código∗/ } //Flag de interrupção da USB
19 if (BitTst(PIR2,6)) { /∗código∗/ } //Flag de mudança na entrada de comparação
20 if (BitTst(PIR2,7)) { /∗código∗/ } //Flag de falha no oscilador
21 if (BitTst(INTCON,0)) { /∗código∗/ } //Flag de mudança na PORTA B
22 if (BitTst(INTCON,1)) { /∗código∗/ } //Flag de interrupção externa INT0
23 if (BitTst(INTCON,2)) { /∗código∗/ } //Flag de overflow no TIMER0
24 if (BitTst(INTCON3,0)) { /∗código∗/ } //Flag de interrupção externa INT1
25 if (BitTst(INTCON3,1)) { /∗código∗/ } //Flag de interrupção externa INT2
26 }
Código 16.2: Tratamento das interrupções
1 static unsigned int ADvalor;
2 static unsigned char Serial;
3 static unsigned int Tecla;
4 void Interrupcao(void) interrupt 1{
5 char i, j;
6 if (BitTst(PIR1,6)){ //AD : fim de conversão
7 ADvalor = ADRESH ; // lê o resultado
8 ADvalor <<= 8;
9 ADvalor += ADRESL;
10 BitClr(PIR1,6); //limpa a flag
11 }
12 if (BitTst(PIR1,5)){ //Serial: recepção
13 //BitClr(PIR1,5);
14 Serial = RCREG; //limpa sozinho quando lê
15 }
16 if (BitTst(INTCON,0)){ //PORTA B : mudou valor
17 for(i = 0; i < 4; i++){ 18 PORTB |= 0xFF; 19 BitClr(PORTB,(i)); 20 for(j=0;j<10;j++); 21 for(j = 0; j < 4; j++){ 22 if (!BitTst(PORTB,j+4)){ 23 BitSet(Tecla,(i*4)+j); 24 }else{ 25 BitClr(Tecla,(i*4)+j); 26 } 27 } 28 } 29 PORTB = 0x00; 30 BitClr(INTCON,0); 31 }
32 if (BitTst(INTCON,2)){ //TIMER0: Overflow
33 //tempo máximo de interrupção do timer 0
34 BitClr(INTCON,2); //limpa a flag
35 TMR0H = 0x00; //reinicia contador de tempo
36 TMR0L = 0x00; //reinicia contador de tempo
37 ADCON0 |= 0b00000010; //inicia conversão
38 }
Código 16.3: Inicialização do sistema com interrupções
1 void main(void){
2 unsigned int i, temp, teclanova=0;
3 TRISD = 0x00;
4 TRISB = 0xF0; //mantém os 4 últimos bits como entrada
5 PORTB = 0x00; //mantém ligadas as 4 colunas
6 7 serialInit(); 8 ssdInit(); 9 lcdInit(); 10 adcInit(); 11 timerInit(); 12
13 // Região de inicialização das interrupções
14 BitClr(RCON,7); // desabilita IPEN (modo de compatibilidade)
15 BitSet(PIE1,6); // liga a interrupção para o AD
16 BitSet(PIE1,5); // liga a interrupção para a recepção na serial
17 BitSet(INTCON,5); // liga a interrupção para o timer 0
18 BitSet(INTCON,3); // liga a interrupção para a porta B
19 BitSet(INTCON,7); // habilita todas as interrupções globais
20 BitSet(INTCON,6); // habilita todas as interrupções de periféricos
21 22 for(;;){ 23 temp = adcRead(); 24 ssdDigit((temp / 1000)%10,3); 25 ssdDigit((temp / 100 )%10,2); 26 ssdDigit((temp / 10 )%10,1); 27 ssdDigit((temp / 1 )%10,0); 28 ssdUpdate(); 29 if (teclanova != Tecla){ 30 teclanova = Tecla;
31 for(i=0;i<16;i++){
32 if (BitTst(Tecla,i)){ 33 serialSend(i+48); 34 } 35 } 36 } 37 for(i = 0; i < 1000; i++); 38 } 39 }
C
APÍTULO17
Watchdog
“Technology is a word that describes something that do- esn’t work yet.”
Douglas Adams
Por algum motivo o software pode travar em algum ponto, seja por um loop infinito ou por esperar a resposta de algum componente através de pooling de uma variável.
A primeira condição pode ser evitada através de um projeto cuidadoso de software aliado a uma boa validação. Já a segunda exige que os hardwares adjacentes funcionem corretamente. Se algum hardware apresenta uma falha e não envia a resposta que o microcontrolador está esperando, este último irá travar. Nestas situações é possível utilizar o watchdog.
O watchdog é um sistema que visa aumentar a segurança do projeto. Ele funciona como um temporizador que precisa constantemente ser reiniciado. Caso não seja reiniciado no tempo exigido, o watchdog reinicia o microcontrolador dando a possibilidade de sair de um loop infinito ou de um pooling sem resposta.
Para habilitar o watchdog é necessário alterar os registros de configuração, especificamente o CONFIG2H (0x300002). Outro método consiste em deixar o watchdog desligado no registro e ligá- lo através de software, como é apresentado no programa 17.1.
Notar o #define criado na primeira linha do programa 17.1. A expressão CLRWDT é o comando em assembler responsável por resetar o watchdog. As diretivas _asm e _endasm informam ao compilador que os comandos utilizados devem ser transcritos exatamente iguais para o arquivo assembler a ser gerado.
Se após ligar o watchdog não realizarmos a operação de reset dele, comentando ou excluindo a função CLRWTD(), o sistema irá travar tão logo o tempo associado ao watchdog tenha expirado pela primeira vez, reiniciando o sistema. Como apenas reiniciar não soluciona o problema, pois o programa criado não terá função para reiniciar o watchdog, o sistema continua sendo reiniciado indefinidamente.
Código 17.1: Inicialização do sistema com interrupções
1 #define CLRWTD() _asm CLRWDT _endasm
2
3 //início do programa
4 void main(void) {
5 unsigned int i;
6 unsigned char temp;
7 TRISD=0x00;
8 PORTD=0x00;
9 BitSet(WDTCON,0); //liga o sistema de watchdog
10 for(;;){ 11 PORTD++; 12 for(i = 0; i < 10000; i++){ 13 CLRWTD(); 14 } 15 } 16 }
P
ar
te
IV
Arquitetura
de
desenvolvimento
de
software
18 ⇀One single loop, 103 19 ⇀Interrupt control system, 105 20 ⇀Cooperative multitasking, 107 21 ⇀Anexos, 114