Mais mudanças e recursos especiais do PIC (parte 1)
Autor: Stephen da Silva Madeira | Publicado: 2016-07-28T12:39:00-03:00

Pra galera que acompanha o blog, até então eu vinha usando os compiladores da Mikroe (tanto o mikroC quanto o mikroBasic no início), mas eles são pagos e limitam na versão demo os recursos e tamanho do programa que você pode escrever. Chegando ao limite dele, precisei achar uma saída, e resolvi então migrar para o compilador e IDE da própria Microchip: o MPLAB e como compilador o XC8. Então, a partir de agora, meus códigos serão todos para XC8.
As diferenças na linguagem não são tantas; a maior dificuldade é lidar com a falta de bibliotecas como havia no mikroC. Porém, como sou legal, me encarreguei de fazer o máximo que pude das bibliotecas mais usuais do mikroC e deixei neste link: só baixar e usar.
E nesse post já farei uso dessa nova plataforma. Hoje vou falar sobre um recurso interessante do PIC e que muitas vezes passa despercebido e raramente é usado, seja por não saber o que é ou por não achar referências muito claras, seja em português ou em inglês: o WatchDog Timer.


WatchDog Timer ou WDT

Vamos supor que seu PIC de alguma forma executou uma instrução que gerou uma operação inválida ou travou em um loop aguardando a resposta de um dispositivo que por algum motivo não respondeu, ou ainda, que sua fonte de clock primário parou. Para não deixar seu dispositivo parado/travado, existe esse módulo interno do MCU: o WDT. Vou deixar os maiores detalhes pra que vocês leiam na internet ou no datasheet, mas basicamente, o WDT opera com um clock próprio. É um timer que incrementa sozinho, mas a diferença é que automaticamente ao haver um overflow o PIC irá resetar. Cabe, então, ao seu programa constantemente limpar o WDT, e para isso temos a instrução clrwdt em assembly. Ela irá limpar o timer do WatchDog. No XC8 temos a função ClrWdt(); que fará o mesmo trabalho.

No exemplo de programa, usarei o PIC18F2550. Temos algumas pequenas diferenças, que explicarei.&nbsp;


Prescaler e base de tempo 




Primeiro de tudo, como falei, esse módulo tem seu próprio clock, e com isso, sua base de tempo. É importante se atentar a isso, que fica bem explícito no datasheet. No caso do PIC18F2550, com prescaler 1:1 temos 4 ms como base de tempo, ou seja, se a cada 4 ms o WDT não for limpo, o PIC irá executar a instrução de RESET. Fazendo uso do prescaler, podemos chegar a pouco mais de 2 minutos. É bom pensar no tempo escolhido, pois caso o PIC pare, ele ficará assim por esse determinado tempo. E aqui entra uma pequena diferença: na família 12F e 16F, o prescaler é selecionado no registrador OPTION_REG, enquanto que na 18F há um um endereço especial dos bits de configuração para ele.
&nbsp;&nbsp;

Controle por Software
No PIC18F2550 ainda temo outro recurso: o controle por Hardware do funcionamento do WDT. Quando este modo é ativado nos bits de configuração, temos um bit chamado SWDTEN (Software Controlled Watchdog Timer Enable bit) que nos permite, dentro do software, ligar ou desligar o módulo.


Limpando o Timer do WatchDog
Mais uma vez, aqui é bom uma conferida no datasheet: o WDT pode ser resetado por mais de um caminho. O comum a todos, como já citado, é a instrução própria para isso. Mas ainda há outros dependendo do PIC: mudança na fonte de clock primário, a instrução SLEEP, e etc.
Mas atentando ao principal: como vou ficar de tempo em tempo passando a instrução clrwdt? Tenho que ficar escrevendo isso linha a linha no código? Não! Aqui usamos um dos Timers disponíveis para gerar interrupções de tempo conhecido e assim passar a instrução. Sabendo a frequência do Clock e o tamanho do Timer podemos definir o prescaler e/ou postscaler para chegar a um tempo de interrupção menor que do WDT, e assim, limpá-lo antes do WDT estourar. Aqui você pode usar a calculadora que fiz :D (não deixe de conferir essa postagem).



Juntando tudo
Aqui segue um exemplo em C para o XC8, PIC18F2550:

<!-- HTML generated using hilite.me -->



&nbsp; 1
&nbsp; 2
&nbsp; 3
&nbsp; 4
&nbsp; 5
&nbsp; 6
&nbsp; 7
&nbsp; 8
&nbsp; 9
&nbsp;10
&nbsp;11
&nbsp;12
&nbsp;13
&nbsp;14
&nbsp;15
&nbsp;16
&nbsp;17
&nbsp;18
&nbsp;19
&nbsp;20
&nbsp;21
&nbsp;22
&nbsp;23
&nbsp;24
&nbsp;25
&nbsp;26
&nbsp;27
&nbsp;28
&nbsp;29
&nbsp;30
&nbsp;31
&nbsp;32
&nbsp;33
&nbsp;34
&nbsp;35
&nbsp;36
&nbsp;37
&nbsp;38
&nbsp;39
&nbsp;40
&nbsp;41
&nbsp;42
&nbsp;43
&nbsp;44
&nbsp;45
&nbsp;46
&nbsp;47
&nbsp;48
&nbsp;49
&nbsp;50
&nbsp;51
&nbsp;52
&nbsp;53
&nbsp;54
&nbsp;55
&nbsp;56
&nbsp;57
&nbsp;58
&nbsp;59
&nbsp;60
&nbsp;61
&nbsp;62
&nbsp;63
&nbsp;64
&nbsp;65
&nbsp;66
&nbsp;67
&nbsp;68
&nbsp;69
&nbsp;70
&nbsp;71
&nbsp;72
&nbsp;73
&nbsp;74
&nbsp;75
&nbsp;76
&nbsp;77
&nbsp;78
&nbsp;79
&nbsp;80
&nbsp;81
&nbsp;82
&nbsp;83
&nbsp;84
&nbsp;85
&nbsp;86
&nbsp;87
&nbsp;88
&nbsp;89
&nbsp;90
&nbsp;91
&nbsp;92
&nbsp;93
&nbsp;94
&nbsp;95
&nbsp;96
&nbsp;97
&nbsp;98
&nbsp;99
100
101
102
103
104
105
106
107
108
109
/*
 * File:&nbsp;&nbsp; WDT.c
 * Author: Stephen
 *
 * Created on 7 de Julho de 2016, 14:33
 */

// PIC18F2550 Configuration Bit Settings
// 'C' source line config statements
// #pragma config statements should precede project file includes.
// Use project enums instead of #define for ON and OFF.

// CONFIG1L
#pragma config PLLDIV = 5&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // PLL Prescaler Selection bits (Divide by 5 (20 MHz oscillator input))
#pragma config CPUDIV = OSC1_PLL2// System Clock Postscaler Selection bits ([Primary Oscillator Src: /1][96 MHz PLL Src: /2])
#pragma config USBDIV = 2&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // USB Clock Selection bit (used in Full-Speed USB mode only; UCFG:FSEN = 1) (USB clock source comes from the 96 MHz PLL divided by 2)

// CONFIG1H
#pragma config FOSC = HS&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Oscillator Selection bits (HS oscillator (HS))
#pragma config FCMEN = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Fail-Safe Clock Monitor Enable bit (Fail-Safe Clock Monitor disabled)
#pragma config IESO = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Internal/External Oscillator Switchover bit (Oscillator Switchover mode disabled)

// CONFIG2L
#pragma config PWRT = ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Power-up Timer Enable bit (PWRT enabled)
#pragma config BOR = ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Brown-out Reset Enable bits (Brown-out Reset enabled in hardware only (SBOREN is disabled))
#pragma config BORV = 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Brown-out Reset Voltage bits (Maximum setting 4.59V)
#pragma config VREGEN = OFF&nbsp;&nbsp;&nbsp;&nbsp; // USB Voltage Regulator Enable bit (USB voltage regulator disabled)

// CONFIG2H
#pragma config WDT = ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Watchdog Timer Enable bit (WDT enabled)
#pragma config WDTPS = 256&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Watchdog Timer Postscale Select bits (1:256)

// CONFIG3H
#pragma config CCP2MX = OFF&nbsp;&nbsp;&nbsp;&nbsp; // CCP2 MUX bit (CCP2 input/output is multiplexed with RB3)
#pragma config PBADEN = OFF&nbsp;&nbsp;&nbsp;&nbsp; // PORTB A/D Enable bit (PORTB&lt;4:0&gt; pins are configured as digital I/O on Reset)
#pragma config LPT1OSC = OFF&nbsp;&nbsp;&nbsp; // Low-Power Timer 1 Oscillator Enable bit (Timer1 configured for higher power operation)
#pragma config MCLRE = ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // MCLR Pin Enable bit (MCLR pin enabled; RE3 input pin disabled)

// CONFIG4L
#pragma config STVREN = ON&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Stack Full/Underflow Reset Enable bit (Stack full/underflow will cause Reset)
#pragma config LVP = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Single-Supply ICSP Enable bit (Single-Supply ICSP disabled)
#pragma config XINST = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Extended Instruction Set Enable bit (Instruction set extension and Indexed Addressing mode disabled (Legacy mode))

// CONFIG5L
#pragma config CP0 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Code Protection bit (Block 0 (000800-001FFFh) is not code-protected)
#pragma config CP1 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Code Protection bit (Block 1 (002000-003FFFh) is not code-protected)
#pragma config CP2 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Code Protection bit (Block 2 (004000-005FFFh) is not code-protected)
#pragma config CP3 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Code Protection bit (Block 3 (006000-007FFFh) is not code-protected)

// CONFIG5H
#pragma config CPB = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Boot Block Code Protection bit (Boot block (000000-0007FFh) is not code-protected)
#pragma config CPD = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Data EEPROM Code Protection bit (Data EEPROM is not code-protected)

// CONFIG6L
#pragma config WRT0 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Write Protection bit (Block 0 (000800-001FFFh) is not write-protected)
#pragma config WRT1 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Write Protection bit (Block 1 (002000-003FFFh) is not write-protected)
#pragma config WRT2 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Write Protection bit (Block 2 (004000-005FFFh) is not write-protected)
#pragma config WRT3 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Write Protection bit (Block 3 (006000-007FFFh) is not write-protected)

// CONFIG6H
#pragma config WRTC = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Configuration Register Write Protection bit (Configuration registers (300000-3000FFh) are not write-protected)
#pragma config WRTB = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Boot Block Write Protection bit (Boot block (000000-0007FFh) is not write-protected)
#pragma config WRTD = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Data EEPROM Write Protection bit (Data EEPROM is not write-protected)

// CONFIG7L
#pragma config EBTR0 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Table Read Protection bit (Block 0 (000800-001FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR1 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Table Read Protection bit (Block 1 (002000-003FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR2 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Table Read Protection bit (Block 2 (004000-005FFFh) is not protected from table reads executed in other blocks)
#pragma config EBTR3 = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Table Read Protection bit (Block 3 (006000-007FFFh) is not protected from table reads executed in other blocks)

// CONFIG7H
#pragma config EBTRB = OFF&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Boot Block Table Read Protection bit (Boot block (000000-0007FFh) is not protected from table reads executed in other blocks)


#define _XTAL_FREQ 20000000
#include  xc.h 
#include "delays.h"


void interrupt high_priority ISR()
{
&nbsp;&nbsp;&nbsp; if(TMR0IF)
&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ClrWdt();
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TMR0H = 179;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TMR0L = 180;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TMR0IF = 0;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LATC6 ^= 1;
&nbsp;&nbsp;&nbsp; }
}

void main(void)
{
&nbsp;&nbsp;&nbsp; ClrWdt();
&nbsp;&nbsp;&nbsp; SWDTEN = 1;
&nbsp;&nbsp;&nbsp; INTCON = 0xE0;
&nbsp;&nbsp;&nbsp; INTCON2 = 0x74;
&nbsp;&nbsp;&nbsp; IPEN = 1;
&nbsp;&nbsp;&nbsp; T0CON = 0x87;
&nbsp;&nbsp;&nbsp; TMR0H = 179;
&nbsp;&nbsp;&nbsp; TMR0L = 180;
&nbsp;&nbsp;&nbsp; TRISC = 0;
&nbsp;&nbsp;&nbsp; LATC = 0;

&nbsp;&nbsp;&nbsp; delay_ms(2000);
&nbsp;&nbsp;&nbsp; LATC7 = 1;
&nbsp;&nbsp;&nbsp; TMR0IE = 0;
&nbsp;&nbsp;&nbsp; while(1);
}





Como podem ver, todos os #pragma são responsáveis por direcionar o compilador a gerar o código pros bits de configuração (essa parte não fica junto com a memória de programa). Explicando o importante, configurei meu PIC para rodar com Clock externo (HS) a 20 MHz, informado ao compilador em Hertz pelo #define _XTAL_FREQ 20000000. Meu prescaler do WDT ficou em 256, pois 256 x 4 ms = 1024 ms, ou aproximadamente um segundo para o WDT resetar o PIC. Habilitei o bit do WDT com opção de controle por software.
o #include "delays.h" inclui no programa o header delays, que será usado exatamente como a função delay_ms(); do mikroC;
Na interrupção, configurada para alta prioridade pelo modificador high_priority, vemos que a coisa é bem simples: executo a função para limpar o WDT, recarrego o Timer0 com seus valores calculados e limpamos a interrupção. De brinde tem um LED no PORTC6 que pisca para indicar que o WDT está sendo limpo. Simples assim! Ela está em modo de alta prioridade pois, se houver outra interrupção dentro do programa, é preferível que esta tenha prioridade, para evitar que o PIC reset por acidente.
Sobre a main, já começo o programa limpando o timer e habilitando via software o WDT. Em seguida, vem a configuração da interrupção para o Timer0 em modo de alta prioridade e a configuração do Timer0 para gerar a interrupção antes de 1024 ms. Dou um delay de 2 segundos e ligo outro LED, dessa vez no PORTC7, para indicar que desligarei a interrupção do Timer0, o que faço em seguida e ponho o PIC em loop eterno. Como desliguei a interrupção, a função de interrupção não será chamada, não limpando o WDT e assim permitindo seu estouro e consequente reset do PIC. O que você verá mesmo é o LED do PORTC6 ligar (avisando que a interrupção foi chamada), o outro LED liga e um segundo depois os dois apagam, indicando que o PIC resetou. 
E é isso! Esta é a base de uso do WDT. Agora é só implementar em seus programas sempre que possível para garantir que jamais seu projeto pare de funcionar. Próximo post falarei sobre o Fail-Safe Clock Monitor (FSCM). Até mais!