diff --git a/F3:F303/MLX90640-allsky/BMP280.c b/F3:F303/MLX90640-allsky/BMP280.c new file mode 100644 index 0000000..374eebf --- /dev/null +++ b/F3:F303/MLX90640-allsky/BMP280.c @@ -0,0 +1,399 @@ +/** + * Ciastkolog.pl (https://github.com/ciastkolog) + * +*/ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016 sheinz (https://github.com/sheinz) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* + * Copyright 2023 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hardware.h" +#include "spi.h" +#include "BMP280.h" + +#include "usb_dev.h" // DBG +#ifdef EBUG +#include "strfunc.h" +extern volatile uint32_t Tms; +#endif + +#include +#include + +#define BMP280_I2C_ADDRESS_MASK (0x76) +#define BMP280_I2C_ADDRESS_0 (0x76) +#define BMP280_I2C_ADDRESS_1 (0x77) +/** + * BMP280 registers + */ +#define BMP280_REG_HUM_LSB 0xFE +#define BMP280_REG_HUM_MSB 0xFD +#define BMP280_REG_HUM (BMP280_REG_HUM_MSB) +#define BMP280_REG_TEMP_XLSB 0xFC /* bits: 7-4 */ +#define BMP280_REG_TEMP_LSB 0xFB +#define BMP280_REG_TEMP_MSB 0xFA +#define BMP280_REG_TEMP (BMP280_REG_TEMP_MSB) +#define BMP280_REG_PRESS_XLSB 0xF9 /* bits: 7-4 */ +#define BMP280_REG_PRESS_LSB 0xF8 +#define BMP280_REG_PRESS_MSB 0xF7 +#define BMP280_REG_PRESSURE (BMP280_REG_PRESS_MSB) +#define BMP280_REG_ALLDATA (BMP280_REG_PRESS_MSB) // all data: P, T & H +#define BMP280_REG_CONFIG 0xF5 /* bits: 7-5 t_sb; 4-2 filter; 0 spi3w_en */ +#define BMP280_REG_CTRL 0xF4 /* bits: 7-5 osrs_t; 4-2 osrs_p; 1-0 mode */ +#define BMP280_REG_STATUS 0xF3 /* bits: 3 measuring; 0 im_update */ +#define BMP280_REG_CTRL_HUM 0xF2 /* bits: 2-0 osrs_h; */ +#define BMP280_REG_RESET 0xE0 +#define BMP280_RESET_VALUE 0xB6 +#define BMP280_REG_ID 0xD0 + +#define BMP280_REG_CALIBA 0x88 +#define BMP280_CALIBA_SIZE (26) // 26 bytes of calibration registers sequence from 0x88 to 0xa1 +#define BMP280_CALIBB_SIZE (7) // 7 bytes of calibration registers sequence from 0xe1 to 0xe7 +#define BMP280_REG_CALIBB 0xE1 + +#define BMP280_MODE_FORSED (1) // force single measurement +#define BMP280_MODE_NORMAL (3) // run continuosly +#define BMP280_STATUS_IMCOPY (1<<0) // im-copy (sensor busy) +#define BME280_STATUS_MSRGOOD (1<<2) // measurement is good (undocumented bit) +#define BMP280_STATUS_MSRNG (1<<3) // measuring in process + +static struct { + // temperature + uint16_t dig_T1; // 0x88 (LSB), 0x98 (MSB) + int16_t dig_T2; // ... + int16_t dig_T3; + // pressure + uint16_t dig_P1; + int16_t dig_P2; + int16_t dig_P3; + int16_t dig_P4; + int16_t dig_P5; + int16_t dig_P6; + int16_t dig_P7; + int16_t dig_P8; + int16_t dig_P9; // 0x9e, 0x9f + // humidity (partially calculated from EEE struct) + uint8_t unused; // 0xA0 + uint8_t dig_H1; // 0xA1 + int16_t dig_H2; // -------------------- + uint8_t dig_H3; // only from EEE + uint16_t dig_H4; + uint16_t dig_H5; + int8_t dig_H6; + // data is ready + uint8_t rdy; +} __attribute__ ((packed)) CaliData = {0}; + +static struct{ + BMP280_Filter filter; // filtering + BMP280_Oversampling p_os; // oversampling for pressure + BMP280_Oversampling t_os; // -//- temperature + BMP280_Oversampling h_os; // -//- humidity + uint8_t ID; // identificator + uint8_t regctl; // control register base value [(params.t_os << 5) | (params.p_os << 2)] +} params = { + .filter = BMP280_FILTER_OFF, + .p_os = BMP280_OVERS16, + .t_os = BMP280_OVERS16, + .h_os = BMP280_OVERS16, + .ID = 0 +}; + +static BMP280_status bmpstatus = BMP280_NOTINIT; + +BMP280_status BMP280_get_status(){ + return bmpstatus; +} + +#define SPI_BUFSIZE (64) +static uint8_t SPIbuf[SPI_BUFSIZE]; + +// work with BME280 register over SPI +static uint8_t *read_regs(uint8_t reg, uint8_t len){ + if(len > SPI_BUFSIZE-2) return NULL; + SPI_CS_0(); + bzero(SPIbuf, sizeof(SPIbuf)); + SPIbuf[0] = reg | 0x80; + int r = spi_writeread(SPIbuf, len+1); + SPI_CS_1(); + if(!r) return NULL; +#ifdef EBUG + USB_sendstr("Register "); USB_sendstr(uhex2str(reg)); USB_sendstr(": "); + hexdump(USB_sendstr, SPIbuf + 1, len); +#endif + return SPIbuf + 1; +} +static int read_reg(uint8_t reg, uint8_t *val){ + uint8_t *got = read_regs(reg, 1); + if(!got) return 0; + if(val) *val = *got; + return 1; +} +static int write_reg(uint8_t reg, uint8_t val){ + SPI_CS_0(); + SPIbuf[0] = reg & 0x7F; // clear MSbit for writing + SPIbuf[1] = val; + uint8_t r = spi_writeread(SPIbuf, 2); + SPI_CS_1(); + return r; +} + + +void BMP280_setup(){ + bmpstatus = BMP280_NOTINIT; + spi_setup(); +} + +// setters for `params` +void BMP280_setfilter(BMP280_Filter f){ + params.filter = f; +} +BMP280_Filter BMP280_getfilter(){ + return params.filter; +} +void BMP280_setOSt(BMP280_Oversampling os){ + params.t_os = os; +} +void BMP280_setOSp(BMP280_Oversampling os){ + params.p_os = os; +} +void BMP280_setOSh(BMP280_Oversampling os){ + params.h_os = os; +} +// get compensation data, return 1 if OK +static int readcompdata(){ + uint8_t *got = read_regs(BMP280_REG_CALIBA, BMP280_CALIBA_SIZE); + if(!got) return 0; + memcpy(&CaliData, got, BMP280_CALIBA_SIZE); + CaliData.rdy = 1; + if(params.ID == BME280_CHIP_ID){ + got = read_regs(BMP280_REG_CALIBB, BMP280_CALIBB_SIZE); + if(got){ + CaliData.dig_H2 = (got[1] << 8) | got[0]; + CaliData.dig_H3 = got[2]; + CaliData.dig_H4 = (got[3] << 4) | (got[4] & 0x0f); + CaliData.dig_H5 = (got[5] << 4) | (got[4] >> 4); + CaliData.dig_H6 = got[6]; + } + } + return 1; +} + +// read compensation data & write registers +int BMP280_init(){ + IWDG->KR = IWDG_REFRESH; + DBG("INI:"); + if(!read_reg(BMP280_REG_ID, ¶ms.ID)){ + DBG("Can't get ID"); + return 0; + } + if(params.ID != BMP280_CHIP_ID && params.ID != BME280_CHIP_ID){ + DBG("Not BMP/BME"); + return 0; + } + if(!write_reg(BMP280_REG_RESET, BMP280_RESET_VALUE)){ + DBG("Can't reset"); + return 0; + } + uint8_t reg = 1; + int ntries = 100; + while((reg & BMP280_STATUS_IMCOPY) && --ntries){ + IWDG->KR = IWDG_REFRESH; + if(!read_reg(BMP280_REG_STATUS, ®)){ + DBG("can't get status"); + return 0; + } + } + if(ntries < 0){ + DBG("Timeout getting status"); + return 0; + } + if(!readcompdata()){ + DBG("Can't read calibration data"); + return 0; + } + // write filter configuration + reg = params.filter << 2; + if(!write_reg(BMP280_REG_CONFIG, reg)){DBG("Can't save filter settings");} + reg = (params.t_os << 5) | (params.p_os << 2); // oversampling for P/T, sleep mode + if(!write_reg(BMP280_REG_CTRL, reg)){ + DBG("Can't write settings for P/T"); + return 0; + } + params.regctl = reg; + if(params.ID == BME280_CHIP_ID){ // write CTRL_HUM only AFTER CTRL! + reg = params.h_os; + if(!write_reg(BMP280_REG_CTRL_HUM, reg)){ + DBG("Can't write settings for H"); + return 0; + } + } + bmpstatus = BMP280_RELAX; + return 1; +} + +// @return 1 if OK, *devid -> BMP/BME +int BMP280_read_ID(uint8_t *devid){ + if(params.ID != BMP280_CHIP_ID && params.ID != BME280_CHIP_ID) return 0; + *devid = params.ID; + return 1; +} + +// start measurement, @return 1 if all OK +int BMP280_start(){ + if(!CaliData.rdy || bmpstatus == BMP280_BUSY){ +#ifdef EBUG + USB_sendstr("rdy="); USB_sendstr(u2str(CaliData.rdy)); + USB_sendstr("\nbmpstatus="); USB_sendstr(u2str(bmpstatus)); + newline(); +#endif + return 0; + } + uint8_t reg = params.regctl | BMP280_MODE_FORSED; + if(!write_reg(BMP280_REG_CTRL, reg)){ + DBG("Can't write CTRL reg"); + return 0; + } + bmpstatus = BMP280_BUSY; + return 1; +} + +void BMP280_process(){ + if(bmpstatus == BMP280_NOTINIT){ + BMP280_init(); return; + } + if(bmpstatus != BMP280_BUSY) return; + // BUSY state: poll data ready + uint8_t reg; + if(!read_reg(BMP280_REG_STATUS, ®)) return; + if(reg & (BMP280_STATUS_MSRNG | BMP280_STATUS_IMCOPY)) return; // still busy + /*if(params.ID == BME280_CHIP_ID && !(reg & BME280_STATUS_MSRGOOD)){ // check if data is good + DBG("Wrong data!"); + bmpstatus = BMP280_RELAX; + read_regs(BMP280_REG_ALLDATA, 8); + return; + }*/ + bmpstatus = BMP280_RDY; // data ready +} + +// return T*100 degC +static inline int32_t compTemp(int32_t adc_temp, int32_t *t_fine){ + int32_t var1, var2; + var1 = ((((adc_temp >> 3) - ((int32_t) CaliData.dig_T1 << 1))) + * (int32_t) CaliData.dig_T2) >> 11; + var2 = (((((adc_temp >> 4) - (int32_t) CaliData.dig_T1) + * ((adc_temp >> 4) - (int32_t) CaliData.dig_T1)) >> 12) + * (int32_t) CaliData.dig_T3) >> 14; + *t_fine = var1 + var2; + return (*t_fine * 5 + 128) >> 8; +} + +// return p*256 hPa +static inline uint32_t compPres(int32_t adc_press, int32_t fine_temp) { + int64_t var1, var2, p; + var1 = (int64_t) fine_temp - 128000; + var2 = var1 * var1 * (int64_t) CaliData.dig_P6; + var2 = var2 + ((var1 * (int64_t) CaliData.dig_P5) << 17); + var2 = var2 + (((int64_t) CaliData.dig_P4) << 35); + var1 = ((var1 * var1 * (int64_t) CaliData.dig_P3) >> 8) + + ((var1 * (int64_t) CaliData.dig_P2) << 12); + var1 = (((int64_t) 1 << 47) + var1) * ((int64_t) CaliData.dig_P1) >> 33; + if (var1 == 0){ + return 0; // avoid exception caused by division by zero + } + p = 1048576 - adc_press; + p = (((p << 31) - var2) * 3125) / var1; + var1 = ((int64_t) CaliData.dig_P9 * (p >> 13) * (p >> 13)) >> 25; + var2 = ((int64_t) CaliData.dig_P8 * p) >> 19; + p = ((p + var1 + var2) >> 8) + ((int64_t) CaliData.dig_P7 << 4); + return p; +} + +// return H*1024 % +static inline uint32_t compHum(int32_t adc_hum, int32_t fine_temp){ + int32_t v_x1_u32r; + v_x1_u32r = fine_temp - (int32_t) 76800; + v_x1_u32r = ((((adc_hum << 14) - (((int32_t)CaliData.dig_H4) << 20) + - (((int32_t)CaliData.dig_H5) * v_x1_u32r)) + (int32_t)16384) >> 15) + * (((((((v_x1_u32r * ((int32_t)CaliData.dig_H6)) >> 10) + * (((v_x1_u32r * ((int32_t)CaliData.dig_H3)) >> 11) + + (int32_t)32768)) >> 10) + (int32_t)2097152) + * ((int32_t)CaliData.dig_H2) + 8192) >> 14); + v_x1_u32r = v_x1_u32r + - (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7) + * ((int32_t)CaliData.dig_H1)) >> 4); + v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r; + v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r; + return v_x1_u32r >> 12; +} + + +// read data & convert it +int BMP280_getdata(float *T, float *P, float *H){ + if(bmpstatus != BMP280_RDY) return 0; + bmpstatus = BMP280_RELAX; + uint8_t datasz = 8; // amount of bytes to read + if(params.ID != BME280_CHIP_ID){ + if(H) *H = 0; + H = NULL; + datasz = 6; + } + uint8_t *data = read_regs(BMP280_REG_ALLDATA, datasz); + if(!data) return 0; + int32_t p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); + int32_t t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); + int32_t t_fine; + if(T){ + int32_t Temp = compTemp(t, &t_fine); + *T = ((float)Temp)/100.f; + } + if(P) *P = ((float)compPres(p, t_fine)) / 256.f; + if(H){ + int32_t h = (data[6] << 8) | data[7]; + *H = ((float)compHum(h, t_fine))/1024.; + } + return 1; +} + +// dewpoint calculation (T in degrC, H in percents) +float Tdew(float T, float H){ + if(H < 1e-3) return -300.f; + float gamma = 17.27f * T / (237.7f + T) + logf(H/100.f); + if(fabsf(17.27f - gamma) < 1e-3) return -300.f; + return (237.7f * gamma)/(17.27f - gamma); +} diff --git a/F3:F303/MLX90640-allsky/BMP280.h b/F3:F303/MLX90640-allsky/BMP280.h new file mode 100644 index 0000000..b5488e4 --- /dev/null +++ b/F3:F303/MLX90640-allsky/BMP280.h @@ -0,0 +1,96 @@ +/** + * Ciastkolog.pl (https://github.com/ciastkolog) + * +*/ +/** + * The MIT License (MIT) + * + * Copyright (c) 2016 sheinz (https://github.com/sheinz) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +/* + * Copyright 2023 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#ifndef BMP280_H__ +#define BMP280_H__ + +#include + +#define BMP280_CHIP_ID 0x58 +#define BME280_CHIP_ID 0x60 + +typedef enum{ // K for filtering: next = [prev*(k-1) + data_ADC]/k + BMP280_FILTER_OFF = 0, // k=1, no filtering + BMP280_FILTER_2 = 1, // k=2, 2 samples to reach >75% of data_ADC + BMP280_FILTER_4 = 2, // k=4, 5 samples + BMP280_FILTER_8 = 3, // k=8, 11 samples + BMP280_FILTER_16 = 4, // k=16, 22 samples + BMP280_FILTERMAX +} BMP280_Filter; + +typedef enum{ // Number of oversampling + BMP280_NOMEASUR = 0, + BMP280_OVERS1 = 1, + BMP280_OVERS2 = 2, + BMP280_OVERS4 = 3, + BMP280_OVERS8 = 4, + BMP280_OVERS16 = 5, + BMP280_OVERSMAX +} BMP280_Oversampling; + +typedef enum{ + BMP280_NOTINIT, // wasn't inited + BMP280_BUSY, // measurement in progress + BMP280_ERR, // error in I2C + BMP280_RELAX, // relaxed state + BMP280_RDY, // data ready - can get it +} BMP280_status; + + +void BMP280_setup(); +int BMP280_init(); +void BMP280_setfilter(BMP280_Filter f); +BMP280_Filter BMP280_getfilter(); +void BMP280_setOSt(BMP280_Oversampling os); +void BMP280_setOSp(BMP280_Oversampling os); +void BMP280_setOSh(BMP280_Oversampling os); +int BMP280_read_ID(uint8_t *devid); +BMP280_status BMP280_get_status(); +int BMP280_start(); +void BMP280_process(); +int BMP280_getdata(float *T, float *P, float *H); +float Tdew(float T, float H); + +#endif // BMP280_H__ diff --git a/F3:F303/MLX90640-allsky/Readme.md b/F3:F303/MLX90640-allsky/Readme.md index f05327d..44789c8 100644 --- a/F3:F303/MLX90640-allsky/Readme.md +++ b/F3:F303/MLX90640-allsky/Readme.md @@ -13,21 +13,29 @@ Protocol: ``` +dn - draw nth image in ASCII +gn - get nth image 'as is' - float array of 768x4 bytes +l - list active sensors IDs +mn - show temperature map of nth image +tn - show nth image aquisition time +B - reinit BME280 +E - get environment parameters (temperature etc) +G - get MLX state +R - reset device +T - print current Tms + Debugging options: aa - change I2C address to a (a should be non-shifted value!!!) c - continue MLX -d - draw image in ASCII i0..4 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!) p - pause MLX -r0..3 - change resolution (0 - 16bit, 3 - 19-bit) -t - show temperature map -C - "cartoon" mode on/off (show each new image) -D - dump MLX parameters -G - get MLX state -Ia addr - set device address +s - stop MLX (and start from zero @ 'c') +C - "cartoon" mode on/off (show each new image) - USB only!!! +Dn - dump MLX parameters for sensor number n +Ia addr [n] - set device address for interactive work or (with n) change address of n'th sensor Ir reg n - read n words from 16-bit register Iw words - send words (hex/dec/oct/bin) to I2C Is - scan I2C bus -T - print current Tms +Us - send string 's' to other interface ``` diff --git a/F3:F303/MLX90640-allsky/allsky.bin b/F3:F303/MLX90640-allsky/allsky.bin index b8a8e40..81bc8a8 100755 Binary files a/F3:F303/MLX90640-allsky/allsky.bin and b/F3:F303/MLX90640-allsky/allsky.bin differ diff --git a/F3:F303/MLX90640-allsky/hardware.c b/F3:F303/MLX90640-allsky/hardware.c index c5623e9..bb9d581 100644 --- a/F3:F303/MLX90640-allsky/hardware.c +++ b/F3:F303/MLX90640-allsky/hardware.c @@ -16,19 +16,89 @@ * along with this program. If not, see . */ +#include "BMP280.h" #include "hardware.h" -static inline void gpio_setup(){ +static bme280_t environment; // current measurements + +#ifndef EBUG +TRUE_INLINE void iwdg_setup(){ + uint32_t tmout = 16000000; + /* Enable the peripheral clock RTC */ + /* (1) Enable the LSI (40kHz) */ + /* (2) Wait while it is not ready */ + RCC->CSR |= RCC_CSR_LSION; /* (1) */ + while((RCC->CSR & RCC_CSR_LSIRDY) != RCC_CSR_LSIRDY){if(--tmout == 0) break;} /* (2) */ + /* Configure IWDG */ + /* (1) Activate IWDG (not needed if done in option bytes) */ + /* (2) Enable write access to IWDG registers */ + /* (3) Set prescaler by 64 (1.6ms for each tick) */ + /* (4) Set reload value to have a rollover each 2s */ + /* (5) Check if flags are reset */ + /* (6) Refresh counter */ + IWDG->KR = IWDG_START; /* (1) */ + IWDG->KR = IWDG_WRITE_ACCESS; /* (2) */ + IWDG->PR = IWDG_PR_PR_1; /* (3) */ + IWDG->RLR = 1250; /* (4) */ + tmout = 16000000; + while(IWDG->SR){if(--tmout == 0) break;} /* (5) */ + IWDG->KR = IWDG_REFRESH; /* (6) */ +} +#endif + +TRUE_INLINE void gpio_setup(){ RCC->AHBENR |= RCC_AHBENR_GPIOAEN | RCC_AHBENR_GPIOBEN; // for USART and LEDs for(int i = 0; i < 10000; ++i) nop(); // USB - alternate function 14 @ pins PA11/PA12; SWD - AF0 @PA13/14 GPIOA->AFR[1] = AFRf(14, 11) | AFRf(14, 12); GPIOA->MODER = MODER_AF(11) | MODER_AF(12) | MODER_AF(13) | MODER_AF(14) | MODER_O(15); - GPIOB->MODER = MODER_O(0) | MODER_O(1); - GPIOB->ODR = 1; + GPIOB->MODER = MODER_O(0) | MODER_O(1) | MODER_O(2); + pin_set(GPIOB, 1<<1); + SPI_CS_1(); + } void hw_setup(){ gpio_setup(); +#ifndef EBUG + iwdg_setup(); +#endif } +int bme_init(){ + BMP280_setup(); + if(!BMP280_init()) return 0; + if(!BMP280_start()) return 0; + return 1; +} + +// process sensor and make measurements +void bme_process(){ + static uint32_t Tmeas = 0; + BMP280_status s = BMP280_get_status(); + if(s != BMP280_NOTINIT){ + if(s == BMP280_ERR) BMP280_init(); + else{ + BMP280_process(); + s = BMP280_get_status(); // refresh status after processing + float Temperature, Pressure, Humidity; + if(s == BMP280_RDY && BMP280_getdata(&Temperature, &Pressure, &Humidity)){ + environment.Tdew = Tdew(Temperature, Humidity); + environment.T = Temperature; + environment.P = Pressure; + environment.H = Humidity; + environment.Tmeas = Tms; + } + if(Tms - Tmeas > ENV_MEAS_PERIOD-1){ + if(BMP280_start()) Tmeas = Tms; + } + } + } +} + +int get_environment(bme280_t *env){ + if(!env) return 0; + *env = environment; + if(Tms - environment.Tmeas > 2*ENV_MEAS_PERIOD) return 0; // data may be wrong + return 1; // good data +} diff --git a/F3:F303/MLX90640-allsky/hardware.h b/F3:F303/MLX90640-allsky/hardware.h index e3a96f8..d9fa981 100644 --- a/F3:F303/MLX90640-allsky/hardware.h +++ b/F3:F303/MLX90640-allsky/hardware.h @@ -25,7 +25,24 @@ #define USBPU_ON() pin_clear(USBPU_port, USBPU_pin) #define USBPU_OFF() pin_set(USBPU_port, USBPU_pin) +// SPI_CS - PB2 +#define SPI_CS_1() pin_set(GPIOB, 1<<2) +#define SPI_CS_0() pin_clear(GPIOB, 1<<2) + +// interval of environment measurements, ms +#define ENV_MEAS_PERIOD (10000) + +typedef struct{ + float T; // temperature, degC + float Tdew; // dew point, degC + float P; // pressure, Pa + float H; // humidity, percents + uint32_t Tmeas; // time of measurement +} bme280_t; + extern volatile uint32_t Tms; void hw_setup(); - +int bme_init(); +void bme_process(); +int get_environment(bme280_t *env); diff --git a/F3:F303/MLX90640-allsky/ir-allsky.creator.user b/F3:F303/MLX90640-allsky/ir-allsky.creator.user index bc2d899..d7eea8f 100644 --- a/F3:F303/MLX90640-allsky/ir-allsky.creator.user +++ b/F3:F303/MLX90640-allsky/ir-allsky.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/F3:F303/MLX90640-allsky/ir-allsky.files b/F3:F303/MLX90640-allsky/ir-allsky.files index 522e29b..eab2e8e 100644 --- a/F3:F303/MLX90640-allsky/ir-allsky.files +++ b/F3:F303/MLX90640-allsky/ir-allsky.files @@ -1,3 +1,5 @@ +BMP280.c +BMP280.h hardware.c hardware.h i2c.c @@ -15,6 +17,8 @@ proto.c proto.h ringbuffer.c ringbuffer.h +spi.c +spi.h strfunc.c strfunc.h usart.c diff --git a/F3:F303/MLX90640-allsky/main.c b/F3:F303/MLX90640-allsky/main.c index c2fc250..e7f91b0 100644 --- a/F3:F303/MLX90640-allsky/main.c +++ b/F3:F303/MLX90640-allsky/main.c @@ -32,7 +32,7 @@ void sys_tick_handler(void){ ++Tms; } -const char *scanend = "SCANEND\n", *foundid = "FOUNDID="; +static const char *const scanend = "SCANEND\n", *const foundid = "FOUNDID="; int main(void){ char inbuff[MAXSTRLEN+1]; @@ -45,6 +45,7 @@ int main(void){ USBPU_OFF(); hw_setup(); i2c_setup(I2C_SPEED_400K); + bme_init(); USB_setup(); usart_setup(115200); USBPU_ON(); @@ -82,8 +83,9 @@ int main(void){ fp_t *im = mlx_getimage(i); if(im){ chsendfun(SEND_USB); - U(Sensno); UN(i2str(i)); - U(Timage); UN(u2str(Tnow)); drawIma(im); + //U(Sensno); UN(i2str(i)); + U(Timage); USB_putbyte('0'+i); USB_putbyte('='); UN(u2str(Tnow)); + drawIma(im); Tlastima[i] = Tnow; } } @@ -95,5 +97,6 @@ int main(void){ const char *ans = parse_cmd(got, SEND_USART); if(ans) usart_sendstr(ans); } + bme_process(); } } diff --git a/F3:F303/MLX90640-allsky/proto.c b/F3:F303/MLX90640-allsky/proto.c index 2805c6a..78c8b3c 100644 --- a/F3:F303/MLX90640-allsky/proto.c +++ b/F3:F303/MLX90640-allsky/proto.c @@ -20,6 +20,7 @@ #include #include +#include "hardware.h" #include "i2c.h" #include "mlxproc.h" #include "proto.h" @@ -64,36 +65,41 @@ void chsendfun(int sendto){ #define printfl(x,n) do{sendfun->S(float2str(x, n));}while(0) // common names for frequent keys -const char *Timage = "TIMAGE="; -const char *Sensno = "SENSNO="; +const char* const Timage = "TIMAGE"; +const char* const Image = "IMAGE"; +static const char *const Sensno = "SENSNO="; -static const char *OK = "OK\n", *ERR = "ERR\n"; -const char *helpstring = +static const char *const OK = "OK\n", *const ERR = "ERR\n"; +const char *const helpstring = "https://github.com/eddyem/stm32samples/tree/master/F3:F303/MLX90640multi build#" BUILD_NUMBER " @ " BUILD_DATE "\n" " management of single IR bolometer MLX90640\n" - "aa - change I2C address to a (a should be non-shifted value!!!)\n" - "c - continue MLX\n" "dn - draw nth image in ASCII\n" "gn - get nth image 'as is' - float array of 768x4 bytes\n" - "i0..4 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!)\n" "l - list active sensors IDs\n" "mn - show temperature map of nth image\n" + "tn - show nth image aquisition time\n" + "B - reinit BME280\n" + "E - get environment parameters (temperature etc)\n" + "G - get MLX state\n" + "R - reset device\n" + "T - print current Tms\n" + " Debugging options:\n" + "aa - change I2C address to a (a should be non-shifted value!!!)\n" + "c - continue MLX\n" + "i0..4 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!)\n" "p - pause MLX\n" "s - stop MLX (and start from zero @ 'c')\n" - "tn - show nth image aquisition time\n" "C - \"cartoon\" mode on/off (show each new image) - USB only!!!\n" "Dn - dump MLX parameters for sensor number n\n" - "G - get MLX state\n" "Ia addr [n] - set device address for interactive work or (with n) change address of n'th sensor\n" "Ir reg n - read n words from 16-bit register\n" "Iw words - send words (hex/dec/oct/bin) to I2C\n" "Is - scan I2C bus\n" - "T - print current Tms\n" "Us - send string 's' to other interface\n" ; TRUE_INLINE const char *setupI2C(char *buf){ - static const char *speeds[I2C_SPEED_AMOUNT] = { + static const char * const speeds[I2C_SPEED_AMOUNT] = { [I2C_SPEED_10K] = "10K", [I2C_SPEED_100K] = "100K", [I2C_SPEED_400K] = "400K", @@ -207,7 +213,7 @@ TRUE_INLINE void dumpparams(const char *buf){ if(N < 0){ sendfun->S(ERR); return; } MLX90640_params *params = mlx_getparams(N); if(!params){ sendfun->S(ERR); return; } - sendfun->S(Sensno); sendfun->S(i2str(N)); N(); + N(); sendfun->S(Sensno); sendfun->S(i2str(N)); sendfun->S("\nkVdd="); printi(params->kVdd); sendfun->S("\nvdd25="); printi(params->vdd25); sendfun->S("\nKvPTAT="); printfl(params->KvPTAT, 4); @@ -263,15 +269,15 @@ TRUE_INLINE void getst(){ sendfun->S(states[s]); N(); } -// `draw`==1 - draw, ==0 - show T map, 2 - send raw float array with prefix 'SENSNO=x\nTimage=y\n' and postfix "ENDIMAGE\n" +// `draw`==1 - draw, ==0 - show T map, 2 - send raw float array with prefix 'TIMAGEX=y\nIMAGEX=' and postfix "ENDIMAGE\n" static const char *drawimg(const char *buf, int draw){ int sensno = getsensnum(buf); if(sensno > -1){ uint32_t T = mlx_lastimT(sensno); fp_t *img = mlx_getimage(sensno); if(img){ - sendfun->S(Sensno); sendfun->S(u2str(sensno)); N(); - sendfun->S(Timage); sendfun->S(u2str(T)); N(); + //sendfun->S(Sensno); sendfun->S(u2str(sensno)); N(); + sendfun->S(Timage); sendfun->P('0'+sensno); sendfun->P('='); sendfun->S(u2str(T)); N(); switch(draw){ case 0: dumpIma(img); @@ -280,7 +286,7 @@ static const char *drawimg(const char *buf, int draw){ drawIma(img); break; case 2: - { + sendfun->S(Image); sendfun->P('0'+sensno); sendfun->P('='); uint8_t *d = (uint8_t*)img; uint32_t _2send = MLX_PIXNO * sizeof(float); // send by portions of 256 bytes (as image is larger than ringbuffer) @@ -290,8 +296,8 @@ static const char *drawimg(const char *buf, int draw){ _2send -= portion; d += portion; } - } sendfun->S("ENDIMAGE"); N(); + break; } return NULL; } @@ -317,10 +323,22 @@ TRUE_INLINE void listactive(){ static void getimt(const char *buf){ int sensno = getsensnum(buf); if(sensno > -1){ - sendfun->S(Timage); sendfun->S(u2str(mlx_lastimT(sensno))); N(); + sendfun->S(Timage); sendfun->P('0'+sensno); sendfun->P('='); sendfun->S(u2str(mlx_lastimT(sensno))); N(); }else sendfun->S(ERR); } +TRUE_INLINE void getenv(){ + bme280_t env; + if(!get_environment(&env)) sendfun->S("BADENVIRONMENT\n"); + sendfun->S("TEMPERATURE="); sendfun->S(float2str(env.T, 2)); + sendfun->S("\nPRESSURE_HPA="); sendfun->S(float2str(env.P/100.f, 2)); + sendfun->S("\nPRESSURE_MM="); sendfun->S(float2str(env.P * 0.00750062f, 2)); + sendfun->S("\nHUMIDITY="); sendfun->S(float2str(env.H, 2)); + sendfun->S("\nTEMP_DEW="); sendfun->S(float2str(env.Tdew, 1)); + sendfun->S("\nT_MEASUREMENT="); sendfun->S(u2str(env.Tmeas)); + N(); +} + /** * @brief parse_cmd - user string parser * @param buf - user data @@ -386,12 +404,21 @@ const char *parse_cmd(char *buf, int sendto){ break; case 's': mlx_stop(); return OK; + case 'B': + if(bme_init()) return OK; + return ERR; case 'C': if(sendto != SEND_USB) return ERR; cartoon = !cartoon; return OK; + case 'E': + getenv(); + break; case 'G': getst(); break; + case 'R': + NVIC_SystemReset(); + break; case 'T': sendfun->S("T="); sendfun->S(u2str(Tms)); N(); break; @@ -420,7 +447,7 @@ void dumpIma(const fp_t im[MLX_PIXNO]){ #define GRAY_LEVELS (16) // 16-level character set ordered by fill percentage (provided by user) -static const char* CHARS_16 = " .':;+*oxX#&%B$@"; +static const char *const CHARS_16 = " .':;+*oxX#&%B$@"; // draw image in ASCII-art void drawIma(const fp_t im[MLX_PIXNO]){ // Find min and max values diff --git a/F3:F303/MLX90640-allsky/proto.h b/F3:F303/MLX90640-allsky/proto.h index 398ba7a..aab3d44 100644 --- a/F3:F303/MLX90640-allsky/proto.h +++ b/F3:F303/MLX90640-allsky/proto.h @@ -18,7 +18,7 @@ #pragma once -extern const char *Timage, *Sensno; +extern const char *const Timage; #define SEND_USB (1) #define SEND_USART (0) diff --git a/F3:F303/MLX90640-allsky/spi.c b/F3:F303/MLX90640-allsky/spi.c new file mode 100644 index 0000000..646b01e --- /dev/null +++ b/F3:F303/MLX90640-allsky/spi.c @@ -0,0 +1,113 @@ +/* + * This file is part of the ir-allsky project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "hardware.h" +#include "spi.h" +#include // memcpy + +#include "usb_dev.h" +#ifdef EBUG +#include "strfunc.h" +#endif + +spiStatus spi_status = SPI_NOTREADY; +#define WAITX(x) do{volatile uint32_t wctr = 0; while((x) && (++wctr < 360000)) IWDG->KR = IWDG_REFRESH; if(wctr==360000){ DBG("timeout"); return 0;}}while(0) + +// init SPI @ ~280kHz (36MHz/128) +void spi_setup(){ + SPI1->CR1 = 0; // clear EN + SPI1->CR2 = 0; + // PB3 - SCK, BP4 - MISO, PB5 - MOSI; AF5 @PB3-5 + GPIOB->AFR[0] = (GPIOB->AFR[0] & ~(GPIO_AFRL_AFRL3 | GPIO_AFRL_AFRL4 | GPIO_AFRL_AFRL5)) | + AFRf(5, 3) | AFRf(5, 4) | AFRf(5, 5); + GPIOB->MODER = (GPIOB->MODER & (MODER_CLR(3) & MODER_CLR(4) & MODER_CLR(5))) | + MODER_AF(3) | MODER_AF(4) | MODER_AF(5); + RCC->APB1RSTR = RCC_APB2RSTR_SPI1RST; + RCC->APB1RSTR = 0; // clear reset + RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; + // software slave management (without hardware NSS pin); RX only; Baudrate = 0b110 - fpclk/128 + // CPOL=1, CPHA=1: + SPI1->CR1 = SPI_CR1_CPOL | SPI_CR1_CPHA | SPI_CR1_SSM | SPI_CR1_SSI | SPI_CR1_MSTR | SPI_CR1_BR_2 | SPI_CR1_BR_1; + // hardware NSS management, RXNE after 8bit; 8bit transfer (default) + // DS=8bit; RXNE generates after 8bit of data in FIFO; + SPI1->CR2 = SPI_CR2_SSOE | SPI_CR2_FRXTH | SPI_CR2_DS_2|SPI_CR2_DS_1|SPI_CR2_DS_0; + spi_status = SPI_READY; + DBG("SPI works"); +} + +void spi_onoff(uint8_t on){ + if(on) SPI1->CR1 |= SPI_CR1_SPE; + else SPI1->CR1 &= ~SPI_CR1_SPE; +} + +// turn off given SPI channel and release GPIO +void spi_deinit(){ + SPI1->CR1 = 0; + SPI1->CR2 = 0; + RCC->APB2ENR &= ~RCC_APB2ENR_SPI1EN; + GPIOB->AFR[0] = GPIOB->AFR[0] & ~(GPIO_AFRL_AFRL3 | GPIO_AFRL_AFRL4 | GPIO_AFRL_AFRL5); + GPIOB->MODER = GPIOB->MODER & (MODER_CLR(3) & MODER_CLR(4) & MODER_CLR(5)); + spi_status = SPI_NOTREADY; +} + +uint8_t spi_waitbsy(){ + WAITX(SPI1->SR & SPI_SR_BSY); + return 1; +} + +/** + * @brief spi_writeread - send data over SPI (change data array with received bytes) + * @param data - data to write/read + * @param n - length of data + * @return 0 if failed + */ +uint8_t spi_writeread(uint8_t *data, uint8_t n){ + if(spi_status != SPI_READY || !data || !n){ + DBG("not ready"); + return 0; + } + // clear SPI Rx FIFO + spi_onoff(TRUE); + for(int i = 0; i < 4; ++i) (void) SPI1->DR; + for(int x = 0; x < n; ++x){ + WAITX(!(SPI1->SR & SPI_SR_TXE)); + *((volatile uint8_t*)&SPI1->DR) = data[x]; + WAITX(!(SPI1->SR & SPI_SR_RXNE)); + data[x] = *((volatile uint8_t*)&SPI1->DR); + } + spi_onoff(FALSE); // turn off SPI + return 1; +} + +// read data through SPI +uint8_t spi_read(uint8_t *data, uint8_t n){ + if(spi_status != SPI_READY || !data || !n){ + DBG("not ready"); + return 0; + } + // clear SPI Rx FIFO + for(int i = 0; i < 4; ++i) (void) SPI1->DR; + spi_onoff(TRUE); + for(int x = 0; x < n; ++x){ + WAITX(!(SPI1->SR & SPI_SR_RXNE)); + data[x] = *((volatile uint8_t*)&SPI1->DR); + } + spi_onoff(FALSE); // turn off SPI + return 1; +} + diff --git a/F3:F303/MLX90640-allsky/spi.h b/F3:F303/MLX90640-allsky/spi.h new file mode 100644 index 0000000..e956f08 --- /dev/null +++ b/F3:F303/MLX90640-allsky/spi.h @@ -0,0 +1,35 @@ +/* + * This file is part of the ir-allsky project. + * Copyright 2025 Edward V. Emelianov . + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once +#include + +typedef enum{ + SPI_NOTREADY, + SPI_READY, + SPI_BUSY +} spiStatus; + +extern spiStatus spi_status; + +void spi_onoff(uint8_t on); +void spi_deinit(); +void spi_setup(); +uint8_t spi_waitbsy(); +uint8_t spi_writeread(uint8_t *data, uint8_t n); +uint8_t spi_read(uint8_t *data, uint8_t n); diff --git a/F3:F303/MLX90640-allsky/usb_dev.c b/F3:F303/MLX90640-allsky/usb_dev.c index 8e519e4..8d61eee 100644 --- a/F3:F303/MLX90640-allsky/usb_dev.c +++ b/F3:F303/MLX90640-allsky/usb_dev.c @@ -165,7 +165,16 @@ int USB_sendall(){ int USB_send(const uint8_t *buf, int len){ if(!buf || !CDCready || !len) return FALSE; while(len){ - int a = RB_write((ringbuffer*)&rbout, buf, len); + IWDG->KR = IWDG_REFRESH; + int l = RB_datalen((ringbuffer*)&rbout); + if(l < 0) continue; + int portion = rbout.length - 1 - l; + if(portion < 1){ + if(lastdsz == 0) send_next(); + continue; + } + if(portion > len) portion = len; + int a = RB_write((ringbuffer*)&rbout, buf, portion); if(a > 0){ len -= a; buf += a; diff --git a/F3:F303/MLX90640-allsky/usb_dev.h b/F3:F303/MLX90640-allsky/usb_dev.h index a85227c..7be0410 100644 --- a/F3:F303/MLX90640-allsky/usb_dev.h +++ b/F3:F303/MLX90640-allsky/usb_dev.h @@ -50,6 +50,12 @@ void linecoding_handler(usb_LineCoding *lc); #define UN(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0) #define U(s) USB_sendstr(s) +#ifdef EBUG +#define DBG(s) do{USB_sendstr(s); USB_putbyte('\n');}while(0) +#else +#define DBG(s) +#endif + int USB_sendall(); int USB_send(const uint8_t *buf, int len); int USB_putbyte(uint8_t byte); diff --git a/F3:F303/MLX90640-allsky/version.inc b/F3:F303/MLX90640-allsky/version.inc index 3b34625..74584f0 100644 --- a/F3:F303/MLX90640-allsky/version.inc +++ b/F3:F303/MLX90640-allsky/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "15" -#define BUILD_DATE "2025-09-29" +#define BUILD_NUMBER "36" +#define BUILD_DATE "2025-10-05"