/*
Chocadeira - Arduino UNO + NTC Wokwi + Charlieplexing
Mantém a lógica original:
- D2..D7 = Charlieplexing
- A0 = NTC Wokwi
- D11 = PWM do aquecedor
- Timer1 = varredura do display
*/
#include <Arduino.h>
#include <avr/io.h>
#include <TimerOne.h>
#include <math.h>
// =====================================================
// Configurações
// =====================================================
#define SCAN_DELAY 100
#define PIN_NTC A0
#define PIN_HEATER 11
#define NTC_BETA 3950.0f
#define CHARLIE_MASK B11111100
#define NUM_SCAN 24
#define Ki 0.03f
#define Kp 0.4f
// =====================================================
// Variáveis globais
// =====================================================
byte Digitos[3] = {0, 0, 0};
int filtro[8] = {0, 0, 0, 0, 0, 0, 0, 0};
int temperatura = 0; // temperatura em centésimos de grau Celsius
volatile uint32_t saida = 0; // 24 bits usados no display
int sensorValue = 0;
int outputValue = 0;
int setpoint = 3505; // 35.05 °C em centésimos de grau
int error = 0;
float proportional = 0.0f;
float integral = 0.0f;
// =====================================================
// Protótipos
// =====================================================
void scanner(void);
int NTC_ADC2Temperature(unsigned int adc_value);
void Controle(int temperaturas);
void JackRipper(void);
// =====================================================
// Setup
// =====================================================
void setup() {
pinMode(PIN_NTC, INPUT);
pinMode(PIN_HEATER, OUTPUT);
analogWrite(PIN_HEATER, 0);
// Desliga apenas os pinos D2..D7
PORTD &= ~CHARLIE_MASK;
DDRD &= ~CHARLIE_MASK;
Timer1.initialize(100); // 100 us por posição
Timer1.attachInterrupt(scanner);
}
// =====================================================
// Loop principal
// =====================================================
void loop() {
long soma = 0;
for (int i = 0; i < 8; i++) {
filtro[i] = NTC_ADC2Temperature(analogRead(PIN_NTC));
soma += filtro[i];
delay(SCAN_DELAY);
}
temperatura = soma / 8;
Controle(temperatura);
JackRipper();
}
// =====================================================
// Tabela de segmentos
// Ordem dos bits: a b c d e f g dp
// bit7=a, bit6=b, bit5=c, bit4=d, bit3=e, bit2=f, bit1=g, bit0=dp
// =====================================================
const byte digits[] = {
0b11111100, // 0
0b01100000, // 1
0b11011010, // 2
0b11110010, // 3
0b01100110, // 4
0b10110110, // 5
0b10111110, // 6
0b11100000, // 7
0b11111110, // 8
0b11110110, // 9
0b10011110, // E
0b00001010, // r
0b10110110, // S
0b00011111 // t
};
// =====================================================
// Tabelas Charlieplexing
// Mantidas compatíveis com seu diagram.json atual
// =====================================================
const byte direcao[] = {
B11000000, B11000000,
B10100000, B10100000,
B10010000, B10010000,
B10001000, B10001000,
B10000100, B10000100,
B01100000, B01100000,
B01010000, B01010000,
B01001000, B01001000,
B01000100, B01000100,
B00110000, B00110000,
B00101000, B00101000,
B00100100, B00100100
};
const byte estados[] = {
B10000000, B01000000,
B10000000, B00100000,
B10000000, B00010000,
B10000000, B00001000,
B10000000, B00000100,
B01000000, B00100000,
B01000000, B00010000,
B01000000, B00001000,
B01000000, B00000100,
B00100000, B00010000,
B00100000, B00001000,
B00100000, B00000100
};
// =====================================================
// Conversão NTC Wokwi
// Retorna temperatura em centésimos de grau Celsius
//
// Exemplo:
// 35.05 °C => 3505
// 25.00 °C => 2500
// =====================================================
int NTC_ADC2Temperature(unsigned int adc_value) {
if (adc_value < 1) {
adc_value = 1;
}
if (adc_value > 1022) {
adc_value = 1022;
}
float celsius =
1.0f / (
log(1.0f / (1023.0f / (float)adc_value - 1.0f)) / NTC_BETA
+ 1.0f / 298.15f
) - 273.15f;
if (celsius >= 0.0f) {
return (int)(celsius * 100.0f + 0.5f);
} else {
return (int)(celsius * 100.0f - 0.5f);
}
}
// =====================================================
// Controle PI original, mantido
// =====================================================
void Controle(int temperaturas) {
sensorValue = temperaturas;
error = sensorValue - setpoint;
proportional = -error * Kp;
float raw = integral + proportional;
if (raw > 0.0f && raw < 255.0f) {
integral -= error * Ki;
}
if (integral > 255.0f) {
integral = 255.0f;
}
if (integral < -254.0f) {
integral = -254.0f;
}
outputValue = (int)(integral + proportional);
if (outputValue < 0) {
outputValue = 0;
}
if (outputValue > 255) {
outputValue = 255;
}
analogWrite(PIN_HEATER, outputValue);
}
// =====================================================
// Montagem dos 3 dígitos
//
// temperatura está em centésimos:
// 3505 => 35.05 °C
//
// O display mostra:
// 35.0
// =====================================================
void JackRipper(void) {
uint32_t temp_saida = 0;
int temperatura2 = temperatura / 10;
// Dígito da direita: décimos
Digitos[0] = abs(temperatura2 % 10);
temp_saida |= (uint32_t)digits[Digitos[0]];
temperatura2 /= 10;
// Dígito do meio: unidades com ponto decimal
Digitos[1] = abs(temperatura2 % 10);
temp_saida |= ((uint32_t)digits[Digitos[1]] | 1UL) << 8;
temperatura2 /= 10;
// Dígito da esquerda: dezenas
Digitos[2] = abs(temperatura2 % 10);
temp_saida |= ((uint32_t)digits[Digitos[2]]) << 16;
noInterrupts();
saida = temp_saida;
interrupts();
}
// =====================================================
// Scanner Charlieplexing
// Sem piscar artificial.
// Preserva PD0 e PD1.
// =====================================================
void scanner(void) {
static byte scanindex = 0;
uint32_t frame = saida;
// Alta impedância somente nos pinos do Charlieplexing: D2..D7
PORTD &= ~CHARLIE_MASK;
DDRD &= ~CHARLIE_MASK;
if (frame & (1UL << scanindex)) {
DDRD = (DDRD & ~CHARLIE_MASK) | direcao[scanindex];
PORTD = (PORTD & ~CHARLIE_MASK) | estados[scanindex];
}
scanindex++;
if (scanindex >= NUM_SCAN) {
scanindex = 0;
}
}