diff --git a/F3:F303/MLX90640-allsky/Makefile b/F3:F303/MLX90640-allsky/Makefile index 094adbe..555c167 100644 --- a/F3:F303/MLX90640-allsky/Makefile +++ b/F3:F303/MLX90640-allsky/Makefile @@ -8,3 +8,5 @@ LDLIBS := -lm include ../makefile.f3 include ../../makefile.stm32 + +$(OBJDIR)/commproto.o: commproto.cpp $(VERSION_FILE) diff --git a/F3:F303/MLX90640-allsky/adc.c b/F3:F303/MLX90640-allsky/adc.c index b0a92cf..1dc8689 100644 --- a/F3:F303/MLX90640-allsky/adc.c +++ b/F3:F303/MLX90640-allsky/adc.c @@ -16,17 +16,26 @@ * along with this program. If not, see . */ +#include + +#ifdef EBUG +#include "strfunc.h" +#endif + #include "adc.h" /** * @brief ADCx_array - arrays for ADC channels with median filtering: * ADC1: - * 0 - Ch0 - ADC1_IN1 - * 1 - Ch1 - ADC1_IN2 - * 2 - internal Tsens - ADC1_IN16 - * 3 - Vref - ADC1_IN18 + * 0 - Ch0 - ADC1_IN1 - NTC1 + * 1 - Ch1 - ADC1_IN2 - NTC2 + * 2 - Ch2 - ADC1_IN3 - NTC3 + * 3 - Ch3 - ADC1_IN4 - NTC4 + * 4 - internal Tsens - ADC1_IN16 + * 5 - Vref - ADC1_IN18 * ADC2: - * 4 - AIN5/DAC_OUT1 - PA4 - DAC1_OUT1 (onboard heater?), PA5 - ADC2_IN2 (DAC output control) + * AIN5/DAC_OUT1 - PA4 - DAC1_OUT1 (onboard heater) + * 6 - PA5 - ADC2_IN2 (DAC output control) */ static uint16_t ADC_array[NUMBER_OF_ADC_CHANNELS*9]; @@ -65,17 +74,19 @@ TRUE_INLINE void enADC(ADC_TypeDef *chnl){ * ADC1 - DMA1_ch1 * ADC2 - DMA2_ch1 */ -// Setup ADC and DAC +// Setup ADC and DAC; ADC/DAC pins should be prepared in gpio_setup void adc_setup(){ RCC->AHBENR |= RCC_AHBENR_ADC12EN; // Enable clocking ADC12_COMMON->CCR = ADC_CCR_TSEN | ADC_CCR_VREFEN | ADC_CCR_CKMODE; // enable Tsens and Vref, HCLK/4 calADC(ADC1); calADC(ADC2); - // ADC1: channels 1,2,16,18; ADC2: channel 2 - ADC1->SMPR1 = ADC_SMPR1_SMP0 | ADC_SMPR1_SMP1; + // ADC1: channels 1,2,3,4,16,18 + ADC1->SMPR1 = ADC_SMPR1_SMP0 | ADC_SMPR1_SMP1 | ADC_SMPR1_SMP2 | ADC_SMPR1_SMP3; ADC1->SMPR2 = ADC_SMPR2_SMP15 | ADC_SMPR2_SMP17; - // 4 conversions in group: 1->2->16->18 - ADC1->SQR1 = (1<<6) | (2<<12) | (16<<18) | (18<<24) | (NUMBER_OF_ADC1_CHANNELS-1); + // 6 conversions in group: 1->2->3->4->16->18 + ADC1->SQR1 = (1<<6) | (2<<12) | (3<<18) | (4<<24) | (NUMBER_OF_ADC1_CHANNELS-1); + ADC1->SQR2 = (16<<0) | (18<<6); + // ADC2: channel 2 ADC2->SMPR1 = ADC_SMPR1_SMP1; ADC2->SQR1 = (2<<6) | (NUMBER_OF_ADC2_CHANNELS-1); // configure DMA for ADC @@ -109,7 +120,8 @@ void adc_setup(){ * @param nch - number of channel * @return */ -uint16_t getADCval(int nch){ +uint16_t getADCval(uint8_t nch){ + if(nch >= NUMBER_OF_ADC_CHANNELS) return 0; register uint16_t temp; #define PIX_SORT(a,b) { if ((a)>(b)) PIX_SWAP((a),(b)); } #define PIX_SWAP(a,b) { temp=(a);(a)=(b);(b)=temp; } @@ -131,7 +143,7 @@ uint16_t getADCval(int nch){ } // get voltage @input nch (V) -float getADCvoltage(int nch){ +float getADCvoltage(uint8_t nch){ float v = getADCval(nch); v *= getVdd(); v /= 4096.f; // 12bit ADC @@ -155,3 +167,114 @@ float getVdd(){ vdd /= getADCval(ADC_VREF); return vdd; } + +// R lookup table for T=-10..59 degreesC +#if 0 +T=[-10:59]+273.15; +R=1000*exp(3950*(1./T-1/298.15)); +for i=1:length(T); printf("\t%.1f,\t// %d \n", R(i), T(i)-273.15); endfor +#endif + +static const float Rlut[] = { + 5824.6, // -10 + 5502.8, // -9 + 5201.1, // -8 + 4917.9, // -7 + 4652.2, // -6 + 4402.6, // -5 + 4168.1, // -4 + 3947.7, // -3 + 3740.5, // -2 + 3545.5, // -1 + 3362.1, // 0 + 3189.3, // 1 + 3026.6, // 2 + 2873.3, // 3 + 2728.8, // 4 + 2592.5, // 5 + 2463.9, // 6 + 2342.5, // 7 + 2227.9, // 8 + 2119.7, // 9 + 2017.5, // 10 + 1920.8, // 11 + 1829.4, // 12 + 1743.0, // 13 + 1661.2, // 14 + 1583.7, // 15 + 1510.4, // 16 + 1440.9, // 17 + 1375.1, // 18 + 1312.7, // 19 + 1253.5, // 20 + 1197.4, // 21 + 1144.1, // 22 + 1093.6, // 23 + 1045.6, // 24 + 1000.0, // 25 + 956.7, // 26 + 915.5, // 27 + 876.4, // 28 + 839.1, // 29 + 803.7, // 30 + 770.0, // 31 + 737.9, // 32 + 707.4, // 33 + 678.3, // 34 + 650.6, // 35 + 624.1, // 36 + 598.9, // 37 + 574.9, // 38 + 552.0, // 39 + 530.1, // 40 + 509.3, // 41 + 489.4, // 42 + 470.3, // 43 + 452.2, // 44 + 434.8, // 45 + 418.2, // 46 + 402.4, // 47 + 387.2, // 48 + 372.7, // 49 + 358.8, // 50 + 345.5, // 51 + 332.8, // 52 + 320.7, // 53 + 309.0, // 54 + 297.8, // 55 + 287.1, // 56 + 276.9, // 57 + 267.1, // 58 + 257.7, // 59 +}; + +#define LUTSZ (sizeof(Rlut) / sizeof(float)) + +/** + * @brief getNTCtemp - stupid LUT-search and linear approximation of T by R + * @param nch - channel of ADC for Tx + * @return temperature in degr.C + */ +float getNTCtemp(uint8_t nch){ + if(nch > ADC_AIN4) return -300.f; // bad number + uint16_t val = getADCval(nch); + if(val < 5) return -400.f; // short cirquit + else if(val > 4090) return -500.f; // no NTC + float R = 1000.f / (4096.f / val - 1.f); // resistance of NTC +#ifdef EBUG + USB_sendstr("R="); USB_sendstr(float2str(R, 1)); newline(); +#endif + int left = 0, right = LUTSZ-1; + if(R > Rlut[0]) right = 1; + else if(R < Rlut[LUTSZ-1]) left = LUTSZ-2; + while(right - left > 1){ + int idx = left + (right - left) / 2; + float Rl = Rlut[idx]; + if(Rl > R) left = idx + 1; + else right = idx - 1; + } + if(left >= (int)LUTSZ) return 60.f; + float Rleft = Rlut[left], Rright = Rlut[left+1]; + float T = (float)left - 9.f - (R - Rright) / (Rleft - Rright); + return T; +} diff --git a/F3:F303/MLX90640-allsky/adc.h b/F3:F303/MLX90640-allsky/adc.h index 77db4da..eb18570 100644 --- a/F3:F303/MLX90640-allsky/adc.h +++ b/F3:F303/MLX90640-allsky/adc.h @@ -19,22 +19,27 @@ #pragma once #include -#define NUMBER_OF_ADC1_CHANNELS (4) +// 4 sensors on 1..4, TS (16) and Vdd (18) +#define NUMBER_OF_ADC1_CHANNELS (6) #define NUMBER_OF_ADC2_CHANNELS (1) // total number of channels - for array #define NUMBER_OF_ADC_CHANNELS ((NUMBER_OF_ADC1_CHANNELS+NUMBER_OF_ADC2_CHANNELS)) // channels of ADC in array -#define ADC_AIN0 (0) -#define ADC_AIN1 (1) -#define ADC_TS (2) -#define ADC_VREF (3) -#define ADC_AIN5 (4) +#define ADC_AIN1 (0) +#define ADC_AIN2 (1) +#define ADC_AIN3 (2) +#define ADC_AIN4 (3) +#define ADC_NTCIN(x) ((x)-1) +#define ADC_TS (4) +#define ADC_VREF (5) +#define ADC_DACIN (6) // starting index of ADC2 #define ADC2START (9*NUMBER_OF_ADC1_CHANNELS) void adc_setup(); float getMCUtemp(); float getVdd(); -uint16_t getADCval(int nch); -float getADCvoltage(int nch); +uint16_t getADCval(uint8_t nch); +float getADCvoltage(uint8_t nch); +float getNTCtemp(uint8_t nch); diff --git a/F3:F303/MLX90640-allsky/allsky.bin b/F3:F303/MLX90640-allsky/allsky.bin index 7bccbf2..a207654 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/commproto.cpp b/F3:F303/MLX90640-allsky/commproto.cpp new file mode 100644 index 0000000..b6d8f5e --- /dev/null +++ b/F3:F303/MLX90640-allsky/commproto.cpp @@ -0,0 +1,627 @@ +#include + +extern "C"{ +#include +#include + +#include "adc.h" +#include "commproto.h" +#include "hardware.h" +#include "i2c.h" +#include "mlxproc.h" +#include "strfunc.h" +#include "usart.h" +#include "usb_dev.h" +} + +// image aquisition time +const char* const Timage = "TIMAGE"; + +// Global senders to send info into other interface +static int (*usb_sender)(const char*) = nullptr; +static int (*usart_sender)(const char*) = nullptr; +static int (*usb_putb)(uint8_t) = nullptr; +static int (*usart_putb)(uint8_t) = nullptr; +static int (*usb_sendbin)(const uint8_t*, int) = nullptr; +static int (*usart_sendbin)(const uint8_t*, int) = nullptr; + +// Function helpers +static int (*SEND)(const char*) = nullptr; +static int (*putb)(uint8_t) = nullptr; +static int (*sendbin)(const uint8_t*, int) = nullptr; + +#define N() putb('\n') +#define printu(x) SEND(u2str(x)) +#define printi(x) SEND(i2str(x)) +#define printuhex(x) SEND(uhex2str(x)) +#define printfl(x,n) SEND(float2str(x, n)) + +void set_senders(int (*usbs)(const char *), + int (*usbb)(uint8_t), + int (*usbbin)(const uint8_t *, int), + int (*usarts)(const char *), + int (*usartb)(uint8_t), + int (*usartbin)(const uint8_t *, int)){ + usb_sender = usbs; + usb_putb = usbb; + usb_sendbin = usbbin; + usart_sender = usarts; + usart_putb = usartb; + usart_sendbin = usartbin; +} + +// Local buffer for I2C data +#define LOCBUFFSZ 32 +static uint16_t locBuffer[LOCBUFFSZ]; +// default I2C address of sensor +static uint8_t I2Caddress = 0x33 << 1; +extern volatile uint32_t Tms; +// show `cartoon` - continuously draw ASCII image of current sensor +uint8_t cartoon = 0; + +// Command list +#define COMMAND_TABLE \ + COMMAND(help, "show this help") \ + COMMAND(ascii, "draw nth image in ASCII (n=0..4)") \ + COMMAND(binary, "get nth image as text array of floats") \ + COMMAND(listids, "list active sensors IDs") \ + COMMAND(tempmap, "show temperature map of nth image") \ + COMMAND(acqtime, "show nth image aquisition time") \ + COMMAND(bmereinit, "reinit BME280") \ + COMMAND(environ, "get environment parameters") \ + COMMAND(state, "get MLX state") \ + COMMAND(reset, "reset MCU") \ + COMMAND(time, "print current Tms") \ + COMMAND(iicaddr, "get/set I2C address (non-shifted)") \ + COMMAND(mlxcont, "continue MLX") \ + COMMAND(iicspeed, "get/set I2C speed (0..4)") \ + COMMAND(mlxpause, "pause MLX") \ + COMMAND(mlxstop, "stop MLX") \ + COMMAND(adc, "get n'th ADC values") \ + COMMAND(ntc, "get n'th NTC temperatures") \ + COMMAND(cartoon, "toggle cartoon mode") \ + COMMAND(mlxdump, "dump MLX parameters for sensor n") \ + COMMAND(mlxaddr, "get/set MLX address of sensor n") \ + COMMAND(readreg, "read I2C register: readreg reg [= nwords]") \ + COMMAND(writedata, "write I2C data: writedata = val1 val2 ...") \ + COMMAND(iicscan, "scan I2C bus") \ + COMMAND(mcutemp, "get MCU temperature") \ + COMMAND(mcuvdd, "get MCU Vdd") \ + COMMAND(dac, "get/set DAC value") \ + COMMAND(pwm, "get/set PWM for channel n (0..100%)") \ + COMMAND(sendstr, "send string to other interface: sendstr = text") + + +// Command prototypes +#define COMMAND(name, desc) static errcodes_t cmd_ ## name(const char*, char*); +COMMAND_TABLE +#undef COMMAND + +// descrtiptions for `help` +typedef struct { + const char *name; + const char *desc; +} CmdInfo; + +static const CmdInfo cmdInfo[] = { +#define COMMAND(name, desc) { #name, desc }, + COMMAND_TABLE +#undef COMMAND +}; + +// Text descriptions for error codes +static const char* errtxt[ERR_AMOUNT] = { + [ERR_OK] = "OK\n", + [ERR_BADCMD] = "BADCMD\n", + [ERR_BADPAR] = "BADPAR\n", + [ERR_BADVAL] = "BADVAL\n", + [ERR_WRONGLEN] = "WRONGLEN\n", + [ERR_CANTRUN] = "CANTRUN\n", + [ERR_BUSY] = "BUSY\n", + [ERR_OVERFLOW] = "OVERFLOW\n", +}; + +const char *EQ = " = "; // equal sign for getters + +// send `command = ` +#define CMDEQ() do{SEND(cmd); SEND(EQ);}while(0) +// send `commandXXX = ` +#define CMDEQP(x) do{SEND(cmd); SEND(u2str((uint32_t)x)); SEND(EQ);}while(0) + +/** + * @brief splitargs - get command parameter and setter from `args` + * @param args (i) - rest of string after command (like `1 = PU OD OUT`) + * @param parno (o) - parameter number or -1 if none + * @return setter (part after `=` without leading spaces) or NULL if none + */ +static const char *splitargs(char *args, int32_t *parno){ + if(!args) return NULL; + uint32_t U32; + const char *next = getnum(args, &U32); + int p = -1; + if(next != args && U32 <= MAXPARNO) p = U32; + if(parno) *parno = p; + next = strchr(next, '='); + if(next){ + if(*(++next)) next = omit_spaces(next); + if(*next == 0) next = NULL; + } + return next; +} + +/** + * @brief argsvals - split `args` into `parno` and setter's value + * @param args - rest of string after command + * @param parno (o) - parameter number or -1 if none + * @param parval - integer setter's value + * @return false if no setter or it's not a number, true - got setter's num + */ +static bool argsvals(char *args, int32_t *parno, int32_t *parval){ + const char *setter = splitargs(args, parno); + if(!setter) return false; + int32_t I32; + const char *next = getint(setter, &I32); + if(next != setter && parval){ + *parval = I32; + return true; + } + return false; +} + + +/************* List of proto functions for each command *************/ + +static errcodes_t cmd_help(const char*, char*){ + SEND(REPOURL); + for(size_t i = 0; i < sizeof(cmdInfo)/sizeof(cmdInfo[0]); ++i){ + SEND(cmdInfo[i].name); + SEND(" - "); + SEND(cmdInfo[i].desc); + SEND("\n"); + } + return ERR_AMOUNT; +} + +static errcodes_t cmd_time(const char* cmd, char*){ + CMDEQ(); + printu(Tms); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_reset(const char*, char*){ + NVIC_SystemReset(); + return ERR_CANTRUN; // unreacheable +} + +// send acquisition time +static void imaqtime(uint8_t sensno){ + uint32_t T = mlx_lastimT(sensno); + SEND(Timage); printu(sensno); SEND(EQ); + printu(T); N(); +} + +// Common image command for ASCII/binary/tempmap +static errcodes_t image_cmd(char* args, int mode){ + int32_t sensno = -1; + splitargs(args, &sensno); + if(sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR; + fp_t *img = mlx_getimage(sensno); + if(!img) return ERR_CANTRUN; + + // Frame number + imaqtime(sensno); + + switch(mode){ + case 0: // tempmap + dumpIma(img); + break; + case 1: // ascii + drawIma(img); + break; + case 2: // binary + SEND("BINARY"); putb('0'+sensno); putb('='); + uint8_t *d = (uint8_t*)img; + uint32_t _2send = MLX_PIXNO * sizeof(float); + // send by portions of 256 bytes + while(_2send){ + uint32_t portion = (_2send > 256) ? 256 : _2send; + if(sendbin(d, portion)){ + _2send -= portion; + d += portion; + } + } + SEND("ENDIMAGE"); N(); + break; + } + return ERR_AMOUNT; +} + +static errcodes_t cmd_ascii(const char* , char* args){ + return image_cmd(args, 1); +} +static errcodes_t cmd_binary(const char* , char* args){ + return image_cmd(args, 2); +} +static errcodes_t cmd_tempmap(const char* , char* args){ + return image_cmd(args, 0); +} + +static errcodes_t cmd_acqtime(const char* , char* args){ + int32_t sensno = -1; + splitargs(args, &sensno); + if(sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR; + imaqtime(sensno); + return ERR_AMOUNT; +} + +static errcodes_t cmd_listids(const char*, char*){ + int N = mlx_nactive(); + if(!N) return ERR_CANTRUN; + uint8_t *ids = mlx_activeids(); + SEND("Found "); printu(N); SEND(" active sensors:\n"); + for(int i = 0; i < N_SENSORS; ++i){ + if(ids[i]){ + SEND("SENSID"); printu(i); SEND(EQ); printuhex(ids[i]>>1); N(); + } + } + return ERR_AMOUNT; +} + +static errcodes_t cmd_bmereinit(const char*, char*){ + if(bme_init()) return ERR_OK; + return ERR_CANTRUN; +} + +static errcodes_t cmd_environ(const char*, char*){ + bme280_t env; + if(!get_environment(&env)) return ERR_CANTRUN; + SEND("TEMPERATURE="); printfl(env.T, 2); N(); + SEND("SKYTEMPERATURE="); printfl(env.Tsky, 2); N(); + SEND("PRESSURE_HPA="); printfl(env.P/100.f, 2); N(); + SEND("PRESSURE_MM="); printfl(env.P * 0.00750062f, 2); N(); + SEND("HUMIDITY="); printfl(env.H, 2); N(); + SEND("TEMP_DEW="); printfl(env.Tdew, 1); N(); + SEND("T_MEASUREMENT="); printu(env.Tmeas); N(); + return ERR_OK; +} + +static errcodes_t cmd_state(const char* cmd, char*){ + static const char *states[] = { + [MLX_NOTINIT] = "not init", + [MLX_WAITPARAMS] = "wait parameters DMA read", + [MLX_WAITSUBPAGE] = "wait subpage", + [MLX_READSUBPAGE] = "wait subpage DMA read", + [MLX_RELAX] = "do nothing" + }; + mlx_state_t s = mlx_state(); + CMDEQ(); SEND(states[s]); N(); + return ERR_AMOUNT; +} + +/********** I2C commands **********/ + +static errcodes_t cmd_iicaddr(const char* cmd, char* args){ + int32_t addr; + if(argsvals(args, NULL, &addr)){ + if(addr < 0 || addr > 0x7f) return ERR_BADVAL; + I2Caddress = (uint8_t)(addr << 1); + mlx_sethwaddr(I2Caddress, addr); + return ERR_AMOUNT; + } + // getter + CMDEQ(); printuhex(I2Caddress >> 1); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_mlxcont(const char*, char*){ + mlx_continue(); + return ERR_OK; +} + +static errcodes_t cmd_iicspeed(const char* cmd, char* args){ + static const char *speeds[] = {"10K","100K","400K","1M","2M"}; + int32_t speed; + // TODO: allow string parameter + if(argsvals(args, NULL, &speed)){ + if (speed < 0 || speed >= I2C_SPEED_AMOUNT) return ERR_BADVAL; + i2c_setup((i2c_speed_t)speed); + } + // getter + CMDEQ(); SEND(speeds[i2c_curspeed]); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_mlxpause(const char*, char*){ + mlx_pause(); + return ERR_OK; +} + +static errcodes_t cmd_mlxstop(const char*, char*){ + mlx_stop(); + return ERR_OK; +} + +static errcodes_t cmd_adc(const char* cmd, char* args){ + if(!args){ // show all values + for(uint8_t i = 0; i < NUMBER_OF_ADC_CHANNELS; ++i){ + CMDEQP(i); printu(getADCval(i)); N(); + } + return ERR_AMOUNT; + } + int32_t addr; + splitargs(args, &addr); + if(addr < 0 || addr >= NUMBER_OF_ADC_CHANNELS) return ERR_BADPAR; + CMDEQP(addr); printu(getADCval(static_cast(addr))); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_ntc(const char* cmd, char* args){ + if(!args){ // show all values + for(uint8_t i = 0; i <= ADC_AIN4; ++i){ + CMDEQP(i); printfl(getNTCtemp(i), 1); N(); + } + return ERR_AMOUNT; + } + int32_t addr; + splitargs(args, &addr); + if(addr < 0 || addr > ADC_AIN4) return ERR_BADPAR; + CMDEQP(addr); printfl(getNTCtemp(static_cast(addr)), 1); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_cartoon(const char*, char*){ + // TODO: should be getter/setter! + cartoon = !cartoon; + return ERR_OK; +} + +static errcodes_t cmd_mlxdump(const char*, char* args){ + int32_t sensno = -1; + splitargs(args, &sensno); + if (sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR; + MLX90640_params *params = mlx_getparams(sensno); + if(!params) return ERR_CANTRUN; + + SEND("SENSNO="); printi(sensno); N(); + SEND("kVdd="); printi(params->kVdd); N(); + SEND("vdd25="); printi(params->vdd25); N(); + SEND("KvPTAT="); printfl(params->KvPTAT, 4); N(); + SEND("KtPTAT="); printfl(params->KtPTAT, 4); N(); + SEND("vPTAT25="); printi(params->vPTAT25); N(); + SEND("alphaPTAT="); printfl(params->alphaPTAT, 2); N(); + SEND("gainEE="); printi(params->gainEE); N(); + SEND("Pixel offset parameters:\n"); + dumpIma(params->offset); + SEND("K_talpha:\n"); + dumpIma(params->kta); + SEND("Kv: "); + for(int i = 0; i < 4; ++i) { printfl(params->kv[i], 2); putb(' '); } + N(); + SEND("cpOffset="); printi(params->cpOffset[0]); SEND(", "); printi(params->cpOffset[1]); N(); + SEND("cpKta="); printfl(params->cpKta, 2); N(); + SEND("cpKv="); printfl(params->cpKv, 2); N(); + SEND("tgc="); printfl(params->tgc, 2); N(); + SEND("cpALpha="); printfl(params->cpAlpha[0], 2); SEND(", "); printfl(params->cpAlpha[1], 2); N(); + SEND("KsTa="); printfl(params->KsTa, 2); N(); + SEND("Alpha:\n"); + dumpIma(params->alpha); + SEND("CT3="); printfl(params->CT[1], 2); N(); + SEND("CT4="); printfl(params->CT[2], 2); N(); + for(int i = 0; i < 4; ++i){ + SEND("KsTo"); putb('0'+i); putb('='); printfl(params->KsTo[i], 2); N(); + SEND("alphacorr"); putb('0'+i); putb('='); printfl(params->alphacorr[i], 2); N(); + } + return ERR_AMOUNT; +} + +static errcodes_t cmd_mlxaddr(const char* cmd, char* args){ + int32_t sensno = -1; + if(!args || !*args) { // without args: show global address + //CMDEQ(); printuhex(I2Caddress>>1); N(); + //return ERR_AMOUNT; + return ERR_BADPAR; + } + const char *setter = splitargs(args, &sensno); + if(sensno < 0 || sensno >= N_SENSORS) return ERR_BADPAR; + + if(setter){ // setter: set current address + uint32_t a; + const char *nxt = getnum(setter, &a); + if(nxt == setter || a > 0x7f) return ERR_BADVAL; + mlx_setaddr(sensno, (uint8_t)a); + return ERR_AMOUNT; + }else{ // getter + uint8_t a = mlx_getaddr(sensno); + CMDEQP(sensno); printuhex(a); N(); + return ERR_AMOUNT; + } +} + +static errcodes_t cmd_readreg(const char* cmd, char* args){ + int32_t reg = -1, nwords = 1; + const char *setter = splitargs(args, ®); + if(reg < 0) return ERR_BADPAR; + if(setter){ // read more than one byte + uint32_t n; + const char *nxt = getnum(setter, &n); + if (nxt == setter || n == 0 || n > I2C_BUFSIZE) return ERR_BADVAL; + nwords = (int32_t)n; + } + uint16_t *data = i2c_read_reg16(I2Caddress, (uint16_t)reg, nwords, 0); + if(!data) return ERR_CANTRUN; + if(nwords == 1){ + CMDEQP(reg); printuhex(*data); N(); + }else{ + CMDEQP(reg); N(); hexdump16(SEND, data, nwords); + } + return ERR_AMOUNT; +} + +static errcodes_t cmd_writedata(const char*, char* args){ + const char *setter = splitargs(args, NULL); + if(!setter) return ERR_BADVAL; + int N = 0; + const char *p = setter; + while (*p){ + if(N >= LOCBUFFSZ) return ERR_AMOUNT; + uint32_t val; + p = getnum(p, &val); + if(p == setter) break; // not a number + locBuffer[N++] = (uint16_t)val; + p = omit_spaces(p); + if (!*p) break; + } + if(N == 0) return ERR_BADVAL; + if(!i2c_write(I2Caddress, locBuffer, N)) return ERR_CANTRUN; + return ERR_OK; +} + +static errcodes_t cmd_iicscan(const char*, char*) { + i2c_init_scan_mode(); + return ERR_OK; +} + +static errcodes_t cmd_mcutemp(const char* cmd, char*){ + CMDEQ(); printfl(getMCUtemp(), 2); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_mcuvdd(const char* cmd, char*){ + CMDEQ(); printfl(getVdd(), 2); N(); + return ERR_AMOUNT; +} + +static errcodes_t cmd_dac(const char* cmd, char* args){ + int32_t val; + if(argsvals(args, NULL, &val)){ + if(val < 0 || val > 4095) return ERR_BADVAL; + DAC1->DHR12R1 = static_cast(val); + } + // getter + CMDEQ(); printu(DAC1->DHR12R1); N(); + return ERR_AMOUNT; +} + +static void showpwm(const char* cmd, uint8_t nch){ + uint16_t ccr; + switch(nch){ + case 0: ccr = TIM3->CCR1; break; + case 1: ccr = TIM3->CCR2; break; + case 2: ccr = TIM3->CCR3; break; + case 3: ccr = TIM3->CCR4; break; + default: return; + } + CMDEQP(nch); printu(ccr); N(); +} + +// four PWM channels: 1,2 - heaters, 3,4 - info +static errcodes_t cmd_pwm(const char* cmd, char* args){ + int32_t ch = -1, val; + const char *setter = splitargs(args, &ch); + if(ch < 0 || ch > PWM_CH_MAX){ // all channels + for(uint8_t i = 0; i <= PWM_CH_MAX; ++i) + showpwm(cmd, i); + return ERR_AMOUNT; + } + if(setter){ + if(!getint(setter, &val) || val < 0 || val > 100) return ERR_BADVAL; + if(!setPWM(static_cast(ch), static_cast(val))) + return ERR_CANTRUN; + } + // getter + showpwm(cmd, (uint8_t)ch); + return ERR_AMOUNT; +} + +static errcodes_t cmd_sendstr(const char*, char* args) { + int32_t dummy; + const char *text = splitargs(args, &dummy); + if(!text || !*text) return ERR_BADVAL; + // switch to other interface + int (*other_sender)(const char*) = (SEND == usb_sender) ? usart_sender : usb_sender; + if (!other_sender) return ERR_CANTRUN; + other_sender(text); + other_sender("\n"); + return ERR_OK; +} + +static constexpr uint32_t hash(const char* str, uint32_t h = 0) { + return *str ? hash(str + 1, h + ((h << 7) ^ *str)) : h; +} + + +const char *parse_cmd(int (*sendfun)(const char*), char *buf) { + if(!buf || !*buf || !sendfun) return NULL; + SEND = sendfun; + if(sendfun == usb_sender){ + putb = usb_putb; + sendbin = usb_sendbin; + }else{ + putb = usart_putb; + sendbin = usart_sendbin; + } + char command[CMD_MAXLEN+1]; + int i = 0; + while(*buf > '@' && i < CMD_MAXLEN) command[i++] = *buf++; + command[i] = 0; + while(*buf && *buf <= ' ') ++buf; + char *args = buf; +#ifdef EBUG + USB_sendstr("__args='"); USB_sendstr(args); USB_sendstr("'\n"); +#endif + if(!*args) args = NULL; + + uint32_t h = hash(command); + errcodes_t ecode = ERR_AMOUNT; + switch (h){ +#define COMMAND(name, desc) case hash(#name): ecode = cmd_##name(command, args); break; + COMMAND_TABLE +#undef COMMAND + default: + SEND("Unknown command, try 'help'\n"); + } + if(ecode < ERR_AMOUNT) return errtxt[ecode]; + return NULL; +} + +void dumpIma(const fp_t im[MLX_PIXNO]){ + for(int row = 0; row < MLX_H; ++row){ + for(int col = 0; col < MLX_W; ++col){ + printfl(*im++, 1); + putb(' '); + } + N(); + } +} + +#define GRAY_LEVELS 16 +static const char *const CHARS_16 = " .':;+*oxX#&%B$@"; +void drawIma(const fp_t im[MLX_PIXNO]){ + fp_t min_val = im[0], max_val = im[0]; + const fp_t *iptr = im; + for(int row = 0; row < MLX_H; ++row) + for(int col = 0; col < MLX_W; ++col){ + fp_t cur = *iptr++; + if (cur < min_val) min_val = cur; + else if (cur > max_val) max_val = cur; + } + fp_t range = max_val - min_val; + SEND("RANGE="); printfl(range, 3); N(); + SEND("MIN="); printfl(min_val, 3); N(); + SEND("MAX="); printfl(max_val, 3); N(); + if(fabsf(range) < 0.001) range = 1.0f; + iptr = im; + char string[MLX_W+2]; + string[MLX_W] = '\n'; string[MLX_W+1] = 0; // end of line + for(int row = 0; row < MLX_H; ++row){ + for(int col = 0; col < MLX_W; ++col){ + fp_t normalized = ((*iptr++) - min_val) / range; + int idx = (int)(normalized * GRAY_LEVELS); + if(idx < 0) idx = 0; + else if(idx >= GRAY_LEVELS) idx = GRAY_LEVELS-1; + string[col] = CHARS_16[idx]; + } + SEND(string); + } + N(); +} diff --git a/F3:F303/MLX90640-allsky/commproto.h b/F3:F303/MLX90640-allsky/commproto.h new file mode 100644 index 0000000..fc4d7ee --- /dev/null +++ b/F3:F303/MLX90640-allsky/commproto.h @@ -0,0 +1,61 @@ +/* + * This file is part of the as3935 project. + * Copyright 2026 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 "mlx90640.h" + +#include "version.inc" + +#ifdef EBUG +#define RLSDBG "debug" +#else +#define RLSDBG "release" +#endif + +#define REPOURL "https://github.com/eddyem/stm32samples/tree/master/F3:F303/MLX90640-allsky " RLSDBG " build #" BUILD_NUMBER "@" BUILD_DATE "\n" + + +// error codes for answer message +typedef enum{ + ERR_OK, // all OK + ERR_BADCMD, // wrong command + ERR_BADPAR, // wrong parameter + ERR_BADVAL, // wrong value (for setter) + ERR_WRONGLEN, // wrong message length + ERR_CANTRUN, // can't run given command due to bad parameters or other + ERR_BUSY, // target interface busy, try later + ERR_OVERFLOW, // string was too long -> overflow + ERR_AMOUNT // amount of error codes or "send nothing" +} errcodes_t; + +// maximal length of command (without trailing zero) +#define CMD_MAXLEN 15 +// maximal available parameter number (for 16-bit registers is 0xffff +#define MAXPARNO 0xffff + +extern const char *EQ; +const char *parse_cmd(int (*sendfun)(const char*), char *buf); +void set_senders(int (*usbs)(const char*), int (*usbb)(uint8_t), int (*usbbin)(const uint8_t*, int), + int (*usarts)(const char*), int (*usartb)(uint8_t), int (*usartbin)(const uint8_t*, int)); + +extern const char *const Timage; + +extern uint8_t cartoon; +void dumpIma(const fp_t im[MLX_PIXNO]); +void drawIma(const fp_t im[MLX_PIXNO]); diff --git a/F3:F303/MLX90640-allsky/hardware.c b/F3:F303/MLX90640-allsky/hardware.c index 4b78c6b..abae11d 100644 --- a/F3:F303/MLX90640-allsky/hardware.c +++ b/F3:F303/MLX90640-allsky/hardware.c @@ -51,16 +51,19 @@ TRUE_INLINE void iwdg_setup(){ 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; USB pullup - PA15 - // PA6 - PWM for external heater (TIM3_CH1 or TIM16_CH1); PA7 - PWM propto (humidity - 50%) + // USB - alternate function 14 @ pins PA11/PA12; SWD - AF0 @PA13/14 + // PA6,PA7 - PWM for external heaters (TIM3_CH1, TIM3_CH2) + // PA0..PA4 - NTC in, PA5 - DAC_OUT1 (board heater), PA6 - ADC in for DAC out + // USART pins will be setup in usart.c GPIOA->AFR[0] = AFRf(2, 6) | AFRf(2, 7); GPIOA->AFR[1] = AFRf(14, 11) | AFRf(14, 12); - GPIOA->MODER = MODER_AI(0) | MODER_AI(1) | MODER_AI(4) | MODER_AI(5) | MODER_AF(6) | - MODER_AF(7) | MODER_AF(11) | MODER_AF(12) | MODER_AF(13) | MODER_AF(14) | MODER_O(15); - // PB0 - PWM propto Text (<=20 - 0%, >=30 - 100%), PB1 - PWM propto (Text-Tsky) (<=-5 - 0%, >=+35 - 100%) PB2 - SPI_CS + // force USB DP to low level for a while + GPIOA->MODER = MODER_AI(0) | MODER_AI(1) | MODER_AI(2) | MODER_AI(3) | MODER_AI(4) | MODER_AI(5) | MODER_AF(6) | + MODER_AF(7) | MODER_AF(11) | MODER_O(12) | MODER_AF(13) | MODER_AF(14) | MODER_O(15); + // PB0 - PWM propto Text (<=20 - 0%, >=30 - 100%), PB1 - PWM propto (Text-Tsky) (<=-5 - 0%, >=+35 - 100%) PB9 - SPI_CS + // SPI and I2C will be setup in spi.c and i2c.c GPIOB->AFR[0] = AFRf(2, 0) | AFRf(2, 1); - GPIOB->MODER = MODER_AF(0) | MODER_AF(1) | MODER_O(2); - pin_set(GPIOB, 1<<1); + GPIOB->MODER = MODER_AF(0) | MODER_AF(1) | MODER_O(9); SPI_CS_1(); } @@ -85,7 +88,7 @@ TRUE_INLINE void pwm_setup(){ // change PWM value in percents; return 0 if `val` is bad or `ch` not 0..3 int setPWM(uint8_t ch, uint8_t val){ - if(ch > 3 || val > PWM_CCR_MAX) return 0; + if(ch >= PWM_CH_MAX || val > PWM_CCR_MAX) return 0; volatile uint32_t *CCRs = &(TIM3->CCR1); CCRs[ch] = val; return 1; @@ -139,7 +142,6 @@ void bme_process(){ // set PWM duty propto humidity float h = (Humidity - 50.f) * 2.f; if(h < 0.f) h = 0.f; else if(h > 100.f) h = 100.f; - setPWM(PWM_CH_HUMIDITY, (uint8_t)h); environment.Tmeas = Tms; // set PWM duty propto external T float t = (Temperature + 20.f) * 2.f; diff --git a/F3:F303/MLX90640-allsky/hardware.h b/F3:F303/MLX90640-allsky/hardware.h index 71f6f04..3b92191 100644 --- a/F3:F303/MLX90640-allsky/hardware.h +++ b/F3:F303/MLX90640-allsky/hardware.h @@ -25,9 +25,9 @@ #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) +// SPI_CS - PB9 +#define SPI_CS_1() pin_set(GPIOB, 1<<9) +#define SPI_CS_0() pin_clear(GPIOB, 1<<9) // interval of environment measurements, ms #define ENV_MEAS_PERIOD (10000) @@ -36,14 +36,11 @@ // Max PWM CCR1 value (->1) #define PWM_CCR_MAX (100) // PWM channels (start from 0 - CH1) -// external heater -#define PWM_CH_HEATER (0) -// propto humidity (the higher - the brighter) -#define PWM_CH_HUMIDITY (1) // propto external T (the higher - the brighter) #define PWM_CH_TEXT (2) // propto Tsky - Text (the higher - the brighter) #define PWM_CH_TSKY (3) +#define PWM_CH_MAX (3) typedef struct{ float T; // temperature, degC diff --git a/F3:F303/MLX90640-allsky/ir-allsky.creator.user b/F3:F303/MLX90640-allsky/ir-allsky.creator.user index 64d8017..c32ec9c 100644 --- a/F3:F303/MLX90640-allsky/ir-allsky.creator.user +++ b/F3:F303/MLX90640-allsky/ir-allsky.creator.user @@ -1,6 +1,6 @@ - + EnvironmentId @@ -86,6 +86,7 @@ true + 0 @@ -153,6 +154,7 @@ true true true + 2 @@ -162,6 +164,7 @@ ProjectExplorer.CustomExecutableRunConfiguration false + true true @@ -185,6 +188,7 @@ true true true + 2 @@ -194,6 +198,7 @@ ProjectExplorer.CustomExecutableRunConfiguration false + true true @@ -204,10 +209,6 @@ ProjectExplorer.Project.TargetCount 1 - - ProjectExplorer.Project.Updater.FileVersion - 22 - Version 22 diff --git a/F3:F303/MLX90640-allsky/ir-allsky.files b/F3:F303/MLX90640-allsky/ir-allsky.files index eabc985..fdf0783 100644 --- a/F3:F303/MLX90640-allsky/ir-allsky.files +++ b/F3:F303/MLX90640-allsky/ir-allsky.files @@ -2,6 +2,8 @@ BMP280.c BMP280.h adc.c adc.h +commproto.cpp +commproto.h hardware.c hardware.h i2c.c @@ -15,8 +17,6 @@ mlx90640.h mlx90640_regs.h mlxproc.c mlxproc.h -proto.c -proto.h ringbuffer.c ringbuffer.h spi.c diff --git a/F3:F303/MLX90640-allsky/main.c b/F3:F303/MLX90640-allsky/main.c index 02c31d9..d83ae9a 100644 --- a/F3:F303/MLX90640-allsky/main.c +++ b/F3:F303/MLX90640-allsky/main.c @@ -20,7 +20,7 @@ #include "hardware.h" #include "i2c.h" #include "mlxproc.h" -#include "proto.h" +#include "commproto.h" #include "strfunc.h" #include "usart.h" #include "usb_dev.h" @@ -43,14 +43,18 @@ int main(void){ StartHSI(); SysTick_Config((uint32_t)48000); // 1ms } - USBPU_OFF(); + USBPU_OFF(); // for development board with managed pullup resistor hw_setup(); adc_setup(); i2c_setup(I2C_SPEED_400K); bme_init(); - USB_setup(); usart_setup(115200); + // setup USB DP as alternate function - for sensors' board with constant pullup resistor + GPIOA->MODER = (GPIOA->MODER & ~GPIO_MODER_MODER12) | MODER_AF(12); USBPU_ON(); + USB_setup(); + // set senders for abiliby of sending messages between interfaces + set_senders(USB_sendstr, USB_putbyte, USB_send, usart_sendstr, usart_putbyte, usart_send); uint32_t ctr = Tms, Tlastima[N_SENSORS] = {0}; mlx_continue(); // init state machine while(1){ @@ -63,7 +67,7 @@ int main(void){ int l = USB_receivestr(inbuff, MAXSTRLEN); if(l < 0) USB_sendstr("USBOVERFLOW\n"); else if(l){ - const char *ans = parse_cmd(inbuff, SEND_USB); + const char *ans = parse_cmd(USB_sendstr, inbuff); if(ans) USB_sendstr(ans); } if(i2c_scanmode){ // send this to both @@ -84,7 +88,6 @@ int main(void){ if(Tnow != Tlastima[i]){ fp_t *im = mlx_getimage(i); if(im){ - chsendfun(SEND_USB); //U(Sensno); UN(i2str(i)); U(Timage); USB_putbyte('0'+i); USB_putbyte('='); UN(u2str(Tnow)); drawIma(im); @@ -96,7 +99,7 @@ int main(void){ if(usart_ovr()) usart_sendstr("USART_OVERFLOW\n"); char *got = usart_getline(NULL); if(got){ - const char *ans = parse_cmd(got, SEND_USART); + const char *ans = parse_cmd(usart_sendstr, got); if(ans) usart_sendstr(ans); } bme_process(); diff --git a/F3:F303/MLX90640-allsky/mlxproc.c b/F3:F303/MLX90640-allsky/mlxproc.c index eea579e..0e62161 100644 --- a/F3:F303/MLX90640-allsky/mlxproc.c +++ b/F3:F303/MLX90640-allsky/mlxproc.c @@ -63,12 +63,16 @@ static int sensno = -1; mlx_state_t mlx_state(){ return MLX_state; } // set address int mlx_setaddr(int n, uint8_t addr){ - if(n < 0 || n > N_SENSORS) return 0; + if(n < 0 || n >= N_SENSORS) return 0; if(addr > 0x7f) return 0; sens_addresses[n] = addr << 1; Tlastimage[n] = Tms; // refresh counter for autoreset I2C in case of error return 1; } +uint8_t mlx_getaddr(int n){ + if(n < 0 || n >= N_SENSORS) return 0; + return sens_addresses[n]; +} // pause state machine and stop void mlx_pause(){ MLX_oldstate = MLX_state; diff --git a/F3:F303/MLX90640-allsky/mlxproc.h b/F3:F303/MLX90640-allsky/mlxproc.h index f211644..25ad544 100644 --- a/F3:F303/MLX90640-allsky/mlxproc.h +++ b/F3:F303/MLX90640-allsky/mlxproc.h @@ -39,6 +39,7 @@ typedef enum{ } mlx_state_t; int mlx_setaddr(int n, uint8_t addr); +uint8_t mlx_getaddr(int n); mlx_state_t mlx_state(); int mlx_nactive(); uint8_t *mlx_activeids(); diff --git a/F3:F303/MLX90640-allsky/proto.c b/F3:F303/MLX90640-allsky/proto.c deleted file mode 100644 index 1acc3c5..0000000 --- a/F3:F303/MLX90640-allsky/proto.c +++ /dev/null @@ -1,539 +0,0 @@ -/* - * 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 -#include -#include - -#include "adc.h" -#include "hardware.h" -#include "i2c.h" -#include "mlxproc.h" -#include "proto.h" -#include "strfunc.h" -#include "usart.h" -#include "usb_dev.h" -#include "version.inc" - -#define LOCBUFFSZ (32) -// local buffer for I2C data to send -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 - -// functions to send data over USB or USART: to change them use flag in `parse_cmd` -typedef struct{ - int (*S)(const char*); // send string - int (*P)(uint8_t); // put byte - int (*B)(const uint8_t*, int); // send raw bytes -} sendfun_t; - -static sendfun_t usbsend = { - .S = USB_sendstr, .P = USB_putbyte, .B = USB_send -}; -static sendfun_t usartsend = { - .S = usart_sendstr, .P = usart_putbyte, .B = usart_send -}; - -static sendfun_t *sendfun = &usbsend; - -void chsendfun(int sendto){ - if(sendto == SEND_USB) sendfun = &usbsend; - else sendfun = &usartsend; -} - -// newline -#define N() sendfun->P('\n') -#define printu(x) do{sendfun->S(u2str(x));}while(0) -#define printi(x) do{sendfun->S(i2str(x));}while(0) -#define printuhex(x) do{sendfun->S(uhex2str(x));}while(0) -#define printfl(x,n) do{sendfun->S(float2str(x, n));}while(0) - -// common names for frequent keys -const char* const Timage = "TIMAGE"; -const char* const Image = "IMAGE"; -static const char *const Sensno = "SENSNO="; - -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" - "dn - draw nth image in ASCII\n" - "gn - get nth image 'as is' - float array of 768x4 bytes\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" - "A - get ADC values\n" - "C - \"cartoon\" mode on/off (show each new image) - USB only!!!\n" - "Dn - dump MLX parameters for sensor number n\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" - "M - get MCU temperature and Vdd value\n" - "O - set output of DAC (0..4095)\n" - "Px - set PWM output (0..100%) or get current value\n" - "Us - send string 's' to other interface\n" -; - -TRUE_INLINE const char *setupI2C(char *buf){ - static const char * const speeds[I2C_SPEED_AMOUNT] = { - [I2C_SPEED_10K] = "10K", - [I2C_SPEED_100K] = "100K", - [I2C_SPEED_400K] = "400K", - [I2C_SPEED_1M] = "1M", - [I2C_SPEED_2M] = "2M" - }; - if(buf && *buf){ - buf = omit_spaces(buf); - int speed = *buf - '0'; - if(speed < 0 || speed >= I2C_SPEED_AMOUNT){ - return ERR; - } - i2c_setup((i2c_speed_t)speed); - } - sendfun->S("I2CSPEED="); sendfun->S(speeds[i2c_curspeed]); N(); - 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(I2Caddress, a)) return ERR; - }else{ - sendfun->S("Wrong number"); N(); - return ERR; - } - }else{ - sendfun->S("Need address"); N(); - return ERR; - } - return OK; -} - -// read sensor's number from `buf`; return -1 if error -static int getsensnum(const char *buf){ - if(!buf || !*buf) return -1; - uint32_t num; - const char *nxt = getnum(buf, &num); - if(!nxt || nxt == buf || num >= N_SENSORS) return -1; - return (int) num; -} - -TRUE_INLINE const char *chaddr(const char *buf){ - uint32_t addr; - const char *nxt = getnum(buf, &addr); - if(nxt && nxt != buf){ - if(addr > 0x7f) return ERR; - I2Caddress = (uint8_t) addr << 1; - int n = getsensnum(nxt); - if(n > -1) mlx_setaddr(n, addr); - }else addr = I2Caddress >> 1; - sendfun->S("I2CADDR="); sendfun->S(uhex2str(addr)); N(); - return NULL; -} - -// read I2C register[s] - only blocking read! (DMA allowable just for config/image reading in main process) -static const char *rdI2C(const char *buf){ - uint32_t N = 0; - const char *nxt = getnum(buf, &N); - if(!nxt || buf == nxt || N > 0xffff) return ERR; - buf = nxt; - uint16_t reg = N, *b16 = NULL; - nxt = getnum(buf, &N); - if(!nxt || buf == nxt || N == 0 || N > I2C_BUFSIZE) return ERR; - if(!(b16 = i2c_read_reg16(I2Caddress, reg, N, 0))) return ERR; - if(N == 1){ - char b[5]; - u16s(*b16, b); - b[4] = 0; - sendfun->S(b); N(); - }else hexdump16(sendfun->S, b16, N); - return NULL; -} - -// read N numbers from buf, @return 0 if wrong or none -TRUE_INLINE uint16_t readNnumbers(const char *buf){ - uint32_t D; - const char *nxt; - uint16_t N = 0; - while((nxt = getnum(buf, &D)) && nxt != buf && N < LOCBUFFSZ){ - buf = nxt; - locBuffer[N++] = (uint16_t) D; - } - return N; -} - -static const char *wrI2C(const char *buf){ - uint16_t N = readNnumbers(buf); - if(N == 0) return ERR; - for(int i = 0; i < N; ++i){ - sendfun->S("byte "); sendfun->S(u2str(i)); - sendfun->S(" :"); sendfun->S(uhex2str(locBuffer[i])); N(); - } - if(!i2c_write(I2Caddress, locBuffer, N)) return ERR; - return OK; -} - -static void dumpfarr(float *arr){ - for(int row = 0; row < 24; ++row){ - for(int col = 0; col < 32; ++col){ - printfl(*arr++, 2); sendfun->P(' '); - } - N(); - } -} -// dump MLX parameters -TRUE_INLINE void dumpparams(const char *buf){ - int N = getsensnum(buf); - if(N < 0){ sendfun->S(ERR); return; } - MLX90640_params *params = mlx_getparams(N); - if(!params){ sendfun->S(ERR); return; } - 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); - sendfun->S("\nKtPTAT="); printfl(params->KtPTAT, 4); - sendfun->S("\nvPTAT25="); printi(params->vPTAT25); - sendfun->S("\nalphaPTAT="); printfl(params->alphaPTAT, 2); - sendfun->S("\ngainEE="); printi(params->gainEE); - sendfun->S("\nPixel offset parameters:\n"); - float *offset = params->offset; - for(int row = 0; row < 24; ++row){ - for(int col = 0; col < 32; ++col){ - printfl(*offset++, 2); sendfun->P(' '); - } - N(); - } - sendfun->S("K_talpha:\n"); - dumpfarr(params->kta); - sendfun->S("Kv: "); - for(int i = 0; i < 4; ++i){ - printfl(params->kv[i], 2); sendfun->P(' '); - } - sendfun->S("\ncpOffset="); - printi(params->cpOffset[0]); sendfun->S(", "); printi(params->cpOffset[1]); - sendfun->S("\ncpKta="); printfl(params->cpKta, 2); - sendfun->S("\ncpKv="); printfl(params->cpKv, 2); - sendfun->S("\ntgc="); printfl(params->tgc, 2); - sendfun->S("\ncpALpha="); printfl(params->cpAlpha[0], 2); - sendfun->S(", "); printfl(params->cpAlpha[1], 2); - sendfun->S("\nKsTa="); printfl(params->KsTa, 2); - sendfun->S("\nAlpha:\n"); - dumpfarr(params->alpha); - sendfun->S("\nCT3="); printfl(params->CT[1], 2); - sendfun->S("\nCT4="); printfl(params->CT[2], 2); - for(int i = 0; i < 4; ++i){ - sendfun->S("\nKsTo"); sendfun->P('0'+i); sendfun->P('='); - printfl(params->KsTo[i], 2); - sendfun->S("\nalphacorr"); sendfun->P('0'+i); sendfun->P('='); - printfl(params->alphacorr[i], 2); - } - N(); -} -// get MLX state -TRUE_INLINE void getst(){ - static const char *states[] = { - [MLX_NOTINIT] = "not init", - [MLX_WAITPARAMS] = "wait parameters DMA read", - [MLX_WAITSUBPAGE] = "wait subpage", - [MLX_READSUBPAGE] = "wait subpage DMA read", - [MLX_RELAX] = "do nothing" - }; - mlx_state_t s = mlx_state(); - sendfun->S("MLXSTATE="); - sendfun->S(states[s]); 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->P('0'+sensno); sendfun->P('='); sendfun->S(u2str(T)); N(); - switch(draw){ - case 0: - dumpIma(img); - break; - case 1: - 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) - while(_2send){ - uint32_t portion = (_2send > 256) ? 256 : _2send; - sendfun->B(d, portion); - _2send -= portion; - d += portion; - } - sendfun->S("ENDIMAGE"); N(); - break; - } - return NULL; - } - } - return ERR; -} - -TRUE_INLINE void listactive(){ - int N = mlx_nactive(); - if(!N){ sendfun->S("No active sensors found!\n"); return; } - uint8_t *ids = mlx_activeids(); - sendfun->S("Found "); sendfun->P('0'+N); - sendfun->S(" active sensors:"); N(); - for(int i = 0; i < N_SENSORS; ++i) - if(ids[i]){ - sendfun->S("SENSID"); - sendfun->S(u2str(i)); sendfun->P('='); - sendfun->S(uhex2str(ids[i] >> 1)); - N(); - } -} - -static void getimt(const char *buf){ - int sensno = getsensnum(buf); - if(sensno > -1){ - 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("\nSKYTEMPERATURE="); sendfun->S(float2str(env.Tsky, 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(); -} - -TRUE_INLINE const char *DAC_chval(const char *buf){ - uint32_t D; - const char *nxt = getnum(buf, &D); - if(!nxt || nxt == buf || D > 4095) return ERR; - DAC1->DHR12R1 = D; - return OK; -} - - -TRUE_INLINE void getADC(){ - sendfun->S("AIN0="); sendfun->S(u2str(getADCval(ADC_AIN0))); - sendfun->S("\nAIN1="); sendfun->S(u2str(getADCval(ADC_AIN1))); - sendfun->S("\nAIN5="); sendfun->S(u2str(getADCval(ADC_AIN5))); - N(); -} - -TRUE_INLINE void getMCUvals(){ - sendfun->S("MCUTEMP="); sendfun->S(float2str(getMCUtemp(), 2)); - sendfun->S("\nMCUVDD="); sendfun->S(float2str(getVdd(), 2)); - N(); -} - -TRUE_INLINE const char* setpwm(const char *buf){ - uint32_t D; - if(!buf || !*buf){ - sendfun->S("PWM1="); sendfun->S(u2str(TIM3->CCR1)); - sendfun->S("\nPWM2="); sendfun->S(u2str(TIM3->CCR2)); - sendfun->S("\nPWM3="); sendfun->S(u2str(TIM3->CCR3)); - sendfun->S("\nPWM4="); sendfun->S(u2str(TIM3->CCR4)); - N(); - return NULL; - } - const char *nxt = getnum(buf, &D); - if(!nxt || nxt == buf || !setPWM(PWM_CH_HEATER, D)) return ERR; - return OK; -} - -/** - * @brief parse_cmd - user string parser - * @param buf - user data - * @param isusb - ==1 to send answer over usb, else send over USART1 - * @return answer OK/ERR or NULL - */ -const char *parse_cmd(char *buf, int sendto){ - if(!buf || !*buf) return NULL; - chsendfun(sendto); - if(buf[1]){ - switch(*buf++){ // "long" commands - case 'a': - return chhwaddr(buf); - case 'd': - return drawimg(buf, 1); - case 'g': - return drawimg(buf, 2); - case 'i': - return setupI2C(buf); - case 'm': - return drawimg(buf, 0); - case 't': - getimt(buf); return NULL; - case 'D': - dumpparams(buf); - return NULL; - break; - case 'I': - buf = omit_spaces(buf); - switch(*buf++){ - case 'a': - return chaddr(buf); - case 'r': - return rdI2C(buf); - case 'w': - return wrI2C(buf); - case 's': - i2c_init_scan_mode(); - return OK; - default: - return ERR; - } - break; - case 'O': - return DAC_chval(buf); - case 'P': - return setpwm(buf); - case 'U': - if(sendto == SEND_USB) chsendfun(SEND_USART); - else chsendfun(SEND_USB); - if(sendfun->S(buf) && N()) return OK; - return ERR; - default: - return ERR; - } - } - switch(*buf){ // "short" (one letter) commands - case 'A': - getADC(); - break; - case 'c': - mlx_continue(); return OK; - break; - case 'i': return setupI2C(NULL); // current settings - case 'l': - listactive(); - break; - case 'p': - mlx_pause(); return OK; - 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 'M': - getMCUvals(); - break; - case 'P': - return setpwm(NULL); - case 'R': - NVIC_SystemReset(); - break; - case 'T': - sendfun->S("T="); sendfun->S(u2str(Tms)); N(); - break; - case '?': // help - case 'h': - case 'H': - sendfun->S(helpstring); - break; - default: - return ERR; - break; - } - return NULL; -} - -// dump image as temperature matrix -void dumpIma(const fp_t im[MLX_PIXNO]){ - for(int row = 0; row < MLX_H; ++row){ - for(int col = 0; col < MLX_W; ++col){ - printfl(*im++, 1); - sendfun->P(' '); - } - N(); - } -} - -#define GRAY_LEVELS (16) -// 16-level character set ordered by fill percentage (provided by user) -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 - fp_t min_val = im[0], max_val = im[0]; - const fp_t *iptr = im; - for(int row = 0; row < MLX_H; ++row){ - for(int col = 0; col < MLX_W; ++col){ - fp_t cur = *iptr++; - if(cur < min_val) min_val = cur; - else if(cur > max_val) max_val = cur; - } - } - fp_t range = max_val - min_val; - sendfun->S("RANGE="); sendfun->S(float2str(range, 3)); - sendfun->S("\nMIN="); sendfun->S(float2str(min_val, 3)); - sendfun->S("\nMAX="); sendfun->S(float2str(max_val, 3)); N(); - if(fabsf(range) < 0.001) range = 1.; // solid fill -> blank - // Generate and print ASCII art - iptr = im; - 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); - // Ensure we stay within bounds - if(index < 0) index = 0; - else if(index > (GRAY_LEVELS-1)) index = (GRAY_LEVELS-1); - sendfun->P(CHARS_16[index]); - } - N(); - } - N(); -} - diff --git a/F3:F303/MLX90640-allsky/proto.h b/F3:F303/MLX90640-allsky/proto.h deleted file mode 100644 index aab3d44..0000000 --- a/F3:F303/MLX90640-allsky/proto.h +++ /dev/null @@ -1,30 +0,0 @@ -/* - * 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 - -extern const char *const Timage; - -#define SEND_USB (1) -#define SEND_USART (0) - -extern uint8_t cartoon; -void chsendfun(int sendto); -const char *parse_cmd(char *buf, int sendto); -void dumpIma(const fp_t im[MLX_PIXNO]); -void drawIma(const fp_t im[MLX_PIXNO]); diff --git a/F3:F303/MLX90640-allsky/version.inc b/F3:F303/MLX90640-allsky/version.inc index a8538a6..f7c8d90 100644 --- a/F3:F303/MLX90640-allsky/version.inc +++ b/F3:F303/MLX90640-allsky/version.inc @@ -1,2 +1,2 @@ -#define BUILD_NUMBER "45" -#define BUILD_DATE "2026-05-01" +#define BUILD_NUMBER "65" +#define BUILD_DATE "2026-05-06"