It works!

This commit is contained in:
Edward Emelianov 2025-09-21 21:32:01 +03:00
parent 9b0ee87891
commit dc2897e783
13 changed files with 185 additions and 18 deletions

View File

@ -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.

View File

@ -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);

View File

@ -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;
}
}
}
}

Binary file not shown.

View File

@ -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<<params->resolEE) / (1<<2); // calibrated resol/current resol
//fp_t resol_corr = (fp_t)(1<<params->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<<params->resolEE) / (1<<mlx_getresolution()); // calibrated resol/current resol
// 11.2.2.2. Supply voltage value calculation
int16_t i16a = (int16_t)IMD_VAL(REG_IVDDPIX);
fp_t dvdd = resol_corr*i16a - params->vdd25;

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.1, 2025-09-20T23:21:28. -->
<!-- Written by QtCreator 17.0.1, 2025-09-21T21:27:05. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>

View File

@ -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]);

View File

@ -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)

View File

@ -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;
}

View File

@ -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);

View File

@ -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;

View File

@ -18,5 +18,5 @@
#pragma once
extern uint8_t cartoon;
char *parse_cmd(char *buf);

View File

@ -1,2 +1,2 @@
#define BUILD_NUMBER "22"
#define BUILD_DATE "2025-09-20"
#define BUILD_NUMBER "38"
#define BUILD_DATE "2025-09-21"