Hoje vamos conhecer o periférico watchdog timer do microcontrolador CH552. Este artigo é parte de uma série que estou escrevendo aqui no blog. Vamos implementar um exemplo onde forçamos o microcontrolador a “parar” dentro de um “while(1)” até que o watchdog seja ativado. Mas como funciona o watchdog timer? segundo a Wikipedia é um periférico que monitora o controlador para travamentos.
Mas como assim? o que temos é basicamente um contador de tempo ou temporizador. Este contador tem que ser resetado de tempos e tempos para nunca chegar no final de sua contagem. Então precisamos sempre setar ou resetar uma variável dentro no nosso loop principal “while(1)”. No caso do CH552 fazemos “WDOG_COUNT = 0x01”, ou seja fazemos com que a variável seja “1”.
Mas porque este valor de “1”? pois o contador do watchdog conta de FF (256) até 0, quando ele reseta. Aí o maior valor possível (deixar passar o maior tempo possível) é “1”, o mais próximo de “0”.E o que acontece caso esta variável não seja setada? após algum tempo (entre milisegundos e segundos) o microcontrolador inteiro é resetado, reinicia do zero. O objetivo é limpar ou sanar falhas que possam estar travando o mesmo em alguma parte do código.
Este tempo é assim calculado: Frequência watchdog: Frequência cristal / 65536 = 24.000.000 / 65536 = 366 Hz. Estes 366 Hz em segundos dá 2,7 milisegundos. Portanto para uma contagem até 255 demoraríamos 0,69 segundos. Este é o tempo no qual sempre temos que (continuamente) setar a variável “WDOG_COUNT = 1”.
Se formos pensar lá no Arduino, naqueles códigos cheios de “delay()”, esta função de watchdog jamais funcionaria. Por isso é importante aprender a não depender do “delay()”, fazer todos os seus códigos não bloqueantes. A função de watchdog também é muito importante durante o desenvolvimento e debug de código, visto que auxilia a achar “pontos lentos” ou fracos do código.
O datasheet do CH552 é um ótimo lugar para começar, lá fala quase tudo. O restante eu pedi auxílio para o ChatGPT, Claude, Copilot e Gemini. Por incrível que pareça tem pouca informação sobre o CH552 na internet no geral. Isto porque apesar de ele ser um 8051, seus periféricos podem ser mais avançados. Por exemplo ele possui periférico USB e também de toque capacitivo.
Hardware/conexões
O hardware para este experimento é sem dúvida um dos mais simples que já implementei aqui no blog. Vamos precisar apenas de um push button, nada mais. Isto por dois motivos:
- O CH552 possui resistores de pull-up internos para seus pinos de GPIO e
- A placa CH552 da WeAct que eu comprei tem um LED onboard no pino P3.0. Compre aqui caso precisar.
O circuito que vamos implementar consiste em pressionar o push button no pino P3.3, momento no qual o LED no pino P3.0 vai começar a piscar. Após alguns ciclos de piscado o watchdog é ativado, reiniciando o microcontrolador. Você vai perceber pois o LED vai apagar. A imagem abaixo traz o diagrama esquemático do experimento. Logo abaixo vemos também uma foto da montagem em minha bancada.


Veja que eu utilizei uma pequena protoboard de 400 pinos, somente. Ainda sobrou metade do espaço útil para você colocar outros componentes e fazer outros testes. Note que na foto há ainda um conversor USB-serial, que não estamos usando neste experimento. Eu deixei ele ali pois estou usando na elaboração de outros artigos.
O firmware/código para nosso teste
Todo o código (completo está abaixo e também neste link do Github, aproveite!.
#include "inc\ch554.h"
#include <stdint.h>
volatile unsigned int tick_10ms = 0;
volatile unsigned int debounceTimer = 0;
volatile unsigned int wdtCounter = 0;
volatile __bit button_irq = 0;
volatile __bit debounce = 0;
volatile __bit ledON = 0;
unsigned int serialTime= 0;
unsigned int counter= 0;
unsigned int t;
unsigned int t2;
static __bit wdt_started = 0;
static unsigned int blink_base = 0;
static unsigned int blink_base2 = 0;
void timer0_ISR(void) __interrupt(1);
void INT1_ISR(void) __interrupt (INT_NO_INT1);
void blink_led(unsigned int t);
void clock_init(void);
void clock_init(void) {
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
CLOCK_CFG |= bOSC_EN_INT;
// Set Fsys = 24 MHz (Fpll/4, bits[2:0] = 110 = 0x06)
CLOCK_CFG = (CLOCK_CFG & ~MASK_SYS_CK_SEL) | 0x06;
SAFE_MOD = 0x00;
}
void INT1_ISR(void) __interrupt (INT_NO_INT1)// You can do __interrupt (2) if you prefer
{
if (!(P3 & (1 << 3))) { // only accept if pin is LOW. This prevents
if (!debounce) { // only register if not already debouncing
button_irq = 1;
debounce = 1; // arm immediately, atomically
}
}
}
void timer0_ISR(void) __interrupt(1) __using(1){
TF0 = 0; // clear overflow flag (important for robustness)
TH0 = 0xB1;
TL0 = 0xE0;
tick_10ms++; // this is the 10ms tick for LED blinking
serialTime++; // this is the timer for Serial_println transmissions
if(debounce){ // if external interrupt happened, activate debounce timer
debounceTimer++;
if(debounceTimer >= 60){ // after 600ms of not detecting the push-button
debounce= 0; // turn debounce delay OFF
debounceTimer= 0; // and clear timer/counter for next time
}
}
}
/*void timer0_ISR(void) __interrupt(1) {
P3 ^= (1 << 0);
}*/
void timer0_init(void) {
// Force Timer0 to use Fsys/12
T2MOD &= ~bTMR_CLK; // disable fast clock mode
T2MOD &= ~bT0_CLK; // Timer0 = Fsys/12
TMOD &= ~0x03; // clear Timer0 mode bits
TMOD |= 0x01; // Timer0 mode 1: 16-bit
// 10ms @ 24MHz: tick = 24M/12 = 2MHz = 0.5us
// 10ms / 0.5us = 20000 ticks; 65536-20000 = 45536 = 0xB1E0
TH0 = 0xB1;
TL0 = 0xE0;
TF0 = 0;
ET0 = 1; // enable Timer0 interrupt
TR0 = 1; // start Timer0
EA = 1;
}
void extint1_init(void) {
IT1 = 1; // falling edge
EX1 = 1; // enable INT1
}
void blink_led(unsigned int t) {
unsigned int phase = t - blink_base;
if (phase < 30) {
P3 |= (1 << 0);
} else if (phase < 60) {
P3 &= ~(1 << 0);
} else {
blink_base = t;
wdtCounter++; // increment watchdog activation "countdown"
}
}
void blink_led2(unsigned int t) {
unsigned int phase2 = t2 - blink_base2;
if (phase2 < 30) {
P3 |= (1 << 1);
} else if (phase2 < 60) {
P3 &= ~(1 << 1);
} else {
blink_base2 = t2;
}
}
void main(void) {
clock_init();
extint1_init();
timer0_init();
// Configure P3.0 (LED) as push-pull output
// P3.0 bit = 0x01
P3_MOD_OC &= ~(1 << 0); // push-pull
P3_DIR_PU |= (1 << 0); // enable strong output drive
// Configure P3.1 (control) as push-pull output
P3_MOD_OC &= ~(1 << 1); // push-pull
P3_DIR_PU |= (1 << 1); // enable strong output drive
// Configure P3.3 as input pull-up for interrupt
P3_MOD_OC &= ~(1 << 3); // normal input
P3_DIR_PU |= (1 << 3); // enable pull-up
P3 |= (1 << 3); // pull-up
P3 &= ~(1 << 0); // Make LED pin P3.0 "start" as OFF
P3 &= ~(1 << 1); // Watchdog indicator pin to LOW
while (1) {
if (!wdt_started && tick_10ms > 50) { // wait ~500ms before activating watchdog
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
GLOBAL_CFG |= bWDOG_EN;
SAFE_MOD = 0x00;
wdt_started = 1;
}
EA = 0;
t = tick_10ms;
t2= tick_10ms;
EA = 1;
// if push button on pin P3.3 is pressed, start LED blink
if (button_irq && debounce) {
button_irq = 0;
//debounce= 1;
if (ledON == 0) {
ledON = 1;
wdtCounter = 0;
} else {
ledON = 0;
}
}
// LED is allowed to blink
if (ledON) {
blink_led(t);
}else{
blink_led2(t2);
}
// Intentional watchdog trigger — stop feeding and hang
if (wdtCounter > 5) {
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
USB_CTRL = 0x00;
SAFE_MOD = 0x00;
EA = 0;
while (1) { __asm nop __endasm; }
}else{ // Feed watchdog every iteration unless we want a reset
WDOG_COUNT = 0x01; // feed normally
}
}
}
Configurando tudo que precisamos
Conforme visto nos artigos anteriores sobre o CH552, são necessários alguns passos para conseguir compilar e gravar código no microcontrolador. Vou resumir um pouco abaixo, mas tudo está aqui. Você vai precisar de:
- Editor de código. Eu uso o Visual Studio code,
- Python instalado na máquina e inserido no PATH,
- Compilador SDCC,
- Software gravador CHprog,
- Um script em Python para automatizar todo o processo. Download aqui no meu Github.
O nome do arquivo onde você vai salvar o código acima é “watchdog-testing.c”. Ele deve estar na mesma pasta do arquivo em Python do nosso script (linkado acima, “build_flash.py”). Na linha de comando do Windows (CMD) navegue até a pasta onde estão os arquivos. No meu caso é:
cd Documents\ch552
Desconecte a placa CH552 do computador. Digite o comando abaixo para iniciar, pressionando ENTER:
python build_flash.py watchdog-testing.c
Após alguns segundos a mensagem abaixo vai aparecer:
>> Put the CH552 into BOOT mode (hold BOOT, plug USB, release).
>> Press ENTER when ready...
Então você vai pressionar e segurar o botão BOOT na placa do CH552; conectar o cabo USB entre o computador e a placa. Apertar ENTER do teclado do computador e só então soltar o botão BOOT. Após alguns segundos a mensagem abaixo aparecerá, indicando sucesso na gravação:
Using v2 protocol
Chip ID=52
Bootloader: 2.5.0
Write & verify complete. All seems OK.
✔ Build + Flash completed successfully!
Não é necessário, mas se quiser pressione o botão RESET da placa do CH552, ou então remova e reinsira o cabo USB. Neste momento o código já vai estar rodando na placa.
O teste final do watchdog timer
O primeiro teste que vamos fazer é com osciloscópio. Você não precisa ter um osciloscópio nem fazer este teste, eu só estou fazendo para te mostrar que funciona. O importante é entende que o watchdog timer está sendo ativado e funcionando.
Observe a imagem abaixo. O canal CH2 está em azul e representa o LED piscando no pino P3.0. São exatamente cinco (5) piscadas antes de entrar no while(1) infinito, controladas por “if (wdtCounter >= 6) {“. Já o canal CH1 amarelo indicar justamente o acionamento do watchdog. Ele está normalmente em zero “0”, quando o push button é pressionado ele vai a “1” e assim fica.
Isto até o momento em que o watchdog timer é acionado, em torno de quase um segundo após a última borda do LED (CH2).Então o watchdog reseta o microcontrolador e pino P3.1 vai a zero “0”, através do comando “P3 &= ~(1 << 1)” no início do main() loop.

Eu controlo o P3.1 em apenas dois lugares e o comando de “0” acontece apenas uma vez no início do programa. Então o fato de este sinal ir a “0” após as cinco (5) piscadas do P3.0, me diz que o watchdog está mesmo resetando o microcontrolador.
Fiz um vídeo para te mostrar tudo isso funcionando, está ali no início do artigo, após o primeiro parágrafo. No vídeo a lógica do código está um pouco diferente:
- O LED no pino 3.1 fica piscando enquanto nada mais acontece,
- Ao pressionar o botão no pino P3.3, o LED no pino P3.1 para de piscar e o LED no pino P3.0 começa a piscar,
- Após 5 piscadas e mais 500 milisegundos, o microcontrolador é resetado.
Aproveitem para conhecer os outros artigos desta série, e se divirtam.