Wat er in de Haddon M1.1 & T1.1 zit
Twee ATmega8-dumps uit de Atlantic/Chofu-controlbox, uit elkaar gehaald met een zelfgeschreven AVR-disassembler, en daarna nagelopen met de meter en een nabouw: de print is pin voor pin doorgemeten en er draait inmiddels een eigen ATmega328P in de IC2-voet, met een echte thermostaat en CV-ketel eraan. Waar het koper de firmware tegensprak, wint het koper — zie De print doorgemeten.
Kort samengevat
M1.1 (IC1) praat via een met Timer1 getimede bit-bang-UART op 666 baud
met de buitenunit (PC3 = TX, PC4 = RX; DIP28-pin 26/27, exact wat WIRING.md zegt). Het
ontleedt de binnenkomende telegrammen: uit bericht-ID 2 haalt het drie temperaturen
— aanvoer, retour en buitentemperatuur — die het door 10 deelt en als hele graden opslaat.
T1.1 (IC2) is geen simpel bedieningspaneel maar een tweezijdige OpenTherm-brug: slave richting de Anna-thermostaat, master richting de CV-ketel. Het laat OpenTherm-frames tussen beide door en herschrijft onderweg specifieke Data-ID's. Dáár zit de bijstook-beslissing.
Ja, een harde lockout bij 5 °C — precies de temperatuur waarbij het in de
praktijk misging. Op adres 0x069c leest M1.1 de opgeslagen buitentemperatuur (SRAM
0x73) en vergelijkt die met 5. Ligt de waarde daaronder (of is hij
negatief), dan wordt de gezondheids-teller hard op 59 gezet — één onder de drempel
van 60 die het vraagrelais uitschakelt. Dat is de enige plek in beide chips die deze teller ónder 60
duwt; alle andere paden klemmen hem er exact op. De code en de waarneming vallen dus samen.
De flash-tabellen die eerder onleesbaar bleven zijn gekraakt: vier telegrammen plus elf per-stand-varianten, alle met CRC-16 = 0. Twee dingen komen daaruit die praktisch van belang zijn: Atlantic moduleert in tien standen waar de ESP32 op zeven staat, en de modusbyte is in de hele flash uitsluitend 0x00 of 0x01 — koelen heeft Atlantic nooit gecommandeerd, terwijl de buitenunit het aantoonbaar wél kan. Zie de telegramsectie.
Op hardware bevestigd gemeten
Na dit verslag is de brug nagebouwd: een eigen ATmega328P in de IC2-socket en een ESP32 in de IC1-socket, die via de 9600-baud-sporen op de print met elkaar praten. Daarmee is een aantal conclusies hieronder niet langer afgeleid maar gemeten.
| Conclusie | Hoe het bevestigd is |
|---|---|
| Beide chips draaien op 8 MHz | Uit de Timer1-herlaadwaarden: M1.1 laadt 0xF84F (1969 cycli) voor een tik van
250 µs, T1.1 0xFC3A (966 cycli) voor 125 µs. Bij 8 MHz komen
beide precies 33 à 34 cycli tekort — de ISR-overhead. Twee verschillende tijdbases,
hetzelfde tekort; bij 4 of 16 MHz loopt het volledig uit de pas. Daarna op de print gevonden:
er ligt een 8 MHz-kristal onder de IC2-voet. En de nagebouwde firmware haalt
over vijf meetintervallen 1,9984 s waar 2,000 s bedoeld is — 0,08% afwijking,
oftewel kristalnauwkeurig. |
| IC1 pin 2/3 ligt gekruist met IC2 pin 3/2 | Volgt al uit de firmware: beide chips zetten UCSRB = 0x98 en maken PD1 uitgang,
dus PD0 is altijd RXD en PD1 altijd TXD — de verbinding móét gekruist zijn. In de
praktijk bevestigd: de ESP32 in IC1 praat met de AVR in IC2 zonder één extra
draad. |
| Socket-pin 13 draagt de OpenTherm van de thermostaat | Een flankenteller op die pin, los van alle decodering, meet 124 flanken per twee seconden met een OpenTherm-thermostaat eraan. Een frame is 34 Manchester-bits, dus 34–68 flanken; twee frames per twee seconden komt daar precies op uit. |
De Manchester-codering uit sub_0892 |
Rust hoog, databit 1 = eerste helft laag, toggle op tik 4, nieuwe bit op tik 8, MSB eerst. Een nagebouwde ontvanger op precies die aannames decodeert de frames van een echte thermostaat met kloppende pariteit. |
| De comparator-leesrichting | ACO rechtstreeks lezen (rust = 1) blijkt goed. Was de lijn omgekeerd,
dan zou het startbit als 0 lezen en zou er überhaupt geen frame uitkomen. |
WackoH's pintabel noemt socket-pin 13 GND en pin 6 NC, waar dit verslag uit de firmware twee volwaardige OpenTherm-kanalen afleidde. Die 124 flanken per twee seconden op pin 13 sluiten de discussie: daar zit de thermostaat.
Zijn waarneming is wel te verklaren. Een OT-ontvangst naar een comparator-ingang heeft vrijwel altijd een weerstandsdeler met één poot aan massa, en met het oog volgen levert dan "pin 13 → GND" op. Zijn schema was naar eigen zeggen nog niet af.
0xFC3A is met de hand afgeregeld
Die herlaadwaarde geeft 966 cycli, geen 1000. De ontbrekende ~34 cycli zijn de interruptlatentie
plus de PUSH's vóór het herladen in Atlantic's eigen ISR. De waarde klopt dus
alleen bij die ISR.
Neem je hem over in code met een langere interruptroutine, dan wordt je tik navenant trager en gaat
het subtiel mis: het startbit lees je nog net binnen zijn tweede helft, en het stopbit klopt per
ongeluk omdat je tegen die tijd de rustlijn leest — maar de 32 databits ertussen zijn rommel.
Je krijgt complete frames met een kapotte pariteit, wat gemakkelijk te lezen is als
een polariteits- of bedradingsprobleem. Zet Timer1 in CTC met
OCR1A = 999, dan reset de teller in hardware en hangt de periode niet meer aan
de lengte van je ISR.
In sub_0a24 staat SBIS PINC,2 → SBI PORTB,2: PC2 laag zet
het relais aan. Direct daarna leest SBIC PINB,2 het relais terug en zet bit 6 van
r25. En sub_0b7e begint met SBRS r25,6 en keert zonder dat bit
meteen terug — staat het relais af, dan zendt T1.1 helemaal niets naar IC1.
Dat pleitte al tegen een simpel warmtevraag-relais, maar wát het schakelde stond nergens in de firmware. Het doormeten geeft het antwoord: PB2 stuurt via T3 twee relais tegelijk aan, K1 én K2, en die verleggen samen het complete aderpaar van de thermostaat rechtstreeks naar de CV-ketel. Het is de noodbrug: valt de controlbox weg, dan praat de thermostaat weer gewoon met de ketel en blijft het huis warm. Zie De drie relais.
De print doorgemeten gemeten
Alles hieronder is met de meter vastgesteld, met beide chips uit de voet en op doorbel/weerstand, zodat geen halfgeleider de meting kon vervuilen. Waar een conclusie uit de firmware kwam en het koper die bevestigt of tegenspreekt, staat dat erbij.
- PC5 is geen "tweede statusuitgang" maar de aansturing van een relais met een wisselcontact van 10 A — het droge contact voor een AAN/UIT-ketel.
- Het statuslampje op de voorkant hangt aan T1.1 PC4, niet aan M1.1 — wat uit de firmware alleen de voor de hand liggende gok is, want M1.1 heeft ook een pin die daarop lijkt. De LED in de CV-schakelaar op het front is wéér iets anders: die hangt aan K3.
- Alle vijf de "nergens beschreven" uitgangen eindigen blind op deze printversie: pin 4, 14, 15, 17 en 18. Het spoor houdt gewoon op. Dat past bij een codebasis die over meerdere modellen gedeeld wordt — de firmware stelt ze in, de print gebruikt ze niet.
IC2-socket, alle 28 pinnen
| Pin | AVR | Gemeten | Betekenis |
|---|---|---|---|
| 1 | RESET | R19 (5k) naar +5 V | pull-up, verder niets |
| 2 | PD0 | naar de IC1-voet, kaal koper | interconnect RX |
| 3 | PD1 | naar de IC1-voet, kaal koper | interconnect TX |
| 4 | PD2 | — | niet aangesloten |
| 5 | PD3 | R13 (330 Ω) naar OK5 pin 3 | zenden naar de ketel, LED-anode kanaal 2, ~11 mA |
| 6 | PD4 | R24 (4k7) naar de emitter van T1 | zenden naar de thermostaat |
| 7 | VCC | +5 V | |
| 8 | GND | massa | ook C5 (100 nF) hierheen |
| 9 | XTAL1 | kristal | 8 MHz bevestigd |
| 10 | XTAL2 | kristal | idem |
| 11 | PD5 | OK5 pin 7 = emitter kanaal 1 | ontvangen van de ketel, zoals voorspeld |
| 12 | PD6 | — | niet aangesloten |
| 13 | PD7 / AIN1 | R3 (33k) omhoog, R4 (4k7) naar massa | ontvangen van de thermostaat, deler 33k/4k7 |
| 14 | PB0 | — | niet aangesloten |
| 15 | PB1 | — | niet aangesloten |
| 16 | PB2 | diode → R26 (10k) → basis T3 | T3 schakelt K1 en K2 tegelijk: de noodbrug |
| 17 | PB3 | — | niet aangesloten |
| 18 | PB4 | — | niet aangesloten |
| 19 | PB5 | nog niet gemeten | open |
| 20 | AVCC | ook naar de basis van T1 | T1 werkt dus in gemeenschappelijke basis |
| 21 | AREF | +5 V | |
| 22 | GND | massa | |
| 23–24 | PC0, PC1 | nog niet gemeten | open |
| 25 | PC2 | dipswitch 2; het andere contact aan massa | ON = laag = normaal bedrijf, OFF = hoog = ketelkant afgeknepen |
| 26 | PC3 | nog niet gemeten | open |
| 27 | PC4 | naar een status-LED | het lampje op de voorkant |
| 28 | PC5 | weerstand naar de basis van T4 | T4 stuurt K3, het aan/uit-ketelcontact |
Van de IC1-socket is tot nu toe alleen pin 14 (PB0) doorgemeten: R21 (10k) naar de status-LED.
Onderdelen
| Ref | Type | Rol |
|---|---|---|
| OK1 | H11D1 | warmtepomp, ontvangen. Pin 5 (collector) aan +5 V |
| OK2 | H11D1 | warmtepomp, zenden. Pin 1 (LED-anode) aan +5 V |
| OK5 | TLP521-2 | de complete ketelkant. Kanaal 1 = ontvangst (pin 8 aan +5 V), kanaal 2 = zenden (pin 3 van PD3) |
| Q1–Q4 | BC558A | PNP, hoogzijde |
| T1–T4 | BC547A | NPN, laagzijde. T2 is de warmtepomp-zendlijn vanaf IC1 pin 26 |
| K1, K2 | relais 24 VDC | samen de noodbrug, beide van T3 af |
| K3 | Omron G2R-1, 12 VDC | 10 A wisselcontact — vermogen, geen signaal |
| LM7818 | regelaar | de 18 V-tak voor OpenTherm; voedt ook de LM7805 |
| LM7805 | regelaar | 5 V-logica, ingang van de uitgang van de LM7818 |
Dat de LM7805 gevoed wordt uit de LM7818 heeft een prettig gevolg: leeft de 5 V, dan leeft de 18 V ook. Een dode OpenTherm-bus bij een werkende chip kan dus nooit een voedingsprobleem in de 18 V-tak zijn.
De drie relais — en waarom er maar één tegelijk mag
+18 V --+-- R27 (100 ohm) --+-- spoel K1 --+
| +-- spoel K2 --+-- collector T3 -- basis: PB2 (pin 16) via diode + R26 (10k)
| |
| +-- spoel K3 ----- collector T4 -- basis: PC5 (pin 28) via R
| emitter T4 ---- frontschakelaar -- massa
|
+-- emitter Q1 / emitter Q4 -- de 18 V-bus naar de thermostaat
Alle drie de spoelen hangen achter dezelfde voorschakelweerstand R27 van 100 Ω. Dat is geen detail: trekken K1+K2 en K3 tegelijk aan, dan zakt de spoelspanning tot een niveau waarop niets meer betrouwbaar schakelt. Atlantic heeft die combinatie ook nooit bedoeld — het is óf een OpenTherm-ketel óf een aan/uit-ketel. Wie deze print nabouwt moet die vergrendeling zelf in de firmware zetten.
| Relais | Aangestuurd door | Functie |
|---|---|---|
| K1 + K2 | PB2 → T3 | Noodbrug. Verlegt het complete aderpaar van de thermostaat rechtstreeks naar de ketel. In het origineel volgt hij PC2; het gedrag "geen relais → geen rapportage aan IC1" past daar precies bij: de controlbox staat dan buitenspel. |
| K3 | PC5 → T4, frontschakelaar in serie in de emitter | Warmtevraag-contact voor een AAN/UIT-ketel. Tien ampere is volstrekt overbodig
voor een signaallijn. De firmware zet PC5 hoog vanaf een ketelsetpoint van 20 °C
(0944 CPI 0x01 / 094c CPI 0x14). De schakelaar op het front is de
vrijgave, en de LED in die schakelaar hangt aan hetzelfde pad. |
Het origineel klakkeloos volgen op PC5 betekent dat je je eigen brug overbrugt zodra er warmtevraag komt — bij een OpenTherm-ketel is dat precies verkeerd. PC5 hoort instelbaar te zijn, met de noodbrug als winnaar in de vergrendeling: die houdt het huis warm als de rest eruit ligt.
De twee OT-interfaces zijn niet symmetrisch gebouwd
Dit is de belangrijkste vondst voor iedereen die deze print aanstuurt. De firmware behandelt beide kanten als spiegelbeeld, maar het koper doet dat niet.
| Ketelkant (master) | Thermostaatkant (slave) | |
|---|---|---|
| Zenden | PD3 → R13 (330 Ω) → OK5 kanaal 2 | PD4 → R24 (4k7) → emitter T1 (basis vast op AVCC) → basis Q4 → Q4 (PNP, emitter op +18 V) |
| Ontvangen | PD5 digitaal, via OK5 kanaal 1 met R18 (10k) als emitterweerstand | PD7 via de analoge comparator tegen de bandgap, deler R3 33k / R4 4k7 |
| Rustniveau op de pin | laag | hoog |
| Hoe het origineel leest | SBIS PIND,5 | SBIC ACSR,5 |
De ketelkant is een keurige optocoupler-interface. De thermostaatkant is een discrete hoogzijdedriver: T1 in gemeenschappelijke basis stuurt Q4 aan, die de 18 V op de bus zet. Bewust ontworpen, geen bijzaak — en met de omgekeerde zendpolariteit ten opzichte van de ketelkant. PD4 moet laag om spanning op de thermostaatbus te zetten, PD3 moet hoog om de opto-LED te laten branden.
Die twee tegengestelde leesinstructies in de originele firmware zijn dus geen slordigheid maar de exacte weerslag van deze asymmetrie. Wie ze gelijktrekt "omdat het toch hetzelfde protocol is", krijgt een kant die zwijgt.
De dipswitches
| Schakelaar | Aansluiting | Betekenis |
|---|---|---|
| dip 1 (pin 1/3) | pin 1 op het NC-contact van K2 | zit in het schakelpad van de noodbrug zelf, niet op een socket-pin. Vermoedelijk de tweede ader van het paar dat de brug verlegt. deels open |
| dip 2 (pin 2/4) | pin 2 op PC2 (socket-pin 25), pin 4 aan massa | Keteltype. ON = PC2 naar massa = laag = normaal OpenTherm-bedrijf. OFF = open = PC2 hoog via de interne pull-up = de hele ketelkant afgeknepen. |
Tijdens de eerste tests met de nagebouwde firmware stonden beide dipswitches op OFF.
PC2 stond daardoor hoog, en in de originele logica knijpt dat de complete ketelkant af: geen zenden
(0x0896), geen doorgeven (0x053e), geen relais (0x0a94). Er werd
dus urenlang gezocht naar een firmware- of bedradingsfout terwijl de print zelf in de verkeerde stand
stond. Er zit geen knop op het front voor deze functie, dus niets wees erop.
Wat de nabouw leerde
De brug draait inmiddels: een eigen ATmega328P in de IC2-voet, een ESP32 in de IC1-voet, met een echte OpenTherm-thermostaat aan de ene kant en een CV-ketel aan de andere. Beide kanten decoderen frames zonder pariteitsfouten. Vijf dingen die daarbij naar boven kwamen en die niet uit de disassembly alleen af te leiden waren:
| Vondst | Wat er misging, en waarom |
|---|---|
| De timerwaarde is niet overdraagbaar | 0xFC3A is met de hand afgeregeld op Atlantic's eigen ISR. Een langere ISR maakt de
tik navenant trager, en het symptoom is verraderlijk: complete frames met kapotte pariteit,
wat leest als een bedradingsprobleem. CTC-modus met OCR1A = 999 haalt de
afhankelijkheid weg. |
| De decoder meet flankafstanden, geen vaste momenten | Het origineel vergelijkt de tijd sinds de vorige flank met 0x06 en leidt daaruit af
of het een bit-midden of een bit-grens was. Sampelen op een vast punt in de bit lijkt equivalent
maar is het niet: het verliest de zelfsynchronisatie die Manchester juist zo robuust maakt. |
| Nooit zwijgen tegen de master | De thermostaat is de OpenTherm-master en verwacht op elke vraag antwoord. Antwoordt de brug niet
— bijvoorbeeld omdat de ketel zelf niet reageert — dan haakt de thermostaat na een paar
pogingen af. Het origineel doet dit goed: buffer 0x89 heeft een
Unknown-DataId-fallback voor precies dit geval. |
| Nul is een gevaarlijk antwoord | Weet je een waarde niet, antwoord dan Data-Invalid en niet 0. Een modulerende thermostaat leest 0 °C ketelwater als ijskoud en vraagt vol vermogen. |
| Een "dood" apparaat moet zichzelf kunnen herontdekken | De brug merkt dat de ketel weg is doordat een antwoord uitblijft, en gaat dan zelf antwoorden. Maar een antwoord komt alleen op een vraag, en die vraag werd in de zelfstandige tak niet meer gesteld. Gevolg: viel de ketel één keer weg terwijl er een thermostaat praatte, dan kwam hij nooit meer terug — ook niet als hij er allang weer was. Elke "is X er nog"-vlag heeft een pad nodig dat hem wéér aan kan zetten, en dat pad mag niet achter de vlag zelf verstopt zitten. |
| Geef alles door, ontleed het boven | De eerste opzet stuurde een samenvatting naar de ESP32: acht waarden die de AVR zelf uitpakte. Elk Data-ID dat je er later bij wilde kostte een nieuwe burn. Nu gaat elk frame rauw door, in beide richtingen, en mag de ESP32 ook zelf frames laten versturen. Wat er vervangen wordt blijft in de AVR — dat gebeurt midden in het doorgeven en een heen-en-weer over de link haalt de OpenTherm-timing niet — maar wát er vervangen wordt komt van boven. De grens ligt niet bij "wat is handig" maar bij wat moet blijven werken als de bovenliggende chip er niet is: timing, doorgeven, nooit zwijgen, de noodbrug en de anti-pendeltijd. De rest is beleid. |
| De poort-init is functioneel | Het origineel zet DDRB = 0x1F, PORTB = 0x00,
DDRC = 0x30, PORTC = 0xCF, DDRD = 0x1E,
PORTD = 0x19 — inclusief de pull-up op RXD en het actief laag houden van pinnen
die verder nergens beschreven worden. Alleen instellen wat je denkt nodig te hebben laat lijnen
zweven, en dat is van buitenaf onzichtbaar. |
Methode — en twee bugs in het gereedschap
Er was geen AVR-toolchain beschikbaar, dus is een eigen recursive-descent disassembler geschreven
(avrdis.py) die de controlflow volgt vanaf de reset- en interrupt-vectoren, zodat
datatabellen niet als instructies worden gelezen. Onderweg zijn twee fouten in dát gereedschap
gevonden en hersteld — beide hadden gevolgen voor de analyse:
Een SBRC/SBRS/SBIC/SBIS gevolgd door
RJMP (het compiler-patroon voor if (!vlag) return;) werd als doodlopend
behandeld. Daardoor belandde bijna alle main-loop-logica in de "data"-bak. Na de fix ging M1.1 van
1052 naar 2024 zinvolle listingregels.
SBRC en SBRS verschillen in bit 9
(1111 110d… vs 1111 111d…); de decoder keek naar bit 3, dat bij allebei 0
is. Gevolg: 17 instructies in M1.1 en 24 in T1.1 hadden de omgekeerde betekenis.
Dat draaide onder meer de polariteit van de 666-baud-zendlijn om en verkeerde takken in de
hoofdlus. Alles hieronder is gebaseerd op de gecorrigeerde listings.
Waar een conclusie voor de hand ligt maar niet klopt, staat dat er expliciet bij. Een verslag dat alleen het eindantwoord geeft, laat de volgende in dezelfde valkuil lopen.
Topologie
Dit is het punt waar de meeste analyses de mist in gaan: T1.1 hangt tussen Anna en de CV-ketel in, niet als eindpunt eraan. Alle warmtevraag loopt dus fysiek door T1.1 heen, en die kan per OpenTherm-bericht besluiten of de vraag naar de ketel gaat of (via M1.1) naar de warmtepomp.
M1.1 (IC1) — de warmtepomp-link
Architectuur bevestigd
Stack-init op 0x045F (RAMEND van een ATmega8), BSS-clear bij boot, vijf init-routines,
dan een oneindige superloop van negen taken. Drie van de 19 interrupt-vectoren zijn actief:
| Vector | Adres | Functie |
|---|---|---|
| Timer1 overflow | 0x0a6c | Systeemtick. Reload 0xF84F = 1969 cycli, plus ~29 cycli ISR-overhead vóór de
reload → ≈250 µs. Kruiscontrole: teller 0x61 telt tot 100 en
0x62 tot 40 → samen 0,98 s, dus een 1-secondetik. Klopt. |
| USART RXC | 0x0b98 | Leest één byte van T1.1, zet een vlag. Geen framing hier. |
| INT0 | 0x0b66 | Lege stub (push/pop). Vermoedelijk alleen wake-from-sleep. |
Je zou de verhouding van ongeveer 6× kunnen afdoen als oversampling en het daarbij laten.
De code is explicieter: de bit-teller 0x68 wordt op 6 geladen (op 0x0376
en drie plekken in sub_083a) en per tick afgeteld. 6 × 250 µs = 1500 µs =
666,7 baud. Exact.
Pin-toewijzing bevestigd
| Pin | Richting | Functie |
|---|---|---|
| PC3 (pin 26) | uit | TX naar buitenunit, 666 baud bit-bang. Na de bugfix blijkt de lijn niet-inverterend: SBRS r20,7 → bit 1 = pin HOOG. |
| PC4 (pin 27) | in | RX van buitenunit |
| PC0 | in | CV-only-schakelaar — actief bij HOOG |
| PC1 | in | Test-knop — actief bij LAAG (ingedrukt) |
| PB0 | uit | vraagrelais — "roep de warmtepomp aan" |
| PB1 | uit | statuslampje (aan zodra statuscode ≠ 0) |
| PB3 | uit | tweede, parallel meelopende bitstroom naast PC3 functie onopgehelderd |
| PB2 / PB4 | uit | statussignalen rond het T1.1-telegram |
| PD0 / PD1 | in / uit | hardware-UART naar T1.1 (9600 baud) |
De temperatuur-parser bevestigd
In sub_0308 vanaf 0x03dc controleert
M1.1 of het ontvangen bericht ID 2 is (CPI r18, 0x02) en haalt er
vervolgens drie 16-bits waarden uit, elk gedeeld door 10 via een aftrek-lus:
| SRAM | Bron in frame | Betekenis | Bijzonderheid |
|---|---|---|---|
| 0x71 | 0x7e–0x7f | aanvoertemperatuur | alleen positief |
| 0x72 | 0x80–0x81 | retourtemperatuur | alleen positief |
| 0x73 | 0x82–0x83 | buitentemperatuur | heeft een aparte negatieve tak (0x0460–0x0496) |
Dat alléén de derde waarde een tekenafhandeling heeft is het beslissende bewijs dat dit de
buitentemperatuur is — dat is de enige van de drie die onder nul komt. De ontvangbuffer begint op
0x7b met de 0x91-header, dus 0x7c = ID, 0x7d =
lengte, daarna de payload: precies de indeling uit het bekende Chofu-protocol.
De 5°C-uitschakeling bevestigd
SRAM 0x65 functioneert als een gezondheids-aftelteller. De Timer1-ISR telt hem
elke 60 ticks (~15 ms) met 1 omlaag; de regellogica laadt hem telkens terug op 180 zolang alles goed
gaat. Zakt hij onder 60, dan schakelt sub_062e het vraagrelais uit
(0x06d0: CPI r16,0x3c → CBI PORTB,0).
De buitentemperatuur-check staat op 0x069c:
069c LDI r30, 0x73 ; laad buitentemperatuur 06a0 LD r16, Z 06a4 CPI r21, 0xc8 ; >= 200 ? (= negatieve temperatuur) 06a6 BRCC 0x06b0 ; ja -> koud 06a8 CPI r21, 0x05 ; < 5 ? 06aa BRCS 0x06b0 ; ja -> koud 06ac ANDI r23, 0x7f ; nee: "koud"-vlag wissen, klaar ... 06b0 ORI r23, 0x80 ; "koud"-vlag zetten ... 06c2 LDI r16, 0x3c ; 60 06c4 DEC r16 ; -> 59 06ca ST [0x65], r16 ; teller HARD onder de drempel
Alle andere paden die 0x65 verlagen klemmen op precies 60
(CPI r16,0x3c; BRCC …; LDI r16,0x3c op 0x0714 en 0x0720) en
kunnen het relais dus nooit uitschakelen. Alleen de koud-tak zet hem op 59. Daarnaast
wordt de teller in sub_04be (0x0518) alleen tot 180 herladen zolang de
"koud"-vlag niet gezet is. Onder de drempel valt de warmtepomp dus uit én blijft hij uit.
De drempel in de code is 5, en dat is ook de buitentemperatuur waarbij de installatie in de praktijk overstapte naar de CV-ketel. Code en waarneming bevestigen elkaar, dus er is geen aanvullende verklaring nodig: dit is gewoon een door Atlantic ingebouwd bivalentiepunt, hard vastgezet in de firmware en niet instelbaar.
De ESP32 kent deze grens niet en regelt puur op aanvoer versus setpoint — het draaibereik onder 5 °C is dus feitelijk vrijgekomen door de vervanging van IC1.
Knoppen en schakelaar bevestigd
- PC0 — CV-only-schakelaar (
sub_02ae,SBIS→ actie bij HOOG): zet het vraagrelais uit en reset de tellers. Met de pull-up op PORTC betekent dit dat de open stand "alleen CV" is; sluiten geeft de warmtepomp vrij. Dat is een verstandige fail-safe: valt de bedrading weg, dan stookt de ketel door. - PC1 — Test-knop (
sub_02d2,SBIC→ actie bij LAAG): zet het vraagrelais weer aan en herlaadt alle tellers, mits de laatst gerapporteerde snelheid ≥ 20 was en er geen communicatiestoring geregistreerd staat. Dit is precies de handmatige tegenhanger van de automatische uitschakeling hierboven — wat verklaart waarom deze knop werkte om de unit terug te dwingen naar warmtepompbedrijf. - Reset-knop: komt nergens in de code voor, dus vrijwel zeker direct op de AVR-RESET-pin bedraad.
- Let op waar het voorpaneel-lampje zit. Beide chips hebben een storingsuitgang,
en dat is verwarrend. M1.1's PB1 (socket-pin 15) is een simpele aan/uit die "foutcode is niet nul"
volgt (
0x04b6). Maar het lampje op de voorkant is de knippercode van T1.1 op PC4, socket-pin 27 — in de praktijk bevestigd: dat lampje stopte met werken zodra de originele T1.1 uit de socket ging, terwijl de ESP32 in IC1 gewoon doordraaide. gemeten
T1.1 (IC2) — de OpenTherm-brug
T1.1 is géén bedieningspaneel dat de ketel naar Anna emuleert. De analoge comparator
wordt niet uitgezet maar juist gebruikt. En sub_0870 ziet eruit als een BCD-conversie,
maar is het niet. Alle drie liggen ze voor de hand bij een eerste lezing; alle drie zijn ze fout.
Waarom het een brug is, geen eindpunt bevestigd
- Twee ontvangstkanalen: de analoge comparator (
SBIC/SBIS ACSR,5= ACO, insub_0286) en PD5 (sub_0398).ACSR = 0x40zet bit 6 (ACBG) — de bandgap-referentie — en laat ACD op 0, dus de comparator staat juist aan. Dat is de klassieke OpenTherm-ontvangstschakeling. - Twee zendkanalen: PD3 (
sub_0892) en PD4 (sub_0958). - Beide richtingen worden gekopieerd:
0x0490kopieert buffer0x89→0x8d, en0x053ekopieert0x8d→0x89. - De berichttypes verraden de rollen. In OpenTherm zit het type in bits 6–4 van de eerste byte:
| Opgebouwde byte | Type | Betekenis | Rol |
|---|---|---|---|
| 0x40 / 0xC0 | 100 | Read-Ack | antwoord van een slave → richting Anna |
| 0x10 / 0x90 | 001 | Write-Data | opdracht van een master → richting ketel |
| 0x80 | 000 | Read-Data | vraag van een master → richting ketel |
| 0x70 | 111 | Unknown-DataId | standaardantwoord voor niet-herkende ID's |
Die laatste is doorslaggevend: een Unknown-DataId als default-tak (0x0850)
is leerboek-slavegedrag en komt in geen enkele andere toepassing voor.
Een doorgeefluik met vijf uitzonderingen bevestigd
T1.1 implementeert de OpenTherm-specificatie niet. Beide richtingen hebben
dezelfde opbouw: kopieer byte 0 en 1 (berichttype + Data-ID) van bron- naar doelbuffer, vertak op de
Data-ID, en val voor al het overige door naar een generiek pad dat byte 2 en 3 ook gewoon
overneemt — een compleet ongewijzigd frame. Die paden staan op 0x0532
(richting ketel) en 0x066a (richting Anna) en doen letterlijk niets anders dan twee bytes
doorschuiven.
| Richting | Onderschepte Data-ID's | Rest |
|---|---|---|
| Anna → ketel | 0, 1, 16 | ongewijzigd door |
| Ketel → Anna | 0, 1, 16, 25, 28 | ongewijzigd door |
Bij ID 25 (Boiler Water Temperature) en ID 28 (Return Water Temperature) vervangt T1.1 de meetwaarde van de ketel door een waarde die via de 9600-baud-link van M1.1 binnenkwam: de aanvoer- respectievelijk retourtemperatuur van de warmtepomp.
Anna denkt dus dat ze met één ketel praat, en ziet de temperaturen die het systeem werkelijk produceert — ook wanneer de gasketel koud staat en de warmtepomp al het werk doet. Zonder die substitutie zou Anna een koude ketel zien terwijl het huis warm wordt, en zou haar eigen modulatielogica op hol slaan. Dit is het scharnierpunt van de hele hybride opzet.
Herleiding. T1.1 leest het telegram van M1.1 vanaf index 6: index 7 (aanvoer)
gaat naar SRAM 0x71, index 8 (retour) naar 0x72, en die twee worden bij
0x05c6 en 0x05f6 in de OT-antwoorden geschoven. Let op de wegwerp-LD
op 0x0212: index 9 (buitentemperatuur) wordt gelezen en meteen overschreven door index 10
(statuscode) — daardoor lijkt de veldindeling één positie verschoven als je er
snel overheen leest.
Voor wie T1.1 wil nabouwen is dit de belangrijkste vondst van het hele onderzoek: er is geen OpenTherm-stack nodig, alleen een transparante brug plus vijf onderscheppingspunten. Alles wat je niet kent geef je door, en dat is meteen ook het veiligste gedrag.
De herkende Data-ID's
| ID | Standaardbetekenis |
|---|---|
| 0 | Status |
| 1 | Control Setpoint (CV-aanvoertemperatuur) |
| 2 / 3 | Master- / Slave-configuratie |
| 7 | Cooling Control Signal |
| 14 | Max. relatief modulatieniveau |
| 16 | Room Setpoint |
| 17 | Relatief modulatieniveau |
| 24 | Room Temperature |
| 25 | Boiler Water Temperature |
| 28 | Return Water Temperature |
| 49 | fabrikant-specifiek bereik |
Pariteit en Manchester bevestigd
sub_0870/sub_0872 is geen BCD-conversie maar de
OpenTherm-pariteitsberekening: negen keer roteren door carry, en bij elke gezette bit
SWAP r18 (togglet 0x08 ↔ 0x80), waarna ANDI r18,0x80 + ADD r16,r18
het pariteitsbit in bit 7 schuift. Bewijs: dit zijn exact de twee ROL-instructies die T1.1
bezit. Dat sub_0872 ook los wordt aangeroepen — zonder de r18-init — laat zien
dat de pariteit over het hele 4-byte frame wordt doorgeteld.
De fysieke laag is Manchester: in sub_0892 wacht de code tot de tick-teller op
4 staat (de helft van 8) en inverteert dan de pin op basis van de
teruggelezen stand (SBIC PIND,3). Timing: reload 0xFC3A = 966 cycli plus
overhead ≈ 124 µs; 8 ticks per bit ≈ 1,00 ms = 1000 bit/s, precies de
OpenTherm-bitrate.
Pin-toewijzing T1.1 bevestigd
| Pin | Richting | Functie |
|---|---|---|
| PD0 / PD1 | in / uit | hardware-UART naar M1.1 (9600 baud) |
| PD3 | uit | OpenTherm zenden naar de CV-ketel (T1.1 als master), Manchester — buffer 0x8d |
| PD4 | uit | OpenTherm zenden naar Anna (T1.1 als slave), Manchester — buffer 0x89 |
| AIN1 = PD7 socket-pin 13 | in | OpenTherm ontvangen van de thermostaat, via ACO + bandgap — buffer 0x89 gemeten |
| PD5 | in | OpenTherm ontvangen van de CV-ketel — buffer 0x8d |
| PC2 | in | knop/modusingang; stuurt ook PB2 aan en kiest tussen de twee brugrichtingen (0x053e) |
| PC4 socket-pin 27 | uit | het statuslampje op de voorkant — knippercode. PORTC = 0xCF bij de init zet hem laag, en 0x0b6c ook zodra de foutcode nul is, dus laag = uit gemeten |
| PC5 socket-pin 28 | uit | stuurt via T4 relais K3 aan: het droge 10 A-contact voor een AAN/UIT-ketel, met de frontschakelaar in serie. Hoog vanaf een ketelsetpoint van 20 °C. Uit de firmware alleen lijkt dit een statusuitgang gemeten |
| PB2 socket-pin 16 | uit | stuurt via T3 K1 en K2 tegelijk aan — de noodbrug die het aderpaar van de thermostaat rechtstreeks naar de ketel verlegt. Volgt PC2, en zonder dit relais zendt T1.1 niets naar IC1 gemeten |
| PD2, PB0, PB1, PB3, PB4 | uit | alle vijf als uitgang geconfigureerd, nergens beschreven, en op deze print niet aangesloten — het spoor eindigt blind gemeten |
Dit viel te herleiden uit de bufferadressen, want elke zijde heeft zijn eigen 4-byte frame-buffer en de code verraadt welke rol daarbij hoort:
- In buffer
0x89worden Read-Ack-antwoorden opgebouwd (0x40/0xC0op0x0628,0x068c,0x06a8,0x06be) plus de Unknown-DataId-fallback. Dat is slavegedrag. Die buffer wordt gezonden doorsub_0958op PD4, en gevuld door de comparator-ontvanger. → PD4 en de comparator vormen de Anna-kant. - In buffer
0x8dworden Read-Data en Write-Data opgebouwd (0x80,0x10,0x90op0x04be,0x04e6,0x051a). Dat is mastergedrag. Die buffer gaat viasub_0892naar PD3 en wordt gevuld door de PD5-ontvanger. → PD3 en PD5 vormen de ketel-kant.
De brug is dus keurig symmetrisch: 0x0490 kopieert 0x89 → 0x8d
(Anna naar ketel) en 0x053e kopieert 0x8d → 0x89 (ketel naar Anna),
met de ID-specifieke herschrijvingen ertussen.
Waar de bijstook-beslissing valt interpretatie
Zoek je naar een CV-ketelrelais, dan vind je het niet — en dan is de verleiding groot om te
concluderen dat bijstook buiten de firmware om geregeld wordt. Dat klopt niet: de ketel wordt
niet via een relais aangestuurd maar via OpenTherm, door T1.1 zelf. De relevante logica staat vanaf 0x0676:
- De van Anna ontvangen Control Setpoint wordt bewaard in
0x6e/0x6f. - Op
0x0688wordt die vergeleken met0x14(= 20). - Onder de drempel antwoordt T1.1 met statusdata
0x00(idle); erboven met0x02(CH-modus actief) of0x0a(CH-modus + vlam). - Op
0x04beschrijft T1.1 een Write-Data ID1 met waarde0x0a00— een Control Setpoint van 10,0 °C richting de ketel, feitelijk "blijf uit".
Dat is dus het omschakelpunt: T1.1 bepaalt met een drempel op de warmtevraag of de ketel een reële aanvoertemperatuur krijgt of op een pro-forma 10 °C wordt gezet. De precieze koppeling met M1.1's vraagrelais heb ik niet volledig uitgewerkt.
Protocol M1.1 ↔ T1.1 bevestigd
Hardware-UART, UBRRL = 0x67 met U2X = 1 → 8.000.000 / (8 × 104) ≈
9615 baud. UCSRC = 0x8E geeft 8 databits, geen pariteit en
twee stopbits — eerdere versies van dit verslag zeiden 8N1. Voor een ontvanger
maakt dat niets uit, die kijkt alleen naar de eerste stopbit. Beide kanten sturen een
12-byte telegram: een kop-/statusbyte (bij M1.1 vast 0xF0), tien
databytes, en als sluitbyte een eenvoudige 8-bits optelsom — geen CRC.
T1.1's zendlus loopt van SRAM 0x7C tot 0x88 — precies twaalf — en
zijn eerste twee bytes zijn 0x00. M1.1 stuurt hierover onder meer de uit ID2
geparste temperaturen en zijn statuscode; T1.1 gebruikt die statuscode voor zijn foutcode-indicator.
De statuscode zit op telegram-index 10 en belandt in T1.1 op SRAM 0x73. De tabel in
sub_0aa0 vertaalt hem naar 1–7 (0x01→1, 0x41→2,
0x46/47/48/52/14/15→3, 0x31→4, 0x45→5,
0x53→6, overig→7), met 0 = alles in orde en 100 = vastgelopen.
Twee dingen vallen op. Ten eerste: M1.1 zet zijn statuscode maar op drie waarden
— 0 bij init, 0x01 (timeout) en 0x64 = 100 (vastgelopen). Dat zijn de
enige LDI-waarden die naar dat veld worden geschreven. Alle andere ingangen van T1.1's
tabel (0x41, 0x46, 0x52 …) kan deze M1.1-firmware
nooit produceren. T1.1 draagt dus een bredere, generieke tabel — vermoedelijk
één firmware die Atlantic over meerdere modellen inzet.
Ten tweede: gelezen als decimale cijfers zijn die waarden 14, 15, 31, 41, 45, 46, 47, 48, 52, 53 — een reeks die sterk lijkt op de storingsnummers die Chofu/Hitachi-buitenunits hanteren. De tabel groepeert ze in zeven weergavecategorieën. Welk nummer bij welke storing hoort, staat niet in deze firmware; daarvoor is Atlantic/Chofu-servicedocumentatie nodig. rest onopgelost
Dat T1.1 een bredere tabel draagt dan deze M1.1 kan vullen, is inmiddels geen vermoeden meer maar aangetoond: beide chips bevatten dode code van elkaar. Zie de dekkingscontrole.
Vergelijking met chofu_wp.cpp
Dat M1.1 vier telegrammen stuurt was al bekend uit JGC's werk. Hieronder staat het uit de binary zelf afgeleid, en dan blijkt het beeld rijker dan "drie vaste plus één uit een opzoektabel".
De uitgaande telegrammen zijn volledig gedecodeerd opgelost
De flash-tabellen rond 0x0baa–0x0c4f zijn gekraakt. De sleutel zat in
sub_083a: de byteteller loopt af van 45, verdeeld over vier segmenten van
15 + 10 + 10 + 10 bytes. Bij 10 bits per byte is dat exact
12 + 8 + 8 + 8 telegrambytes.
De codering is: 8 databits LSB-first, dan een stopbit (1) en meteen de startbit (0) van de
volgende byte, aaneengeschakeld en MSB-first in de flash-bytes gepakt. Omdat 10 niet op 8
deelt, schuift de framegrens elke byte 2 bits op — en precies daarom werkt bit-omkering per
flash-byte alleen voor de eerste byte (die geeft correct 0x19) en loopt het
daarna uit de pas. Met de juiste framing valt alles op zijn plek.
Van elk gedecodeerd record is de CRC-16/CCITT-FALSE over het hele telegram 0x0000 — precies de geldigheidseigenschap van dit protocol. Ook alle stopbits staan op hun plaats. De kans dat een verkeerde decodering dat zestien keer op rij haalt, is nul.
De vier telegrammen, in de volgorde waarin ze per cyclus worden gezonden:
@0x0bbe (15B) 19 01 0c 00 00 00 00 00 00 00 aa 35 ID1, 12 bytes @0x0be2 (10B) 19 02 08 <modus> <stand> 00 <crc> ID2, variabel (zie tabel) @0x0bce (10B) 19 03 08 b2 02 00 c1 9a ID3 @0x0bd8 (10B) 19 00 08 00 00 00 d9 b5 ID0
Het variabele ID2-telegram is geen berekening maar een vooraf uitgerekende tabel van elf
varianten, geadresseerd als 0x0be2 + 10 × (stand / 4):
| Adres | Telegram | Modus | Stand |
|---|---|---|---|
| 0x0be2 | 19 02 08 00 00 00 9d 36 | 0x00 = uit | 0 |
| 0x0bec | 19 02 08 01 01 00 99 37 | 0x01 = verwarmen | 1 |
| 0x0bf6 | 19 02 08 01 02 00 cc 64 | 0x01 | 2 |
| … | … | … | 3 t/m 9 |
| 0x0c46 | 19 02 08 01 0a 00 45 cd | 0x01 | 10 |
1. Atlantic gebruikt tien standen, de ESP32 maar zeven. De tabel loopt door tot
stand 10, en dat is ook werkelijk bereikbaar: in sub_04be wordt de stand-variabele
begrensd op 40 (CPI r16,0x28) en de index is stand / 4, dus 40 / 4 =
10. In chofu_wp.h staat max_stand_{7}, en in de YAML is
max_stand uitgecommentarieerd — dus die 7 geldt. Er liggen drie vermogenstrappen
ongebruikt.
2. Koelen zit er nergens in. In alle elf varianten is de modusbyte uitsluitend
0x00 of 0x01. Een 0x02 komt in de hele flash niet voor.
Atlantic heeft koelbedrijf via deze binnenunit dus nooit gecommandeerd.
Dat betekent alleen niet dat de buitenunit het niet kan.
gemeten Modus 0x02 sturen werkt: de compressor
start en de aanvoer zakt onder de retour, wat onmiskenbaar koelbedrijf is. De binnenunit vroeg er
nooit om, de buitenunit begrijpt het prima. Dat is precies het soort ruimte dat je wint door de
controller te vervangen in plaats van hem te bedienen.
Ten slotte staan er op 0x0baa en 0x0bb4 nog twee geldige telegrammen
(19 02 08 00 ff 00 9e c9 en 19 03 08 00 00 00 37 67, beide CRC-correct) die
door géén enkele adresberekening in de code worden bereikt — restanten van een eerdere
firmwareversie, of van een variant met een tweede ID3-vorm.
| Aspect | Atlantic M1.1 | ESP32 chofu_wp.cpp |
|---|---|---|
| Zendtelegrammen | ID1, ID2, ID3, ID0 per cyclus — identieke bytes als DATA1/3/0 | 4 roterend (DATA0–3), zelfde inhoud |
| Aantal standen | 1 t/m 10 | 1 t/m 7 (drie trappen onbenut) |
| Koelen (modus 0x02) | nooit — alleen 0x00 en 0x01 in flash | aanwezig en op hardware bewezen werkend |
| Ontvangst | parseert ID2 → aanvoer/retour/buiten, gedeeld door 10 | parseert ID1/2/3, /10 met decimaal |
| CRC-16 | niet berekend maar vooraf uitgerekend en in flash meegebakken (nul ROL-instructies in de hele binary) | live berekend — flexibeler |
| Buitentemp-lockout | ja, bij 5 °C (en bij negatief) | geen — regelt puur op aanvoer vs. setpoint |
| Bijstook | via T1.1's OpenTherm-master-kanaal naar de ketel | alleen een indicatie-vlag; de gebruiker regelt de ketel zelf |
De eindconclusie. Qua telegraminhoud is er geen gat: de bytes die M1.1 zendt zijn byte-voor-byte dezelfde als die de ESP32 opbouwt, en de live CRC-berekening is zelfs algemener dan Atlantics vooraf uitgerekende tabel. Maar qua bereik zijn er twee echte verschillen, beide in het voordeel van de ESP32 op één punt en in het nadeel op het andere: de 5 °C-lockout is verdwenen (winst), en het standbereik is van 10 naar 7 teruggebracht (verlies).
Dekkingscontrole: zit er nog iets in de chips? volledig geauditeerd
Op de vraag "zit er nog iets in wat we gemist hebben" is het eerlijke antwoord: dat weet je pas als je alles boekt. Daarom is elke byte van beide dumps ingedeeld als code, tabel of leeg. Dat leverde twee nieuwe feiten op en een sluitend bewijs.
| Boeking | M1.1 | T1.1 |
|---|---|---|
| Bereikbare code | 2940 B | ~3000 B |
| Telegramtabellen | 166 B | — |
| Onbereikbare code | 12 B | 116 B |
| Rest van de 8 KB flash | volledig 0xFF (onbeschreven) | |
| Losse 512 B achter de flash | de EEPROM — volledig 0xFF | |
Geen van beide chips bevat een IJMP of ICALL. Er zijn dus geen berekende
sprongen, en daarmee is een recursive-descent-trace vanaf de vectoren aantoonbaar compleet:
er kan geen code zijn die alleen via een sprongtabel bereikbaar is. Ook geen enkele
EEPROM-schrijfactie en geen SPM, dus niets dat zichzelf tijdens bedrijf aanpast.
Vondst 1: M1.1 heeft dode ADC-code
De 12 onverklaarde bytes op 0x0158 zijn twee complete, maar nooit aangeroepen
initroutines (geverifieerd: geen enkele sprong of call komt hier uit):
0158 LDI r16, 0x86 / OUT ADCSRA, r16 / RET <- zet de ADC aan (prescaler /64) 015e LDI r16, 0x40 / OUT ACSR, r16 / RET <- zet de comparator aan (bandgap)
Een eerdere revisie las dus een analoge sensor uit. In deze build is die mogelijkheid bewust uitgeschakeld door de aanroep te verwijderen. Dat bevestigt de eerdere conclusie dat M1.1 geen eigen sensor gebruikt — maar nu met de nuance dat de hardware-voorziening er wel was.
Vondst 2: de "gedeelde firmware"-theorie is nu bewezen
De tweede dode routine in M1.1 is byte voor byte identiek aan sub_015c
in T1.1 — waar hij wél wordt aangeroepen, als OpenTherm-ontvangstweg. Omgekeerd bevat T1.1
116 bytes onbereikbare code in drie blokken, waarvan twee onderling identiek. Onder die dode code zit
een pad dat het pariteitsbit omklapt en bit 0 van de statusdata zet — in OpenTherm het
storingsbit, dus een "meld een fout aan Anna"-route die deze build nooit neemt.
Dat beide chips dode code van elkaar bevatten, maakt van de eerdere veronderstelling een vaststelling: Atlantic compileert één gedeelde codebasis per model en laat de ongebruikte paden in flash staan. Dat verklaart ook waarom T1.1's foutcodetabel veel meer waarden kent dan deze M1.1 ooit kan versturen.
Omdat de EEPROM leeg is en geen van beide chips er ooit naar schrijft, bestaat er geen opgeslagen configuratie: geen kalibratie, geen inbedrijfstellingsparameters, niets. Alles zit hardcoded in flash. De 5 °C-grens was dus ook niet instelbaar geweest, hoe diep een installateur ook in een servicemenu had gekeken.
Open vragen
Inmiddels is de brug nagebouwd en op echte hardware gedraaid, dus een deel hiervan is niet meer afgeleid maar gemeten — zie Op hardware bevestigd. Wat resteert:
- Deels open De rest van de printtopologie. Nog niet doorgemeten: waar de collector van Q4 uitkomt, waar de basis van Q1 vandaan komt, de common van K1, wat K2 precies schakelt, en waar dip-pin 3 en de gelijkrichterbrug op uitkomen. Zie De print doorgemeten voor wat er wél ligt.
- Onopgelost Wat PB3 doet. Wel scherper in beeld:
PC3 draagt de echte telegrammen uit flash (CRC-geverifieerd), terwijl PB3 in hetzelfde tempo bytes
uit de RX-buffer op
0x7buitschuift. Die stroom heeft géén geldige UART-framing, dus PB3 kan geen tweede protocollijn zijn — een diagnose-/echo-uitgang of een niet-bevolkte voorziening in deze printversie is aannemelijker. Voor het nabouwen van M1.1's gedrag maakt het niets uit: het commando zit volledig op PC3. - Rest onopgelost Welke storing bij welk foutnummer hoort. Dat de nummers uit de buitenunit komen en in zeven categorieën worden gegroepeerd is nu duidelijk (zie hierboven), maar de betekenis per nummer staat niet in deze firmware. Daarvoor is servicedocumentatie nodig.
- De flash-tabellen zijn volledig gedecodeerd: vier telegrammen plus elf per-stand-varianten, alle met CRC = 0 — inclusief de exacte bitcodering.
- PD3/PD4 op T1.1 zijn toegewezen zonder doormeten: PD4 + comparator = thermostaat (slave), PD3 + PD5 = CV-ketel (master). De thermostaatkant is daarna ook gemeten: 124 flanken per twee seconden op socket-pin 13, wat WackoH's "pin 13 = GND" weerlegt.
- De kloksnelheid is vastgesteld op 8 MHz, langs drie onafhankelijke wegen: de Timer1-herlaadwaarden van beide chips, het kristal onder de IC2-voet, en de gemeten intervaltijd van de nagebouwde firmware.
- De Manchester-codering en de comparator-leesrichting zijn bevestigd doordat een nagebouwde ontvanger frames van een echte thermostaat met kloppende pariteit decodeert.
- De 5 °C-lockout is gevonden en verklaard, en blijkt samen te vallen met de praktijk.
- Het relais op socket-pin 16 is de noodbrug. PB2 stuurt via T3 K1 en K2 tegelijk aan, en die verleggen het aderpaar van de thermostaat rechtstreeks naar de ketel. Dat verklaart meteen waarom T1.1 zonder dat relais ook stopt met rapporteren aan IC1.
- PC5 stuurt K3 aan, een droog 10 A-contact voor een AAN/UIT-ketel — Atlantic's tweede keteltype. Uit de firmware alleen lijkt het een statusuitgang, want hij wordt nergens anders geschreven; het koper laat een contact van 10 A zien.
- Socket-pin 6 komt inderdaad op de thermostaatklem uit, via een discrete hoogzijdedriver (T1 → Q4) op de 18 V. Een echte thermostaat reageert op de antwoorden van de nagebouwde brug.
- De "buitentemperatuur" uit ID 2 is echt de buitenlucht, geen verdampervoeler. Over 350 uur vergeleken met een tweede thermostaat leest hij consequent ~2,5 °C hoger, en — dit is het bewijs — dat verschil verandert niet als de compressor draait (mediaan +2,6 stil tegen +2,5 in bedrijf). Een verdamper zou onder belasting juist wegzakken. Die offset van 2,5 is normaal voor een voeler aan een gevel tegenover een weerdienstwaarde in de schaduw, en je moet hem meerekenen als je op die waarde stuurt.
doorgevoerd Het standbereik. Atlantic moduleert 1 t/m 10,
chofu_wp stond op max_stand_{7}. Dat plafond is inmiddels naar 10 te zetten
en in aurea-wp.yaml geactiveerd. De telegrammen voor stand 8, 9 en 10 zijn bekend en
CRC-correct, dus dit is een éénregelige wijziging. Wel eerst controleren of jouw buitenunit
die trappen ondersteunt — de tabel zit in de binnenunit-firmware en zegt niets over wat de
compressor aankan.
Bestanden
Alle bestanden staan naast dit document in dezelfde map. De listings zijn opnieuw gegenereerd met de gecorrigeerde disassembler.
Bronmateriaal
- Haddon M1.1 - ATMEGA8@DIP28.BIN ruwe flash-dump IC1 · 8704 bytes
- Haddon T1.1 - ATMEGA8@DIP28.BIN ruwe flash-dump IC2 · 8704 bytes
Listings
- Haddon_M1.1_disassembly.lst volledige listing IC1 · gecorrigeerd
- Haddon_T1.1_disassembly.lst volledige listing IC2 · gecorrigeerd
Reconstructies
- Haddon_M1.1_reversed.ino C-reconstructie IC1 · verouderd, zie waarschuwing
- Haddon_T1.1_reversed.ino C-reconstructie IC2 · verouderd, zie waarschuwing
Beide .ino-bestanden dateren van vóór de SBRS/SBRC-fix en bevatten daardoor nog
omgekeerde conditionals; ze missen bovendien de temperatuur-parser en de 5 °C-lockout. Bovenin
elk bestand staat een blok dat precies opsomt wat er niet klopt. Dit verslag en de listings zijn
leidend.
De links werken zolang dit document in dezelfde map staat als de bestanden. Bekijk je deze pagina via een gedeelde webversie, dan zijn de bestanden daar niet meegepubliceerd.