tedi_k
23-02-2022, 23:27
Wątek nie dotyczy stricte multimediów samochodowych, ale postanowiłem go umieścić w tym dziale jako "odnoszący się do szeroko rozumianej elektroniki"...:p
Rzecz będzie o tym, "co zrobić jeśli w aucie nam czegoś brakuje a mamy pomysł jak to uzupełnić".
Czasy, gdy wystarczył jakiś przekaźnik, trochę przewodów i szczypce płaskie "były wzięły i bezpowrotnie minęły"..:p
Dzisiaj wszystko sterowane jest sterownikami wpiętymi w sieć CAN albo LIN i uruchamiane zazwyczaj w oparciu o komunikaty na tych magistralach.
Tak więc aby (to są przykłady...):
- dodać tylne światło przeciwmgielne w samochodzie Escape 2013-2019 (tym z USA...) i by to działało "jak z fabryki" (europejskiej...) potrzebne jest "magiczne pudełeczko" podpięte do szyny CAN;
- by działała (jak z fabryki...) podgrzewana przednia szyba w autach z USA czasem (o dziwo...) wystarczą przekaźniki (po wymianie panelu przycisków klimatyzacji na wersję EU) a czasami znów trzeba użyć "magicznego pudełeczka" wpiętego w CAN.
Takich przykładów można mnożyć, ale "nie o tym jest ten wątek"...
Postaram się tu pokazać - na rzeczywistym przykładzie - jak rozwiązuje się problemy podobne do powyżej wskazanych.
Opis będzie dotyczył mojego Mitsubishi Outlandera 2019 PHEV - w którym (dość dawno temu...) wymieniłem szybę przednią na podgrzewaną (identyczną jak w Fordzie...) bo mi się trafiła za akceptowalne pieniądze i "czułem wewnętrzną potrzebę jej montażu"...:p
W Mitsubishi grzana szyba przednia (w skrócie tu nazywana HWS) sterowana jest przez panel klimatyzacji. Musi on być w wersji obsługującej tę opcję. Niestety mój panel (P/N 7820B232) nie obsługiwał tej opcji, a takiego co mógłby go skutecznie w tym zastąpić (P/N 7820B231) na całym świecie na Allegro, eBay, Amazon, OLX i gdzie tam jeszcze nie można było przez ponad rok znaleźć. Nowy w ASO przez Mitsubishi został wyceniony na "drobne" 1100 (tysiąc sto...) EURO! - więc "nie rozważałem tej opcji"...
W związku z powyższym, by moja HWS "nie marnowała się w bezczynności" musiałem jakoś sobie "auto uzupełnić".
Na początek postanowiłem spróbować "przerobić" (uzupełnić) mój panel 7820B232 do wersji 7820B231 - porobiłem stosowne zdjęcia płytek panelu 231 i z kolegą zabraliśmy się za uzupełninie brakujących elementów.
Udało nam się wszystko pouzupełniać (jak nam się wydawało...) zgodnie z posiadanymi zdjęciami i - o dziwo - panel dalej żył... Niestety szczęścia mieliśmy "tylko połowę". Okazało się, że funkcjonuje poprawnie cały tor wejściowy (naciśnięcie klawisza podgrzewania szyby jest "zauważane" przez panel klimatyzacji, są kontrolowane wszelkie warunki włączenia szyby oraz czas jej grzanie itp., kontrolka podgrzewania szyby na mojej fabrycznej navi się pokazuje - niestety tor wyjściowy sygnału "nie ruszył" i przekaźniki uruchamiania przetwornicy napięcia do grzania HWS nie włączają się...
Czyli mamy tu sytuację "wypisz wymaluj" dokładnie jak w owym Escape przywołanym powyżej...:one:
Tak więc w moim rozumieniu "szklanka była do połowy PEŁNA" i mogłem zacząć działać by ją "napełnić do pełna"...:p
No i tu wreszcie jest miejsce by na scenę wszedł nasz główny bohater - ARDUINO...
Sytuacja jest niejako "wymarzona" do jego użycia - najwyraźniej generowane są stosowne komunikaty CAN o włączeniu/wyłączeniu HWS - no bo nawigacja "umie" włączyć ikonę uruchomionej podgrzewanej szyby... Wystarczy więc "tylko" znaleźć te komunikaty i użyć ich do wypracowania "brakujących" sygnałów załączenia przekaźnika włączającego przetwornicę napięcia do podgrzewania HWS.
No więc "DO DZIEŁA".
Do realizacji mojego projektu wybrałem "niedawno odkryty" mardzo przyzwoity moduł z MCU ATtiny167 - DigiSpark ATtiny 167 Pro - https://images92.fotosik.pl/575/73511c3d896afdbdgen.jpg (https://images92.fotosik.pl/575/73511c3d896afdbd.jpg)
Sam MCU Atmel ATtiny167 opisany jest w stosownym datasheet (http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8265-8-bit-AVR-Microcontroller-tinyAVR-ATtiny87-ATtiny167_datasheet.pdf) - więc nie będę tu przytaczał opisu, ale dodam, iż bardzo małe rozmiary modułu, 16kB flash (po odjęciu obszaru bootloadera pozostaje ponad 14kB pamięci na program użytkownika), dostęp niezbędnych interfejsów (jako ciekawostkę dodam, iż wbudowany USART posiada możliwość działania jako kontroler LIN...) czynią z tego MCU wprost idealne narzędzie do tworzenia całkiem sporych projektów.
Niestety - jak większość MCU na jądrze AVR - nie ma on wbudowanego interfejsu CAN - więc niezbędne jest użycie zewnętrznego modułu.
Akurat w tym obszarze "nie ma tłoku" i narzuca się użycie najpopularniejszego modułu opartego o chip MCP2515.
https://images89.fotosik.pl/574/62f6174439047cefgen.jpg (https://images89.fotosik.pl/574/62f6174439047cef.jpg)
Dla zapewnienia niezawodności działania urządzenia w aucie ostatnio przestałem polegać na niezawodności stabilizatorów liniowych montowanych na modułach arduino (zbyt dużo poszło mi ich "z dymem"...) i zacząłem stosować miniaturowe przetwornice napięcia - bardzo mi się spodobała taka:
https://images91.fotosik.pl/574/3ec9c59f41e3be20gen.jpg (https://images91.fotosik.pl/574/3ec9c59f41e3be20.jpg)
(tu sfotografowana "z resztą towarzystwa"...) aktualnie dostępna na tej aukcji Allegro (https://allegro.pl/oferta/przetwornica-mini-360-dc-dc-stepdown-1-17v-1-8a-9838986819)
Jak widać na zdjęciu - moduł MCP2515 (mimo, że już przeze mnie sporo "przycięty"...) to w stosunku do przetwornicy i ATtiny167 "prawdziwy gigant"...
Właśnie ze względów na te rozmiary oraz naprawdę spore możliwości - polecam taki zestaw do realizacji zdecydowanej większości projektów "w aucie" i nie tylko...
Ponieważ już wszystko widać "na czym działamy", to pora wreszcie przystąpić do "clou programu" - zaprezentować sam program właśnie... W nomenklaturze Arduino zwany "sketch".
Prezentowany poniżej sketch napisany został "w wersji maksymalnej" - w obsłudze magistrali CAN uzywa filtrów sprzętowych modułu MCP2515 oraz przerwań generowanych przez ten moduł.
Ponadto - trochę dla "zabawy" oraz z tego powodu, ze mój moduł działa w aucie caly czas (także na wyłączonym zapłonie) - to sketch używa trybu obniżonego poboru energii (uśpienia modułu) właśnie w czasie gdy zapłon jest wyłączony. Oczywiście tryb "sleep" - jak wyżej napisałem - zaprogramowałem "przykładowo" i "dla zabawy" bo cały zestaw zużywa poniżej 100mA...
Oto cały kod programu:
// sketch napisany dla modułu ATtiny167 Digispark Pro
// ==================================================
// Moduł CAN MCP2515 obsługiwany z użyciem przerwań
// oraz masek/filtów do selekcji odbieranych ID ramek
// ==================================================
// w czasie gdy zapłon jest wyłączony (stan !_READY)
// MCU ATtiny167 jest w stanie niskiego poboru energii
//
// (c) Tedi_k 2022 - użycie oraz modyfikacje dopuszczone
// pod warunkiem wskazania autora
//
#include <SPI.h>
#include <mcp2515.h>
#include <avr/sleep.h>
#define RDY_LED 1 // wewnętrzna (w module) kontrolka stanu READY/HSW ON (jak miga)
#define HWS_OUT 2 // grzana przednia szyba - przekaźnik/tanzystor wystawić ma masę.
#define PINT0 3 // INT0 jest na pinie PB6 - a to jest D3
#define HWS_IN 5 // włącznik grzania HSV - sterowanie masą
#define CS_MCP2515 9 // chip select modułu CAN (reszta na standardowym SPI)
#define NUSED1 0 // lista pinów nieużywanych - do inicjalizacji dla trybu sleep()
#define NUSED2 4
#define NUSED3 6
#define NUSED4 7
#define NUSED5 12
#define _SW_time 2500UL // force_ON wymaga trzymania przełącznika HWS wciśniętego przez 2,5 sekundy
#define _ON_time 180000UL // w trybie force_ON HWS grzeje przez 180 sekund (3 minuty)
#define _BLNK_time 500UL // blink co 0,5 sekundy
#define _CAN_time 2000UL // 2 sek. bez ramki na CAN == "śmierć CAN"
#define ON true
#define OFF false
#define REL_ON HIGH // sterowanie przekaźnikiem/tranzystorem z portu (wysokim)
#define REL_OFF LOW
#define LED_ON HIGH // dioda-kontrolka sterowana z portu (wysokim)
#define LED_OFF LOW
boolean wrk, WINDSHIELD_H_ON, force_ON, prev_HWS_IN;
boolean _READY, _blnk;
uint32_t _t_ON, _t_BLNK, _t_CAN; // czas (millis()) włączenia trybu force_ON oraz sterowanie miganiem diody
int temp;
volatile bool interrupt = false;
struct can_frame canMsg;
MCP2515 mcp2515(CS_MCP2515); // pin CS dla interfejsu SPI do modułu mcp2515
void irqHandler() {
interrupt = true;
}
void (* resetFunc) (void) = 0; // "Programowe" wykonanie RESET modułu (skok pod adres "0").
void setup() {
WINDSHIELD_H_ON = force_ON = _READY = false;
_blnk = true;
prev_HWS_IN = HIGH;
temp = 0;
pinMode(HWS_OUT, OUTPUT);
pinMode(RDY_LED, OUTPUT);
pinMode(HWS_IN, INPUT_PULLUP);
pinMode(PINT0, INPUT_PULLUP);
pinMode(NUSED1, OUTPUT); // Inicjalizacja pinów nieużywanych dla potrzeb trybu oszczędzania energii
pinMode(NUSED2, OUTPUT);
pinMode(NUSED3, OUTPUT);
pinMode(NUSED4, OUTPUT);
pinMode(NUSED5, OUTPUT);
digitalWrite(NUSED1, LOW);
digitalWrite(NUSED2, LOW);
digitalWrite(NUSED3, LOW);
digitalWrite(NUSED4, LOW);
digitalWrite(NUSED5, LOW);
digitalWrite(RDY_LED, LED_OFF); // Wyłączenie LED (stan "zanegowany")
mcp2515.reset();
mcp2515.setBitrate(CAN_250KBPS, MCP_16MHZ); // Sets CAN at speed 250KBPS and Clock 16MHz - MS-CAN
// (digitalPinToInterrupt(PINT0) - nie kompiluje się)
mcp2515.setFilterMask(MCP2515::MASK0, false, (uint32_t) 0x3ff); // Trzeba zainicjować obie maski w MCP2515 (ramki poniżej diagnostycznych)
mcp2515.setFilterMask(MCP2515::MASK1, false, (uint32_t) 0x3ff);
mcp2515.setFilter(MCP2515::RXF0, false, (uint32_t) 0x315); // MCP2515 ma 6 filtrów (ramka z temperaturą zewnętrzną)
mcp2515.setFilter(MCP2515::RXF1, false, (uint32_t) 0x188); // ramka ze statusem klimatyzacji
mcp2515.setNormalMode(); // setNormalMode() MUSI być za definicjami masek i filtrów
attachInterrupt(INT0, irqHandler, FALLING); // INT0 dla przyjętego pinu 6 do podłączenia INT z MCP2515
set_sleep_mode(SLEEP_MODE_IDLE); // Aby działało SPI nie można użyć wyższego trybu uśpienia
_t_BLNK = millis();
while(CAN_MsgRead() != MCP2515::ERROR_OK) led_BLNK(); // sprawdzamy "czy CAN żyje"...
digitalWrite(RDY_LED, LED_OFF); // Wyłączenie LED (stan "zanegowany")
_t_ON =_t_CAN = millis();
}
void loop() {
if ((uint32_t) (millis() - _t_CAN) > _CAN_time) { // CAN "umarł" - RESET całego sterowania
HWS(OFF);
resetFunc();
}
if (interrupt && CAN_MsgRead() == MCP2515::ERROR_OK) {
interrupt = false;
_t_CAN = millis(); // jest ramka na CAN
switch ((unsigned int)canMsg.can_id) {
case (unsigned int) 0x315: // ramka z temperaturą zewnętrzną
temp = (int) (canMsg.data[6]>>1) - 40;
break;
case (unsigned int) 0x188: // ramka statusu klimatyzacji
if (canMsg.data[0] == (byte) 0x03)
_READY = true;
else // po wyłączeniu silnika natychmiast wyłączamy HWS
_READY = force_ON = false;
{
byte s = canMsg.data[2];
s &= 0xF0;
if (_READY && s == 0x80) {
HWS(ON); // windshield ON
force_ON = false; // HWS włączona wtrybie normalnym - kasuje to tryb force_ON
}
else
if (_READY && s != 0x80 && !force_ON) {
if (WINDSHIELD_H_ON) // HWS właśnie wyłączana w trybie normalnym
prev_HWS_IN = LOW; // zabezpieczenie przed interpretacją przycisku HWS
HWS(OFF); // HWS wyłączona w trybie normalnym
}
}
break;
default: // Przy prawidłowych filtrach tu nigdy nie trafia...
break;
}
}
if (!WINDSHIELD_H_ON)
digitalWrite(RDY_LED, (_READY) ? LED_ON : LED_OFF);
else
led_BLNK(); // Przy włączonej HSW kontrolka led miga
if (!_READY) {
HWS(OFF); // natychmiastowe wyłączenie HWS w trybie "not READY"
sleep_enable();
sei();
sleep_mode(); // przejście w stan uśpienia do czasu przerwania INT0 od następnej ramki CAN
sleep_disable();
}
else {
if (temp <= 5) return; // poniżej 5st. C jedynie tryb "normalny" HWS
if (!force_ON && WINDSHIELD_H_ON) return; // Jeśli HWS włączona w trybie normalnym to nic do zrobienia
wrk = digitalRead(HWS_IN);
if (force_ON) {
if(wrk == LOW && wrk == prev_HWS_IN)
return; // czekamy na puszczenie klawisza HWS;
if (wrk == LOW)
HWS(OFF); // ręczne wyłączenie HWS w trybie force_ON
if ((uint32_t) (millis() - _t_ON) > _ON_time)
HWS(OFF); // upłynął czas włączenia HWS w trybie force_ON
prev_HWS_IN = wrk;
return;
}
if (wrk == HIGH) {
prev_HWS_IN = HIGH;
return; // przycisk włączenia HWS nie jest wciśnięty;
}
if (wrk != prev_HWS_IN) { // obsługa włączania tryby force_ON
if (prev_HWS_IN == HIGH) // właśnie wciśnięto przycisk HWS
_t_ON = millis();
prev_HWS_IN = LOW;
}
else // klawisz HWS nadal wciśnięty...
if ((uint32_t) (millis() - _t_ON) > _SW_time) {
_t_ON = millis();
force_ON = true;
HWS(ON);
}
}
}
void led_BLNK() {
if ((uint32_t) (millis() - _t_BLNK) > _BLNK_time) {
_t_BLNK = millis();
_blnk = !_blnk;
}
digitalWrite(RDY_LED, _blnk);
}
void HWS(bool on_off) {
digitalWrite(HWS_OUT, (on_off) ? REL_ON : REL_OFF); // przekaźnik HWS
WINDSHIELD_H_ON = on_off;
if (!on_off && force_ON) {
force_ON = false;
_t_ON = millis();
}
}
int CAN_MsgRead() {
uint8_t irq = mcp2515.getInterrupts();
if (irq & MCP2515::CANINTF_RX0IF) return(mcp2515.readMessage(MCP2515::RXB0, &canMsg));
if (irq & MCP2515::CANINTF_RX1IF) return(mcp2515.readMessage(MCP2515::RXB1, &canMsg));
return(MCP2515::ERROR_NOMSG);
}
Kod ten można traktować jako "szablon" na wszystkie podobne przypadki.
Po podmianie logiki związanej z tą moją HWS na logikę np. włączania "tylnego światła przeciwmgielnego w Escape" zmieni się w powyższej treści nie więcej niż jakieś 10% kodu...
Kluczowym problemem - który w tym poście nie jest nawet sygnalnie opisany ... - jest zidentyfikowanie ramek CAN do użycia w naszym projekcie...
Ale tego to już nie da się opisać "pokrótce". To po prostu trzeba umieć zrobić. Sporo godzin z komputerem w aucie, cierpliwość, spostrzegawczość i doświadczenie to zestaw niezbędnych cech...
Każdy tu musi już sobie jakoś sam poradzić...
Jak łatwo sprawdzić - "w całym Internecie" nie da się znaleźć informacji w jakiej ramce CAN w Mitsubishi Outlanderze PHEV jest status "włączonego zapłonu"... A jednak "jest namierzony"...:one:
Jak ktoś będzie chciał skonsultować swoje pomysły - to służę pomocą w tym wątku.
Rzecz będzie o tym, "co zrobić jeśli w aucie nam czegoś brakuje a mamy pomysł jak to uzupełnić".
Czasy, gdy wystarczył jakiś przekaźnik, trochę przewodów i szczypce płaskie "były wzięły i bezpowrotnie minęły"..:p
Dzisiaj wszystko sterowane jest sterownikami wpiętymi w sieć CAN albo LIN i uruchamiane zazwyczaj w oparciu o komunikaty na tych magistralach.
Tak więc aby (to są przykłady...):
- dodać tylne światło przeciwmgielne w samochodzie Escape 2013-2019 (tym z USA...) i by to działało "jak z fabryki" (europejskiej...) potrzebne jest "magiczne pudełeczko" podpięte do szyny CAN;
- by działała (jak z fabryki...) podgrzewana przednia szyba w autach z USA czasem (o dziwo...) wystarczą przekaźniki (po wymianie panelu przycisków klimatyzacji na wersję EU) a czasami znów trzeba użyć "magicznego pudełeczka" wpiętego w CAN.
Takich przykładów można mnożyć, ale "nie o tym jest ten wątek"...
Postaram się tu pokazać - na rzeczywistym przykładzie - jak rozwiązuje się problemy podobne do powyżej wskazanych.
Opis będzie dotyczył mojego Mitsubishi Outlandera 2019 PHEV - w którym (dość dawno temu...) wymieniłem szybę przednią na podgrzewaną (identyczną jak w Fordzie...) bo mi się trafiła za akceptowalne pieniądze i "czułem wewnętrzną potrzebę jej montażu"...:p
W Mitsubishi grzana szyba przednia (w skrócie tu nazywana HWS) sterowana jest przez panel klimatyzacji. Musi on być w wersji obsługującej tę opcję. Niestety mój panel (P/N 7820B232) nie obsługiwał tej opcji, a takiego co mógłby go skutecznie w tym zastąpić (P/N 7820B231) na całym świecie na Allegro, eBay, Amazon, OLX i gdzie tam jeszcze nie można było przez ponad rok znaleźć. Nowy w ASO przez Mitsubishi został wyceniony na "drobne" 1100 (tysiąc sto...) EURO! - więc "nie rozważałem tej opcji"...
W związku z powyższym, by moja HWS "nie marnowała się w bezczynności" musiałem jakoś sobie "auto uzupełnić".
Na początek postanowiłem spróbować "przerobić" (uzupełnić) mój panel 7820B232 do wersji 7820B231 - porobiłem stosowne zdjęcia płytek panelu 231 i z kolegą zabraliśmy się za uzupełninie brakujących elementów.
Udało nam się wszystko pouzupełniać (jak nam się wydawało...) zgodnie z posiadanymi zdjęciami i - o dziwo - panel dalej żył... Niestety szczęścia mieliśmy "tylko połowę". Okazało się, że funkcjonuje poprawnie cały tor wejściowy (naciśnięcie klawisza podgrzewania szyby jest "zauważane" przez panel klimatyzacji, są kontrolowane wszelkie warunki włączenia szyby oraz czas jej grzanie itp., kontrolka podgrzewania szyby na mojej fabrycznej navi się pokazuje - niestety tor wyjściowy sygnału "nie ruszył" i przekaźniki uruchamiania przetwornicy napięcia do grzania HWS nie włączają się...
Czyli mamy tu sytuację "wypisz wymaluj" dokładnie jak w owym Escape przywołanym powyżej...:one:
Tak więc w moim rozumieniu "szklanka była do połowy PEŁNA" i mogłem zacząć działać by ją "napełnić do pełna"...:p
No i tu wreszcie jest miejsce by na scenę wszedł nasz główny bohater - ARDUINO...
Sytuacja jest niejako "wymarzona" do jego użycia - najwyraźniej generowane są stosowne komunikaty CAN o włączeniu/wyłączeniu HWS - no bo nawigacja "umie" włączyć ikonę uruchomionej podgrzewanej szyby... Wystarczy więc "tylko" znaleźć te komunikaty i użyć ich do wypracowania "brakujących" sygnałów załączenia przekaźnika włączającego przetwornicę napięcia do podgrzewania HWS.
No więc "DO DZIEŁA".
Do realizacji mojego projektu wybrałem "niedawno odkryty" mardzo przyzwoity moduł z MCU ATtiny167 - DigiSpark ATtiny 167 Pro - https://images92.fotosik.pl/575/73511c3d896afdbdgen.jpg (https://images92.fotosik.pl/575/73511c3d896afdbd.jpg)
Sam MCU Atmel ATtiny167 opisany jest w stosownym datasheet (http://ww1.microchip.com/downloads/en/DeviceDoc/Atmel-8265-8-bit-AVR-Microcontroller-tinyAVR-ATtiny87-ATtiny167_datasheet.pdf) - więc nie będę tu przytaczał opisu, ale dodam, iż bardzo małe rozmiary modułu, 16kB flash (po odjęciu obszaru bootloadera pozostaje ponad 14kB pamięci na program użytkownika), dostęp niezbędnych interfejsów (jako ciekawostkę dodam, iż wbudowany USART posiada możliwość działania jako kontroler LIN...) czynią z tego MCU wprost idealne narzędzie do tworzenia całkiem sporych projektów.
Niestety - jak większość MCU na jądrze AVR - nie ma on wbudowanego interfejsu CAN - więc niezbędne jest użycie zewnętrznego modułu.
Akurat w tym obszarze "nie ma tłoku" i narzuca się użycie najpopularniejszego modułu opartego o chip MCP2515.
https://images89.fotosik.pl/574/62f6174439047cefgen.jpg (https://images89.fotosik.pl/574/62f6174439047cef.jpg)
Dla zapewnienia niezawodności działania urządzenia w aucie ostatnio przestałem polegać na niezawodności stabilizatorów liniowych montowanych na modułach arduino (zbyt dużo poszło mi ich "z dymem"...) i zacząłem stosować miniaturowe przetwornice napięcia - bardzo mi się spodobała taka:
https://images91.fotosik.pl/574/3ec9c59f41e3be20gen.jpg (https://images91.fotosik.pl/574/3ec9c59f41e3be20.jpg)
(tu sfotografowana "z resztą towarzystwa"...) aktualnie dostępna na tej aukcji Allegro (https://allegro.pl/oferta/przetwornica-mini-360-dc-dc-stepdown-1-17v-1-8a-9838986819)
Jak widać na zdjęciu - moduł MCP2515 (mimo, że już przeze mnie sporo "przycięty"...) to w stosunku do przetwornicy i ATtiny167 "prawdziwy gigant"...
Właśnie ze względów na te rozmiary oraz naprawdę spore możliwości - polecam taki zestaw do realizacji zdecydowanej większości projektów "w aucie" i nie tylko...
Ponieważ już wszystko widać "na czym działamy", to pora wreszcie przystąpić do "clou programu" - zaprezentować sam program właśnie... W nomenklaturze Arduino zwany "sketch".
Prezentowany poniżej sketch napisany został "w wersji maksymalnej" - w obsłudze magistrali CAN uzywa filtrów sprzętowych modułu MCP2515 oraz przerwań generowanych przez ten moduł.
Ponadto - trochę dla "zabawy" oraz z tego powodu, ze mój moduł działa w aucie caly czas (także na wyłączonym zapłonie) - to sketch używa trybu obniżonego poboru energii (uśpienia modułu) właśnie w czasie gdy zapłon jest wyłączony. Oczywiście tryb "sleep" - jak wyżej napisałem - zaprogramowałem "przykładowo" i "dla zabawy" bo cały zestaw zużywa poniżej 100mA...
Oto cały kod programu:
// sketch napisany dla modułu ATtiny167 Digispark Pro
// ==================================================
// Moduł CAN MCP2515 obsługiwany z użyciem przerwań
// oraz masek/filtów do selekcji odbieranych ID ramek
// ==================================================
// w czasie gdy zapłon jest wyłączony (stan !_READY)
// MCU ATtiny167 jest w stanie niskiego poboru energii
//
// (c) Tedi_k 2022 - użycie oraz modyfikacje dopuszczone
// pod warunkiem wskazania autora
//
#include <SPI.h>
#include <mcp2515.h>
#include <avr/sleep.h>
#define RDY_LED 1 // wewnętrzna (w module) kontrolka stanu READY/HSW ON (jak miga)
#define HWS_OUT 2 // grzana przednia szyba - przekaźnik/tanzystor wystawić ma masę.
#define PINT0 3 // INT0 jest na pinie PB6 - a to jest D3
#define HWS_IN 5 // włącznik grzania HSV - sterowanie masą
#define CS_MCP2515 9 // chip select modułu CAN (reszta na standardowym SPI)
#define NUSED1 0 // lista pinów nieużywanych - do inicjalizacji dla trybu sleep()
#define NUSED2 4
#define NUSED3 6
#define NUSED4 7
#define NUSED5 12
#define _SW_time 2500UL // force_ON wymaga trzymania przełącznika HWS wciśniętego przez 2,5 sekundy
#define _ON_time 180000UL // w trybie force_ON HWS grzeje przez 180 sekund (3 minuty)
#define _BLNK_time 500UL // blink co 0,5 sekundy
#define _CAN_time 2000UL // 2 sek. bez ramki na CAN == "śmierć CAN"
#define ON true
#define OFF false
#define REL_ON HIGH // sterowanie przekaźnikiem/tranzystorem z portu (wysokim)
#define REL_OFF LOW
#define LED_ON HIGH // dioda-kontrolka sterowana z portu (wysokim)
#define LED_OFF LOW
boolean wrk, WINDSHIELD_H_ON, force_ON, prev_HWS_IN;
boolean _READY, _blnk;
uint32_t _t_ON, _t_BLNK, _t_CAN; // czas (millis()) włączenia trybu force_ON oraz sterowanie miganiem diody
int temp;
volatile bool interrupt = false;
struct can_frame canMsg;
MCP2515 mcp2515(CS_MCP2515); // pin CS dla interfejsu SPI do modułu mcp2515
void irqHandler() {
interrupt = true;
}
void (* resetFunc) (void) = 0; // "Programowe" wykonanie RESET modułu (skok pod adres "0").
void setup() {
WINDSHIELD_H_ON = force_ON = _READY = false;
_blnk = true;
prev_HWS_IN = HIGH;
temp = 0;
pinMode(HWS_OUT, OUTPUT);
pinMode(RDY_LED, OUTPUT);
pinMode(HWS_IN, INPUT_PULLUP);
pinMode(PINT0, INPUT_PULLUP);
pinMode(NUSED1, OUTPUT); // Inicjalizacja pinów nieużywanych dla potrzeb trybu oszczędzania energii
pinMode(NUSED2, OUTPUT);
pinMode(NUSED3, OUTPUT);
pinMode(NUSED4, OUTPUT);
pinMode(NUSED5, OUTPUT);
digitalWrite(NUSED1, LOW);
digitalWrite(NUSED2, LOW);
digitalWrite(NUSED3, LOW);
digitalWrite(NUSED4, LOW);
digitalWrite(NUSED5, LOW);
digitalWrite(RDY_LED, LED_OFF); // Wyłączenie LED (stan "zanegowany")
mcp2515.reset();
mcp2515.setBitrate(CAN_250KBPS, MCP_16MHZ); // Sets CAN at speed 250KBPS and Clock 16MHz - MS-CAN
// (digitalPinToInterrupt(PINT0) - nie kompiluje się)
mcp2515.setFilterMask(MCP2515::MASK0, false, (uint32_t) 0x3ff); // Trzeba zainicjować obie maski w MCP2515 (ramki poniżej diagnostycznych)
mcp2515.setFilterMask(MCP2515::MASK1, false, (uint32_t) 0x3ff);
mcp2515.setFilter(MCP2515::RXF0, false, (uint32_t) 0x315); // MCP2515 ma 6 filtrów (ramka z temperaturą zewnętrzną)
mcp2515.setFilter(MCP2515::RXF1, false, (uint32_t) 0x188); // ramka ze statusem klimatyzacji
mcp2515.setNormalMode(); // setNormalMode() MUSI być za definicjami masek i filtrów
attachInterrupt(INT0, irqHandler, FALLING); // INT0 dla przyjętego pinu 6 do podłączenia INT z MCP2515
set_sleep_mode(SLEEP_MODE_IDLE); // Aby działało SPI nie można użyć wyższego trybu uśpienia
_t_BLNK = millis();
while(CAN_MsgRead() != MCP2515::ERROR_OK) led_BLNK(); // sprawdzamy "czy CAN żyje"...
digitalWrite(RDY_LED, LED_OFF); // Wyłączenie LED (stan "zanegowany")
_t_ON =_t_CAN = millis();
}
void loop() {
if ((uint32_t) (millis() - _t_CAN) > _CAN_time) { // CAN "umarł" - RESET całego sterowania
HWS(OFF);
resetFunc();
}
if (interrupt && CAN_MsgRead() == MCP2515::ERROR_OK) {
interrupt = false;
_t_CAN = millis(); // jest ramka na CAN
switch ((unsigned int)canMsg.can_id) {
case (unsigned int) 0x315: // ramka z temperaturą zewnętrzną
temp = (int) (canMsg.data[6]>>1) - 40;
break;
case (unsigned int) 0x188: // ramka statusu klimatyzacji
if (canMsg.data[0] == (byte) 0x03)
_READY = true;
else // po wyłączeniu silnika natychmiast wyłączamy HWS
_READY = force_ON = false;
{
byte s = canMsg.data[2];
s &= 0xF0;
if (_READY && s == 0x80) {
HWS(ON); // windshield ON
force_ON = false; // HWS włączona wtrybie normalnym - kasuje to tryb force_ON
}
else
if (_READY && s != 0x80 && !force_ON) {
if (WINDSHIELD_H_ON) // HWS właśnie wyłączana w trybie normalnym
prev_HWS_IN = LOW; // zabezpieczenie przed interpretacją przycisku HWS
HWS(OFF); // HWS wyłączona w trybie normalnym
}
}
break;
default: // Przy prawidłowych filtrach tu nigdy nie trafia...
break;
}
}
if (!WINDSHIELD_H_ON)
digitalWrite(RDY_LED, (_READY) ? LED_ON : LED_OFF);
else
led_BLNK(); // Przy włączonej HSW kontrolka led miga
if (!_READY) {
HWS(OFF); // natychmiastowe wyłączenie HWS w trybie "not READY"
sleep_enable();
sei();
sleep_mode(); // przejście w stan uśpienia do czasu przerwania INT0 od następnej ramki CAN
sleep_disable();
}
else {
if (temp <= 5) return; // poniżej 5st. C jedynie tryb "normalny" HWS
if (!force_ON && WINDSHIELD_H_ON) return; // Jeśli HWS włączona w trybie normalnym to nic do zrobienia
wrk = digitalRead(HWS_IN);
if (force_ON) {
if(wrk == LOW && wrk == prev_HWS_IN)
return; // czekamy na puszczenie klawisza HWS;
if (wrk == LOW)
HWS(OFF); // ręczne wyłączenie HWS w trybie force_ON
if ((uint32_t) (millis() - _t_ON) > _ON_time)
HWS(OFF); // upłynął czas włączenia HWS w trybie force_ON
prev_HWS_IN = wrk;
return;
}
if (wrk == HIGH) {
prev_HWS_IN = HIGH;
return; // przycisk włączenia HWS nie jest wciśnięty;
}
if (wrk != prev_HWS_IN) { // obsługa włączania tryby force_ON
if (prev_HWS_IN == HIGH) // właśnie wciśnięto przycisk HWS
_t_ON = millis();
prev_HWS_IN = LOW;
}
else // klawisz HWS nadal wciśnięty...
if ((uint32_t) (millis() - _t_ON) > _SW_time) {
_t_ON = millis();
force_ON = true;
HWS(ON);
}
}
}
void led_BLNK() {
if ((uint32_t) (millis() - _t_BLNK) > _BLNK_time) {
_t_BLNK = millis();
_blnk = !_blnk;
}
digitalWrite(RDY_LED, _blnk);
}
void HWS(bool on_off) {
digitalWrite(HWS_OUT, (on_off) ? REL_ON : REL_OFF); // przekaźnik HWS
WINDSHIELD_H_ON = on_off;
if (!on_off && force_ON) {
force_ON = false;
_t_ON = millis();
}
}
int CAN_MsgRead() {
uint8_t irq = mcp2515.getInterrupts();
if (irq & MCP2515::CANINTF_RX0IF) return(mcp2515.readMessage(MCP2515::RXB0, &canMsg));
if (irq & MCP2515::CANINTF_RX1IF) return(mcp2515.readMessage(MCP2515::RXB1, &canMsg));
return(MCP2515::ERROR_NOMSG);
}
Kod ten można traktować jako "szablon" na wszystkie podobne przypadki.
Po podmianie logiki związanej z tą moją HWS na logikę np. włączania "tylnego światła przeciwmgielnego w Escape" zmieni się w powyższej treści nie więcej niż jakieś 10% kodu...
Kluczowym problemem - który w tym poście nie jest nawet sygnalnie opisany ... - jest zidentyfikowanie ramek CAN do użycia w naszym projekcie...
Ale tego to już nie da się opisać "pokrótce". To po prostu trzeba umieć zrobić. Sporo godzin z komputerem w aucie, cierpliwość, spostrzegawczość i doświadczenie to zestaw niezbędnych cech...
Każdy tu musi już sobie jakoś sam poradzić...
Jak łatwo sprawdzić - "w całym Internecie" nie da się znaleźć informacji w jakiej ramce CAN w Mitsubishi Outlanderze PHEV jest status "włączonego zapłonu"... A jednak "jest namierzony"...:one:
Jak ktoś będzie chciał skonsultować swoje pomysły - to służę pomocą w tym wątku.