Goed, het is me gelukt om de temperatuur en humidity uit te lezen, maar vraag me niet hoe.
In ieder geval heb ik nu iets wat werkt en wat ik kan proberen te 'refactoren'.
Het is erg timing gevoelig lijkt het, wat ook niet heel gek is, want ik doe reverse-bitbanging
.
Wat ik als eerste probeerde, was om met 2 masters op de I2C bus te werken. Dus de esp als 2de master. Na veel proberen en meten lijkt het er op dat de I2C hardware in de ESP er niet goed tegen kan dat er nog een andere controller op de bus zit. Tijdens idle van de ESP, zie ik heel veel 'ruis' op de bus, waardoor zowel de originele master als de slave de weg kwijt raken.
Daarna probeerde ik om, met de print met het display (en dus de MCU) er af, de sensor te besturen met de ESP, dat gaat ook niet. Ik krijg steeds NACK terug, wat wijst op een hardere pull up op de bus dan de slave kan sinken, of hij 'hoort' me niet.
Ik heb weinig zin om daar aanpassingen aan te doen, aangezien ik de resideo zelf zo min mogelijk wil aanpassen.
Dus, dan maar I2C 'lezen' met GPIO.. Het gaat allemaal erg traag (11-30 kHz), dus dat gaat nog wel. Maar ik merk wel dat je vooral niet te veel dingen tussen twee bytes moet uitvoeren.
Hoe ik het nu gedaan heb is eerst alle 36 bits uitlezen en dan de data er uit plukken. Niet heel netjes, erg verbose en omslachtig, maar het lijkt wel consistent te werken.
Ik wil wel kijken of ik dat nog netter en leesbaarder kan maken.
De code wacht nu op een I2C bericht van de master (de MCU) om te lezen uit adres 0x40, en begint dan mee te lezen. Afhankelijk van hoe je het interval zet in de yaml file, kan het dus zijn dat ie 1 of 2 berichten mist, maar uiteindelijk pakt ie 't wel gewoon weer op.
Code:
In ieder geval heb ik nu iets wat werkt en wat ik kan proberen te 'refactoren'.
Het is erg timing gevoelig lijkt het, wat ook niet heel gek is, want ik doe reverse-bitbanging
Wat ik als eerste probeerde, was om met 2 masters op de I2C bus te werken. Dus de esp als 2de master. Na veel proberen en meten lijkt het er op dat de I2C hardware in de ESP er niet goed tegen kan dat er nog een andere controller op de bus zit. Tijdens idle van de ESP, zie ik heel veel 'ruis' op de bus, waardoor zowel de originele master als de slave de weg kwijt raken.
Daarna probeerde ik om, met de print met het display (en dus de MCU) er af, de sensor te besturen met de ESP, dat gaat ook niet. Ik krijg steeds NACK terug, wat wijst op een hardere pull up op de bus dan de slave kan sinken, of hij 'hoort' me niet.
Ik heb weinig zin om daar aanpassingen aan te doen, aangezien ik de resideo zelf zo min mogelijk wil aanpassen.
Dus, dan maar I2C 'lezen' met GPIO.. Het gaat allemaal erg traag (11-30 kHz), dus dat gaat nog wel. Maar ik merk wel dat je vooral niet te veel dingen tussen twee bytes moet uitvoeren.
Hoe ik het nu gedaan heb is eerst alle 36 bits uitlezen en dan de data er uit plukken. Niet heel netjes, erg verbose en omslachtig, maar het lijkt wel consistent te werken.
Ik wil wel kijken of ik dat nog netter en leesbaarder kan maken.
De code wacht nu op een I2C bericht van de master (de MCU) om te lezen uit adres 0x40, en begint dan mee te lezen. Afhankelijk van hoe je het interval zet in de yaml file, kan het dus zijn dat ie 1 of 2 berichten mist, maar uiteindelijk pakt ie 't wel gewoon weer op.
Code:
C++: cht8305_sensor.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
| #include "esphome.h" #define SDA_PIN D2 #define SCL_PIN D1 class Cht8305Sensor : public PollingComponent, public Sensor { private: public: Sensor *temperature_sensor = new Sensor(); Sensor *humidity_sensor = new Sensor(); Cht8305Sensor(uint32_t update_interval) : PollingComponent(update_interval) { } float get_setup_priority() const { return setup_priority::DATA; } void setup() override { pinMode(SDA_PIN, INPUT); pinMode(SCL_PIN, INPUT); } void update() override { // Check for a possible start of a new transaction while(digitalRead(SDA_PIN) == HIGH && digitalRead(SCL_PIN) == HIGH) {} ESP_LOGD("I2C", "wait for pins to be high"); if (digitalRead(SDA_PIN) == HIGH && digitalRead(SCL_PIN) == HIGH) { delayMicroseconds(5); // Wait a short time to ensure stability after start condition // Check again to see if the bus is still idle if (digitalRead(SDA_PIN) == HIGH && digitalRead(SCL_PIN) == HIGH) { // Now, it's likely the bus is idle, start listening while (digitalRead(SCL_PIN) == HIGH) {} // Wait for the start of a new byte // Assume that the bus is stable; start reading bits byte receivedByte = 0; for (int i = 0; i < 7; i++) { while (digitalRead(SCL_PIN) == LOW) {} // Wait for clock pulse receivedByte = (receivedByte << 1) | digitalRead(SDA_PIN); while (digitalRead(SCL_PIN) == HIGH) {} // Wait for SCL to go low (end of bit) } // read operation bit bool ReadOp; while (digitalRead(SCL_PIN) == LOW) {} // Wait for clock pulse ReadOp = digitalRead(SDA_PIN) == HIGH?true:false; while (digitalRead(SCL_PIN) == HIGH) {} // Wait for SCL to go low (end of bit) // read ACK/NACK bool ACK; while (digitalRead(SCL_PIN) == LOW) {} // Wait for clock pulse ACK = digitalRead(SDA_PIN) == LOW?true:false; while (digitalRead(SCL_PIN) == HIGH) {} // Wait for SCL to go low (end of bit) // this should be the address // ESP_LOGD("I2C", "received address %02X, op:%s result:%s", receivedByte, ReadOp?"R":"W", ACK?"ACK":"NACK"); if (receivedByte != 0x40 || !ReadOp || !ACK) { ESP_LOGW("I2C", "invalid data, breaking off. addr=%02X, read=%u, ack=%u", receivedByte, ReadOp, ACK); return; } bool bits[36]; for (int i = 0; i < 36; i++) { while (digitalRead(SCL_PIN) == LOW) {} // Wait for clock pulse bits[i] = digitalRead(SDA_PIN); while (digitalRead(SCL_PIN) == HIGH) {} // Wait for SCL to go low (end of bit) } uint16_t temp_raw = bits[0]; for (int i=1; i<8; i++) { temp_raw = (temp_raw << 1) | bits[i]; } //i=8 is ack for (int i=9; i<17; i++) { temp_raw = (temp_raw << 1) | bits[i]; } double temperature = 165 * (temp_raw / 65535.0) - 40; temperature -= 1.4; // as seen on the display ESP_LOGD("cht8305", "temperature: %f raw:%i", temperature, temp_raw); temperature_sensor->publish_state(temperature); // i=17 is ack uint16_t hum_raw = bits[18]; for (int i=19; i<26; i++) { hum_raw = (hum_raw << 1) | bits[i]; } // i=26 is ack for (int i=27; i<35; i++) { hum_raw = (hum_raw << 1) | bits[i]; } double humidity = (hum_raw / 65535.0) * 100; humidity += 2; // as seen on the display ESP_LOGD("cht8305", "humidity: %f raw:%i", humidity, hum_raw); humidity_sensor->publish_state(humidity); } } } }; |
C++: cm1106_sensor.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
| // put this file in your esphome folder // protocol implemented as described in https://en.gassensor.com.cn/Product_files/Specifications/CM1106-C%20Single%20Beam%20NDIR%20CO2%20Sensor%20Module%20Specification.pdf #include "esphome.h" class CM1106 : public UARTDevice { public: CM1106(UARTComponent *parent) : UARTDevice(parent) {} int16_t getCo2PPM() { byte expectedBytes[] = {0x16, 0x05, 0x01}; int currentPos = 0; uint8_t response[8] = {0}; while (readUartResponse(response+currentPos, 1)) { if (response[currentPos] == expectedBytes[currentPos]) { currentPos++; } if (currentPos != sizeof(expectedBytes)) continue; // haven't read them all yet readUartResponse(response+3, sizeof(response)-3); uint8_t checksum = calcCRC(response, sizeof(response)); int16_t ppm = response[3] << 8 | response[4]; if (response[7] == checksum) { ESP_LOGD(TAG, "CM1106 Received CO₂=%uppm DF3=%02X DF4=%02X", ppm, response[5], response[6]); return ppm; } else { ESP_LOGW(TAG, "Got wrong UART checksum: 0x%02X - Calculated: 0x%02X, ppm data: %u", response[7], checksum, ppm); return -1; } } return -1; } private: const char *TAG = "cm1106"; // Checksum: 256-(HEAD+LEN+CMD+DATA)%256 uint8_t calcCRC(uint8_t* response, size_t len) { uint8_t crc = 0; // last byte of response is checksum, don't calculate it for (int i = 0; i < len - 1; i++) { crc -= response[i]; } return crc; } bool readUartResponse(uint8_t *response, size_t responseLen) { return read_array(response, responseLen); } }; class CM1106Sensor : public PollingComponent, public Sensor { private: CM1106 *cm1106; public: CM1106Sensor(UARTComponent *parent, uint32_t update_interval) : PollingComponent(update_interval) { cm1106 = new CM1106(parent); } float get_setup_priority() const { return setup_priority::DATA; } void setup() override { } void update() override { int16_t ppm = cm1106->getCo2PPM(); if (ppm > -1) { publish_state(ppm); } } virtual ~CM1106Sensor() { delete cm1106; } }; |
YAML: resideo.yaml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
| esphome: name: resideo includes: - cm1106_sensor.h - cht8305_sensor.h esp8266: board: d1_mini uart: rx_pin: D6 baud_rate: 9600 id: cm1106_uart sensor: - platform: custom lambda: |- auto cm1106Sensor = new CM1106Sensor(id(cm1106_uart), 1000); App.register_component(cm1106Sensor); return {cm1106Sensor}; sensors: - name: "CO₂ Sensor" accuracy_decimals: 0 unit_of_measurement: "ppm" icon: "mdi:molecule-co2" - platform: custom lambda: |- auto cht8305Sensor = new Cht8305Sensor(2000); App.register_component(cht8305Sensor); return {cht8305Sensor->temperature_sensor, cht8305Sensor->humidity_sensor}; sensors: - name: "Temperature" accuracy_decimals: 1 unit_of_measurement: "°C" - name: "Humidity" accuracy_decimals: 1 unit_of_measurement: "%" |