333 lines
11 KiB
C
333 lines
11 KiB
C
/* firmware for the ALPATEC DH-10 0101E dehumidifier humidity sensor board
|
|
* Copyright (C) 2020 King Kévin <kingkevin@cuvoodoo.info>
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*/
|
|
/* firmware to read out temperature and relative humidity from AHT20 sensor and send it to Aplatec DH 10 dehumidifier */
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
#include "stm8s.h"
|
|
|
|
// get length of array
|
|
#define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
|
|
|
|
// pin to send the humidity value to the humidifier
|
|
// WHTM-03 board
|
|
//#define DEHUMIDIFIER_PORT GPIO_PD
|
|
//#define DEHUMIDIFIER_PIN PD5
|
|
// DH10 board
|
|
#define DEHUMIDIFIER_PORT GPIO_PD
|
|
#define DEHUMIDIFIER_PIN PD2
|
|
|
|
// the I²C address of the AHT20
|
|
#define SENSOR_ADDR 0x38
|
|
#define AHT20_CMD_RESET 0xBA
|
|
#define AHT20_CMD_INIT 0xBE
|
|
#define AHT20_CMD_TRIGGER 0xAC
|
|
|
|
// flag set when value should be sent
|
|
volatile bool periodic_flag = false;
|
|
|
|
// blocking wait, in us
|
|
static void wait_us(uint16_t time)
|
|
{
|
|
TIM1->ARRH.reg = (time >> 8) & 0xff; // set timeout
|
|
TIM1->ARRL.reg = (time >> 0) & 0xff; // set timeout
|
|
TIM1->CR1.fields.CEN = 1; // enable timer
|
|
while (TIM1->CR1.fields.CEN); // wait until time passed
|
|
}
|
|
|
|
// send bit to dehumidifier
|
|
static void send_bit(bool bit)
|
|
{
|
|
// pulse low
|
|
DEHUMIDIFIER_PORT->ODR.reg &= ~DEHUMIDIFIER_PIN; // set low
|
|
wait_us(500 - 5); // time with function call delay
|
|
// pulse high
|
|
DEHUMIDIFIER_PORT->ODR.reg |= DEHUMIDIFIER_PIN; // set high
|
|
if (bit) {
|
|
wait_us(1500 - 7); // long high pulse
|
|
} else {
|
|
wait_us(500 - 5); // short high pulse
|
|
}
|
|
}
|
|
|
|
// send byte to dehumidifier (LSb first)
|
|
static void send_byte(uint8_t byte)
|
|
{
|
|
for (int8_t i = 7; i >= 0; i--) {
|
|
send_bit((byte >> i) & 0x01);
|
|
}
|
|
}
|
|
|
|
// send break condition to dehumidifier (after sending the 4 data bytes)
|
|
static void send_break(void)
|
|
{
|
|
// pulse low
|
|
DEHUMIDIFIER_PORT->ODR.reg &= ~DEHUMIDIFIER_PIN; // set low
|
|
wait_us(12250 - 759); // long low pulse
|
|
DEHUMIDIFIER_PORT->ODR.reg |= DEHUMIDIFIER_PIN; // set high
|
|
}
|
|
|
|
static uint8_t i2c_transmit(uint8_t addr, const uint8_t* data, uint8_t len)
|
|
{
|
|
if (0 == len || 0 == data) {
|
|
return 0;
|
|
}
|
|
I2C_SR2 = 0; // clear errors
|
|
I2C_CR2 |= I2C_CR2_START; // send start condition
|
|
while (!(I2C_SR1 & I2C_SR1_SB)) { // wait until start bit is sent
|
|
if (I2C_SR2 & (I2C_SR2_ARLO | I2C_SR2_BERR)) { // bus error occurred
|
|
return 1;
|
|
}
|
|
}
|
|
if (!(I2C_SR3 & I2C_SR3_MSL)) { // ensure we are in master mode
|
|
return 2;
|
|
}
|
|
I2C_SR2 = 0; // clear errors
|
|
I2C_DR = (addr << 1) | 0; // select device in transmit
|
|
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address has been transmitted
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 3;
|
|
}
|
|
}
|
|
if (I2C_SR2 & I2C_SR2_AF) { // no ACK received
|
|
return 4;
|
|
}
|
|
if (!(I2C_SR3 & I2C_SR3_TRA)) { // ensure we are in transmit mode
|
|
return 5;
|
|
}
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
while (!(I2C_SR1 & I2C_SR1_TXE)) { // wait until transmit register is empty
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 6;
|
|
}
|
|
}
|
|
if (I2C_SR2 & I2C_SR2_AF) { // no ACK received
|
|
return 7;
|
|
}
|
|
I2C_DR = data[i]; // send data byte
|
|
}
|
|
while (!(I2C_SR1 & I2C_SR1_TXE)) { // wait until transmit register is empty
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 8;
|
|
}
|
|
}
|
|
while (!(I2C_SR1 & I2C_SR1_BTF)) { // wait until transmission completed
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 9;
|
|
}
|
|
}
|
|
I2C_CR2 |= I2C_CR2_STOP; // send stop
|
|
while (I2C_SR3 & I2C_SR3_BUSY) { // wait until transmission ended
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 10;
|
|
}
|
|
}
|
|
if (I2C_SR3 & I2C_SR3_MSL) { // ensure we are back in slave mode
|
|
return 11;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static uint8_t i2c_receive(uint8_t addr, uint8_t* data, uint8_t len)
|
|
{
|
|
if (0 == len || 0 == data) {
|
|
return 0;
|
|
}
|
|
I2C_SR2 = 0; // clear errors
|
|
I2C_CR2 |= I2C_CR2_START; // send start condition
|
|
while (!(I2C_SR1 & I2C_SR1_SB)) { // wait until start bit is sent
|
|
if (I2C_SR2 & (I2C_SR2_ARLO | I2C_SR2_BERR)) { // bus error occurred
|
|
return 1;
|
|
}
|
|
}
|
|
if (!(I2C_SR3 & I2C_SR3_MSL)) { // ensure we are in master mode
|
|
return 2;
|
|
}
|
|
I2C_SR2 = 0; // clear errors
|
|
I2C_DR = (addr << 1) | 1; // select device in receive
|
|
while (!(I2C_SR1 & I2C_SR1_ADDR)) { // wait until address has been transmitted
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 3;
|
|
}
|
|
}
|
|
if (I2C_SR2 & I2C_SR2_AF) { // no ACK received
|
|
return 4;
|
|
}
|
|
if (I2C_SR3 & I2C_SR3_TRA) { // ensure we are in receive mode
|
|
return 5;
|
|
}
|
|
data[0] = I2C_DR; // just to clear the flag
|
|
if (1 == len) {
|
|
I2C_CR2 &= ~I2C_CR2_ACK; // already send NACK
|
|
I2C_CR2 |= I2C_CR2_STOP; // already program stop
|
|
while (!(I2C_SR1 & I2C_SR1_RXNE)) { // wait to receive data
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 6;
|
|
}
|
|
}
|
|
data[0] = I2C_DR; // save receive data
|
|
} else if (2 == len) {
|
|
I2C_CR2 &= ~I2C_CR2_ACK; // already send NACK
|
|
while (!(I2C_SR1 & I2C_SR1_BTF)) { // wait until 2 bytes received
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 7;
|
|
}
|
|
}
|
|
I2C_CR2 |= I2C_CR2_STOP; // send stop
|
|
} else {
|
|
I2C_CR2 |= I2C_CR2_ACK; // send ACK
|
|
for (uint8_t i = 0; i < len; i++) {
|
|
while (!(I2C_SR1 & I2C_SR1_RXNE)) { // wait to receive data
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 8;
|
|
}
|
|
}
|
|
data[i] = I2C_DR; // save receive data
|
|
if (i + 1 == len) {
|
|
I2C_CR2 &= ~I2C_CR2_ACK; // send NACK
|
|
I2C_CR2 |= I2C_CR2_STOP; // send stop
|
|
}
|
|
}
|
|
}
|
|
while (I2C_SR3 & I2C_SR3_BUSY) { // wait until transmission ended
|
|
if (I2C_SR2 & I2C_SR2_BERR) { // bus error occurred
|
|
return 10;
|
|
}
|
|
}
|
|
if (I2C_SR3 & I2C_SR3_MSL) { // ensure we are back in slave mode
|
|
return 11;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void main(void)
|
|
{
|
|
sim(); // disable interrupts (while we reconfigure them)
|
|
|
|
CLK->CKDIVR.fields.HSIDIV = CLK_CKDIVR_HSIDIV_DIV0; // don't divide internal 16 MHz clock
|
|
CLK->CKDIVR.fields.CPUDIV = CLK_CKDIVR_CPUDIV_DIV0; // don't divide CPU frequency to 16 MHz
|
|
while (!CLK->ICKR.fields.HSIRDY); // wait for internal oscillator to be ready
|
|
|
|
// configure dehumidifier output pin
|
|
DEHUMIDIFIER_PORT->DDR.reg |= DEHUMIDIFIER_PIN; // switch pin to output
|
|
DEHUMIDIFIER_PORT->CR1.reg &= ~DEHUMIDIFIER_PIN; // switch output to open-drain
|
|
//DEHUMIDIFIER_PORT->CR1.reg |= DEHUMIDIFIER_PIN; // switch output to push/pull
|
|
DEHUMIDIFIER_PORT->ODR.reg |= DEHUMIDIFIER_PIN; // set idle high
|
|
|
|
// configure I²C to communicate with ATH20 humidity sensor
|
|
CLK_PCKENR1 |= CLK_PCKENR1_I2C; // enable I²C peripheral (should be on per default)
|
|
GPIO_PB->CR1.reg |= (PB4 | PB5); // enable internal pull-up on SCL/SDA (there should also be external pull-up resistors)
|
|
GPIO_PB->DDR.reg &= ~(PB4 | PB5); // set SCL/SDA as input before it is used as alternate function by the peripheral
|
|
I2C_CR1 |= I2C_CR1_PE; // enable I²C peripheral (must be done before any other register is written)
|
|
I2C_CR2 |= I2C_CR2_STOP; // release lines
|
|
I2C_CR2 |= I2C_CR2_SWRST; // reset peripheral, in case we got stuck and the dog bit
|
|
while (!(GPIO_PB->IDR.reg & PB4)); // wait for SCL line to be released
|
|
while (!(GPIO_PB->IDR.reg & PB5)); // wait for SDA line to be released
|
|
I2C_CR2 &= ~I2C_CR2_SWRST; // release reset
|
|
I2C_CR1 &= ~I2C_CR1_PE; // disable I²C peripheral to configure it
|
|
I2C_FREQR = 16; // the peripheral frequency is 16 MHz (must match CPU frequency)
|
|
I2C_CCRH = (uint8_t)(8000 >> 8); // set control clock to 100 kHz (16 MHz / 2 * 8000), standard mode
|
|
I2C_CCRL = (uint8_t)(8000 >> 0); // set control clock to 100 kHz (16 MHz / 2 * 8000)
|
|
I2C_TRISER = 0x09; // set maximum rise time for SCL (100 ns in standard mode)
|
|
I2C_CR1 |= I2C_CR1_PE; // re-enable I²C peripheral
|
|
|
|
// configure timer 2 for periodic 250 ms value sending
|
|
TIM2->CR1.fields.URS = 1; // only interrupt on overflow
|
|
TIM2->PSCR.fields.PSC = 6; // best prescaler for most precise timer for up to 0.262 second (1.0/(16E6/(2**6)) * 2**16)
|
|
TIM2->ARRH.reg = 62500 >> 8; // set the timeout to 250 ms
|
|
TIM2->ARRL.reg = 62500 & 0xff; // set the timeout to 250 ms
|
|
TIM2->IER.fields.UIE = 1; // enable update/overflow interrupt
|
|
TIM2->CR1.fields.CEN = 1; // enable timer for periodic value output
|
|
|
|
// configure timer 1 for pulse sending (500 us)
|
|
TIM1->CR1.fields.OPM = 1; // only run once
|
|
TIM1->CR1.fields.URS = 1; // only interrupt on overflow
|
|
TIM1->PSCRH.reg = ((16 - 1) >> 8); // set prescaler to have us
|
|
TIM1->PSCRL.reg = ((16 - 1) >> 0); // set prescaler to have us
|
|
TIM1->CNTRH.reg = 0; // reset counter value (should be per default)
|
|
TIM1->CNTRL.reg = 0; // reset counter value (should be per default)
|
|
wait_us(2); // test it once
|
|
|
|
// configure independent watchdog (very loose, just it case the firmware hangs)
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_ENABLE; // start watchdog
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_ACCESS; // allows changing the prescale
|
|
IWDG->PR.fields.PR = IWDG_PR_DIV256; // set prescale to longest time (1.02s)
|
|
IWDG->KR.fields.KEY = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
|
|
rim(); // re-enable interrupts
|
|
bool action = false; // if an action has been performed
|
|
|
|
|
|
wait_us(40000); // wait 40 ms for device to start up
|
|
const uint8_t aht20_reset[] = {AHT20_CMD_RESET};
|
|
uint8_t rc = i2c_transmit(SENSOR_ADDR, aht20_reset, ARRAY_LENGTH(aht20_reset)); // send software reset
|
|
if (rc) {
|
|
while(true); // let the dog bite
|
|
}
|
|
wait_us(20000); // wait 20 ms for reset to complete
|
|
uint8_t rx_buffer[7]; // to receive the measurements
|
|
rc = i2c_receive(SENSOR_ADDR, rx_buffer, 1); // read the status
|
|
if (rc) {
|
|
while(true); // let the dog bite
|
|
}
|
|
if (!(rx_buffer[0] & 0x08)) { // ensure it is calibrated
|
|
const uint8_t aht20_init[] = {AHT20_CMD_INIT};
|
|
rc = i2c_transmit(SENSOR_ADDR, aht20_init, ARRAY_LENGTH(aht20_init)); // initialise device (not sure what it does)
|
|
if (rc) {
|
|
while(true); // let the dog bite
|
|
}
|
|
wait_us(10000); // wait 10 ms for calibration to complete
|
|
}
|
|
// trigger measurement
|
|
const uint8_t aht20_trigger[] = {AHT20_CMD_TRIGGER, 0x33, 0x00};
|
|
rc = i2c_transmit(SENSOR_ADDR, aht20_trigger, ARRAY_LENGTH(aht20_trigger));
|
|
if (rc) {
|
|
while(true); // let the dog bite
|
|
}
|
|
wait_us(80000); // wait 80 ms for measurement to complete
|
|
|
|
uint8_t humidity = 99;
|
|
uint8_t temp = 0;
|
|
while (true) {
|
|
IWDG_KR = IWDG_KR_KEY_REFRESH; // reset watchdog
|
|
if (periodic_flag) { // time to get and send measurements
|
|
periodic_flag = false; // clear flag
|
|
rc = i2c_receive(SENSOR_ADDR, rx_buffer, ARRAY_LENGTH(rx_buffer)); // read measurement
|
|
if (rc) {
|
|
while(true); // let the dog bite
|
|
}
|
|
if (!(rx_buffer[0] & 0x80)) { // not busy doing the measurement
|
|
humidity = (rx_buffer[1] * (uint16_t)100) / 256; // save new humidity value, only use the 8 MSb out of 20
|
|
temp = (((uint8_t)(rx_buffer[3] << 4) + (uint8_t)(rx_buffer[4] >> 4)) * (uint16_t)200) / 256 - 50; // save new temperature, only use the 8 MSB (out of 20)
|
|
// trigger next measurement
|
|
rc = i2c_transmit(SENSOR_ADDR, aht20_trigger, ARRAY_LENGTH(aht20_trigger));
|
|
if (rc) {
|
|
while(true); // let the dog bite
|
|
}
|
|
}
|
|
send_byte(temp);
|
|
send_byte(humidity);
|
|
send_byte(temp ^ 0xff);
|
|
send_byte(humidity ^ 0xff);
|
|
send_break();
|
|
}
|
|
if (action) { // something has been performed, check if other flags have been set meanwhile
|
|
action = false; // clear flag
|
|
} else { // nothing down
|
|
wfi(); // go to sleep (wait for periodic ping)
|
|
}
|
|
}
|
|
}
|
|
|
|
void tim2_isr(void) __interrupt(IRQ_TIM2_UO) // timer 2 update interrupt service routine
|
|
{
|
|
if (TIM2->SR1.fields.UIF) { // 250 ms passed, time to send data
|
|
TIM2->SR1.fields.UIF = 0; // clear flag
|
|
periodic_flag = true; // notify main loop
|
|
}
|
|
}
|
|
|