Continuando nossa série de artigos sobre o microcontrolador CH552, vamos aprender a usar a entrada analógica do CH552. Faremos isto com o compilador SDCC e diretamente no bare-metal, manipulando registradores.
A forma de mostrar os valor lido será um pouco diferente, usando o conceito já aprendido neste artigo do passado. Basicamente vamos ler o valor analógico como inteiros entre 0 e 255 (pois a entrada analógica do CH552 é 8 bit) e converte-lo para 0 – 100. Então vamos usar dois LEDs para sinalizar o valor. Por exemplo: se o valor analógico lido for 3,68V, isto significa um valor inteiro de +/-187, pois de 0 a 255 (0 a 5V), 3,68V mapeia para +/- 187.
Ao converter 0-255 inteiros para 0-100%, 187 significa +/- 73. O que vamos fazer é ter um LED para dezena e outro para unidade. Então com um valor de 73:
- O LED da dezena pisca 7 vezes
- O LED da unidade pisca 3 vezes.
Isto significa que um valor analógico de 3,68V, convertido de (0-5V) para inteiros (0-255) e para 0-100% representa 73. Usando dois LEDS piscantes, um LED vai piscar 7 vezes e o outro 3.

Desta forma evitamos o uso tanto de displays como de comunicação serial com o computador. É uma forma de visualizar dados muito barata e eficiente. Se pensarmos na escala completa, podemos ter desde 0% (nada piscando) até 99% (cada LEDs pisca 9 vezes).
O hardware
Como de costume aqui no blog, o hardware é bem simples e fácil de montar. Neste experimentos usaremos o microcontrolador CH552 montado sobre uma protoboard de 400 pinos, mais que suficientes. Usaremos também um potenciômetro qualquer, com valor entre 1k Ohm e 500k Ohm. Dentro do sugerido, pode ser qualquer valor mesmo; o tipo (maior ou menor) também não importa.
E finalmente dois LEDs com resistores de 1k Ohm em série (cada LED com seu resistor). O valor do resistor pode ser entre 220 Ohm e 1,5k Ohm, tanto faz. É só uma recomendação, valores que eu uso no dia-a-dia. O LED das dezenas estará no pino P3.4 e o da unidade no pino P3.5 do CH552.
Já o potenciômetro é alimentado com 5V e seu pino central vai na entrada analógica AIN2 (P1.5). O diagrama esquemático completo está na imagem abaixo, para sua apreciação e para copiar.

Para programar o exemplo de entrada analógica do CH552 basta conecta-lo ao computador via cabo USB tipo C. Também é via cabo USB que vem toda energia necessária para fazer o exemplo funcionar. Sem fontes externas nem fiação solta pela bancada.
Entendendo o firmware/código
O código completo para nosso exemplo de entrada analógica do CH552 está no meu Github e também abaixo. Pode copiar, colar e usar como desejar.
#include "inc\ch554.h"
#include "adc\adc.h"
#include <stdint.h>
volatile unsigned int tick_100us = 0;
volatile unsigned int blinkCounter = 0;
volatile unsigned int tens = 0;
volatile unsigned int unit = 0;
unsigned int countUnits = 0;
unsigned int countTens = 0;
uint8_t rawAnalog = 0;
uint16_t mVanalog = 0;
uint8_t val = 0;
__bit conversionFinished = 1;
__bit blinkNowToggle = 0;
__bit unitON = 0;
__bit tensON = 0;
__bit buttonPrev = 0;
__bit ledBlinkEnabled = 0;
void timer0_ISR(void) __interrupt(1) __using(1);
void blink_led(void);
void clock_init(void);
uint8_t analogReading(void) {
ADC_START = 1;
while (ADC_START);
ADC_IF = 0;
return ADC_DATA;
}
void clock_init(void) {
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
// Set Fsys = 24 MHz — CH554 datasheet section 2.2
CLOCK_CFG = (CLOCK_CFG & ~MASK_SYS_CK_SEL) | 0x06;
SAFE_MOD = 0x00;
}
void timer0_ISR(void) __interrupt(INT_NO_TMR0) {
TF0 = 0;
// 100us @ 24MHz: 24M/12=2MHz, 100us/0.5us=200 ticks, 65536-200=0xFF38
TH0 = 0xFF;
TL0 = 0x38;
tick_100us++;
blinkCounter++;
}
void timer0_init(void) {
// CH554 Timer0 mode 1: 16-bit — CH554 datasheet section 9
TMOD &= ~0x03;
TMOD |= 0x01;
TH0 = 0xFF;
TL0 = 0x38;
ET0 = 1;
TR0 = 1;
EA = 1;
}
void blink_led(void) {
// 2500 * 100us = 250ms ON, 250ms OFF
if (tick_100us < 2500) {
P3 |= (1 << 0);
} else if (tick_100us < 5000) {
P3 &= ~(1 << 0);
} else {
tick_100us = 0;
}
}
uint16_t rawToMillivolts(uint8_t raw) {
//if (raw < 46) return 0;
// linear mapping: Vref=3.3V, 8-bit ADC, offset-corrected
//return 300 + ((uint32_t)raw * 3000) / 117;
return ((uint32_t)raw * 5000) / 255;
}
void blinkTwoLEDs(uint8_t v) {
tens = v / 10;
unit = v % 10;
// double: each digit needs ON+OFF transitions
//tens = tens * 2;
//unit = unit * 2;
// reset state machine for fresh run
countUnits = 0;
countTens = 0;
unitON = 0;
tensON = 0;
blinkCounter = 0;
blinkNowToggle = 1;
}
void blinkNow(void) {
if (!blinkNowToggle) return;
// kick off: decide starting phase
if (countUnits == 0 && countTens == 0) {
if (unit > 0) {
countUnits = 1;
} else if (tens > 0) {
countTens = 1;
} else {
blinkNowToggle = 0;
conversionFinished = 1;
return;
}
}
// 2500 * 100us = 250ms per half-blink
if (blinkCounter < 2500) return;
blinkCounter = 0;
// Phase 1: blink units on P3.5
if (countUnits == 1) {
if (unit > 0) {
if (unitON == 0) {
P3 |= (1 << 5); // LED ON
unitON = 1;
} else {
P3 &= ~(1 << 5); // LED OFF
unitON = 0;
unit--; // decrement only on OFF edge — one full blink per count
}
} else {
P3 &= ~(1 << 5); // FIX: was (1<<4), must clear units LED (P3.5) before switching phase
unitON = 0;
countUnits = 0;
countTens = (tens > 0) ? 1 : 0; // FIX: only enter tens phase if tens > 0
if (countTens == 0) {
blinkNowToggle = 0;
conversionFinished = 1;
}
}
return;
}
// Phase 2: blink tens on P3.4
if (countTens == 1) {
if (tens > 0) {
if (tensON == 0) {
P3 |= (1 << 4); // LED ON
tensON = 1;
} else {
P3 &= ~(1 << 4); // LED OFF
tensON = 0;
tens--; // decrement only on OFF edge — one full blink per count
}
} else {
P3 &= ~(1 << 4); // FIX: was (1<<5), must clear tens LED (P3.4) when done
tensON = 0;
countTens = 0;
blinkNowToggle = 0;
conversionFinished = 1;
}
}
}
void main(void) {
clock_init();
timer0_init();
// Disable watchdog — CH554 datasheet section 4.3
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
GLOBAL_CFG &= ~bWDOG_EN;
SAFE_MOD = 0x00;
// P3.0 push-pull output (toggle LED, button-controlled)
P3_MOD_OC &= ~(1 << 0);
P3_DIR_PU |= (1 << 0);
// P3.4 conflicts with T0 (Timer0 ext clock) and UART1 alt pin
// Ensure Timer0 uses internal clock not P3.4 — CH554 datasheet TMOD register
TMOD &= ~bT0_CT; // bT0_CT=0: timer, not counter on P3.4
// Also clear UART1 alternate pin remap off P3.4 — ch554.h line 267
PIN_FUNC &= ~bUART1_PIN_X;
// P3.4, P3.5 push-pull output (unit/tens blink LEDs)
P3_MOD_OC &= ~((1 << 4) | (1 << 5));
P3_DIR_PU |= (1 << 4) | (1 << 5);
P3 &= ~((1 << 4) | (1 << 5));
// P1.4 input with pull-up (button)
// MOD_OC=1 + DIR_PU=1 = quasi-bidirectional with pullup — CH554 datasheet section 3
P1_MOD_OC |= (1 << 4);
P1_DIR_PU |= (1 << 4);
// ADC on AIN2 (P1.5) — slow clock, polling mode
// ch554.h line 438: ADC channel 2 = AIN2 = P1.5
ADCInit(0);
ADC_ChannelSelect(2);
while (1) {
uint8_t buttonNow = !(P1 & (1 << 4));
// edge detection: toggle ledBlinkEnabled on each press
if (buttonNow && !buttonPrev) {
ledBlinkEnabled = !ledBlinkEnabled;
if (!ledBlinkEnabled) P3 &= ~(1 << 0); // turn off immediately
}
buttonPrev = buttonNow;
if (ledBlinkEnabled) {
blink_led();
}
// read ADC only when no blink sequence is running
if (conversionFinished && !blinkNowToggle) {
mVanalog = rawToMillivolts(analogReading());
val = mVanalog / 50;
if (val > 99){
val = 99;
}/*else if(val < 0){
val = 0;
}*/
blinkTwoLEDs(val);
conversionFinished = 0;
}
blinkNow();
}
}
Conforme já mencionado nos outros artigos da série, como não estamos falando de Arduino, precisamos configurar bastante coisas antes de começar a programar de verdade. Por exemplo precisamos configurar o clock, no nosso caso um cristal de 24MHz montado na placa do CH552:
void clock_init(void) {
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
// Set Fsys = 24 MHz — CH554 datasheet section 2.2
CLOCK_CFG = (CLOCK_CFG & ~MASK_SYS_CK_SEL) | 0x06;
SAFE_MOD = 0x00;
}
Após isso inicializamos o temporiador Timer0, a fim de gerar uma interrupção de 100 us (cem microsegundos) que vai controlar todo nosso código. O Timer0 começa a contar de 0xFFFF e desceria até 0x0000, então definimos que ele vai parar (gerar interrupção) em 0xFF38:
void timer0_init(void) {
// CH554 Timer0 mode 1: 16-bit — CH554 datasheet section 9
TMOD &= ~0x03;
TMOD |= 0x01;
TH0 = 0xFF;
TL0 = 0x38;
ET0 = 1;
TR0 = 1;
EA = 1;
}
Isto significa que ele conta até 200 (FF descendo até 38), que em 2MHz significa 100 us. Ainda inicializamos o mesmo (ET0= 1), começamos a contar (TR0= 1) e inicializamos as interrupções no geral (EA= 1). Então a cada 100 us o programa cai na função abaixo, que é a interrupção de temporizador do Timer0:
void timer0_ISR(void) __interrupt(INT_NO_TMR0) {
TF0 = 0;
// 100us @ 24MHz: 24M/12=2MHz, 100us/0.5us=200 ticks, 65536-200=0xFF38
TH0 = 0xFF;
TL0 = 0x38;
tick_100us++;
blinkCounter++;
}
Aqui controlamos todo o código com a variável “tick_100us” que incrementar em uma unidadade a cada 100us. O mesmo para a variável “blinkCounter” que controla o piscar de um LED. Este está colocado no pino P3.0, não é necessário mas achei legal colocar no programa.
A mágica do nosso código acontece na função “analogReading()”, que faz uma única leitura analógica por vez. Damos um “ADC_START= 1” e esperamos ele inicializar. Neste momento já temos nossa leitura analógica, que fica em “ADC_DATA”. Ainda antes de finalizar a função nós limpamos a flag da leitura colocando “ADC_IF= 0”. Quando uma leitura ocorre este flag vai para “1” automaticamente, então temos que limpa-lo após ler o valor analógico produzido.
uint8_t analogReading(void) {
ADC_START = 1;
while (ADC_START);
ADC_IF = 0;
return ADC_DATA;
}
A função main()
Dentro da função principal nós inicialmente configuramos tudo que é necessário: chamamos a inicialização do timer e do clock, definimos as funções de cada pino digital e analógico. Definimos também qual canal analógico vamos ler.
clock_init();
timer0_init();
// Disable watchdog — CH554 datasheet section 4.3
SAFE_MOD = 0x55;
SAFE_MOD = 0xAA;
GLOBAL_CFG &= ~bWDOG_EN;
SAFE_MOD = 0x00;
// P3.0 push-pull output (toggle LED, button-controlled)
P3_MOD_OC &= ~(1 << 0);
P3_DIR_PU |= (1 << 0);
// P3.4 conflicts with T0 (Timer0 ext clock) and UART1 alt pin
// Ensure Timer0 uses internal clock not P3.4 — CH554 datasheet TMOD register
TMOD &= ~bT0_CT; // bT0_CT=0: timer, not counter on P3.4
// Also clear UART1 alternate pin remap off P3.4 — ch554.h line 267
PIN_FUNC &= ~bUART1_PIN_X;
// P3.4, P3.5 push-pull output (unit/tens blink LEDs)
P3_MOD_OC &= ~((1 << 4) | (1 << 5));
P3_DIR_PU |= (1 << 4) | (1 << 5);
P3 &= ~((1 << 4) | (1 << 5));
// P1.4 input with pull-up (button)
// MOD_OC=1 + DIR_PU=1 = quasi-bidirectional with pullup — CH554 datasheet section 3
P1_MOD_OC |= (1 << 4);
P1_DIR_PU |= (1 << 4);
// ADC on AIN2 (P1.5) — slow clock, polling mode
// ch554.h line 438: ADC channel 2 = AIN2 = P1.5
ADCInit(0);
ADC_ChannelSelect(2);
Tudo isto é feito apenas uma vez, porém toda vez que o microcontrolador é inicializado. Então partimos para o loop “while(1)”, que vai ser executado indefinidamente até desligar o microcontrolador. Abaixo está a funçao principal do nosso programa, que lê o valor analógico e agencia a piscada dos dois LEDs em sequência.
// read ADC only when no blink sequence is running
if (conversionFinished && !blinkNowToggle) {
mVanalog = rawToMillivolts(analogReading());
val = mVanalog / 50;
if (val > 99){
val = 99;
}
blinkTwoLEDs(val);
conversionFinished = 0;
}
O valor analógico é lido antes de iniciar a piscada, isto significa que uma vez que os LEDs começaram a piscar, uma nova leitura só será feita ao final de toda a sequência da piscada. Isto é feito em sequência sem parar. No vídeo de exemplo você vai ver melhor como isso funciona.
Função bônus
Eu implementei também uma função similar àquela do artigo passado, onde um botão push button controla a piscada de um LED no P3.0. O push button está no pino P1.4. Ao pressionar o botão o LED começa a piscar, momento em que você pode soltar o botão. Ao pressionar o botão novamente o LED para de piscar.
uint8_t buttonNow = !(P1 & (1 << 4));
// edge detection: toggle ledBlinkEnabled on each press
if (buttonNow && !buttonPrev) {
ledBlinkEnabled = !ledBlinkEnabled;
if (!ledBlinkEnabled) P3 &= ~(1 << 0); // turn off immediately
}
buttonPrev = buttonNow;
if (ledBlinkEnabled) {
blink_led();
}
Tudo isto está dentro do loop infinito “while(1)”, e é controlado pela contagem da variável “blinkCounter”. Diferente dos códigos em Arduino onde as pessoas usam muito “delay()” para controlar o programa, nestes exemplo que estou te trazendo usamos apenas temporizadores e contradores.
Isto significa que além de ler a entrada analógica do CH552, nós ainda estamos piscando um LED controlado por um botão. Tudo isso ao mesmo tempo e sem perder nada.
Testando o código que implementamos
Conforme comentado nos artigos anteriores, eu criei um script em Pyhon com ajuda das inteligências artificiais. O objetivo do script é para automatizar a compilação, conversão, linkagem e gravação do chip CH552. O script se chama “build_flash.py” e está aqui neste link do Github para você usar.
O que você precisa fazer para se organizar é salvar tudo em uma pasta específica. No meu caso estou salvando tudo em:
C:\Users\Clovisf\Documents\ch552
O nome do arquivo do teste que estamos fazendo, da entrada analógica é “adc-example-leds-hmi.c”. Para compilar e gravar este arquivo, na linha de comando (digitando “cmd” no menu do Windows”) digite:
python build_flash.py adc-example-leds-hmi.c
Obs: você precisa ter o Python instalado, bem como o compilador SDCC e o software gravador “chprog”. Todos os três softwares precisam estar no “PATH” do Windows. Volte ao primeiro artigo da série para ver como fazer. Após digitar o comando acima na tela do CMD você vai ver uma tela conforme imagem abaixo, te pedindo para conectar a placa ao computador e pressionar ENTER.

Pressione e segure o botão BOOT, enquanto você conectar o cabo USB da placa ao computador. Aguarde uns 2 segundo e então pressione ENTER no teclado. Muito rapidamente a mensagem abaixo vai aparecer, indicando sucesso. O código vai imediatamente começar a rodar.

Fiz um vídeo ilustrando como este exemplo funciona, para você entender melhor. Acompanhe o mesmo lá no início do Artigo. Lembrando que você pode comprar esta plaquinha do CH552 nos meus links no Aliexpress (aqui) ou (aqui). Até a próxima pessoal, e continuem programando.





