diff --git a/F1:F103/MLX90640/MLX90640.bin b/F1:F103/MLX90640/MLX90640.bin index 430e890..e1978e6 100755 Binary files a/F1:F103/MLX90640/MLX90640.bin and b/F1:F103/MLX90640/MLX90640.bin differ diff --git a/F1:F103/MLX90640/Makefile b/F1:F103/MLX90640/Makefile index 2059675..8143eab 100644 --- a/F1:F103/MLX90640/Makefile +++ b/F1:F103/MLX90640/Makefile @@ -10,7 +10,7 @@ DENSITY ?= MD # change this linking script depending on particular MCU model, LDSCRIPT ?= stm32f103x8.ld # debug -#DEFS = -DEBUG +DEFS = -DEBUG # autoincremental version & build date VERSION_FILE = version.inc diff --git a/F1:F103/MLX90640/hardware.c b/F1:F103/MLX90640/hardware.c index 4d54572..b1ead54 100644 --- a/F1:F103/MLX90640/hardware.c +++ b/F1:F103/MLX90640/hardware.c @@ -20,14 +20,16 @@ static inline void gpio_setup(){ // Enable clocks to the GPIO subsystems (PB for ADC), turn on AFIO clocking to disable SWD/JTAG - RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN; + RCC->APB2ENR |= RCC_APB2ENR_IOPAEN | RCC_APB2ENR_IOPBEN | RCC_APB2ENR_IOPCEN | RCC_APB2ENR_AFIOEN; // turn off SWJ/JTAG // AFIO->MAPR = AFIO_MAPR_SWJ_CFG_DISABLE; AFIO->MAPR = AFIO_MAPR_SWJ_CFG_JTAGDISABLE; // for PA15 // Set led as opendrain output - GPIOC->CRH |= CRH(13, CNF_ODOUTPUT|MODE_SLOW); + GPIOC->CRH |= CRH(13, CNF_ODOUTPUT | MODE_SLOW); // USB pullup (PA15) - pushpull output - GPIOA->CRH = CRH(15, CNF_PPOUTPUT|MODE_SLOW); + GPIOA->CRH = CRH(15, CNF_PPOUTPUT | MODE_SLOW); + // PB5 is powered MLX sensor (less than 23mA) - pushpull output + GPIOB->CRL = CRL(5, CNF_PPOUTPUT | MODE_SLOW); } void hw_setup(){ diff --git a/F1:F103/MLX90640/hardware.h b/F1:F103/MLX90640/hardware.h index 6d49fdf..0a44612 100644 --- a/F1:F103/MLX90640/hardware.h +++ b/F1:F103/MLX90640/hardware.h @@ -29,8 +29,13 @@ // USB pullup (not present in bluepill, should be soldered) - PA15 #define USBPU_port GPIOA #define USBPU_pin (1<<15) +#define MLXPOW_port GPIOB +#define MLXPOW_pin (1<<5) #define USBPU_ON() pin_set(USBPU_port, USBPU_pin) #define USBPU_OFF() pin_clear(USBPU_port, USBPU_pin) +#define MLXPOW_ON() pin_set(MLXPOW_port, MLXPOW_pin) +#define MLXPOW_OFF() pin_clear(MLXPOW_port, MLXPOW_pin) +#define MLXPOW_VAL() pin_read(MLXPOW_port, MLXPOW_pin) #define LED_blink(x) pin_toggle(x ## _port, x ## _pin) #define LED_on(x) pin_clear(x ## _port, x ## _pin) diff --git a/F1:F103/MLX90640/i2c.c b/F1:F103/MLX90640/i2c.c index 8aa8d97..7b8e4e2 100644 --- a/F1:F103/MLX90640/i2c.c +++ b/F1:F103/MLX90640/i2c.c @@ -18,6 +18,11 @@ #include "hardware.h" #include "i2c.h" +/* don't run debugging info */ +#ifdef EBUG +#undef EBUG +#endif + #include "strfunct.h" extern volatile uint32_t Tms; diff --git a/F1:F103/MLX90640/main.c b/F1:F103/MLX90640/main.c index 7a4a625..e4dd2c1 100644 --- a/F1:F103/MLX90640/main.c +++ b/F1:F103/MLX90640/main.c @@ -41,7 +41,9 @@ int main(void){ USBPU_OFF(); USB_setup(); +#ifndef EBUG iwdg_setup(); +#endif USBPU_ON(); i2c_setup(TRUE); i2c_set_addr7(MLX_DEFAULT_ADDR); diff --git a/F1:F103/MLX90640/mlx90640.c b/F1:F103/MLX90640/mlx90640.c index 63de249..6d40ea9 100644 --- a/F1:F103/MLX90640/mlx90640.c +++ b/F1:F103/MLX90640/mlx90640.c @@ -16,35 +16,60 @@ * along with this program. If not, see . */ +#include "hardware.h" #include "i2c.h" #include "mlx90640.h" +#include "mlx90640_regs.h" #include "strfunct.h" -static uint8_t dataarray[1536]; -static int portionlen = 0; +#ifdef EBUG +extern volatile uint32_t Tms; +#endif + +mlx90640_state mlx_state = M_ERROR; + +MLX90640_params params; + +#if REG_CALIBRDATA_LEN > MLX_DMA_MAXLEN || MLX_PIXARRSZ > MLX_DMA_MAXLEN +#error "MLX_DMA_MAXLEN should be >= REG_CALIBRDATA_LEN" +#endif +static uint16_t dataarray[MLX_DMA_MAXLEN]; // array for raw data from sensor +static int portionlen = 0; // data length in `dataarray` +float mlx_image[MLX_PIXNO]; // ready image + +#define CREG_VAL(reg) dataarray[CREG_IDX(reg)] +#define IMD_VAL(reg) dataarray[IMD_IDX(reg)] + +static uint8_t simpleimage = 0; // ==1 not to calibrate T +static uint8_t subpageno = 0; // subpage number + +// reg_control values for subpage #0 and #1 +static const uint16_t reg_control_val[2] = { + REG_CONTROL_CHESS | REG_CONTROL_RES18 | REG_CONTROL_REFR_2HZ | REG_CONTROL_SUBPSEL | REG_CONTROL_DATAHOLD | REG_CONTROL_SUBPEN, + REG_CONTROL_CHESS | REG_CONTROL_RES18 | REG_CONTROL_REFR_2HZ | REG_CONTROL_SUBP1 | REG_CONTROL_SUBPSEL | REG_CONTROL_DATAHOLD | REG_CONTROL_SUBPEN +}; // read register value int read_reg(uint16_t reg, uint16_t *val){ - uint8_t _2bytes[2]; - _2bytes[0] = reg >> 8; // big endian! - _2bytes[1] = reg & 0xff; - if(I2C_OK != i2c_7bit_send(_2bytes, 2, 0)){ + reg = __REV16(reg); + if(I2C_OK != i2c_7bit_send((uint8_t*)®, 2, 0)){ DBG("Can't send address"); - return 0; + return FALSE; } - i2c_status s = i2c_7bit_receive_twobytes(_2bytes); + uint16_t d; + i2c_status s = i2c_7bit_receive_twobytes((uint8_t*)&d); if(I2C_OK != s){ #ifdef EBUG DBG("Can't get info, s="); printu(s); NL(); #endif - return 0; + return FALSE; } - *val = _2bytes[0] << 8 | _2bytes[1]; // big endian -> little endian - return 1; + *val = __REV16(d); + return TRUE; } -// read N uint16_t values starting from `reg` +// blocking read N uint16_t values starting from `reg` // @return N of registers read int read_data(uint16_t reg, uint16_t *data, int N){ if(N < 1 ) return 0; @@ -63,35 +88,418 @@ int write_reg(uint16_t reg, uint16_t val){ _4bytes[1] = reg & 0xff; _4bytes[2] = val >> 8; _4bytes[3] = val & 0xff; - if(I2C_OK != i2c_7bit_send(_4bytes, 4, 1)) return 0; - return 1; + if(I2C_OK != i2c_7bit_send(_4bytes, 4, 1)) return FALSE; + return TRUE; } +/** + * @brief read_data_dma - read big data buffer by DMA + * @param reg - starting register number + * @param N - amount of data (in 16-bit words) + * @return FALSE if can't run operation + */ int read_data_dma(uint16_t reg, int N){ - if(N < 1) return 0; - uint8_t _2bytes[2]; + if(N < 1 || N > MLX_DMA_MAXLEN) return FALSE; + /*uint8_t _2bytes[2]; _2bytes[0] = reg >> 8; // big endian! - _2bytes[1] = reg & 0xff; + _2bytes[1] = reg & 0xff;*/ + reg = __REV16(reg); portionlen = N; - if(I2C_OK != i2c_7bit_send(_2bytes, 2, 0)){ + if(I2C_OK != i2c_7bit_send((uint8_t*)®, 2, 0)){ DBG("DMA: can't send address"); - return 0; + return FALSE; } - if(I2C_OK != i2c_7bit_receive_DMA(dataarray, N*2)) return 0; - return 1; + if(I2C_OK != i2c_7bit_receive_DMA((uint8_t*)dataarray, N*2)) return FALSE; + return TRUE; } -void mlx90640_process(){ - if(i2cDMAr == I2C_DMA_READY){ - i2cDMAr = I2C_DMA_RELAX; - uint8_t *ptr = dataarray; - for(uint16_t i = 0; i < portionlen; ++i, ptr += 2){ - printu(i); - addtobuf(" "); - uint16_t d = (ptr[0] << 8) | ptr[1]; - printuhex(d); - newline(); - } - sendbuf(); +/***************************************************************************** + Calculate parameters & values + *****************************************************************************/ +// calculate Vdd from vddRAM register +/* +static float getVdd(uint16_t vddRAM){ + int16_t ram = (int16_t) vddRAM; + float vdd = (float)ram - params.vdd25; + return vdd / params.kVdd + 3.3f; +}*/ + +// fill OCC/ACC row/col arrays +static void occacc(int8_t *arr, int l, uint16_t *regstart){ + int n = l >> 2; // divide by 4 + int8_t *p = arr; + for(int i = 0; i < n; ++i){ + register uint16_t val = *regstart++; + *p++ = (val & 0x000F) >> 0; + *p++ = (val & 0x00F0) >> 4; + *p++ = (val & 0x0F00) >> 8; + *p++ = (val ) >> 12; + } + for(int i = 0; i < l; ++i, ++arr){ + if(*arr > 0x07) *arr -= 0x10; } } + +// get all parameters' values from `dataarray`, return FALSE if something failed +static int get_parameters(){ +#ifdef EBUG + SEND("0 Tms="); printu(Tms); newline(); +#endif + int8_t i8; + int16_t i16, *pi16; + uint16_t *pu16; + uint16_t val = CREG_VAL(REG_VDD); + i8 = (int8_t) (val >> 8); + params.kVdd = i8 << 5; + if(params.kVdd == 0) return FALSE; + i16 = val & 0xFF; + params.vdd25 = ((i16 - 0x100) << 5) - (1<<13); + val = CREG_VAL(REG_KVTPTAT); + i16 = (val & 0xFC00) >> 10; + if(i16 > 0x1F) i16 -= 0x40; + params.KvPTAT = (float)i16 / (1<<12); + i16 = (val & 0x03FF); + if(i16 > 0x1FF) i16 -= 0x400; + params.KtPTAT = (float)i16 / 8.f; + params.vPTAT25 = (int16_t) CREG_VAL(REG_PTAT); + val = CREG_VAL(REG_APTATOCCS) >> 12; + params.alphaPTAT = val / 4.f + 8.f; + params.gainEE = (int16_t)CREG_VAL(REG_GAIN); + if(params.gainEE == 0) return FALSE; +#ifdef EBUG + SEND("1 Tms="); printu(Tms); newline(); +#endif + int8_t occRow[24]; + int8_t occColumn[32]; + occacc(occRow, 24, &CREG_VAL(REG_OCCROW14)); + occacc(occColumn, 32, &CREG_VAL(REG_OCCCOL14)); + int8_t accRow[24]; + int8_t accColumn[32]; + occacc(accRow, 24, &CREG_VAL(REG_ACCROW14)); + occacc(accColumn, 32, &CREG_VAL(REG_ACCCOL14)); + val = CREG_VAL(REG_APTATOCCS); + // need to do multiplication instead of bitshift, so: + float occRemScale = 1<<(val&0x0F), + occColumnScale = 1<<((val>>4)&0x0F), + occRowScale = 1<<((val>>8)&0x0F); + int16_t offavg = (int16_t) CREG_VAL(REG_OSAVG); + // even/odd column/row numbers are for starting from 1, so for starting from 0 we chould swap them: + // even - for 1,3,5,...; odd - for 0,2,4,... etc + int8_t ktaavg[4]; + // 0 - odd row, odd col; 1 - odd row even col; 2 - even row, odd col; 3 - even row, even col + val = CREG_VAL(REG_KTAAVGODDCOL); + ktaavg[2] = (int8_t)(val & 0xFF); // odd col, even row -> col 0,2,..; row 1,3,.. + ktaavg[0] = (int8_t)(val >> 8);; // odd col, odd row -> col 0,2,..; row 0,2,.. + val = CREG_VAL(REG_KTAAVGEVENCOL); + ktaavg[3] = (int8_t)(val & 0xFF); // even col, even row -> col 1,3,..; row 1,3,.. + ktaavg[1] = (int8_t)(val >> 8); // even col, odd row -> col 1,3,..; row 0,2,.. + // so index of ktaavg is 2*(row&1)+(col&1) + val = CREG_VAL(REG_KTAVSCALE); + uint8_t scale1 = ((val & 0xFF)>>4) + 8, scale2 = (val&0xF); + if(scale1 == 0 || scale2 == 0) return FALSE; + float mul = (float)(1<> 12); + diva *= (float)(1<<30); // alpha_scale + float accRowScale = 1<<((val & 0x0f00)>>8), + accColumnScale = 1<<((val & 0x00f0)>>4), + accRemScale = 1<<(val & 0x0f); + pi16 = params.offset; + pu16 = &CREG_VAL(REG_OFFAK1); + float *fp = params.kta; +#ifdef EBUG + SEND("2 Tms="); printu(Tms); newline(); +#endif + for(int row = 0; row < 24; ++row){ + int idx = (row&1)<<1; + for(int col = 0; col < 32; ++col){ + // offset + register uint16_t rv = *pu16++; + i16 = (rv & 0xFC00) >> 10; + if(i16 > 0x1F) i16 -= 0x40; + register float oft = (float)offavg + occRow[row]*occRowScale + occColumn[col]*occColumnScale + i16*occRemScale; + *pi16++ = (int16_t)oft; + // kta + i16 = (rv & 0xF) >> 1; + if(i16 > 0x03) i16 -= 0x08; + *fp++ = (ktaavg[idx|(col&1)] + i16*mul) / div; + // alpha + i16 = (rv & 0x3F0) >> 4; + if(i16 > 0x1F) i16 -= 0x40; + oft = (float)a_r + accRow[row]*accRowScale + accColumn[col]*accColumnScale +i16*accRemScale; + *a++ = oft / diva; + //*a++ /= diva; + } + } +#ifdef EBUG + SEND("3 Tms="); printu(Tms); newline(); +#endif + scale1 = (CREG_VAL(REG_KTAVSCALE) >> 8) & 0xF; // kvscale + div = (float)(1<> 12; if(i16 > 0x07) i16 -= 0x10; + ktaavg[0] = i16; // odd col, odd row + i16 = (val & 0xF0) >> 4; if(i16 > 0x07) i16 -= 0x10; + ktaavg[1] = i16; // even col, odd row + i16 = (val & 0x0F00) >> 8; if(i16 > 0x07) i16 -= 0x10; + ktaavg[2] = i16; // odd col, even row + i16 = val & 0x0F; if(i16 > 0x07) i16 -= 0x10; + ktaavg[3] = i16; // even col, even row + for(int i = 0; i < 4; ++i) params.kv[i] = ktaavg[i] / div; + val = CREG_VAL(REG_CPOFF); + params.cpOffset[0] = (val & 0x03ff); + if(params.cpOffset[0] > 0x1ff) params.cpOffset[0] -= 0x400; + params.cpOffset[1] = val >> 10; + if(params.cpOffset[1] > 0x1f) params.cpOffset[1] -= 0x40; + params.cpOffset[1] += params.cpOffset[0]; + val = ((CREG_VAL(REG_KTAVSCALE) & 0xF0) >> 4) + 8; + i8 = (int8_t)(CREG_VAL(REG_KVTACP) & 0xFF); + params.cpKta = (float)i8 / (1<> 8; + i16 = CREG_VAL(REG_KVTACP) >> 8; + if(i16 > 0x7F) i16 -= 0x100; + params.cpKv = (float)i16 / (1< 0x7F) i16 -= 0x100; + params.tgc = (float)i16; + params.tgc /= 32.; +#ifdef EBUG + SEND("4 Tms="); printu(Tms); newline(); +#endif + val = (CREG_VAL(REG_SCALEACC)>>12); // alpha_scale_CP + i16 = CREG_VAL(REG_ALPHA)>>10; // cp_P1_P0_ratio + if(i16 > 0x1F) i16 -= 0x40; + div = (float)(1<> 8); + params.KsTa = (float)i8/(1<<13); + div = 1<<((CREG_VAL(REG_CT34) & 0x0F) + 8); // kstoscale + val = CREG_VAL(REG_KSTO12); + i8 = (int8_t)(val & 0xFF); + params.ksTo[0] = (float)i8 / div; + i8 = (int8_t)(val >> 8); + params.ksTo[1] = (float)i8 / div; + val = CREG_VAL(REG_KSTO34); + i8 = (int8_t)(val & 0xFF); + params.ksTo[2] = (float)i8 / div; + i8 = (int8_t)(val >> 8); + params.ksTo[3] = (float)i8 / div; + params.CT[0] = 0.f; // 0degr - between ranges 1 and 2 + val = CREG_VAL(REG_CT34); + mul = ((val & 0x3000)>>12)*10.f; // step + params.CT[1] = ((val & 0xF0)>>4)*mul; // CT3 - between ranges 2 and 3 + params.CT[2] = ((val & 0x0F00) >> 8)*mul + params.CT[1]; // CT4 - between ranges 3 and 4 + params.alphacorr[0] = 1.f/(1.f + params.ksTo[0] * 40.f); + params.alphacorr[1] = 1.f; + params.alphacorr[2] = (1.f + params.ksTo[2] * params.CT[1]); + params.alphacorr[3] = (1.f + params.ksTo[3] * (params.CT[2] - params.CT[1])) * params.alphacorr[2]; + // Don't forget to check 'outlier' flags for wide purpose +#ifdef EBUG + SEND("end Tms="); printu(Tms); + NL(); +#endif + return TRUE; +} + + +// calculate Vsup, Tamb, gain, off, Vdd, Ta +static void stage1(){ + int16_t i16a = (int16_t)IMD_VAL(REG_IVDDPIX); + float dvdd = i16a - params.vdd25; + dvdd = dvdd / params.kVdd; + float vdd = dvdd + 3.3f; + SEND("Vd="); float2str(vdd, 2); newline(); + i16a = (int16_t)IMD_VAL(REG_ITAPTAT); + int16_t i16b = (int16_t)IMD_VAL(REG_ITAVBE); + float Ta = (float)i16a / (i16a * params.alphaPTAT + i16b); // vptatart + Ta *= (float)(1<<18); + Ta = (Ta / (1 + params.KvPTAT*dvdd) - params.vPTAT25); + Ta = Ta / params.KtPTAT + 25.; + SEND("Ta="); float2str(Ta, 2); newline(); + i16a = (int16_t)IMD_VAL(REG_IGAIN); + float Kgain = params.gainEE / (float)i16a; + SEND("Kgain="); float2str(Kgain, 2); newline(); + ; + //int idx = (row&1)<<1; + //for(int col = 0; col < 32; ++col){ + // *fp++ = (ktaavg[idx|(col&1)] + // pix_gain = pix*Kgain + // pix_os = pix_gain - offset*(1+kta*(Ta-Ta0))*(1+kv*(vdd-vdd0)) +} + +/** + * @brief process_subpage - calculate all parameters from `dataarray` into `mlx_image` + */ +static void process_subpage(){ + DBG("process_subpage()"); + SEND("subpage="); printu(subpageno); newline(); + (void)subpageno; (void)simpleimage; +for(int i = 0; i < 32; ++i){ + printi((int8_t)dataarray[i]); bufputchar(' '); +} newline(); + stage1(); + NL(); +} + +// start image acquiring for next subpage +static int startima(){ + DBG("startima()"); + if(!write_reg(REG_CONTROL, reg_control_val[subpageno]) || + !write_reg(REG_STATUS, REG_STATUS_OVWEN)) return FALSE; + return TRUE; +} + +/** + * @brief parse_buffer - swap bytes in `dataarray` (after receiving or before transmitting data) + */ +static void parse_buffer(){ + uint16_t *ptr = dataarray; + DBG("parse_buffer()"); + for(uint16_t i = 0; i < portionlen; ++i, ++ptr){ + *ptr = __REV16(*ptr); +#if 0 + printu(i); + addtobuf(" "); + printuhex(*ptr); + newline(); +#endif + } +#if 0 + sendbuf(); +#endif +} + +/** + * @brief mlx90640_process - main finite-state machine + */ +void mlx90640_process(){ +#define chstate(s) do{errctr = 0; Tlast = Tms; mlx_state = s;}while(0) +#define chkerr() do{if(++errctr > MLX_MAXERR_COUNT){chstate(M_ERROR); DBG("-> M_ERROR");}}while(0) +#define chktmout() do{if(Tms - Tlast > MLX_TIMEOUT){chstate(M_ERROR); DBG("Timeout! -> M_ERROR"); }}while(0) + static int errctr = 0; + static uint32_t Tlast = 0; + uint8_t gotdata = 0; + uint16_t reg; + if(i2cDMAr == I2C_DMA_READY){ // convert received data into little-endian + i2cDMAr = I2C_DMA_RELAX; + parse_buffer(); + gotdata = 1; + } + switch(mlx_state){ + case M_FIRSTSTART: // init working mode by request + if(write_reg(REG_CONTROL, reg_control_val[0]) + && read_reg(REG_CONTROL, ®)){ + SEND("REG_CTRL="); printuhex(reg); NL(); + if(read_reg(REG_STATUS, ®)){ + SEND("REG_STATUS="); printuhex(reg); NL();} + if(read_data_dma(REG_CALIDATA, REG_CALIDATA_LEN)){ + chstate(M_READCONF); + DBG("-> M_READCONF"); + }else chkerr(); + }else chkerr(); + break; + case M_READCONF: + if(gotdata){ // calculate calibration parameters + if(get_parameters()){ + chstate(M_RELAX); + DBG("-> M_RELAX"); + }else{ // error -> go to M_FIRSTSTART again + chstate(M_FIRSTSTART); + DBG("-> M_FIRSTSTART"); + } + }else chktmout(); + break; + case M_STARTIMA: + subpageno = 0; + if(startima()){ + chstate(M_PROCESS); + DBG("-> M_PROCESS"); + }else{ + chstate(M_ERROR); + DBG("can't start sp0 -> M_ERROR"); + } + break; + case M_PROCESS: + if(read_reg(REG_STATUS, ®)){ + if(reg & REG_STATUS_NEWDATA){ + if(subpageno != (reg & REG_STATUS_SPNO)){ + chstate(M_ERROR); + DBG("wrong subpage number -> M_ERROR"); + }else{ // all OK, run image reading + if(read_data_dma(REG_IMAGEDATA, MLX_PIXARRSZ)){ + chstate(M_READOUT); + DBG("-> M_READOUT"); + }else chkerr(); + } + }else chktmout(); + }else chkerr(); + break; + case M_READOUT: + if(gotdata){ + process_subpage(); + if(++subpageno > 1){ // image ready + chstate(M_RELAX); + DBG("Image READY!"); + }else{ + if(startima()){ + chstate(M_PROCESS); + DBG("-> M_PROCESS"); + }else{ + chstate(M_ERROR); + DBG("can't start sp1 -> M_ERROR"); + } + } + }else chktmout(); + break; + case M_POWERON: + if(Tms - Tlast > MLX_POWON_WAIT){ + if(params.kVdd == 0){ // get all parameters + chstate(M_FIRSTSTART); + DBG("M_FIRSTSTART"); + }else{ // rewrite settings register + if(write_reg(REG_CONTROL, reg_control_val[0])){ + chstate(M_RELAX); + DBG("-> M_RELAX"); + }else chkerr(); + } + } + break; + case M_POWEROFF1: + MLXPOW_OFF(); + chstate(M_POWEROFF); + DBG("-> M_POWEROFF"); + break; + case M_POWEROFF: + if(Tms - Tlast > MLX_POWOFF_WAIT){ + MLXPOW_ON(); + chstate(M_POWERON); + DBG("-> M_POWERON"); + } + break; + default: + break; + } +} + +void mlx90640_restart(){ + DBG("restart"); + mlx_state = M_POWEROFF1; +} + +// if state of MLX allows, make an image else return error +// @param simple ==1 for simplest image processing (without T calibration) +int mlx90640_take_image(uint8_t simple){ + simpleimage = simple; + if(mlx_state != M_RELAX) return FALSE; + if(params.kVdd == 0){ // no parameters -> make first run + mlx_state = M_FIRSTSTART; + DBG("no params -> M_FIRSTSTART"); + return TRUE; + } + mlx_state = M_STARTIMA; + return TRUE; +} diff --git a/F1:F103/MLX90640/mlx90640.h b/F1:F103/MLX90640/mlx90640.h index 0996aa4..697dc5d 100644 --- a/F1:F103/MLX90640/mlx90640.h +++ b/F1:F103/MLX90640/mlx90640.h @@ -21,13 +21,74 @@ #include +// timeout for reading operations, ms +#define MLX_TIMEOUT 1000 +// counter of errors, when > max -> M_ERROR +#define MLX_MAXERR_COUNT 10 +// wait after power off, ms +#define MLX_POWOFF_WAIT 500 +// wait after power on, ms +#define MLX_POWON_WAIT 2000 + +// amount of pixels +#define MLX_PIXNO (24*32) +// pixels + service data +#define MLX_PIXARRSZ (MLX_PIXNO + 64) + +typedef struct{ + int16_t kVdd; + int16_t vdd25; + float KvPTAT; + float KtPTAT; + int16_t vPTAT25; + float alphaPTAT; + int16_t gainEE; + float tgc; + float cpKv; // K_V_CP + float cpKta; // K_Ta_CP + float KsTa; + float CT[3]; // range borders (0, 160, 320 degrC?) + float ksTo[4]; // K_S_To for each range + float alphacorr[4]; // Alpha_corr for each range + float alpha[MLX_PIXNO]; // full - with alpha_scale + int16_t offset[MLX_PIXNO]; + float kta[MLX_PIXNO]; // full K_ta - with scale1&2 + float kv[4]; // full - with scale; 0 - odd row, odd col; 1 - odd row even col; 2 - even row, odd col; 3 - even row, even col + float cpAlpha[2]; // alpha_CP_subpage 0 and 1 + int16_t cpOffset[2]; +} MLX90640_params; + +extern MLX90640_params params; + +typedef enum{ + M_ERROR, // error: need to reboot sensor + M_RELAX, // base state + M_FIRSTSTART, // first start after power on + M_READCONF, // read configuration data + M_STARTIMA, // start image aquiring + M_PROCESS, // process subpage - wait for image ready + M_READOUT, // wait while subpage data be read + M_POWERON, // wait for 100ms after power is on before -> firststart + M_POWEROFF1, // turn off power + M_POWEROFF, // wait for 500ms without power + // + M_STATES_AMOUNT // amount of states +} mlx90640_state; + +extern mlx90640_state mlx_state; +extern float mlx_image[MLX_PIXNO]; + // default I2C address #define MLX_DEFAULT_ADDR (0x33) +// max datalength by one read (in 16-bit values) +#define MLX_DMA_MAXLEN (832) -void mlx90640_process(); int read_reg(uint16_t reg, uint16_t *val); int write_reg(uint16_t reg, uint16_t val); int read_data(uint16_t reg, uint16_t *data, int N); int read_data_dma(uint16_t reg, int N); +void mlx90640_process(); +int mlx90640_take_image(uint8_t simple); +void mlx90640_restart(); #endif // MLX90640__ diff --git a/F1:F103/MLX90640/mlx90640_regs.h b/F1:F103/MLX90640/mlx90640_regs.h new file mode 100644 index 0000000..705edd7 --- /dev/null +++ b/F1:F103/MLX90640/mlx90640_regs.h @@ -0,0 +1,82 @@ +/* + * This file is part of the MLX90640 project. + * Copyright 2022 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 MLX90640_REGS_H__ +#define MLX90640_REGS_H__ + +#define REG_STATUS 0x8000 +#define REG_STATUS_OVWEN (1<<4) +#define REG_STATUS_NEWDATA (1<<3) +#define REG_STATUS_SPNO (1<<0) +#define REG_STATUS_SPMASK (3<<0) +#define REG_CONTROL 0x800D +#define REG_CONTROL_CHESS (1<<12) +#define REG_CONTROL_RES18 (2<<10) +#define REG_CONTROL_RESMASK (3<<10) +#define REG_CONTROL_REFR_2HZ (2<<7) +#define REG_CONTROL_SUBP1 (1<<4) +#define REG_CONTROL_SUBPMASK (3<<4) +#define REG_CONTROL_SUBPSEL (1<<3) +#define REG_CONTROL_DATAHOLD (1<<2) +#define REG_CONTROL_SUBPEN (1<<0) + +// calibration data start & len +#define REG_CALIDATA 0x2410 +#define REG_CALIDATA_LEN 816 + +#define REG_APTATOCCS 0x2410 +#define REG_OSAVG 0x2411 +#define REG_OCCROW14 0x2412 +#define REG_OCCCOL14 0x2418 +#define REG_SCALEACC 0x2420 +#define REG_SENSIVITY 0x2421 +#define REG_ACCROW14 0x2422 +#define REG_ACCCOL14 0x2428 +#define REG_GAIN 0x2430 +#define REG_PTAT 0x2431 +#define REG_KVTPTAT 0x2432 +#define REG_VDD 0x2433 +#define REG_KVAVG 0x2434 +#define REG_ILCHESS 0x2435 +#define REG_KTAAVGODDCOL 0x2436 +#define REG_KTAAVGEVENCOL 0x2437 +#define REG_KTAVSCALE 0x2438 +#define REG_ALPHA 0x2439 +#define REG_CPOFF 0x243A +#define REG_KVTACP 0x243B +#define REG_KSTATGC 0x243C +#define REG_KSTO12 0x243D +#define REG_KSTO34 0x243E +#define REG_CT34 0x243F +#define REG_OFFAK1 0x2440 +// index of register in array (from REG_CALIDATA) +#define CREG_IDX(addr) ((addr)-REG_CALIDATA) + +#define REG_IMAGEDATA 0x0400 +#define REG_ITAVBE 0x0700 +#define REG_ICPSP0 0x0708 +#define REG_IGAIN 0x070A +#define REG_ITAPTAT 0x0720 +#define REG_ICPSP1 0x0728 +#define REG_IVDDPIX 0x072A +// indeg of register in array (from REG_IMAGEDATA) +#define IMD_IDX(addr) ((addr)-REG_IMAGEDATA) + + +#endif // MLX90640_REGS_H__ diff --git a/F1:F103/MLX90640/proto.c b/F1:F103/MLX90640/proto.c index 5986592..42c29bd 100644 --- a/F1:F103/MLX90640/proto.c +++ b/F1:F103/MLX90640/proto.c @@ -25,12 +25,82 @@ #define D16LEN (256) +extern uint32_t Tms; + +static const char* _states[M_STATES_AMOUNT] = { + [M_ERROR] = "error", + [M_RELAX] = "do nothing", + [M_FIRSTSTART] = "first start", + [M_READCONF] = "read config", + [M_STARTIMA] = "start image", + [M_PROCESS] = "process subframe", + [M_READOUT] = "read subpage data", + [M_POWERON] = "wait after power on", + [M_POWEROFF1] = "turn power off", + [M_POWEROFF] = "wait without power", +}; + +// dump floating point array 24x32 +static void dumpfarr(float *arr){ + for(int row = 0; row < 24; ++row){ + for(int col = 0; col < 32; ++col){ + float2str(*arr++, 2); bufputchar(' '); + } + newline(); + } +} + +static void dumpparams(){ + int16_t *pi16; + SEND("\nkVdd="); printi(params.kVdd); + SEND("\nvdd25="); printi(params.vdd25); + SEND("\nKvPTAT="); float2str(params.KvPTAT, 4); + SEND("\nKtPTAT="); float2str(params.KtPTAT, 4); + SEND("\nvPTAT25="); printi(params.vPTAT25); + SEND("\nalphaPTAT="); float2str(params.alphaPTAT, 2); + SEND("\ngainEE="); printi(params.gainEE); + SEND("\nPixel offset parameters:\n"); + pi16 = params.offset; + for(int row = 0; row < 24; ++row){ + for(int col = 0; col < 32; ++col){ + printi(*pi16++); bufputchar(' '); + } + newline(); + } + SEND("K_talpha:\n"); + dumpfarr(params.kta); + SEND("Kv: "); + for(int i = 0; i < 4; ++i){ + float2str(params.kv[i], 2); bufputchar(' '); + } + SEND("\ncpOffset="); + printi(params.cpOffset[0]); SEND(", "); printi(params.cpOffset[1]); + SEND("\ncpKta="); float2str(params.cpKta, 2); + SEND("\ncpKv="); float2str(params.cpKv, 2); + SEND("\ntgc="); float2str(params.tgc, 2); + SEND("\ncpALpha="); float2str(params.cpAlpha[0], 2); + SEND(", "); float2str(params.cpAlpha[1], 2); + SEND("\nKsTa="); float2str(params.KsTa, 2); + SEND("\nAlpha:\n"); + dumpfarr(params.alpha); + SEND("\nCT3="); float2str(params.CT[1], 2); + SEND("\nCT4="); float2str(params.CT[2], 2); + for(int i = 0; i < 4; ++i){ + SEND("\nKsTo"); bufputchar('0'+i); bufputchar('='); + float2str(params.ksTo[i], 2); + SEND("\nalphacorr"); bufputchar('0'+i); bufputchar('='); + float2str(params.alphacorr[i], 2); + } + NL(); +} + const char *parse_cmd(char *buf){ int32_t Num = 0; uint16_t r, d; uint16_t data[D16LEN]; - char *ptr; - switch(*buf++){ + const float pi = 3.1415927f, e = 2.7182818f; + char *ptr, cmd = *buf++; + switch(cmd){ case 'a': if(buf != getnum(buf, &Num)){ if(Num & 0x80) return "Enter 7bit address"; @@ -48,6 +118,27 @@ const char *parse_cmd(char *buf){ }else return "Need amount"; }else return "Need reg"; break; + case 'E': + case 'e': + if(!mlx90640_take_image(cmd == 'e')) return "FAILED"; + else return "OK"; + break; + case 'f': + SEND("Float test: "); + float2str(0.f, 2); addtobuf(", "); + float2str(pi, 1); addtobuf(", "); + float2str(-e, 2); addtobuf(", "); + float2str(-pi, 3); addtobuf(", "); + float2str(e, 4); addtobuf(", "); + uint32_t uu = INF | 0x80000000; + float *f = (float*)&uu; + float2str(*f, 4); addtobuf(", "); + uu = NAN; + f = (float*)&uu; + float2str(*f, 4); + NL(); + return NULL; + break; case 'g': if(buf != (ptr = getnum(buf, &Num))){ r = Num; @@ -74,6 +165,19 @@ const char *parse_cmd(char *buf){ i2c_setup(TRUE); return "I2C restarted"; break; + case 'M': + SEND("MLX state: "); SEND(_states[mlx_state]); + SEND("\npower="); printu(MLXPOW_VAL()); NL(); + return NULL; + break; + case 'O': + mlx90640_restart(); + return "Power off/on"; + break; + case 'P': + dumpparams(); + return NULL; + break; case 'r': if(buf != (ptr = getnum(buf, &Num))){ if(read_reg(Num, &d)){ @@ -86,6 +190,10 @@ const char *parse_cmd(char *buf){ USB_sendstr("Soft reset\n"); NVIC_SystemReset(); break; + case 'T': + SEND("Tms="); printu(Tms); NL(); + return NULL; + break; case 'w': if(buf == (ptr = getnum(buf, &Num))) return "Need register"; r = Num; @@ -109,10 +217,16 @@ const char *parse_cmd(char *buf){ "MLX90640 build #" BUILD_NUMBER " @" BUILD_DATE "\n\n" "'a addr' - change MLX I2C address to `addr`\n" "'d reg N' - read registers starting from `reg` using DMA\n" + "'Ee' - expose image: E - full, e - simple\n" + "'f' - test float printf (0.00, 3.1, -2.72, -3.142, 2.7183, -INF, NAN)\n" "'g reg N' - read N (<256) registers starting from `reg`\n" "'I' - restart I2C\n" + "'M' - MLX state\n" + "'O' - turn On or restart MLX sensor\n" + "'P' - dump params\n" "'r reg' - read `reg`\n" "'R' - software reset\n" + "'T' - get Tms\n" "'w reg dword' - write `dword` to `reg`\n" "'W d0 d1 ...' - write N (<256) 16-bit words directly to I2C\n" ); diff --git a/F1:F103/MLX90640/strfunct.c b/F1:F103/MLX90640/strfunct.c index 0cc60b7..5c3d6d5 100644 --- a/F1:F103/MLX90640/strfunct.c +++ b/F1:F103/MLX90640/strfunct.c @@ -44,7 +44,7 @@ char *get_USB(){ } static char buff[OBUFSZ+1], *bptr = buff; -static uint8_t blen = 0; +static uint16_t blen = 0; void sendbuf(){ if(blen == 0) return; @@ -63,6 +63,7 @@ void bufputchar(char ch){ } void addtobuf(const char *txt){ + if(!txt) return; while(*txt) bufputchar(*txt++); } @@ -191,3 +192,85 @@ char *getnum(char *txt, int32_t *N){ } return getdec(txt, N); } + +// be careful: if pow10 would be bigger you should change str[] size! +static const float pwr10[] = {1., 10., 100., 1000., 10000.}; +static const float rounds[] = {0.5, 0.05, 0.005, 0.0005, 0.00005}; +#define P10L (sizeof(pwr10)/sizeof(uint32_t) - 1) +void float2str(float x, uint8_t prec){ + if(prec > P10L) prec = P10L; + static char str[16] = {0}; // -117.5494E-36\0 - 14 symbols max! + uint32_t *u = (uint32_t*)&x; + /* if(*u && (*u == (*u & DENORM))){ + SEND("DENORM"); return; + }*/ + switch(*u){ + case INF: + SEND("INF"); + return; + break; + case MINF: + SEND("-INF"); + return; + break; + case NAN: + SEND("NAN"); + return; + default: + break; + } + char *s = str + 14; // go to end of buffer + uint8_t minus = 0; + if(x < 0){ + x = -x; + minus = 1; + } + int pow = 0; // xxxEpow + // now convert float to 1.xxxE3y + while(x > 1000.f){ + x /= 1000.f; + pow += 3; + } + if(x > 0.) while(x < 1.){ + x *= 1000.f; + pow -= 3; + } + // print Eyy + if(pow){ + uint8_t m = 0; + if(pow < 0){pow = -pow; m = 1;} + while(pow){ + register int p10 = pow/10; + *s-- = '0' + (pow - 10*p10); + pow = p10; + } + if(m) *s-- = '-'; + *s-- = 'E'; + } + // now our number is in [1, 1000] + uint32_t units; + if(prec){ + units = (uint32_t) x; + uint32_t decimals = (uint32_t)((x-units+rounds[prec])*pwr10[prec]); + // print decimals + while(prec){ + register int d10 = decimals / 10; + *s-- = '0' + (decimals - 10*d10); + decimals = d10; + --prec; + } + // decimal point + *s-- = '.'; + }else{ // without decimal part + units = (uint32_t) (x + 0.5f); + } + // print main units + if(units == 0) *s-- = '0'; + else while(units){ + register uint32_t u10 = units / 10; + *s-- = '0' + (units - 10*u10); + units = u10; + } + if(minus) *s-- = '-'; + addtobuf(s+1); +} diff --git a/F1:F103/MLX90640/strfunct.h b/F1:F103/MLX90640/strfunct.h index b0327ef..5f3a3b9 100644 --- a/F1:F103/MLX90640/strfunct.h +++ b/F1:F103/MLX90640/strfunct.h @@ -22,6 +22,19 @@ #include "stm32f1.h" +#ifndef DENORM +#define DENORM (0x007FFFFF) +#endif +#ifndef NAN +#define NAN (0x7FC00000) +#endif +#ifndef INF +#define INF (0x7F800000) +#endif +#ifndef MINF +#define MINF (0xFF800000) +#endif + #define OBUFSZ (64) #define IBUFSZ (256) @@ -49,5 +62,6 @@ void printuhex(uint32_t val); void sendbuf(); char *omit_spaces(char *buf); char *getnum(char *buf, int32_t *N); +void float2str(float x, uint8_t prec); #endif // STRFUNCT_H__ diff --git a/F1:F103/MLX90640/version.inc b/F1:F103/MLX90640/version.inc index 62ea984..4396fae 100644 --- a/F1:F103/MLX90640/version.inc +++ b/F1:F103/MLX90640/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "46" -#define BUILD_DATE "2022-05-10" +#define BUILD_NUMBER "141" +#define BUILD_DATE "2022-05-19"