@ -1,7 +1,8 @@
/* firmware template for STM8S microcontroller
* Copyright ( C ) 2019 - 20 20 King Kévin < kingkevin @ cuvoodoo . info >
/* 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"
@ -9,11 +10,196 @@
// get length of array
# define ARRAY_LENGTH(x) (sizeof(x) / sizeof((x)[0]))
// blocking wait (in 10 us steps, up to UINT32_MAX / 10)
static void wait_10us ( uint32_t us10 )
// 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 )
{
us10 = ( ( us10 / ( 1 < < CLK - > CKDIVR . fields . HSIDIV ) ) * 1000 ) / 206 ; // calibrated for 1 ms
for ( volatile uint32_t t = 0 ; t < us10 ; t + + ) ; // burn energy
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 )
@ -24,12 +210,45 @@ void main(void)
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 auto-wakeup (AWU) to be able to refresh the watchdog
// 128 kHz LSI used by default in option bytes CKAWUSEL
// we skip measuring the LS clock frequency since there is no need to be precise
AWU - > TBR . fields . AWUTB = 10 ; // interval range: 128-256 ms
AWU - > APR . fields . APR = 0x3e ; // set time to 256 ms
AWU_CSR | = AWU_CSR_AWUEN ; // enable AWU (start only when entering wait or active halt mode)
// 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
@ -40,18 +259,74 @@ void main(void)
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 any interrupt, including periodic AWU)
wfi ( ) ; // go to sleep (wait for periodic ping )
}
}
}
void awu ( void ) __interrupt ( IRQ_AWU ) // auto wakeup
void tim2_isr ( void ) __interrupt ( IRQ_TIM2_UO ) // timer 2 update interrupt service routine
{
volatile uint8_t awuf = AWU_CSR ; // clear interrupt flag by reading it (reading is required, and volatile prevents compiler optimization)
// let the main loop kick the dog
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
}
}