Reverse-engineering verslag · versie 4 · 6 augustus 2026

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.

Chip2× ATmega8, DIP28, 8 MHz extern
M1.1 rolIC1 — warmtepomp-link (verving je door ESP32)
T1.1 rolIC2 — OpenTherm-brug Anna ↔ CV-ketel
Flash gebruikt~3,2–3,3 KB van 8 KB (rest 0xFF)
Bevestigd uit de binary Onzeker / onopgelost Interpretatie

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.

Antwoord: is er een minimum-buitentemperatuur ingebouwd?

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.

En de telegrammen zijn nu volledig ontcijferd

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.

ConclusieHoe 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.
Pin 13 is geen massa — het meningsverschil is beslecht

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.

Een valkuil voor wie dit nabouwt: 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.

Opgelost: het relais op PB2 is de noodbrug

In sub_0a24 staat SBIS PINC,2SBI 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.

Drie conclusies uit de firmware moesten worden bijgesteld
  • 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.
PinAVRGemetenBetekenis
1RESETR19 (5k) naar +5 Vpull-up, verder niets
2PD0naar de IC1-voet, kaal koperinterconnect RX
3PD1naar de IC1-voet, kaal koperinterconnect TX
4PD2niet aangesloten
5PD3R13 (330 Ω) naar OK5 pin 3zenden naar de ketel, LED-anode kanaal 2, ~11 mA
6PD4R24 (4k7) naar de emitter van T1zenden naar de thermostaat
7VCC+5 V
8GNDmassaook C5 (100 nF) hierheen
9XTAL1kristal8 MHz bevestigd
10XTAL2kristalidem
11PD5OK5 pin 7 = emitter kanaal 1ontvangen van de ketel, zoals voorspeld
12PD6niet aangesloten
13PD7 / AIN1R3 (33k) omhoog, R4 (4k7) naar massaontvangen van de thermostaat, deler 33k/4k7
14PB0niet aangesloten
15PB1niet aangesloten
16PB2diode → R26 (10k) → basis T3T3 schakelt K1 en K2 tegelijk: de noodbrug
17PB3niet aangesloten
18PB4niet aangesloten
19PB5nog niet gemetenopen
20AVCCook naar de basis van T1T1 werkt dus in gemeenschappelijke basis
21AREF+5 V
22GNDmassa
23–24PC0, PC1nog niet gemetenopen
25PC2dipswitch 2; het andere contact aan massaON = laag = normaal bedrijf, OFF = hoog = ketelkant afgeknepen
26PC3nog niet gemetenopen
27PC4naar een status-LEDhet lampje op de voorkant
28PC5weerstand naar de basis van T4T4 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.

RefTypeRol
OK1H11D1warmtepomp, ontvangen. Pin 5 (collector) aan +5 V
OK2H11D1warmtepomp, zenden. Pin 1 (LED-anode) aan +5 V
OK5TLP521-2de complete ketelkant. Kanaal 1 = ontvangst (pin 8 aan +5 V), kanaal 2 = zenden (pin 3 van PD3)
Q1–Q4BC558APNP, hoogzijde
T1–T4BC547ANPN, laagzijde. T2 is de warmtepomp-zendlijn vanaf IC1 pin 26
K1, K2relais 24 VDCsamen de noodbrug, beide van T3 af
K3Omron G2R-1, 12 VDC10 A wisselcontact — vermogen, geen signaal
LM7818regelaarde 18 V-tak voor OpenTherm; voedt ook de LM7805
LM7805regelaar5 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.

+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.

RelaisAangestuurd doorFunctie
K1 + K2PB2 → 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.
K3PC5 → 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.
Een valkuil bij het nabouwen

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.

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 laaghoog
Hoe het origineel leest SBIS PIND,5SBIC 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.

SchakelaarAansluitingBetekenis
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.
Dit heeft dagen gekost

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.

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:

VondstWat 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:

Bug 1 — skip-instructies beëindigden de trace

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.

Bug 2 — elke SBRS stond als SBRC in de listing

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:

VectorAdresFunctie
Timer1 overflow0x0a6c 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 RXC0x0b98Leest één byte van T1.1, zet een vlag. Geen framing hier.
INT00x0b66Lege stub (push/pop). Vermoedelijk alleen wake-from-sleep.
666 baud — nu hard onderbouwd

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

PinRichtingFunctie
PC3 (pin 26)uitTX naar buitenunit, 666 baud bit-bang. Na de bugfix blijkt de lijn niet-inverterend: SBRS r20,7 → bit 1 = pin HOOG.
PC4 (pin 27)inRX van buitenunit
PC0inCV-only-schakelaar — actief bij HOOG
PC1inTest-knop — actief bij LAAG (ingedrukt)
PB0uitvraagrelais — "roep de warmtepomp aan"
PB1uitstatuslampje (aan zodra statuscode ≠ 0)
PB3uittweede, parallel meelopende bitstroom naast PC3 functie onopgehelderd
PB2 / PB4uitstatussignalen rond het T1.1-telegram
PD0 / PD1in / uithardware-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:

SRAMBron in frameBetekenisBijzonderheid
0x710x7e–0x7faanvoertemperatuuralleen positief
0x720x80–0x81retourtemperatuuralleen positief
0x730x82–0x83buitentemperatuurheeft een aparte negatieve tak (0x04600x0496)

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,0x3cCBI 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
Waarom dit de smoking gun is

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.

Komt overeen met de praktijk

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

Drie voor de hand liggende conclusies die niet kloppen

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, in sub_0286) en PD5 (sub_0398). ACSR = 0x40 zet 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: 0x0490 kopieert buffer 0x890x8d, en 0x053e kopieert 0x8d0x89.
  • De berichttypes verraden de rollen. In OpenTherm zit het type in bits 6–4 van de eerste byte:
Opgebouwde byteTypeBetekenisRol
0x40 / 0xC0100Read-Ackantwoord van een slave → richting Anna
0x10 / 0x90001Write-Dataopdracht van een master → richting ketel
0x80000Read-Datavraag van een master → richting ketel
0x70111Unknown-DataIdstandaardantwoord 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.

RichtingOnderschepte Data-ID'sRest
Anna → ketel0, 1, 16ongewijzigd door
Ketel → Anna0, 1, 16, 25, 28ongewijzigd door
Anna krijgt de temperaturen van de wármtepomp

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

IDStandaardbetekenis
0Status
1Control Setpoint (CV-aanvoertemperatuur)
2 / 3Master- / Slave-configuratie
7Cooling Control Signal
14Max. relatief modulatieniveau
16Room Setpoint
17Relatief modulatieniveau
24Room Temperature
25Boiler Water Temperature
28Return Water Temperature
49fabrikant-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

PinRichtingFunctie
PD0 / PD1in / uithardware-UART naar M1.1 (9600 baud)
PD3uitOpenTherm zenden naar de CV-ketel (T1.1 als master), Manchester — buffer 0x8d
PD4uitOpenTherm zenden naar Anna (T1.1 als slave), Manchester — buffer 0x89
AIN1 = PD7
socket-pin 13
inOpenTherm ontvangen van de thermostaat, via ACO + bandgap — buffer 0x89 gemeten
PD5inOpenTherm ontvangen van de CV-ketel — buffer 0x8d
PC2inknop/modusingang; stuurt ook PB2 aan en kiest tussen de twee brugrichtingen (0x053e)
PC4
socket-pin 27
uithet 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
uitstuurt 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
uitstuurt 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
uitalle vijf als uitgang geconfigureerd, nergens beschreven, en op deze print niet aangesloten — het spoor eindigt blind gemeten
Welke lijn naar wie gaat — opgelost zonder doormeten

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 0x89 worden Read-Ack-antwoorden opgebouwd (0x40/0xC0 op 0x0628, 0x068c, 0x06a8, 0x06be) plus de Unknown-DataId-fallback. Dat is slavegedrag. Die buffer wordt gezonden door sub_0958 op PD4, en gevuld door de comparator-ontvanger. → PD4 en de comparator vormen de Anna-kant.
  • In buffer 0x8d worden Read-Data en Write-Data opgebouwd (0x80, 0x10, 0x90 op 0x04be, 0x04e6, 0x051a). Dat is mastergedrag. Die buffer gaat via sub_0892 naar 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 0x0688 wordt die vergeleken met 0x14 (= 20).
  • Onder de drempel antwoordt T1.1 met statusdata 0x00 (idle); erboven met 0x02 (CH-modus actief) of 0x0a (CH-modus + vlam).
  • Op 0x04be schrijft T1.1 een Write-Data ID1 met waarde 0x0a00 — 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.

Wat de foutcodes zijn — deels opgehelderd

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

Dit komt uit de binary, niet uit het forum

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 0x0baa0x0c4f 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.

Waterdicht bewijs

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):

AdresTelegramModusStand
0x0be219 02 08 00 00 00 9d 360x00 = uit0
0x0bec19 02 08 01 01 00 99 370x01 = verwarmen1
0x0bf619 02 08 01 02 00 cc 640x012
3 t/m 9
0x0c4619 02 08 01 0a 00 45 cd0x0110
Twee bevindingen die er praktisch uit springen

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.

AspectAtlantic M1.1ESP32 chofu_wp.cpp
ZendtelegrammenID1, ID2, ID3, ID0 per cyclus — identieke bytes als DATA1/3/04 roterend (DATA0–3), zelfde inhoud
Aantal standen1 t/m 101 t/m 7 (drie trappen onbenut)
Koelen (modus 0x02)nooit — alleen 0x00 en 0x01 in flashaanwezig en op hardware bewezen werkend
Ontvangstparseert ID2 → aanvoer/retour/buiten, gedeeld door 10parseert ID1/2/3, /10 met decimaal
CRC-16niet berekend maar vooraf uitgerekend en in flash meegebakken (nul ROL-instructies in de hele binary)live berekend — flexibeler
Buitentemp-lockoutja, bij 5 °C (en bij negatief)geen — regelt puur op aanvoer vs. setpoint
Bijstookvia T1.1's OpenTherm-master-kanaal naar de ketelalleen 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.

BoekingM1.1T1.1
Bereikbare code2940 B~3000 B
Telegramtabellen166 B
Onbereikbare code12 B116 B
Rest van de 8 KB flashvolledig 0xFF (onbeschreven)
Losse 512 B achter de flashde EEPROM — volledig 0xFF
Waarom dit nu een bewijs is en niet een bewering

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.

En wat dit uitsluit

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:

  1. 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.
  2. 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 0x7b uitschuift. 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.
  3. 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.
Opgelost in deze ronde
  • 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.
Concreet punt voor de ESP32

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

Listings

Reconstructies

Waarschuwing bij de twee reconstructies

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.