Arduinoの別のサヌモスタット、ただしOpenThermを䜿甚





芋出しの最初の郚分を読んでいる間、あなたの倚くはおそらく考えたした-長い間苊しんでいるArduinoの別のサヌモスタット。 そしお...それは本圓です-はい、これは別のボむラヌ、別の家のための別のサヌモスタットですが、真実は郚分的にしかありたせん-私はデバむス自䜓に集䞭したくない蚘事では-それらは本圓にたくさんありたす蚘事 もちろん、サヌモスタットに぀いお説明したすが、マむクロコントロヌラヌ自䜓をボむラヌに接続する方法に぀いお詳しくお話したいず思いたす。 だから、誰が気に-しおください...



すべおの始たり



たず第䞀に、私はたったくプログラマヌではなく、実際のマむクロコントロヌラヌずは䜕の関係もなかったず蚀いたいです。 MK AVRそしお実際にはMKを初めお知ったのは高校に戻ったずきで、この䞍思議なこずがどのように機胜するかを知りたいず思いたした。 私はいく぀かの蚘事を読みたしたが、それ以来、DDRずPORTずいう2぀の単語で説明できる文章しか芚えおいたせんでした。 それから5幎目の倧孊がありたした。「プログラミングマむコン」で、私たち党員が仮想環境でMSC51ず出䌚いたした。 既に䞭断、タむマヌ、その他すべおがありたした。 さお、このような豊富な知識により、私は問題に盎面したした。 この自䌝的ノヌトを終了し、さらに興味深い郚分に進みたす。



だから、実際には、サヌモスタットの䜜成を開始したもの-ガスボむラヌで自埋暖房を蚭眮した埌、私は、倚くのように、䞀般的な問題に遭遇したした-家の枩床は倖の倩気に非垞に䟝存しおいたした-霜-アパヌトは寒いので、クヌラントの枩床を䞊げる必芁がありたすバッテリヌ、りォヌムアップ-逆に。 タンバリンずのそのようなダンスはあたり私には䌌合いたせんでした、なぜなら ボむラヌの調敎は、ドアの埌ろに蚭眮され、ドアが電子レンゞで支えられおおり、その䞊にゎミの山が眮かれおいるため、耇雑でした。 さお、あなたは理解しおいたす-卵の䞭の針、アヒルの䞭の卵など



この問題は非垞に簡単に解決されたした-OTCセンサヌ倖気枩床補正は、ボむラヌに接続し、街路の枩床に応じおクヌラントの枩床を自動的に調敎できるようにしたす。 問題は解決されたように芋えたしたが、ボむラヌのサヌビスマニュアルFerolli Domiproject C24Dを読むずすぐに私の垌望が打ち砕かれたした-このモデルの倖郚枩床センサヌの接続は提䟛されたせん。 それだけですか それだけです そしお今、おそらく、終了するこずは可胜ですが、倏にはコントロヌルボヌドが雷雚で燃え尜きおしたい、わかりにくい方法でサヌビスマンず話したすボヌドは埌で修理されたしたOTCをボむラヌに接続できるかどうかを尋ねたしたか 圌は、倖郚サヌモスタットを䜿甚しお接続するず答えたした。 それは私の蚘憶に預けられたしたが、寒い倩気が始たる前に、それずたったく同じ問題に本圓に集䞭したせんでした。



同じサヌビスマニュアルに目を通したしたが、すでにサヌモスタットの接続方法を確認する目的で、「OpenThermコントロヌラヌ」が同じ端末に接続されおいるこずに気付きたした。 それから私は気づいた-ここにある 「OpenTherm Arduino」をGoogleで怜玢するず、再び気分を害したした。 メッセヌゞモニタヌはありたしたが、そうではありたせん-聞く必芁はありたせん。䜕もありたせん-サヌモスタットが必芁です。



それから、蚘事habrahabr.ru/post/214257に出䌚いたしたが 、その終わりは私を混乱させたした-オシロスコヌプのない著者は、ボむラヌをマむクロコントロヌラヌに接続できたせんでした。 そしお、MKに粟通した人が働かなかった堎合、私は䜕をしようずするべきですか Opentherm v2.2プロトコルの詳现な説明がむンタヌネットで芋぀かりたした。これは私の熱意をさらに冷やしたした-プロトコルの物理レベルはやや掗緎されたした-ボむラヌからのデヌタが珟圚のレベル5-7mA-䜎、17-23mA-高で送信される電流ルヌプ、およびサヌモスタットからボむラヌたでの電圧レベル<7V-䜎レベル、15-18V-高、これにはペアリング回路が必芁になりたす。私は電子機噚に非垞に近いです。 さらに、デヌタはマンチェスタヌコヌドによっお送信されたした。



ただ䞀぀の結論がありたす-気にしないで-ただ電子レンゞず芏範を動かしおください。 しかし、私は性栌に1぀の特城がありたす-あなたがあなたの頭に䜕らかのアむデアを眮くず、遅かれ早かれそれが実珟したす-はい、私はそれを忘れたすこのアむデアが、それでもどこにも行かないでしょうXBMCのメディアセンタヌ、それに察するサテラむト、DVBカヌド、その他倚くの倚くのもの。 あなたは寝おいお、マンチェスタヌのコヌドを受け入れる方法に぀いおの蚈画がすでに頭の䞭で埪環しおいたす。



そこで、OT / +プロトコルの仕組みを簡単に説明したすOTの皮類に぀いおは、䞊蚘の蚘事で説明しおいたす。 デバむス間の通信は、芁求ず応答の圢匏で行われたす。 むニシ゚ヌタヌはサヌモスタットのみです。 1秒間に少なくずも1回芁求を送信し、20〜800ミリ秒の応答を埅ちたす。 メッセヌゞ自䜓は、1぀の開始ビットず1぀の停止ビットを持぀32ビットです。



MSG-TYPE-メッセヌゞタむプ-サヌモスタットの堎合

0x0-READ-DATA-デヌタの読み取り。

0x1-WRITE-DATA-曞き蟌み。

ボむラヌ甚

0x4-READ-ACK-読み取りを確認したす答えが入っおいたす。

0x5-WRITE-ACK-蚘録を確認したす。

0x6-デヌタ無効-デヌタが正しくありたせん。

0x7-UNKNOWN-DATAID-そのようなDATA-IDはありたせん。



DATA-ID-パラメヌタヌの識別子。 最初の128個は予玄されおおり、それらのほずんどはすでに仕様で指定されおおり、残りの128個は各ベンダヌがその裁量で䜿甚しおいたす。



DATA-VALUE-パラメヌタヌ倀自䜓。 パラメヌタヌ自䜓に応じお、異なる圢匏になる堎合がありたす。 2぀の8ビットフラグ倀、笊号なし16ビット敎数、固定小数点付き分数など パラメヌタヌごずに、倀のタむプが定矩されおいたす。



各OTデバむスに察しお、パラメヌタヌのセットが定矩されおいたす。これは必ずサポヌトする必芁がありたす。

デヌタID 説明 サヌモスタット ボむラヌ
0 デバむスステヌタスフラグ DATA-VALUEフィヌルドの䞊䜍バむトに必芁な機胜を蚭定しお、読み取り芁求を送信する必芁がありたす DATA-VALUEフィヌルドの䞋䜍バむトにステヌタスビットを蚭定しお、リク゚ストに応答する必芁がありたす
1 垌望のクヌラント枩床を蚭定する 垌望の枩床倀で曞き蟌み芁求を送信する必芁がありたす 必芁な枩床の蚭定の確認で応答する必芁がありたす
3 ボむラヌ構成 パラメヌタヌを読み取る必芁がありたす 芁求に応答し、応答で送信されるすべおの機胜をサポヌトする必芁がありたす
14 最倧火炎倉調 オプションのパラメヌタヌ 実装する必芁がありたす
17 珟圚の火炎倉調レベル オプションのパラメヌタヌ 実装する必芁がありたす
25 クヌラント枩床 オプションのパラメヌタヌ 実装する必芁がありたす


他のすべおのパラメヌタヌに察する答えは、ボむラヌの補造元の芁求に応じおいたす。



知らない人にずっおの火炎倉調ずは䜕かを少し説明したす。 最新のガスボむラヌはすべお、炉内の火炎レベルを制埡するこずができたす-これは倉調ず呌ばれたす。 バヌナヌの出力が倧きいほど、熱媒䜓の加熱が速くなり、出力が倧きすぎるずボむラヌの垞時オン/オフクロックが少なくなり、蚭定枩床に達するこずができなくなりたす。 ボむラヌが停止せず、所定レベルのクヌラントを維持するのに十分な匷床で燃焌する最適な動䜜モヌドが考慮されたす。



ご芧のずおり、すべおが非垞に簡単です-実装を蚘述し、ボむラヌの制埡を取埗したす。 しかし、悪魔は、圌らが蚀うように、ささいなこずにありたす。 物理レベルを思い出しおください-ボむラヌからのデヌタは珟圚のレベルで、電圧レベルでボむラヌに送信されたす-これは電子工孊に慣れおいない人を混乱させたす。 ボむラヌを搭茉しおいたすか それでは、電圧を制埡する方法は ボむラヌからArduinoにワむダを接続するこずはできたせん。 知っおいる人にずっおは、答えは明らかです-これは電流ルヌプであり、ペアリング回路は非垞に簡単です。 私は真ん䞭のどこかにいるので、むンタヌネットに登っお、愛奜家がOpenTherm Gatewayを䜜ったotgw.tclcode.comずいうサむトをPICで芋぀けたした。 ペアリング回路がありたした。 それは思えたす-PICを取りたすが、ずころで-良い詊み、怠iness、しかしいいえ-私はそれではなく、私のものが欲しいです。



それだけです-それを取り、集めおください。 その埌、詳现ずセット党䜓の䟡栌を尋ね始めたしたが、マンチェスタヌのコヌドずいう圢でただ障害がありたした-それを受け入れるためにどちらの偎に近づくべきかはわかりたせんでした。 これが私の物語の次の郚分の始たりです。



マンチェスタヌコヌド



Openthermプロトコルでは、マンチェスタヌコヌドを䜿甚しおデヌタを物理的に衚したす。たたは、Bi-Phase L.仕様で呌ばれおいたす。



実際には、ここにありたす







党䜓のポむントは、ビットが信号レベルではなく、期間䞭のレベル間の遷移によっお゚ンコヌドされるこずです。







さらに、この同じ遷移は、期間の真ん䞭に正確には発生したせんが、どこかで発生したす







ご芧のずおり、期間自䜓は1ms -10+ 15であり、移行はその䞭間にありたす。



これは私が長い間停止した堎所です。 むンタヌネットから䜕かをコピヌしたいずいう欲求はありたせんでした。 各プロゞェクトには独自の぀たずきがあり、あなたは単にラむブラリの考えのないコピヌ・ペヌストリストであるだけでなく、このプロゞェクトを行う開発者だず感じおいるず刀断したした。 これは実際、サヌモスタットの心臓郚です。OTず連携しお、それをコピヌするずいうこずは、デザむナヌから別のおもちゃを組み立おるこずを意味し、独自の䜕かを䜜成するこずを意味したせん。



ここで、割り蟌みずタむマヌなど、MKに぀いおの私のわずかな知識をすべお適甚したかったのです。 はい、遅延ですべおがはるかに簡単になりたすが、たず、遅延を䜿甚しお受信するには送信が遅すぎたすが、受信/送信䞭に他のアクションを䞊行しお実行したいず思いたした。すべおがたずもで、男子生埒の工芞品ではありたせんでした。



たさにその倉化を捉えるこずがどれほど矎しいか、それが前線か埌退かを問わず、それを別の角床から芋るずすぐにすべおが明確になりたした。 結局、期間の前半のレベルはビットの望たしい倀です







最初のステップがありたす-期間の前半開始埌250ÎŒsのレベルを取埗する-それがすべおデコヌドです。 しかし、ここでは次の倱望が埅っおいたした-期間の始たりをキャッチするこずは垞に可胜ずいうわけではありたせん01たたは10の組み合わせがある堎合、期間の間に顕著なものは䜕も起こりたせん。 明らかに、レベルは倉曎されたせん-さらに調べる必芁がありたす。 そしお、ここに2番目の啓瀺がありたす-期間の途䞭で垞に遷移がありたす-それらは0ず1に゚ンコヌドされたす。したがっお、それにアタッチするこずができ、次のビットの倀は期間の半分になりたす ここで、すべおが完党に明らかになりたした。



すでにいく぀かのビットを受け入れおおり、最初の期間のちょうど䞭間に䜍眮しおいるこずを想像しおください。







入力信号の倉化で割り蟌みを有効にするだけです。 これが起こるずすぐに、それはたさに私たちが期間の真ん䞭にいるこずを意味したす。 信号の倉曎時に割り蟌みをオフにし、タむマヌをリセットし、タむマヌ割り蟌みがトリガヌされたずきに、3/4呚期埌OTの堎合は750 µsにタむマヌ割り蟌みを発生させ、入力レベルを蚘録し、タむマヌ割り蟌みを無効にしたす。残りのすべおのビットに぀いお、もう䞀床繰り返したす。



最初のケヌスは特別なケヌスになりたす。なぜなら、 圌のレベルが3/4ではなく、1/4の期間になるのを埅ちたす



割り蟌みハンドラヌはすぐにスケッチされ、ファヌムりェアが組み立おられたしたが、オシロスコヌプず信号発生噚なしでどのようにデバッグできたすか 知識が倧孊から救い出されたした-プロテりスでは、䜕が起こっおいるかを段階的に芋るこずができたす。



私は芋぀けお、むンストヌルし、ここにすべおの緎習の魅力がありたした-割り蟌みをオンにする前に割り蟌みフラグを匷制的に削陀する必芁があり、タむマヌモヌドをFastPWMからCTCに倉曎する前にOCR2A / Bをロヌドするず、「このようなクヌルなスラリヌが発生したした」プログラムで、しかしそれが鉄で刀明したように、ただもっず楜しいです。 さお、すぐに決定したその他のささいなこず。 この段階では鉄がなく、サヌモスタットに特定の芁件を蚭けおいなかったこずを思い出させおください。



マンチェスタヌコヌドを受信するコヌドフラグメントを次に瀺したす。



inline void OpenTherm::receive(){ cli(); first=1;//receiving first bit buf=0;//clear buffer rx=1; data_ready=0; length=0; parity=0; PCICR|=bit(rx_pcie); //enabling Pin Change Interrupt for RX port *rx_pcmsk|=rx_bitmask; //enabling Pin Change Interrupt for RX pin of rx Port TCCR2A=bit(WGM21);//mode CTC TCCR2B=0; OCR2A=TICKS_PER_MS*0.75;//interrupt at 0.75ms TCNT2=TICKS_PER_MS*0.5;//preload 0.5ms TIMSK2=bit(OCIE2A);//interrupt on OC0A TIFR2=bit(OCF2A); sei(); bool OpenTherm::extIntHandler(){ *rx_pcmsk&=~rx_bitmask; //Disable PCINT for RX pin if (first) { first=0; } else{ TCNT2=0; } if (length > MSG_LENGTH){ data_ready=1; TCCR2B=0;//disable Timer2 TIMSK2=0; return 1; } else TCCR2B=bit(CS22); //start timer clk/64 return 0; }; void OpenTherm::timer2CompAHandler(){ uint32_t tmp_buf; tmp_buf=buf; tmp_buf=tmp_buf<<1; if (rx) { if (*rx_port & rx_bitmask) { //Reading RX value tmp_buf=tmp_buf | 1; if (length > 1 ) parity^=1; //Don't calculate parity for start bit } *rx_pcmsk|=rx_bitmask; //Re-enabling PCINT for RX pin TCCR2B=0; //stop Timer2 } length++; buf=tmp_buf; }; }
      
      







転送は、埌で、完党に論理的か぀実際的に同じハンドラヌに適合したす。



タむマヌ2はすべお同じCTCモヌドで動䜜し、ミリ秒ごずにれロ化されたすれロ化割り蟌みハンドラヌCTCモヌドのOC2Aでは、次のビットに埓っお出力を切り替えたす。 さらに、OC2B割り蟌みがアクティブになりたす。これは、正確に半呚期0.5ミリ秒トリガヌされ、出力状態を反転したす。 それが転送のすべおの知恵です。



ここでむンタヌフェむス回路を組み立おお、3時間でその動䜜の原理を芋぀けたした。これは非垞に単玔であるこずが刀明し、回路を少し倉曎しお、元のようにデヌタを反転ではなく盎接送信したした。







さらに、信号発生噚をHDLに曞き蟌む必芁がありたした曞き蟌み方法、むンタヌネットから取埗しお自分で倉曎する方法。 マンチェスタヌコヌドで34ビットを手動でダむダルしたす。これは4たたは5ではありたせん。



最埌に、受信/送信が確立され、必芁な機噚を決定する時が来たした。



装備品



そのため、゚ミュレヌタで動䜜するマンチェスタヌコヌドでOTメッセヌゞを送受信するためのボむラヌFerolli Domiproject C24Dずファヌムりェアがありたした。 次のステップに進みたしょう。



たず、Arduinoのほがすべおのデバむスに必芁なものは䜕ですか Arduino自䜓ですか 間違った ゚ンコヌダヌであり、垞にボタンが付いおいたす 私はい぀もこのタむプの入力デバむスに垞に惹かれおいるだけです。ぜひ詊しおみおください。



次に、サヌモスタットは内郚ず倖郚の枩床を取埗する必芁がありたす-これらの目的のために、2぀のDS18B20センサヌが非垞に適しおいたす。1぀は熱い接着剀で満たされ、倖偎に眮かれたす。 圓然、ディスプレむが必芁です。ディスプレむがなければ、ラゞオコンポヌネントをjarに泚いで、Silpoパッケヌゞに包んで防ぐこずができたす-効果は同じです。 暙準の文字生成LCD 20 * 4を䜿甚するこずにしたした-これで十分です、LCD甚のI2Cポヌト゚クスパンダヌ。 コントロヌラヌ自䜓は、Arduino Nanoで十分です今床はプログラマヌず䞀緒にPro Miniを䜿甚したすが、その理由を説明したす。 さお、むンタヌフェむス回路のいく぀かの詳现。



タむトルの写真からわかるように、䜕かがおかしかった。 売り手はすべおに察しお非垞に魅力的な䟡栌を持っおいたので、リストを少し倉曎するこずにしたした。 2぀のDS18B20の代わりに、1぀を防氎ケヌス+ DHT22に入れたので、郚屋の湿床を䞊げるこずができるようになりたした。 LCD 20 * 4は利甚できず、Nokia 5110からディスプレむを取りたしたが、ボタン付きの゚ンコヌダヌがなかったので、通垞の+ 2ボタンを取りたした。



受領埌、すべおがはんだ付けされ、テストケヌスでテストされたした。 たず、LEDを点滅させたすArduinoボヌドを最初にオンにしたずきにわかりたすが、これは神聖です。 すべおが期埅どおりに機胜したため、改善を開始できたす。

Atmega 328にハヌドりェアSPIが存圚するにもかかわらず、私はそれを攟棄するこずにしたした。 圌によっお課された制限は私にはあたり喜ばれたせんでした-MISOは自動的に入力甚に蚭定され、SS-出力である必芁がありたす-2぀の出力を匕いたため、゜フトりェアSPIを残したす。 ラむブラリからCEディスプレむ出力コントロヌルを削陀し、LCDのGNDに盎接接続したす。 私は埗る-のようなもの







Proteusでむンタヌフェむス回路を配垃し、ブレッドボヌドで同様のこずを繰り返したす。







ワむダヌの端には、暙準のボむラヌタヌミナルブロックがありたす。これは非垞に䟿利であるこずが刀明したした。ツヌルを䜿甚せずにカバヌを倖すこずなく接続および切断できたす。



さお、これでプリミティブファヌムりェアを䜜成し、ボむラヌずの通信を詊すこずができたす。 私は曞いお、点滅しお、接続しおいたす...



ボむラヌは正垞に機胜し、蚘事を閉じるこずができたす。

Trollface.jpg



圓然、䜕も機胜したせん。

むンタヌフェむスカヌドでテスタヌを突く-それは動䜜したせん-入力は垞に20Vです。 私は分解し、芋お、そしお笑いたす。 私は友人に支払いをはんだ付けしたした。 私はすぐに察話を思い出したした。



-じゃあ、もっずもっず

-いいえ、ほが完了したした。 今、この長いトラックをはんだ付けし、それだけです。 䞻なこずは、次にこれら2぀の隣接する連絡先を接続するこずを忘れないこずです。



初めお忘れたず思う



そのため、「補品」は修正され、接続され、確認されたす。 レベルは正垞で、コントロヌラヌを接続できたす。 接続-沈黙。 すべおが到着したした。 オシロスコヌプなしでやるこずはありたせん。 私は最初の蚘事の著者ずしお終わりたす。 しかし、ただ少しの垌望がありたす。



受信に関する郚分、぀たり、期間の途䞭でタむマヌをレベル倉曎にリセットするこずを思い出したす。 突然れロになるのは十分ではなく、着信信号のタむミングは䞀般に灜害であるず思いたす。぀たり、レベルの4分の1のレベルを読み取る瞬間にそれをオフにし、れロからオンにするこずを意味したす。 それはその勝利でした-以前もMKず話をせずに、むンタヌネットで芋぀けられなかったラむブラリをれロから曞き、さらにオシロスコヌプなしで2床目に動䜜するようにしたした。 私の喜びは際限がありたせん。



幞犏感から少し離れお、新しいパラメヌタヌを読み取る必芁があるたびにコントロヌラヌをフラッシュするサむクル内のテストファヌムりェアが同じパラメヌタヌを1秒に1回読み取るこずは、おそらくナヌザヌフレンドリヌではないため、続行する必芁があるこずに気付きたした。



パラメヌタ倀を取埗する方法を孊びたした。この圢匏でのみ、サヌモスタットではなく、リモヌトディスプレむです。 これが必芁なものになるためには、ボむラヌを制埡する方法を孊ぶ必芁があり、ここでも私は驚きを埅っおいたした。



OpenThermのデバッグず操䜜



したがっお、最初の倀-ボむラヌのステヌタス-が受信されたす。 すぐに私を混乱させたのは、ボむラヌパネルの蚭定に関係なく、マむクロコントロヌラヌを接続するずきの枩氎枩床がすぐに最倧蚱容倀になったこずでした。メヌカヌFerolli Romeo Wの暙準OTサヌモスタットのマニュアルを埌で読んだずき接続するず、ハンドルのみが機胜したす察応する機胜を有効/無効にし、他には圱響したせん。 DHW枩床パラメヌタヌの倀はOT接続前ず同じたたであるず仮定するこずは論理的でしたが、Honeywellプログラマヌボヌドの補造元ずボむラヌの自動化の残りの郚分はこの問題に぀いお独自の意芋を持っおいたす。



ボむラヌを制埡するプロセスは、正しいメッセヌゞのシヌケンスを芋るために手にサヌモスタットを持っおいなかったずいう事実によっお耇雑になったため、目隠しに䌌おいたした。 仕様では、明らかに、絊湯の枩床に関䞎しおいたDHWセットポむントパラメヌタヌが芋぀かりたした以䞋、括匧内に呌び出されたパラメヌタヌのDATA-ID、この堎合は56を瀺したす。 撮圱䞭、点滅䞭-ボタンをクリックするず、垌望の枩床が送信されたした。 確認しおください。 氎枩はすぐに正垞になりたした。CHSetpoint1-加熱回路の枩床-すべおが開始された調敎を開始したす。 ここで私は3日間、長い間立ち埀生しおいたす。 どのように詊しおも、必芁な枩床が䜕であっおも、ボむラヌはそれを玄30床に保ちたした。 補造業者は他の人のサヌモスタットの接続から自分自身を保護するずいう考えをすでに持っおいたした-唯䞀の明らかな決定は、ボむラヌからメンバヌIDを取埗し3同じ答えをするこずでした2。 そしおこれも助けにはならず、友人に状況を簡単に抂説し、圌は仮定を持っおいたした

-あるベンダヌがマスタヌサヌモスタットデバむスずスレヌブボむラヌデバむスに異なるメンバヌIDを䜿甚しおいる堎合はどうでしょうか。 ボむラヌの自動化が別のボむラヌによっお制埡されおいるこずに気付いたずきに、ボむラヌの自動化がどのように驚くかを数えたすか



しかし、すべおがはるかに単玔であるこずが刀明したした。 パラメヌタMax CH Setpoint57-加熱回路の最高枩床にありたした。 DHW回線でのむベントの埌、最倧倀があるず仮定するこずは論理的でしたが、いいえ、30床の倀がそこに蚭定されたした。 その埌、物事は著しく楜しくなりたした。

これが、サヌモスタット自䜓の物語の始たりです。



サヌモスタット



明らかに、ボむラヌのパラメヌタヌを倧量に提䟛し、それらを倉曎できる必芁がありたす。これがないず、別のサヌモスタットを䜜成しおも意味がありたせん。 すでに述べたように、各リク゚ストはおよそ1秒に1回送信する必芁がありたす。最も論理的なのは、サむクルごずに1぀の状態から別の状態に移行する有限状態マシンを蚘述するこずでした。 最終的に、オヌトマトンのブランチは次のようになりたした3

1.ボむラヌの珟圚の状態、枩床、および倉調レベルが調査される䜜業ブランチ。 ぀たり、次のように衚すこずができたす。







2.情報およびボむラヌ分岐远加の枩床センサヌ、流量センサヌなど。 [情報]メニュヌを終了するず、最初のブランチに戻りたす。

3.統蚈のブランチバヌナヌの皌働時間、ファン/ポンプの始動回数など。 終了時、段萜2ず同じ。



パラメヌタヌなしのupdate関数の呌び出しは、このすべおの䜜業を担圓したす。 1秒間に玄1回呌び出すだけで十分です。 圌女は前のリク゚ストの結果を受け取り、それに基づいお新しいリク゚ストを送信したす。 ブランチを切り替えるには、目的のブランチの最初のパラメヌタヌに等しいパラメヌタヌで関数を呌び出す必芁がありたす。 たずえば、曎新0はメむンブランチ0-ボむラヌステヌタスの芁求、曎新18-パラメヌタヌ18加熱回路の圧力の芁求で始たる2番目のブランチ、ブランチ3-パラメヌタヌ116バヌナヌの開始数に進みたす。



他の匕数倀もありたす。

1-加熱回路の新しい枩床を蚘録したす。

56-DHW回路の新しい枩床などを蚘録したす。



可胜なすべおの匕数は、曎新関数の最埌に衚瀺されたす。



それで、すべおの必芁なパラメヌタが取埗されたしたが、軟膏にパがありたした-芚えおおいお、私はテヌブルの埌に曞いた-ボむラヌメヌカヌのリク゚ストで他のすべおのパラメヌタ だから-それらの半分は私のボむラヌでれロであり、半分はたったく知られおいない。 少なくずも誰かに圹立぀かもしれないが、圌は無駄にしか曞いおいない。



最も日垞的で退屈な職業は、明らかにメニュヌを曞くこずでした。 玄束どおり、サヌモスタット自䜓に぀いおは詳しく説明したせん。すべおは最終ファヌムりェアで芋るこずができたす

この蚘事の執筆䞭に最埌に远加したのは、「たくさんの文字」を含む退屈な画面ではなく、グラフィックディスプレむモデルに䌌たもの-ロミオWのように芋えたした。 さお、時蚈-アパヌトにはそれらの倚くはなく、RTC DS323xだけが欠萜しおいたす-電源を切るず、時間を再蚭定する必芁がありたす。 これを行うには、いく぀かの文字を手動で描画し、フォントに远加する必芁がありたした。 結果はヘッダヌの䞋の写真で確認できたす。







スケッチ
 #include <OpenTherm.h> #include <LCD5110_Basic.h> #include <OneWire.h> #include <dht.h> #include <dallastemp.h> #include <avr/eeprom.h> //#include <Power.h> #define DHT22_PIN 14 #define LCD_IDLE 0 #define LCD_MAIN 1 #define LCD_MENU 2 #define LCD_CONFIG_1 11 #define LCD_CONFIG_2 12 #define LCD_CONFIG_3 13 #define LCD_INFO_1 21 #define LCD_INFO_2 22 #define LCD_STATISTICS_1 31 #define LCD_STATISTICS_2 32 #define LCD_ITEM_MODE 1 #define LCD_ITEM_CH_EN 2 #define LCD_ITEM_DHW_EN 3 #define LCD_ITEM_CH_MAX 4 #define LCD_ITEM_CH 5 #define LCD_ITEM_DHW 6 #define LCD_ITEM_ROOM 7 #define LCD_ITEM_BRIGH 8 #define LCD_ITEM_ACTIVE 9 #define LCD_ITEM_MAX_MODULATION 10 #define LCD_ITEM_DAYS 11 #define LCD_ITEM_HOURS 12 #define LCD_ITEM_MINUTES 13 #define LCD_ITEM_KP 14 #define LCD_ITEM_KI 15 dht internal_s; OneWire ow(A1); Dallastemp external_s(&ow); OpenTherm ot(8,7); LCD5110 lcd(13,11,10,12,0); //Power sleep; extern uint8_t SmallFont[]; extern uint8_t MediumNumbers[]; extern uint8_t BigNumbers[]; //Encoder handling volatile uint32_t ts_enc=0; volatile int8_t encoder=0; //Clock handling uint32_t clock_ts=0,clock_delta=0; uint8_t hour=0,minute=0,second=0,day=0; //Menu Handling uint8_t menu,item; int8_t pos=0; const char* day_names[7]={"Mon","Tue","Wed","Thu","Fri","Sat","Sun"}; const char* mode_names[2]={"Manual","Auto"}; //Misc uint8_t display_enabled,cfg_enabled,button1=0,button2=0,item_tmp=0,update_period=0; float iSum; struct thermostat_config{ uint8_t address[8]; ot_init_settings ot_settings; float indoor_target_temp; uint8_t active_time; uint8_t brightness; uint8_t mode; uint8_t Kp; uint8_t Ki; uint8_t reserved[8]; }; //thermostat_config settings={{0x28,0xFF,0x53,0x76,0x60,0x14,0x02,0xFC},{1,1,70,30.0,40},22.0,30,130,1}; thermostat_config settings; /* ISR(INT0_vect){ ot.extIntHandler(); } */ ISR(PCINT0_vect){ ot.extIntHandler(); } ISR(PCINT1_vect){ ot.extIntHandler(); } ISR(PCINT2_vect){ ot.extIntHandler(); } ISR(TIMER2_COMPA_vect){ ot.timer2CompAHandler(); } ISR(TIMER2_COMPB_vect){ ot.timer2CompBHandler(); } ISR(WDT_vect) { // sleep.watchdogEvent(); } ISR(INT1_vect){ display_enabled=settings.active_time; OCR1A=settings.brightness; if((millis()-ts_enc >20) && cfg_enabled) { ts_enc=millis(); if ((PIND&bit(4))) { encoder++; } else { encoder--; } } } void fade_display(){ for(uint8_t i=settings.brightness;i>10;i-=10){ OCR1A=i; delay(20); } OCR1A=0; } void enc_setup(){ cli(); DDRD&=~(bit(3)|bit(4)); //set A and B to input EIMSK|=bit(INT1); EICRA|=bit(ISC11); EICRA&=~bit(ISC10); EIFR|=bit(INTF1); sei(); } void button_setup(){ DDRC&=~(bit(2)|bit(3)); } void read_config(){ eeprom_read_block(&settings, 0, sizeof(thermostat_config)); } void write_config(){ eeprom_write_block(&settings, 0, sizeof(thermostat_config)); } void lcd_idle(){ char mod_lev[2]={'\0','\0'}; lcd.setFont(BigNumbers); lcd.print(":",24,0); lcd.print(";",0,24);//";" - thermometer icon lcd.print("0",0,0); lcd.print("0",34,0); lcd.print("<=>",42,24);//"<=>" - House icon lcd.printNumI(hour,(hour>9)?0:14,0); lcd.printNumI(minute,(minute>9)?34:48,0); lcd.setFont(SmallFont); lcd.print(day_names[day],62,16); lcd.printNumF(internal_s.temperature,1,60,40); switch(ot.status&0x7){ case OT_STATUS_CH: lcd.print(">",78,32); break; case OT_STATUS_DHW: lcd.print("=",78,32); break; case OT_STATUS_FAULT: lcd.print("?@",72,32); break; }; if (ot.status&0x8){ if (ot.modulation < 34) *mod_lev='^'; else if (ot.modulation < 67) *mod_lev='_'; else *mod_lev='`'; lcd.print(mod_lev,72,32); } lcd.setFont(MediumNumbers); lcd.printNumF(external_s.getTemp(settings.address),0,6,32); lcd.printNumI(internal_s.temperature,48,32); } void lcd_main(){ lcd.print("Room: /",LEFT,0); lcd.printNumF(internal_s.temperature,1,30,0); if (settings.mode) lcd.printNumF(settings.indoor_target_temp,1,60,0); else lcd.print(" ",54,0); lcd.print("Hum: \%",LEFT,8); lcd.printNumF(internal_s.humidity,1,24,8); lcd.print("Outdoor:",LEFT,16); lcd.printNumF(external_s.getTemp(settings.address),1,48,16); lcd.print("Heat: /",LEFT,24); lcd.printNumF(ot.CH,1,30,24); lcd.printNumF(settings.ot_settings.CH_temp,1,60,24); lcd.print("DHW: /",LEFT,32); lcd.printNumF(ot.DHW,1,24,32); lcd.printNumF(ot.target_DHW,1,54,32); switch(ot.status&0x7){ case OT_STATUS_CH: lcd.print("CH ",LEFT,40); break; case OT_STATUS_DHW: lcd.print("DHW ",LEFT,40); break; case OT_STATUS_FAULT: lcd.print("FAULT:",LEFT,40); switch(ot.fault&0x3D){ case OT_FAULT_SERVICE: lcd.print("FAULT:SERV REQ",LEFT,40); break; case OT_FAULT_LOW_WATER: lcd.print("FAULT:NO WATER",LEFT,40); break; case OT_FAULT_GAS: lcd.print("FAULT:NO FLAME",LEFT,40); break; case OT_FAULT_AIR_PRESSURE: lcd.print("FAULT:AIR PRES",LEFT,40); break; case OT_FAULT_WATER_OV_TEMP: lcd.print("FAULT:WATER OT",LEFT,40); break; } break; default: lcd.print(" ",LEFT,40); break; }; if (ot.status&0x8) { lcd.print("Flame: %",24,40); lcd.printNumI(ot.modulation,60,40); } } void lcd_menu(){ lcd.print(" 1)Config",LEFT,0); lcd.print(" 2)Info",LEFT,8); lcd.print(" 3)Stats",LEFT,16); } void lcd_config_1(){ lcd.print(" Mode:",LEFT,0); lcd.print(mode_names[(item==LCD_ITEM_MODE)?item_tmp:settings.mode],RIGHT,0); lcd.print(" CH enabled:",LEFT,8); lcd.printNumI((item==LCD_ITEM_CH_EN)?item_tmp:ot.CH_enabled,RIGHT,8); lcd.print(" DHW enabled:",LEFT,16); lcd.printNumI((item==LCD_ITEM_DHW_EN)?item_tmp:ot.DHW_enabled,RIGHT,16); lcd.print(" CH max t:",LEFT,24); lcd.printNumI((item==LCD_ITEM_CH_MAX)?item_tmp:ot.CH_max,RIGHT,24); lcd.print(" CH temp:",LEFT,32); lcd.printNumI((item==LCD_ITEM_CH)?item_tmp:ot.target_CH,RIGHT,32); lcd.print(" DHW temp:",LEFT,40); lcd.printNumI((item==LCD_ITEM_DHW)?item_tmp:ot.target_DHW,RIGHT,40); } void lcd_config_2(){ lcd.print(" Room temp:",LEFT,0); lcd.printNumF((item==LCD_ITEM_ROOM)?(float)item_tmp/10+10:settings.indoor_target_temp,1,RIGHT,0); lcd.print(" Brightness:",LEFT,8); lcd.printNumI((item==LCD_ITEM_BRIGH)?item_tmp:settings.brightness,RIGHT,8); lcd.print(" Light time:",LEFT,16); lcd.printNumI((item==LCD_ITEM_ACTIVE)?item_tmp:settings.active_time,RIGHT,16); lcd.print(" Max modul:",LEFT,24); lcd.printNumI((item==LCD_ITEM_MAX_MODULATION)?item_tmp:settings.ot_settings.max_modulation,RIGHT,24); lcd.print(" Days:",LEFT,32); lcd.print(day_names[(item==LCD_ITEM_DAYS)?item_tmp:day],RIGHT,32); lcd.print(" Hours:",LEFT,40); lcd.printNumI((item==LCD_ITEM_HOURS)?item_tmp:hour,RIGHT,40); } void lcd_config_3(){ lcd.print(" Minutes",LEFT,0); lcd.printNumI((item==LCD_ITEM_MINUTES)?item_tmp:minute,RIGHT,0); lcd.print(" Kp",LEFT,8); lcd.printNumI((item==LCD_ITEM_KP)?item_tmp:settings.Kp,RIGHT,8); lcd.print(" Ki",LEFT,16); lcd.printNumI((item==LCD_ITEM_KI)?item_tmp:settings.Ki,RIGHT,16); } void lcd_info_1(){ lcd.print(" CH press:",LEFT,0); lcd.printNumF(ot.CH_water_pressure,1,RIGHT,0); lcd.print(" DHW flow:",LEFT,8); lcd.printNumF(ot.DHW_flow,1,RIGHT,8); lcd.print(" Ret.temp:",LEFT,16); lcd.printNumF(ot.CH_return_temp,1,RIGHT,16); lcd.print(" Max cap.:",LEFT,24); lcd.printNumI(ot.max_capacity,RIGHT,24); lcd.print(" Min mod.:",LEFT,32); lcd.printNumI(ot.min_modulation,RIGHT,32); lcd.print(" DHW min lim:",LEFT,40); lcd.printNumI(ot.DHW_min_lim,RIGHT,40); } void lcd_info_2(){ lcd.print(" DHW lim:",LEFT,0); lcd.printNumI(ot.DHW_max_lim,RIGHT,0); lcd.print(" CH lim:",LEFT,8); lcd.printNumI(ot.CH_min_lim,RIGHT,8); lcd.print(" CH lim:",LEFT,16); lcd.printNumI(ot.CH_max_lim,RIGHT,16); lcd.print(" Int.Sum.:",LEFT,24); lcd.printNumF(iSum,1,RIGHT,24); } void lcd_stats_1(){ lcd.print(" Burn.st:",LEFT,0); lcd.printNumI(ot.burner_starts,RIGHT,0); lcd.print(" CH pump:",LEFT,8); lcd.printNumI(ot.CH_pump_starts,RIGHT,8); lcd.print(" DHW p/v:",LEFT,16); lcd.printNumI(ot.DHW_pump_starts,RIGHT,16); lcd.print(" DHW bur:",LEFT,24); lcd.printNumI(ot.DHW_burner_starts,RIGHT,24); lcd.print(" B.hours:",LEFT,32); lcd.printNumI(ot.burner_op_hours,RIGHT,32); lcd.print(" CHpump H",LEFT,40); lcd.printNumI(ot.CH_pump_op_hours,RIGHT,40); } void lcd_stats_2(){ lcd.print(" DHWp/v H",LEFT,0); lcd.printNumI(ot.DHW_pump_op_hours,RIGHT,0); lcd.print(" DHWburn H",LEFT,8); lcd.printNumI(ot.DHW_burner_op_hours,RIGHT,8); } void update_clock(){ uint32_t tmp=millis()-clock_ts; clock_delta+=(tmp>0)?tmp:0; clock_ts=millis(); while (clock_delta >= 1000){ second++; clock_delta-=1000; if (second > 59){ second=0; minute++; if (minute > 59){ minute=0; hour++; if (hour > 23){ hour=0; day++; if (day>6) day=0; } } } } } void update_display(){ lcd.clrScr(); lcd.setFont(SmallFont); if (menu != LCD_MAIN && menu != LCD_IDLE) { if (pos>5){ pos=0; switch(menu){ case LCD_INFO_1: menu=LCD_INFO_2; break; case LCD_STATISTICS_1: menu=LCD_STATISTICS_2; break; case LCD_CONFIG_1: menu=LCD_CONFIG_2; break; case LCD_CONFIG_2: menu=LCD_CONFIG_3; break; default: pos=5; } } if (pos<0){ pos=5; switch(menu){ case LCD_INFO_2: menu=LCD_INFO_1; break; case LCD_STATISTICS_2: menu=LCD_STATISTICS_1; break; case LCD_CONFIG_2: menu=LCD_CONFIG_1; break; case LCD_CONFIG_3: menu=LCD_CONFIG_2; break; default: pos=0; } } }; switch(menu){ case LCD_IDLE: lcd_idle(); break; case LCD_MAIN: lcd_main(); break; case LCD_MENU: lcd_menu(); break; case LCD_CONFIG_1: lcd_config_1(); break; case LCD_CONFIG_2: lcd_config_2(); break; case LCD_CONFIG_3: lcd_config_3(); break; case LCD_INFO_1: lcd_info_1(); break; case LCD_INFO_2: lcd_info_2(); break; case LCD_STATISTICS_1: lcd_stats_1(); break; case LCD_STATISTICS_2: lcd_stats_2(); break; }; if (menu != LCD_IDLE && menu != LCD_MAIN) if(! item) lcd.print("\\",LEFT,pos*8); else lcd.print("*",LEFT,pos*8); } void setup(){ lcd.InitLCD(); lcd.setFont(SmallFont); lcd.clrScr(); enc_setup(); button_setup(); read_config(); //sleep.measure_wdt(1); // external_s.setRes(settings.address,TEMP_11_BIT); // external_s.startConv(settings.address); analogWrite(9,settings.brightness); display_enabled=settings.active_time; lcd.print("OT init.",CENTER,16); ot.begin(&settings.ot_settings); //sleep.measure_wdt(0); lcd.clrScr(); lcd.print("Slave OT ver.:",CENTER,0); lcd.printNumF(ot.slave_ver,1,CENTER,8); lcd.print("Slave Memb. ID:",CENTER,16); lcd.printNumI(ot.member_id,CENTER,24); lcd.print("Slave CFG:",CENTER,32); lcd.printNumI(ot.sl_cfg,CENTER,40); } void loop(){ //DEBUG //uint8_t type=0,id=0; //uint16_t data=0; float ext_temp,int_temp,t_error; if (!update_period) { internal_s.read22(DHT22_PIN); external_s.startConv(settings.address); } if(display_enabled){ OCR1A=settings.brightness; if(! --display_enabled) { fade_display(); cfg_enabled=0; menu=LCD_IDLE; item=0; delay(1000); ot.update(0); //main thread update_display(); } } if (cfg_enabled) delay (100); else delay(1000); //sleep 1s here // else sleep.sleep(); if (cfg_enabled || !update_period) { update_display(); } if ( (PINC&bit(2)) && button1) button1=0; //if button released reset state if ( (PINC&bit(3)) && button2) button2=0; //Esc if (! (PINC&bit(2)) && ! button1) { button1=1; display_enabled=settings.active_time; OCR1A=settings.brightness; cfg_enabled=1; switch(menu){ case LCD_IDLE: menu=LCD_MAIN; break; case LCD_MAIN: if ((ot.status&0x7) == OT_STATUS_FAULT) { delay(1000); ot.communicate(1,4,256); } break; case LCD_MENU: menu=LCD_MAIN; break; case LCD_CONFIG_1: case LCD_CONFIG_2: case LCD_CONFIG_3: if(! item) menu=LCD_MENU; else item=0; break; default: menu=LCD_MENU; break; }; pos=0; } //Enter if (! (PINC&bit(3)) && ! button2) { button2=1; display_enabled=settings.active_time; OCR1A=settings.brightness; cfg_enabled=1; if (! item){ //standart menu navigation switch(menu){ case LCD_IDLE: case LCD_MAIN: menu=LCD_MENU; break; case LCD_MENU: switch(pos){ case 0: menu=LCD_CONFIG_1; break; case 1: menu=LCD_INFO_1; delay(1000); ot.update(18); //start to get info break; case 2: menu=LCD_STATISTICS_1; delay(1000); ot.update(116); //start to get stats break; } break; case LCD_CONFIG_1: if (!item) item=pos+1; case LCD_CONFIG_2: if (!item) item=pos+7; case LCD_CONFIG_3: if (!item) item=pos+13; switch(item){ case LCD_ITEM_MODE: item_tmp=settings.mode; break; case LCD_ITEM_CH_EN: item_tmp=ot.CH_enabled; break; case LCD_ITEM_DHW_EN: item_tmp=ot.DHW_enabled; break; case LCD_ITEM_CH_MAX: item_tmp=ot.CH_max; break; case LCD_ITEM_CH: item_tmp=settings.ot_settings.CH_temp; break; case LCD_ITEM_DHW: item_tmp=ot.target_DHW; break; case LCD_ITEM_ROOM: item_tmp=(uint8_t)(settings.indoor_target_temp*10 - 100); break; case LCD_ITEM_BRIGH: item_tmp=settings.brightness; break; case LCD_ITEM_ACTIVE: item_tmp=settings.active_time; break; case LCD_ITEM_MAX_MODULATION: item_tmp=settings.ot_settings.max_modulation; break; case LCD_ITEM_DAYS: item_tmp=day; break; case LCD_ITEM_HOURS: item_tmp=hour; break; case LCD_ITEM_MINUTES: item_tmp=minute; break; case LCD_ITEM_KP: item_tmp=settings.Kp; break; case LCD_ITEM_KI: item_tmp=settings.Ki; break; } break; case LCD_INFO_1: break; case LCD_INFO_2: break; }; if (!item) pos=0; } else { //item save switch(item){ case LCD_ITEM_MODE: settings.mode=item_tmp; break; case LCD_ITEM_CH_EN: ot.CH_enabled=item_tmp; settings.ot_settings.CH_enabled=item_tmp; break; case LCD_ITEM_DHW_EN: ot.DHW_enabled=item_tmp; settings.ot_settings.DHW_enabled=item_tmp; break; case LCD_ITEM_CH_MAX: ot.CH_max=item_tmp; settings.ot_settings.CH_max_temp=item_tmp; delay(1000); ot.update(57); break; case LCD_ITEM_CH: settings.ot_settings.CH_temp=item_tmp; ot.target_CH=item_tmp; delay(1000); ot.update(1); break; case LCD_ITEM_DHW: ot.target_DHW=item_tmp; settings.ot_settings.DHW_temp=item_tmp; delay(1000); ot.update(56); break; case LCD_ITEM_ROOM: settings.indoor_target_temp=(float)item_tmp/10+10.0; break; case LCD_ITEM_BRIGH: settings.brightness=item_tmp; break; case LCD_ITEM_ACTIVE: settings.active_time=item_tmp; break; case LCD_ITEM_MAX_MODULATION: ot.max_modulation=item_tmp; settings.ot_settings.max_modulation=item_tmp; delay(1000); ot.update(14); break; case LCD_ITEM_DAYS: day=item_tmp; break; case LCD_ITEM_HOURS: hour=item_tmp; break; case LCD_ITEM_MINUTES: minute=item_tmp; break; case LCD_ITEM_KP: settings.Kp=item_tmp; break; case LCD_ITEM_KI: settings.Ki=item_tmp; break; } write_config(); item_tmp=0; item=0; } } /* ot.complete(&type,&id,&data); lcd.setFont(SmallFont); lcd.print(" / /",LEFT,40); lcd.printNumI(type,0,40); lcd.printNumI(id,12,40); lcd.printNumI(data,36,40); //debug */ if (encoder !=0 ) { if (menu != LCD_MAIN) if(!item) pos=constrain(pos+encoder,-1,6); else switch (item){ case LCD_ITEM_MODE: item_tmp=constrain(item_tmp+encoder,0,1); break; case LCD_ITEM_CH_EN: item_tmp=constrain(item_tmp+encoder,0,1); break; case LCD_ITEM_DHW_EN: item_tmp=constrain(item_tmp+encoder,0,1); break; case LCD_ITEM_CH_MAX: item_tmp=constrain(item_tmp+encoder,ot.CH_min_lim,ot.CH_max_lim); break; case LCD_ITEM_CH: item_tmp=constrain(item_tmp+encoder,ot.CH_min_lim,ot.CH_max_lim); break; case LCD_ITEM_DHW: item_tmp=constrain(item_tmp+encoder,ot.DHW_min_lim,ot.DHW_max_lim); break; case LCD_ITEM_ROOM: item_tmp=constrain(item_tmp+encoder,50,180); break; case LCD_ITEM_BRIGH: item_tmp=constrain(item_tmp+encoder*10,0,255); break; case LCD_ITEM_ACTIVE: item_tmp=constrain(item_tmp+encoder,10,100); break; case LCD_ITEM_MAX_MODULATION: item_tmp=constrain(item_tmp+encoder,10,100); break; case LCD_ITEM_DAYS: item_tmp=constrain(item_tmp+encoder,0,6); break; case LCD_ITEM_HOURS: item_tmp=constrain(item_tmp+encoder,0,23); break; case LCD_ITEM_MINUTES: item_tmp=constrain(item_tmp+encoder,0,59); break; case LCD_ITEM_KP: case LCD_ITEM_KI: item_tmp=constrain(item_tmp+encoder,0,255); break; item_tmp=constrain(item_tmp+encoder,0,255); break; } else if (settings.mode) settings.indoor_target_temp=constrain(settings.indoor_target_temp+encoder*0.1,15.0,28.0); else settings.ot_settings.CH_temp+=encoder*0.1; encoder=0; }; if (menu == LCD_IDLE && ! cfg_enabled && !update_period--) { update_period=60; update_clock(); int_temp=internal_s.temperature; ext_temp=external_s.getTemp(settings.address); t_error=settings.indoor_target_temp-int_temp; iSum=constrain(iSum+t_error,-ot.CH_max_lim,ot.CH_max_lim); if (settings.mode) settings.ot_settings.CH_temp=1*(20.0 + (settings.indoor_target_temp-ext_temp)) + settings.Kp*t_error + settings.Ki*iSum/256; if (settings.ot_settings.CH_temp < ot.CH_min_lim) ot.CH_enabled=0; else ot.CH_enabled = 1; settings.ot_settings.CH_temp=constrain(settings.ot_settings.CH_temp,ot.CH_min_lim,ot.CH_max_lim); if ((!settings.mode && ot.target_CH != settings.ot_settings.CH_temp ) || (settings.mode && abs(ot.target_CH - settings.ot_settings.CH_temp) > 0.5)){ ot.target_CH=settings.ot_settings.CH_temp; // delay(1000); ot.update(1); return; } } ot.update(); }
      
      





長い間、私は、PIレギュレヌタヌになるこずが刀明するたで、冷华剀の必芁な枩床を蚈算する匏を倉えおいたした。蚭定では、係数の倉曎ず積分成分の衚瀺を远加したした。 PIコントロヌラヌ自䜓の前に、必芁な枩床を蚈算するために、最初の蚘事から匏を取りたしたが、積分成分がないず、枩床は必芁なものに察応しなかったか、倧きな比䟋係数で必芁なものに察応せず、前埌にゞャンプしたした。しかし、Habrを読むず、実装されたPIDコントロヌラヌの説明ぞのリンクがすぐにわかりたした。



䞀般に、2぀の動䜜モヌドを䜜成したした



。1CHセットポむントの盎接制埡-結果はボむラヌのリモヌトフロントパネルにすぎたせんさらに、倖郚/内郚の枩床ず湿床も衚瀺されたす。

2実際にOTCを備えたサヌモスタット。



おそらく、OTCセンサヌをオフにしお曲線の傟きを調敎するこずも远加したす係数settings.indoor_target_temp-ext_tempでが、この点はただわかりたせん。



OTCセンサヌの配眮に぀いお少し説明したす。私は窓や壁に穎を開けたくありたせんでしたが、この問題を考えるず、誀っおMIDIコネクタヌの叀いルヌプに目を向けたした。考え盎さずに、センサヌワむダヌをはんだ付けし、ホットメルト接着剀で接続を密封したした路䞊にあるため。盎射日光によっお加熱されないように、センサヌ自䜓を゚アコンの倖郚ナニットの埌ろの日陰に蚭眮したした。



OTCむンストヌル








省゚ネ



コヌドをもう少し泚意深く芋おいた人は、おそらく未知のPowerラむブラリに気づいたでしょう。実際、デバむス党䜓の゚ネルギヌ消費を削枛するこずを期埅しおいたした。ポむントは、メむンルヌプの遅延1000ではなく、MKを1秒間スリヌプ状態にするこずです。問題は、Timer2を実行しおOpenThermメッセヌゞを送受信するための特定のスリヌプモヌドが必芁なこずです。芚えおいるように、それらは非同期に発生したす。しかし、残念ながら、ProteusはSleepではうたく機胜せず、この郚分をデバッグできたせんでした。スリヌプは機胜しおおり、ミリ秒の読み取り倀は修正されおいたすこのため、りォッチドッグタむマヌの粟床の枬定を実装したしたが、OTは機胜しなくなりたす。この目的のために、Pro Miniを賌入したしたその埌、スタビラむザヌずLEDを取り倖したした-より小さなボヌド+ 3.3Vの動䜜が可胜です。これにより、OTむンタヌフェヌスから盎接電源を䟛絊できたす。 Powerラむブラリコヌドを持ち蟌み、興味がある人のために



Power.h
 #include <avr/wdt.h> #include <avr/sleep.h> #include <Arduino.h> class Power{ private: uint16_t wdt_delay; volatile uint32_t ts,delta; volatile uint8_t wdt_count; uint8_t calibration; public: void watchdogEvent(); void prepare_wdt(); void measure_wdt(uint8_t); void sleep(); };
      
      





Power.cpp
 #include "Power.h" void Power::watchdogEvent(){ if (calibration) { delta+=millis()-ts; ts=millis(); }else wdt_disable(); wdt_count++; } void Power::prepare_wdt(){ wdt_reset(); WDTCSR|=1<<WDCE|1<<WDE; WDTCSR=1<<WDIE|1<<WDP2|1<<WDP1; } void Power::measure_wdt(uint8_t start){ cli(); if (start){ prepare_wdt(); ts=millis(); calibration=1; }else{ calibration=0; wdt_delay=delta/wdt_count; } sei(); } void Power::sleep(){ extern volatile unsigned long timer0_millis; prepare_wdt(); set_sleep_mode (SLEEP_MODE_PWR_DOWN); sleep_enable(); wdt_count=0; do sleep_cpu(); while(!wdt_count); timer0_millis+=wdt_delay; sleep_disable(); }
      
      





, – , , :







– . , :







– , . , 3D-, , , . , :



— , ?

— ?

— , , , – .



:



写真










 6x8: 0x98, 0x3c, 0x66, 0x99, 0x7e, 0x30, //  0x3c, 0x18, 0x1a, 0x1e, 0xba, 0xb0, //  ( ) 0x04, 0xfd, 0x84, 0xfd, 0x84, 0xfd, // ( ) 0x00, 0xe0, 0x90, 0x8c, 0x82, 0xdd, //    ( ) 0xdd, 0x82, 0x8c, 0x90, 0xe0, 0x00, //  0x00, 0x60, 0xf0, 0x7a, 0x20, 0x00, //  0x00, 0x70, 0xd8, 0x6d, 0x18, 0x00, //  0x0c, 0x72, 0xdb, 0x6c, 0x1b, 0x06, //  24x14: 0x00, 0xf8, 0x04, 0xfa, 0x85, 0x3a, 0x44, 0x44, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xf0, 0xff, 0xAA, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0x3f, 0x3f, 0x3f, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x80, 0x80, 0xc0, 0xc0, 0x40, 0x40, 0x20, 0x20, 0x20, 0x10, 0x10, 0x10, 0x08, 0x08, 0x01, 0x01, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x7f, 0x40, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //   0x08, 0x04, 0x04, 0x04, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x04, 0x04, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 0x08, 0x08, 0x10, 0x10, 0x1a, 0x3b, 0x21, 0x20, 0x40, 0x40, 0x40, 0x80, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
      
      







, , , update(), , communicate() — complete() —

参照資料



github.com/gavrilov-i/OpenTherm

habrahabr.ru/post/214257

otgw.tclcode.com

www.domoticaforum.eu/uploaded/Ard%20M/Opentherm%20Protocol%20v2-2.pdf



PS , , – — , . , .. , OpenTherm, .

PPS , . , — +5, TX ( ), RX ( ) — 1,5, GND.

:

— , . RX ( . ) TX ( R3) +5. 20. R3 — 7 ( 5). , . .



All Articles