diff --git a/F3:F303/MLX90640/Readme.md b/F3:F303/MLX90640/Readme.md new file mode 100644 index 0000000..aef3897 --- /dev/null +++ b/F3:F303/MLX90640/Readme.md @@ -0,0 +1,35 @@ +Works with single MLX90640 sensor +================================= + +When attached, udev will create symlink /dev/mlx_sensor0. This is the udev rule: + +``` + +ACTION=="add", ENV{USB_IDS}=="0483:5740", ATTRS{interface}=="?*", PROGRAM="/bin/bash -c \"ls /dev | grep $attr{interface} | wc -l \"", SYMLINK+="$attr{interface}%c", MODE="0666", GROUP="tty" + +``` + +Protocol: + +``` + +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 +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 + + +``` + +To call this help just print '?', 'h' or 'H' in terminal. diff --git a/F3:F303/MLX90640/i2c.h b/F3:F303/MLX90640/i2c.h index 8692481..f525880 100644 --- a/F3:F303/MLX90640/i2c.h +++ b/F3:F303/MLX90640/i2c.h @@ -43,7 +43,7 @@ int i2c_busy(); uint8_t *i2c_read(uint8_t addr, uint16_t nbytes); uint8_t i2c_read_dma16(uint8_t addr, uint16_t nwords); -uint16_t *i2c_read_reg16(uint8_t addr, uint16_t reg16, uint16_t nbytes, uint8_t isdma); +uint16_t *i2c_read_reg16(uint8_t addr, uint16_t reg16, uint16_t nwords, uint8_t isdma); uint8_t i2c_write(uint8_t addr, uint16_t *data, uint8_t nwords); uint8_t i2c_write_dma16(uint8_t addr, uint16_t *data, uint8_t nwords); diff --git a/F3:F303/MLX90640/main.c b/F3:F303/MLX90640/main.c index 05f1464..c042d24 100644 --- a/F3:F303/MLX90640/main.c +++ b/F3:F303/MLX90640/main.c @@ -44,7 +44,7 @@ int main(void){ i2c_setup(I2C_SPEED_100K); USB_setup(); USBPU_ON(); - uint32_t ctr = Tms; + uint32_t ctr = Tms, Tlastima = 0; while(1){ if(Tms - ctr > 499){ ctr = Tms; @@ -66,5 +66,13 @@ int main(void){ } } mlx_process(); + if(cartoon){ + uint32_t Tnow; + fp_t *i = mlx_getimage(&Tnow); + if(i && Tnow != Tlastima){ + U("Timage="); USND(u2str(Tnow)); drawIma(i); + Tlastima = Tnow; + } + } } } diff --git a/F3:F303/MLX90640/mlx90640.bin b/F3:F303/MLX90640/mlx90640.bin index be149a0..35d519e 100755 Binary files a/F3:F303/MLX90640/mlx90640.bin and b/F3:F303/MLX90640/mlx90640.bin differ diff --git a/F3:F303/MLX90640/mlx90640.c b/F3:F303/MLX90640/mlx90640.c index af9f235..4fb95ef 100644 --- a/F3:F303/MLX90640/mlx90640.c +++ b/F3:F303/MLX90640/mlx90640.c @@ -24,6 +24,7 @@ #include "mlx90640.h" #include "mlx90640_regs.h" +#include "mlxproc.h" // static const char *OK = "OK\n", *OKs = "OK ", *NOTEQ = "NOT equal!\n", *NOTEQi = "NOT equal on index "; @@ -57,15 +58,17 @@ void drawIma(const fp_t im[MLX_PIXNO]){ } } fp_t range = max_val - min_val; + U("RANGE="); USND(float2str(range, 3)); + U("MIN="); USND(float2str(min_val, 3)); + U("MAX="); USND(float2str(max_val, 3)); if(fabsf(range) < 0.001) range = 1.; // solid fill -> blank // Generate and print ASCII art iptr = im; - newline(); for(int row = 0; row < MLX_H; ++row){ for(int col = 0; col < MLX_W; ++col){ fp_t normalized = ((*iptr++) - min_val) / range; // Map to character index (0 to 15) - int index = (int)(normalized * (GRAY_LEVELS-1) + 0.5); + int index = (int)(normalized * GRAY_LEVELS); // Ensure we stay within bounds if(index < 0) index = 0; else if(index > (GRAY_LEVELS-1)) index = (GRAY_LEVELS-1); @@ -259,9 +262,7 @@ fp_t *process_subpage(MLX90640_params *params, const int16_t Frame[MLX_DMA_MAXLE #define IMD_VAL(reg) Frame[IMD_IDX(reg)] // 11.2.2.1. Resolution restore // temporary: - fp_t resol_corr = (fp_t)(1<resolEE) / (1<<2); // calibrated resol/current resol - //fp_t resol_corr = (fp_t)(1<resolEE) / (1<<((reg_control_val[subpageno]&0x0C00)>>10)); // calibrated resol/current resol - //DBG("resolEE=%d, resolCur=%d", params->resolEE, ((reg_control_val[subpageno]&0x0C00)>>10)); + fp_t resol_corr = (fp_t)(1<resolEE) / (1<vdd25; diff --git a/F3:F303/MLX90640/mlx90640.creator.user b/F3:F303/MLX90640/mlx90640.creator.user index 3b0ec84..61a21ac 100644 --- a/F3:F303/MLX90640/mlx90640.creator.user +++ b/F3:F303/MLX90640/mlx90640.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId diff --git a/F3:F303/MLX90640/mlx90640.h b/F3:F303/MLX90640/mlx90640.h index c689868..2dbc4f0 100644 --- a/F3:F303/MLX90640/mlx90640.h +++ b/F3:F303/MLX90640/mlx90640.h @@ -59,6 +59,7 @@ typedef struct{ // full amount of IMAGE data + EXTRA data (counts of uint16_t!) #define MLX_DMA_MAXLEN (834) +int ch_resolution(uint8_t newresol); int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *params); fp_t *process_subpage(MLX90640_params *params, const int16_t Frame[MLX_DMA_MAXLEN], int subpageno, int simpleimage); void dumpIma(const fp_t im[MLX_PIXNO]); diff --git a/F3:F303/MLX90640/mlx90640_regs.h b/F3:F303/MLX90640/mlx90640_regs.h index 273c0ca..43e9d77 100644 --- a/F3:F303/MLX90640/mlx90640_regs.h +++ b/F3:F303/MLX90640/mlx90640_regs.h @@ -43,6 +43,8 @@ #define REG_CONTROL_SUBPSEL (1<<3) #define REG_CONTROL_DATAHOLD (1<<2) #define REG_CONTROL_SUBPEN (1<<0) +#define REG_MLXADDR 0x8010 +#define REG_MLXADDR_MASK (0xff) // default value #define REG_CONTROL_DEFAULT (REG_CONTROL_CHESS|REG_CONTROL_RES18|REG_CONTROL_REFR_2HZ|REG_CONTROL_SUBPEN) diff --git a/F3:F303/MLX90640/mlxproc.c b/F3:F303/MLX90640/mlxproc.c index 0d3fdba..0f0590a 100644 --- a/F3:F303/MLX90640/mlxproc.c +++ b/F3:F303/MLX90640/mlxproc.c @@ -21,6 +21,10 @@ #include "i2c.h" #include "mlxproc.h" #include "mlx90640_regs.h" +//#include "usb_dev.h" +//#include "strfunc.h" + +extern volatile uint32_t Tms; // current state and state before `stop` called static mlx_state_t MLX_state = MLX_NOTINIT, MLX_oldstate = MLX_NOTINIT; @@ -29,6 +33,8 @@ static int parsrdy = 0; static fp_t *ready_image = NULL; // will be pointer to `mlx_image` after both subpages process static uint8_t MLX_address = 0x33 << 1; static int errctr = 0; // errors counter - cleared by mlx_continue +static uint32_t Tlastimage = 0; +static uint8_t resolution = 2; // default: 18bit // get current state mlx_state_t mlx_state(){ return MLX_state; } @@ -36,6 +42,7 @@ mlx_state_t mlx_state(){ return MLX_state; } int mlx_setaddr(uint8_t addr){ if(addr > 0x7f) return 0; MLX_address = addr << 1; + Tlastimage = Tms; // refresh counter for autoreset I2C in case of error return 1; } // temporary stop @@ -60,12 +67,14 @@ void mlx_continue(){ } void mlx_process(){ - //static int subpageno = 0; // wait for given subpage + static int subpageno = 0; // wait for given subpage + // static uint32_t Tlast = 0; switch(MLX_state){ case MLX_NOTINIT: // start reading parameters - if(i2c_read_reg16(MLX_address, REG_CALIDATA, MLX_DMA_MAXLEN, 1)) + if(i2c_read_reg16(MLX_address, REG_CALIDATA, MLX_DMA_MAXLEN, 1)){ + errctr = 0; MLX_state = MLX_WAITPARAMS; - else ++errctr; + }else ++errctr; break; case MLX_WAITPARAMS: // check DMA ends and calculate parameters if(i2c_dma_haderr()) MLX_state = MLX_NOTINIT; @@ -74,6 +83,7 @@ void mlx_process(){ if(buf){ if(len != MLX_DMA_MAXLEN) MLX_state = MLX_NOTINIT; else if(get_parameters(buf, &p)){ + errctr = 0; MLX_state = MLX_WAITSUBPAGE; // fine! we could wait subpage parsrdy = 1; } @@ -81,14 +91,40 @@ void mlx_process(){ } break; case MLX_WAITSUBPAGE: // wait for subpage N ready - ; + {uint16_t *got = i2c_read_reg16(MLX_address, REG_STATUS, 1, 0); + if(got && *got & REG_STATUS_NEWDATA){ + if(subpageno == (*got & REG_STATUS_SPNO)){ + if(i2c_read_reg16(MLX_address, REG_IMAGEDATA, MLX_DMA_MAXLEN, 1)){ + errctr = 0; + MLX_state = MLX_READSUBPAGE; + //U("spstart="); USND(u2str(Tms - Tlast)); + }else ++errctr; + } + }} break; case MLX_READSUBPAGE: // wait ends of DMA read and calculate subpage - ; + if(i2c_dma_haderr()) MLX_state = MLX_NOTINIT; + else{ + uint16_t len, *buf = i2c_dma_getbuf(&len); + if(buf){ + //U("spread="); USND(u2str(Tms - Tlast)); + if(len != MLX_DMA_MAXLEN) MLX_state = MLX_WAITSUBPAGE; + else if((ready_image = process_subpage(&p, (int16_t*)buf, subpageno, 2))){ + errctr = 0; + MLX_state = MLX_WAITSUBPAGE; // fine! we could wait subpage + //U("spgot="); USND(u2str(Tms - Tlast)); + if(subpageno){ Tlastimage = Tms; + /*U("imgot="); USND(u2str(Tms - Tlast)); Tlast = Tms; */ + } + subpageno = !subpageno; + } + } + } break; default: return; } + if(MLX_state != MLX_RELAX && Tms - Tlastimage > MLX_I2CERR_TMOUT){ i2c_setup(i2c_curspeed); Tlastimage = Tms; } if(errctr > MLX_MAX_ERRORS) mlx_stop(); } @@ -99,6 +135,32 @@ int mlx_getparams(MLX90640_params *pars){ return 1; } -fp_t *mlx_getimage(){ +fp_t *mlx_getimage(uint32_t *Tgot){ + if(Tgot) *Tgot = Tlastimage; return ready_image; } + +uint8_t mlx_getresolution(){ + return resolution; +} + +int mlx_sethwaddr(uint8_t addr){ + if(addr > 0x7f) return 0; + uint16_t data[2], *ptr; + if(!(ptr = i2c_read_reg16(MLX_address, REG_MLXADDR, 1, 0))) return 0; + data[0] = REG_MLXADDR; + data[1] = (*ptr & ~REG_MLXADDR_MASK) | addr; + if(!i2c_write(MLX_address, data, 2)) return 0; + return 1; +} + +int mlx_setresolution(uint8_t newresol){ + if(newresol > 3) return 0; + uint16_t data[2], *ptr; + if(!(ptr = i2c_read_reg16(MLX_address, REG_CONTROL, 1, 0))) return 0; + data[0] = REG_CONTROL; + data[1] = (*ptr & ~REG_CONTROL_RESMASK) | (newresol << 10); + if(!i2c_write(MLX_address, data, 2)) return 0; + resolution = newresol; + return 1; +} diff --git a/F3:F303/MLX90640/mlxproc.h b/F3:F303/MLX90640/mlxproc.h index 8cf9610..1b56df5 100644 --- a/F3:F303/MLX90640/mlxproc.h +++ b/F3:F303/MLX90640/mlxproc.h @@ -24,6 +24,8 @@ // maximal errors number to stop processing #define MLX_MAX_ERRORS (11) +// if there's no new data by this time - reset bus +#define MLX_I2CERR_TMOUT (5000) typedef enum{ MLX_NOTINIT, // just start - need to get parameters @@ -39,3 +41,7 @@ void mlx_stop(); void mlx_continue(); void mlx_process(); int mlx_getparams(MLX90640_params *pars); +fp_t *mlx_getimage(uint32_t *Tgot); +int mlx_setresolution(uint8_t newresol); +uint8_t mlx_getresolution(); +int mlx_sethwaddr(uint8_t addr); diff --git a/F3:F303/MLX90640/proto.c b/F3:F303/MLX90640/proto.c index 35d6543..4f6a141 100644 --- a/F3:F303/MLX90640/proto.c +++ b/F3:F303/MLX90640/proto.c @@ -30,12 +30,20 @@ static uint16_t locBuffer[LOCBUFFSZ]; static uint8_t I2Caddress = 0x33 << 1; extern volatile uint32_t Tms; +uint8_t cartoon = 0; // "cartoon" mode: refresh image each time we get new static const char *OK = "OK\n", *ERR = "ERR\n"; const char *helpstring = "https://github.com/eddyem/stm32samples/tree/master/F3:F303/mlx90640 build#" BUILD_NUMBER " @ " BUILD_DATE "\n" " management of single IR bolometer MLX90640\n" - "i0..3 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!)\n" + "aa - change I2C address to a (a should be non-shifted value!!!)\n" + "c - continue MLX\n" + "d - draw image in ASCII\n" + "i0..4 - setup I2C with speed 10k, 100k, 400k, 1M or 2M (experimental!)\n" + "p - pause MLX\n" + "r0..3 - change resolution (0 - 16bit, 3 - 19-bit)\n" + "t - show temperature map\n" + "C - \"cartoon\" mode on/off (show each new image)\n" "D - dump MLX parameters\n" "G - get MLX state\n" "Ia addr - set device address\n" @@ -65,6 +73,26 @@ TRUE_INLINE const char *setupI2C(char *buf){ return NULL; } +TRUE_INLINE const char *chhwaddr(const char *buf){ + uint32_t a; + if(buf && *buf){ + const char *nxt = getnum(buf, &a); + if(nxt && nxt != buf) if(!mlx_sethwaddr(a)) return ERR; + } else return ERR; + return OK; +} + +TRUE_INLINE const char *chres(const char *buf){ + uint32_t r; + if(buf && *buf){ + const char *nxt = getnum(buf, &r); + if(nxt && nxt != buf) if(!mlx_setresolution(r)) return ERR; + } + r = mlx_getresolution(); + U("MLXRESOLUTION="); USND(u2str(r)); + return NULL; +} + TRUE_INLINE const char *chaddr(const char *buf){ uint32_t addr; const char *nxt = getnum(buf, &addr); @@ -187,10 +215,15 @@ TRUE_INLINE void getst(){ const char *parse_cmd(char *buf){ if(!buf || !*buf) return NULL; + uint32_t u32; if(buf[1]){ switch(*buf){ // "long" commands + case 'a': + return chhwaddr(buf + 1); case 'i': return setupI2C(buf + 1); + case 'r': + return chres(buf + 1); case 'I': buf = omit_spaces(buf + 1); switch(*buf){ @@ -212,7 +245,26 @@ const char *parse_cmd(char *buf){ } } switch(*buf){ // "short" (one letter) commands + case 'c': + mlx_continue(); return OK; + break; + case 'd': + {fp_t *i = mlx_getimage(&u32); + if(i){ U("Timage="); USND(u2str(u32)); drawIma(i); } + else U(ERR);} + break; case 'i': return setupI2C(NULL); // current settings + case 'p': + mlx_stop(); return OK; + break; + case 'r': return chres(NULL); + case 't': + {fp_t *i = mlx_getimage(&u32); + if(i){ U("Timage="); USND(u2str(u32)); dumpIma(i); } + else U(ERR);} + break; + case 'C': + cartoon = !cartoon; return OK; case 'D': dumpparams(); break; diff --git a/F3:F303/MLX90640/proto.h b/F3:F303/MLX90640/proto.h index 72c0e7d..1d48512 100644 --- a/F3:F303/MLX90640/proto.h +++ b/F3:F303/MLX90640/proto.h @@ -18,5 +18,5 @@ #pragma once +extern uint8_t cartoon; char *parse_cmd(char *buf); - diff --git a/F3:F303/MLX90640/version.inc b/F3:F303/MLX90640/version.inc index 48efeab..3ef587e 100644 --- a/F3:F303/MLX90640/version.inc +++ b/F3:F303/MLX90640/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "22" -#define BUILD_DATE "2025-09-20" +#define BUILD_NUMBER "38" +#define BUILD_DATE "2025-09-21"