Compare commits

..

23 Commits

Author SHA1 Message Date
Edward Emelianov
aeb5cce6ee refresh my hash 2026-03-07 18:23:37 +03:00
Edward Emelianov
055d7f7f59 fixed, tested 2026-03-07 17:45:48 +03:00
Edward Emelianov
d18ab25440 example of helpcmds.in 2026-03-07 15:12:20 +03:00
Edward Emelianov
524a39b8a2 fixed for new libusefull_macros 2026-03-07 15:08:35 +03:00
Edward Emelianov
4a09776d27 ADD BMP580 2025-12-08 20:57:40 +03:00
Edward Emelianov
ebc8144271 some little joke 2025-10-29 23:39:05 +03:00
Edward Emelianov
ca7a53ee1c ESP simplest server 2025-10-17 00:32:56 +03:00
Edward Emelianov
19fbf250a9 Add readme 2025-10-14 17:50:53 +03:00
Edward Emelianov
ea0de3c904 create library and make two examples of usage 2025-10-14 00:17:07 +03:00
Edward Emelianov
7a37dc0d2f Add BMP280/BME280 (tested only on BME); add SHT3x (bug: reads only once) 2025-10-13 00:46:21 +03:00
Edward Emelianov
b0097d5ee6 Made refactoring for multi-sensor ability (N same sensors); check AHT21. I cry: I need this pointer in C!!! 2025-10-12 01:49:06 +03:00
Edward Emelianov
7f85861d6c Add SI7005 2025-10-11 21:10:55 +03:00
Edward Emelianov
675b02f1a3 add AHT10 2025-10-11 01:17:59 +03:00
Edward Emelianov
5c424fc617 start from BMP180 2025-10-10 23:40:09 +03:00
Edward Emelianov
a893589a9c found my misprint, now tests are OK. Next is trying on MCU. 2025-09-10 21:10:16 +03:00
Edward Emelianov
dfdc4222fd add MLX test; not fully works yet, try to find my error in calculations due to datashit 2025-09-09 23:06:59 +03:00
Edward Emelianov
664566aa47 thermal 2025-09-04 23:08:27 +03:00
Edward Emelianov
9211cfaf43 add deepseek 2025-09-01 22:19:23 +03:00
5f4e06bb7b some fixes with dictionary; add aliases 2025-07-17 17:06:13 +03:00
0074eac5d8 add server for realtime monitoring and parameters changing 2025-07-15 17:37:45 +03:00
c35f38ccd6 pre-alpha 2025-07-11 11:13:40 +03:00
01abbf4a25 added "relay type 1" for non-standard 4-relay board (based on STM8S003) 2025-06-05 11:10:57 +03:00
1a3dba4676 add simplest web interface 2025-05-26 17:00:27 +03:00
115 changed files with 8541 additions and 193 deletions

34
ESP8266/CMakeLists.txt Normal file
View File

@@ -0,0 +1,34 @@
cmake_minimum_required(VERSION 3.16)
set(PROJ esp8266)
project(${PROJ} LANGUAGES C)
set(CMAKE_COLOR_MAKEFILE ON)
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
option(DEBUG "Compile in debug mode" OFF)
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -Og -g3 -ggdb -Werror")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto=auto")
if(DEBUG)
add_definitions(-DEBUG)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_VERBOSE_MAKEFILE "ON")
else()
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto=auto")
set(CMAKE_BUILD_TYPE RELEASE)
endif()
find_package(PkgConfig REQUIRED)
pkg_check_modules(${PROJ} REQUIRED usefull_macros)
add_executable(esp8266 ${SOURCES})
target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES} -lm)
include(GNUInstallDirs)
install(TARGETS ${PROJ}
# LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
)

2
ESP8266/Readme Normal file
View File

@@ -0,0 +1,2 @@
Simplest server based on AT-commands using ESP8266.
I tried to make code as nearest to MCU as possible.

170
ESP8266/esp8266.c Normal file
View File

@@ -0,0 +1,170 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <usefull_macros.h>
#include <stdio.h>
#include "esp8266.h"
#include "serial.h"
#define ESPMSG(x) do{if(!serial_send_msg(x)) return FALSE;}while(0)
// sometimes ESP hangs with message "WIFI GOT IP" and I can do nothing except waiting
const char *msgptr = NULL; // pointer to received message
static int receivedlen = 0;
// check module working
int esp_check(){
// try simplest check for three times
for(int i = 0; i < 3; ++i) if( ANS_OK == serial_sendwans("AT")) break;
//esp_close();
// now try next even if no answer for "AT"
if( ANS_OK == serial_sendwans("ATE0") // echo off
&& ANS_OK == serial_sendwans("AT+CWMODE_CUR=1") // station mode
) return TRUE;
return FALSE;
}
// check established wifi connection
esp_cipstatus_t esp_cipstatus(){
if(ANS_OK != serial_sendwans("AT+CIPSTATUS")) return ESP_CIP_FAILED;
char *l = serial_getline(NULL, NULL);
const char *n = NULL;
if(!l || !(n = serial_tidx(l, "STATUS:"))) return ESP_CIP_FAILED;
return (esp_cipstatus_t)serial_s2i(n);
}
// connect to AP
int esp_connect(const char *SSID, const char *pass){
ESPMSG("AT+CWJAP_CUR=\"");
ESPMSG(SSID);
ESPMSG("\",\"");
ESPMSG(pass);
if(ANS_OK != serial_sendwans("\"")) return FALSE;
return TRUE;
}
// just send AT+CIFSR
int esp_myip(){
return (ANS_OK == serial_sendwans("AT+CIFSR"));
}
// start server on given port
int esp_start_server(const char *port){
if(ANS_OK != serial_sendwans("AT+CIPMUX=1")){ // can't start server without mux
// what if already ready?
char *x = serial_getline(NULL, NULL);
if(!x || !serial_tidx(x, "link is builded")) return FALSE;
}
ESPMSG("AT+CIPSERVER=1,");
if(ANS_OK != serial_sendwans(port)) return FALSE;
return TRUE;
}
// stop server
int ep_stop_server(){
return (ANS_OK == serial_sendwans("AT+CIPSERVER=0"));
}
// next (or only) line of received data
const char *esp_msgline(){
DBG("receivedlen=%d", receivedlen);
if(msgptr){
const char *p = msgptr;
msgptr = NULL;
return p;
}
if(receivedlen < 1) return NULL;
int l, d;
const char *got = serial_getline(&l, &d);
receivedlen -= l + d;
return got;
}
// process connection/disconnection/messages
// fd - file descriptor of opened/closed connections
esp_clientstat_t esp_process(int *fd){
msgptr = NULL;
receivedlen = 0;
int l, d;
char *got = serial_getline(&l, &d);
if(!got) return ESP_CLT_IDLE;
const char *x = serial_tidx(got, "+IPD,");
if(x){
if(fd) *fd = serial_s2i(x);
x = serial_tidx(x, ",");
if(!x) return ESP_CLT_ERROR;
int r = serial_s2i(x);
x = serial_tidx(x, ":");
if(!x) return ESP_CLT_ERROR;
receivedlen = r - d - (l - (x-got)); // this is a rest of data (if any)
msgptr = x;
return ESP_CLT_GETMESSAGE;
}
// check for CONNECT/CLOSE
if((x = serial_tidx(got, ",CONNECT"))){
if(fd) *fd = serial_s2i(got);
return ESP_CLT_CONNECTED;
}
if((x = serial_tidx(got, ",CLOSED"))){
if(fd) *fd = serial_s2i(got);
return ESP_CLT_DISCONNECTED;
}
DBG("Unknown message: '%s'", got);
return ESP_CLT_IDLE;
}
int esp_send(int fd, const char *msg){
DBG("send '%s' to %d", msg, fd);
ESPMSG("AT+CIPSENDEX=");
if(!serial_putchar('0' + fd)) return FALSE;
if(ANS_OK != serial_sendwans(",2048")) return FALSE;
int got = 0;
// try several times
for(int i = 0; i < 10; ++i){
got = serial_getch();
if(got == '>') break;
}
if(got != '>'){
DBG("Didn't found '>'");
serial_send_msg("\\0"); // terminate message
serial_clr();
return FALSE; // go into terminal mode
}
serial_clr(); // remove space after '>'
ESPMSG(msg);
if(ANS_OK == serial_sendwans("\\0")) return TRUE;
DBG("Didn't sent");
return FALSE;
}
void esp_reset(){
serial_sendwans("AT+RST");
}
void esp_close(){
serial_sendwans("AT+CIPMUX=0");
serial_sendwans("AT+CIPCLOSE");
}
int esp_listAP(){
if(ANS_OK == serial_sendwans("AT+CWLAP")) return TRUE;
return FALSE;
}

53
ESP8266/esp8266.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
// really only 5 clients allowed
#define ESP_MAX_CLT_NUMBER 8
typedef enum{
ESP_CIP_0,
ESP_CIP_1,
ESP_CIP_GOTIP,
ESP_CIP_CONNECTED,
ESP_CIP_DISCONNECTED,
ESP_CIP_FAILED
} esp_cipstatus_t;
typedef enum{
ESP_CLT_IDLE, // nothing happened
ESP_CLT_CONNECTED, // new client connected
ESP_CLT_DISCONNECTED, // disconnected
ESP_CLT_ERROR, // error writing or other
ESP_CLT_GETMESSAGE, // receive message from client
ESP_CLT_OK, // sent OK
} esp_clientstat_t;
int esp_check();
void esp_close();
void esp_reset();
esp_cipstatus_t esp_cipstatus();
int esp_connect(const char *SSID, const char *pass);
int esp_myip();
int esp_start_server(const char *port);
int ep_stop_server();
esp_clientstat_t esp_process(int *fd);
int esp_send(int fd, const char *msg);
const char *esp_msgline();
int esp_listAP();

182
ESP8266/main.c Normal file
View File

@@ -0,0 +1,182 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "esp8266.h"
#include "serial.h"
static struct{
int help;
char *serialdev;
char *SSID;
char *SSpass;
int speed;
char *port;
int reset;
int list;
} G = {
.speed = 115200,
.serialdev = "/dev/ttyUSB0",
.port = "1111",
};
static sl_option_t options[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.serialdev), "serial device path (default: /dev/ttyUSB0)"},
{"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&G.speed), "serial speed"},
{"ssid", NEED_ARG, NULL, 0, arg_string, APTR(&G.SSID), "SSID to connect"},
{"pass", NEED_ARG, NULL, 0, arg_string, APTR(&G.SSpass), "SSID password"},
{"port", NEED_ARG, NULL, 'p', arg_string, APTR(&G.port), "servr port (default: 1111)"},
{"reset", NO_ARGS, NULL, 0, arg_int, APTR(&G.reset), "reset ESP"},
{"list", NO_ARGS, NULL, 'l', arg_int, APTR(&G.list), "list available APs"},
end_option
};
void signals(int signo){
esp_close();
serial_close();
exit(signo);
}
static esp_clientstat_t parse_message(int fd){
// this isn't a good idea to send data while not received all; so we use buffer
static sl_ringbuffer_t *rb = NULL;
if(!rb) rb = sl_RB_new(4096);
const char *msg = NULL;
char buf[4096];
while((msg = esp_msgline())){
printf("Received line from fd %d: %s\n", fd, msg);
// do something with this data
if(0 == strcmp(msg, "help")){
sl_RB_writestr(rb, "Hey, we don't have any help yet, try `time`\n");
}else if(0 == strcmp(msg, "time")){
snprintf(buf, 64, "TIME=%.3f\n", sl_dtime());
sl_RB_writestr(rb, buf);
}else{
if(*msg){
snprintf(buf, 127, "Part of your message: _%s_\n", msg);
sl_RB_writestr(rb, buf);
}
}
}
size_t L = 4094;
char *b = buf;
while(sl_RB_readline(rb, b, L) > 0){ // there's a bug in my library! Need to fix (sl_RB_readline returns NOT an amount of bytes)
size_t got = strlen(b);
L -= ++got;
b += got;
b[-1] = '\n';
}
if(L == 4094) return ESP_CLT_OK; // nothing to send
return esp_send(fd, buf) ? ESP_CLT_OK : ESP_CLT_ERROR;
}
static void processing(){
uint8_t connected[ESP_MAX_CLT_NUMBER] = {0};
esp_clientstat_t oldstat[ESP_MAX_CLT_NUMBER] = {0};
double T0 = sl_dtime();
int N = -1;
void chkerr(){
if(oldstat[N] == ESP_CLT_ERROR){
DBG("error again -> turn off");
connected[N] = 0;
}
}
while(1){
esp_clientstat_t s = esp_process(&N);
if(N > -1 && N < ESP_MAX_CLT_NUMBER){ // parsing
//if(s == ESP_CLT_IDLE){ usleep(1000); continue; }
switch(s){
case ESP_CLT_CONNECTED:
connected[N] = 1;
green("Connection on fd=%d\n", N);
break;
case ESP_CLT_DISCONNECTED:
connected[N] = 0;
green("fd=%d disconnected\n", N);
break;
case ESP_CLT_ERROR:
DBG("Error from %d", N);
chkerr();
break;
case ESP_CLT_GETMESSAGE:
DBG("%d have message", N);
s = parse_message(N);
if(s == ESP_CLT_ERROR) chkerr();
break;
default: break;
}
oldstat[N] = s;
}
// and here we can do something for all
int R = rand() % 5000;
// example of `broadcast` message
if(R < 2){
DBG("Send 'broadcasting' message");
char buf[64];
snprintf(buf, 63, "Hello, there's %.2f seconds from start\n", sl_dtime() - T0);
for(int i = 0; i < ESP_MAX_CLT_NUMBER; ++i){
if(!connected[i]) continue;
if(ESP_CLT_ERROR == esp_send(i, buf)) chkerr();
}
}
usleep(1000);
}
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, options);
if(G.help) sl_showhelp(-1, options);
if(!serial_init(G.serialdev, G.speed)) ERRX("Can't open %s at speed %d", G.serialdev, G.speed);
if(G.reset){
red("Resetting, please wait!\n");
esp_reset();
usleep(500000);
DBG("Get buff");
char *str;
while((str = serial_getline(NULL, NULL))) if(*str) printf("\t%s\n", str);
signals(0);
}
if(!esp_check()) ERRX("No answer from ESP");
if(G.list){
if(esp_listAP()){
green("Available wifi:\n");
char *str;
while(str = serial_getline(NULL, NULL)) printf("\t%s\n", str);
}else WARNX("Error listing");
}
esp_cipstatus_t st = esp_cipstatus();
if(st != ESP_CIP_GOTIP && st != ESP_CIP_CONNECTED){
DBG("Need to connect");
if(!esp_connect(G.SSID, G.SSpass)) ERRX("Can't connect");
}
if(esp_myip()){
green("Connected\n");
char *str;
while(str = serial_getline(NULL, NULL)) if(*str) printf("\t%s\n", str);
}
ep_stop_server(); // stop just in case
if(!esp_start_server(G.port)) ERRX("Can't start server");
processing();
serial_close();
return 0;
}

178
ESP8266/serial.c Normal file
View File

@@ -0,0 +1,178 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "serial.h"
// ALL functions here aren't thread-independent, as you can't use the same line simultaneously
static sl_tty_t *device = NULL;
static double timeout = 30.; // timeout, s
static sl_ringbuffer_t *rbin = NULL; // input ring buffer
// read all incoming
void serial_clr(){
if(!device) return;
while(sl_tty_read(device) > 0);
}
int serial_init(char *path, int speed){
device = sl_tty_new(path, speed, 256);
if(!device) return FALSE;
device = sl_tty_open(device, 1);
if(!device) return FALSE;
rbin = sl_RB_new(4096);
if(!rbin){
sl_tty_close(&device);
return FALSE;
}
sl_tty_tmout(1000); // set select() timeout to 1ms
// clear buffer
serial_clr();
return TRUE;
}
void serial_close(){
if(device) sl_tty_close(&device);
if(rbin) sl_RB_delete(&rbin);
}
int serial_set_timeout(double tms){
if(tms < 0.1) return FALSE;
timeout = tms;
return TRUE;
}
// send messages over serial,
// without EOL:
int serial_send_msg(const char *msg){
if(!msg || !device) return FALSE;
int l = strlen(msg);
DBG("Write message `%s` (%d bytes)", msg, l);
if(sl_tty_write(device->comfd, msg, l)) return FALSE;
return TRUE;
}
// and with:
int serial_send_cmd(const char *msg){
if(!msg || !device) return FALSE;
if(!serial_send_msg(msg)) return FALSE;
DBG("Write EOL");
if(sl_tty_write(device->comfd, "\r\n", 2)) return FALSE;
return TRUE;
}
int serial_putchar(char ch){
if(!device) return FALSE;
if(sl_tty_write(device->comfd, &ch, 1)) return FALSE;
return TRUE;
}
static void fillinbuff(){
ssize_t got;
while((got = sl_tty_read(device))){
if(got < 0){
WARNX("Serial device disconnected!");
serial_close();
}else if(got){
if((size_t)got != sl_RB_write(rbin, (const uint8_t*)device->buf, got)){
WARNX("Rinbguffer overflow?");
sl_RB_clearbuf(rbin);
return;
}
}
}
}
// read one string line from serial
// @arg deleted - N symbols deleted from rest of string (1 in case of '\n' and 2 in case of "\r\n")
// @arg len - strlen of data
char *serial_getline(int *len, int *deleted){
if(!device) return NULL;
static char buf[BUFSIZ];
fillinbuff();
// read old records
if(!sl_RB_readline(rbin, buf, BUFSIZ-1)) return NULL;
// remove trailing '\r'
int l = strlen(buf), d = 1;
if(l > -1 && buf[l - 1] == '\r'){
++d;
buf[--l] = 0;
}
if(deleted) *deleted = d;
if(len) *len = l;
DBG("read: '%s'", buf);
return buf;
}
// get symbol
int serial_getch(){
if(!device) return -1;
fillinbuff();
char C;
DBG("rb size: %zd", sl_RB_datalen(rbin));
size_t rd = sl_RB_read(rbin, (uint8_t*)&C, 1);
DBG("got %zd : '%c'", rd, C);
if(rd != 1) return -1;
//if(1 != sl_RB_read(rbin, (uint8_t*)&C, 1)) return -1;
return (int) C;
}
serial_ans_t serial_sendwans(const char *msg){
if(!msg || !device) return ANS_FAILED;
if(!serial_send_cmd(msg)) return ANS_FAILED;
double t0 = sl_dtime();
int ret = ANS_FAILED;
while(sl_dtime() - t0 < timeout && device){
char *ans = NULL;
if(!(ans = serial_getline(NULL, NULL))){ usleep(500); continue; }
DBG("Get line: '%s' (%zd bytes)", ans, strlen(ans));
if(!*ans) continue; // empty string
if(strcmp(ans, "OK") == 0 || strcmp(ans, "SEND OK") == 0){ ret = ANS_OK; goto rtn; }
if(strcmp(ans, "ERROR") == 0){ ret = ANS_ERR; goto rtn; }
if(strcmp(ans, "FAIL") == 0){ ret = ANS_FAILED; goto rtn; }
DBG("Return '%s' into buff", ans);
sl_RB_writestr(rbin, ans); // put other data into ringbuffer for further processing
sl_RB_putbyte(rbin, '\n');
}
rtn:
DBG("proc time: %g", sl_dtime() - t0);
return ret;
}
// return NULL if `s` don't contain `t`, else return next symbol in `s`
const char *serial_tidx(const char *s, const char *t){
if(!s) return NULL;
DBG("check '%s' for '%s'", s, t);
int pos = 0;
if(t){
const char *sub = strstr(s, t);
if(!sub) return NULL;
int l = strlen(t);
pos = (sub - s) + l;
}
DBG("pos = %d", pos);
return s + pos;
}
int serial_s2i(const char *s){
if(!s) return -1;
DBG("conv '%s' to %d", s, atoi(s));
return atoi(s);
}

40
ESP8266/serial.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
typedef enum{
ANS_FAILED,
ANS_OK,
ANS_ERR
} serial_ans_t;
void serial_clr();
int serial_set_timeout(double tms);
int serial_init(char *path, int speed);
void serial_close();
int serial_send_msg(const char *msg);
int serial_send_cmd(const char *msg);
int serial_putchar(char ch);
serial_ans_t serial_sendwans(const char *msg);
char *serial_getline(int *len, int *deleted);
int serial_getch();
// conversion functions for ESP
const char *serial_tidx(const char *s, const char *t);
int serial_s2i(const char *s);

247
I2Csensors/BMP180.c Normal file
View File

@@ -0,0 +1,247 @@
/*
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "BMP180.h"
#include "i2c.h"
#include "sensors_private.h"
enum{
BMP180_OVERS_1 = 0, // oversampling is off
BMP180_OVERS_2 = 1,
BMP180_OVERS_4 = 2,
BMP180_OVERS_8 = 3,
BMP180_OVERSMAX = 3
};
#define BMP180_CHIP_ID 0x55
/**
* BMP180 registers
*/
#define BMP180_REG_OXLSB (0xF8)
#define BMP180_REG_OLSB (0xF7)
#define BMP180_REG_OMSB (0xF6)
#define BMP180_REG_OUT (BMP180_REG_OMSB)
#define BMP180_REG_CTRLMEAS (0xF4)
#define BMP180_REG_SOFTRESET (0xE0)
#define BMP180_REG_ID (0xD0)
#define BMP180_REG_CALIB (0xAA)
// shift for oversampling
#define BMP180_CTRLM_OSS_SHIFT (6)
// start measurement
#define BMP180_CTRLM_SCO (1<<5)
// measurements of P flag
#define BMP180_CTRLM_PRES (1<<4)
// write it to BMP180_REG_SOFTRESET for soft reset
#define BMP180_SOFTRESET_VAL (0xB6)
// start measurement of T/P
#define BMP180_READ_T (0x0E)
#define BMP180_READ_P (0x14)
// mind that user can't change this
static const uint8_t bmp180_os = BMP180_OVERSMAX;
typedef struct {
int16_t AC1;
int16_t AC2;
int16_t AC3;
uint16_t AC4;
uint16_t AC5;
uint16_t AC6;
int16_t B1;
int16_t B2;
int16_t MB;
int16_t MC;
int16_t MD;
int32_t MCfix;
int32_t AC1_fix;
int32_t Tuncomp; // uncompensated T value
uint8_t calibrated; // ==1 if calibration done
} __attribute__ ((packed)) CaliData_t;
/*
static void BMP180_setOS(BMP180_oversampling os){
bmp180_os = os & 0x03;
}*/
// get compensation data, return 1 if OK
static int readcompdata(sensor_t *s){
FNAME();
if(!s->privdata){
s->privdata = calloc(1, sizeof(CaliData_t));
DBG("ALLOCA");
}
if(!i2c_read_data8(BMP180_REG_CALIB, sizeof(CaliData_t), (uint8_t*)s->privdata)) return FALSE;
CaliData_t *CaliData = (CaliData_t*)s->privdata;
// convert big-endian into little-endian
uint16_t *arr = (uint16_t*)(s->privdata);
for(int i = 0; i < 11; ++i) arr[i] = __builtin_bswap16(arr[i]);
// prepare for further calculations
CaliData->MCfix = CaliData->MC << 11;
CaliData->AC1_fix = CaliData->AC1 << 2;
CaliData->calibrated = 1;
DBG("Calibration rdy");
return TRUE;
}
// do a soft-reset procedure
static int BMP180_reset(sensor_t _U_ *s){
if(!i2c_write_reg8(BMP180_REG_SOFTRESET, BMP180_SOFTRESET_VAL)){
DBG("Can't reset\n");
return FALSE;
}
return TRUE;
}
// read compensation data & write registers
static int BMP180_init(sensor_t *s){
s->status = SENS_NOTINIT;
if(!BMP180_reset(s)) return FALSE;
uint8_t devID;
if(!i2c_read_reg8(BMP180_REG_ID, &devID)){
DBG("Can't read BMP180_REG_ID");
return FALSE;
}
DBG("Got device ID: 0x%02x", devID);
if(devID != BMP180_CHIP_ID){
DBG("Not BMP180\n");
return FALSE;
}
if(!readcompdata(s)){
DBG("Can't read calibration data\n");
return FALSE;
}else{
#ifdef EBUG
CaliData_t *CaliData = (CaliData_t*)s->privdata;
#endif
DBG("AC1=%d, AC2=%d, AC3=%d, AC4=%u, AC5=%u, AC6=%u", CaliData->AC1, CaliData->AC2, CaliData->AC3, CaliData->AC4, CaliData->AC5, CaliData->AC6);
DBG("B1=%d, B2=%d", CaliData->B1, CaliData->B2);
DBG("MB=%d, MC=%d, MD=%d", CaliData->MB, CaliData->MC, CaliData->MD);
}
s->status = SENS_RELAX;
return TRUE;
}
// start measurement, @return 1 if all OK
static int BMP180_start(sensor_t *s){
if(!s->privdata || s->status == SENS_BUSY || ((CaliData_t*)s->privdata)->calibrated == 0) return FALSE;
uint8_t reg = BMP180_READ_T | BMP180_CTRLM_SCO;
if(!i2c_write_reg8(BMP180_REG_CTRLMEAS, reg)){
s->status = SENS_ERR;
DBG("Can't write CTRL reg\n");
return FALSE;
}
s->status = SENS_BUSY;
return TRUE;
}
// calculate T degC and P in Pa
static inline void compens(sensor_t *s, uint32_t Pval){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
// T:
int32_t X1 = ((CaliData->Tuncomp - CaliData->AC6)*CaliData->AC5) >> 15;
int32_t X2 = CaliData->MCfix / (X1 + CaliData->MD);
int32_t B5 = X1 + X2;
s->data.T = (B5 + 8.) / 160.;
// P:
int32_t B6 = B5 - 4000;
X1 = (CaliData->B2 * ((B6*B6) >> 12)) >> 11;
X2 = (CaliData->AC2 * B6) >> 11;
int32_t X3 = X1 + X2;
int32_t B3 = (((CaliData->AC1_fix + X3) << bmp180_os) + 2) >> 2;
X1 = (CaliData->AC3 * B6) >> 13;
X2 = (CaliData->B1 * ((B6 * B6) >> 12)) >> 16;
X3 = ((X1 + X2) + 2) >> 2;
uint32_t B4 = (CaliData->AC4 * (uint32_t) (X3 + 32768)) >> 15;
uint32_t B7 = (uint32_t)((int32_t)Pval - B3) * (50000 >> bmp180_os);
int32_t p = 0;
if(B7 < 0x80000000){
p = (B7 << 1) / B4;
}else{
p = (B7 / B4) << 1;
}
X1 = p >> 8;
X1 *= X1;
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) / 65536;
s->data.P = (p + ((X1 + X2 + 3791) / 16)) / 100.; // convert to hPa
}
static sensor_status_t BMP180_process(sensor_t *s){
uint8_t reg, stat;
uint8_t uncomp_data[3];
CaliData_t *CaliData = (CaliData_t*)s->privdata;
if(s->status != SENS_BUSY) goto ret;
if(!i2c_read_reg8(BMP180_REG_CTRLMEAS, &stat)){ s->status = SENS_ERR; goto ret; }
DBG("stat=0x%02X", stat);
if(stat & BMP180_CTRLM_SCO) goto ret; // still measure
if((stat & BMP180_CTRLM_PRES) == 0){ // wait for temperature
// get uncompensated data
DBG("Read uncompensated T\n");
if(!i2c_read_data8(BMP180_REG_OUT, 2, uncomp_data)){
s->status = SENS_ERR;
goto ret;
}
CaliData->Tuncomp = uncomp_data[0] << 8 | uncomp_data[1];
DBG("Tuncomp=%d, Start P measuring\n", CaliData->Tuncomp);
reg = BMP180_READ_P | BMP180_CTRLM_SCO | (bmp180_os << BMP180_CTRLM_OSS_SHIFT);
if(!i2c_write_reg8(BMP180_REG_CTRLMEAS, reg)){
s->status = SENS_ERR;
goto ret;
}
}else{ // wait for pressure
DBG("Read uncompensated P\n");
if(!i2c_read_data8(BMP180_REG_OUT, 3, uncomp_data)){
s->status = SENS_ERR;
goto ret;
}
uint32_t Pval = uncomp_data[0] << 16 | uncomp_data[1] << 8 | uncomp_data[2];
Pval >>= (8 - bmp180_os);
DBG("Puncomp=%d", Pval);
// calculate compensated values
compens(s, Pval);
DBG("All data ready\n");
s->status = SENS_RDY; // data ready
}
ret:
return s->status;
}
static sensor_props_t BMP180_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .P = 1};
return p;
}
static int s_heater(sensor_t _U_ *s, int _U_ on){
return FALSE;
}
sensor_t BMP180 = {
.name = "BMP180",
.address = 0x77,
.status = SENS_NOTINIT,
.init = BMP180_init,
.start = BMP180_start,
.heater = s_heater,
.process = BMP180_process,
.properties = BMP180_props,
};

21
I2Csensors/BMP180.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t BMP180;

385
I2Csensors/BMP280.c Normal file
View File

@@ -0,0 +1,385 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "BMP280.h"
#include "i2c.h"
#include "sensors_private.h"
/**
* BMP280 registers
*/
#define BMP280_REG_HUM_LSB 0xFE
#define BMP280_REG_HUM_MSB 0xFD
#define BMP280_REG_HUM (BMP280_REG_HUM_MSB)
#define BMP280_REG_TEMP_XLSB 0xFC /* bits: 7-4 */
#define BMP280_REG_TEMP_LSB 0xFB
#define BMP280_REG_TEMP_MSB 0xFA
#define BMP280_REG_TEMP (BMP280_REG_TEMP_MSB)
#define BMP280_REG_PRESS_XLSB 0xF9 /* bits: 7-4 */
#define BMP280_REG_PRESS_LSB 0xF8
#define BMP280_REG_PRESS_MSB 0xF7
#define BMP280_REG_PRESSURE (BMP280_REG_PRESS_MSB)
#define BMP280_REG_ALLDATA (BMP280_REG_PRESS_MSB) // all data: P, T & H
#define BMP280_REG_CONFIG 0xF5 /* bits: 7-5 t_sb; 4-2 filter; 0 spi3w_en */
#define BMP280_REG_CTRL 0xF4 /* bits: 7-5 osrs_t; 4-2 osrs_p; 1-0 mode */
#define BMP280_REG_STATUS 0xF3 /* bits: 3 measuring; 0 im_update */
#define BMP280_STATUS_MSRNG (1<<3) // measuring flag
#define BMP280_STATUS_UPDATE (1<<0) // update flag
#define BMP280_REG_CTRL_HUM 0xF2 /* bits: 2-0 osrs_h; */
#define BMP280_REG_RESET 0xE0
#define BMP280_RESET_VALUE 0xB6
#define BMP280_REG_ID 0xD0
#define BMP280_REG_CALIBA 0x88
#define BMP280_CALIBA_SIZE (26) // 26 bytes of calibration registers sequence from 0x88 to 0xa1
#define BMP280_CALIBB_SIZE (7) // 7 bytes of calibration registers sequence from 0xe1 to 0xe7
#define BMP280_REG_CALIB_H1 0xA1 // dig_H1
#define BMP280_REG_CALIBB 0xE1
#define BMP280_MODE_FORSED (1) // force single measurement
#define BMP280_MODE_NORMAL (3) // run continuosly
#define BMP280_CHIP_ID 0x58
#define BME280_CHIP_ID 0x60
typedef enum{ // K for filtering: next = [prev*(k-1) + data_ADC]/k
BMP280_FILTER_OFF = 0, // k=1, no filtering
BMP280_FILTER_2 = 1, // k=2, 2 samples to reach >75% of data_ADC
BMP280_FILTER_4 = 2, // k=4, 5 samples
BMP280_FILTER_8 = 3, // k=8, 11 samples
BMP280_FILTER_16 = 4, // k=16, 22 samples
} BMP280_Filter;
typedef enum{ // Number of oversampling
BMP280_NOMEASUR = 0,
BMP280_OVERS1 = 1,
BMP280_OVERS2 = 2,
BMP280_OVERS4 = 3,
BMP280_OVERS8 = 4,
BMP280_OVERS16 = 5,
} BMP280_Oversampling;
typedef struct{
BMP280_Filter filter; // filtering
BMP280_Oversampling p_os; // oversampling for pressure
BMP280_Oversampling t_os; // -//- temperature
BMP280_Oversampling h_os; // -//- humidity
uint8_t ID; // identificator
uint8_t regctl; // control register base value [(params.t_os << 5) | (params.p_os << 2)]
} BPM280_params_t;
// default parameters for initialized s->privdata
static const BPM280_params_t defparams = {
.filter = BMP280_FILTER_4,
.p_os = BMP280_OVERS16,
.t_os = BMP280_OVERS16,
.h_os = BMP280_OVERS16,
.ID = 0
};
typedef struct {
// temperature
uint16_t dig_T1; // 0x88 (LSB), 0x98 (MSB)
int16_t dig_T2; // ...
int16_t dig_T3;
// pressure
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9; // 0x9e, 0x9f
// humidity (partially calculated from EEE struct)
uint8_t unused; // 0xA0
uint8_t dig_H1; // 0xA1
int16_t dig_H2; // 0xE1...
uint8_t dig_H3; // only from EEE
uint16_t dig_H4;
uint16_t dig_H5;
int8_t dig_H6;
// calibration done
uint8_t calibrated;
// parameters
BPM280_params_t params;
} __attribute__ ((packed)) CaliData_t;
/*
// setters for `params`
void BMP280_setfilter(sensor_t *s, BMP280_Filter f){
((CaliData_t*)s->privdata)->params.filter = f;
}
void BMP280_setOSt(sensor_t *s, BMP280_Oversampling os){
((CaliData_t*)s->privdata)->params.t_os = os;
}
void BMP280_setOSp(sensor_t *s, BMP280_Oversampling os){
((CaliData_t*)s->privdata)->params.p_os = os;
}
void BMP280_setOSh(sensor_t *s, BMP280_Oversampling os){
((CaliData_t*)s->privdata)->params.h_os = os;
}*/
// get compensation data, return 1 if OK
static int readcompdata(sensor_t *s){
FNAME();
CaliData_t *CaliData = (CaliData_t*)s->privdata;
if(!i2c_read_data8(BMP280_REG_CALIBA, BMP280_CALIBA_SIZE, (uint8_t*)CaliData)){
DBG("Can't read calibration A data");
return FALSE;
}
if(CaliData->params.ID == BME280_CHIP_ID){
uint8_t EEE[BMP280_CALIBB_SIZE] = {0};
if(!i2c_read_reg8(BMP280_REG_CALIB_H1, &CaliData->dig_H1)){
WARNX("Can't read dig_H1");
return FALSE;
}
if(!i2c_read_data8(BMP280_REG_CALIBB, BMP280_CALIBB_SIZE, EEE)){
WARNX("Can't read rest of dig_Hx");
return FALSE;
}
// E5 is divided by two parts so we need this sex
CaliData->dig_H2 = (EEE[1] << 8) | EEE[0];
CaliData->dig_H3 = EEE[2];
CaliData->dig_H4 = (EEE[3] << 4) | (EEE[4] & 0x0f);
CaliData->dig_H5 = (EEE[5] << 4) | (EEE[4] >> 4);
CaliData->dig_H6 = EEE[6];
}
CaliData->calibrated = 1;
DBG("Calibration rdy");
return TRUE;
}
// do a soft-reset procedure
static int s_reset(){
if(!i2c_write_reg8(BMP280_REG_RESET, BMP280_RESET_VALUE)){
DBG("Can't reset\n");
return FALSE;
}
return TRUE;
}
// read compensation data & write registers
static int s_init(sensor_t *s){
s->status = SENS_NOTINIT;
uint8_t devid;
if(!i2c_read_reg8(BMP280_REG_ID, &devid)){
DBG("Can't read BMP280_REG_ID");
return FALSE;
}
DBG("Got device ID: 0x%02x", devid);
if(devid != BMP280_CHIP_ID && devid != BME280_CHIP_ID){
WARNX("Not BM[P/E]280\n");
return FALSE;
}
if(!s_reset()) return FALSE;
// wait whlie update done
uint8_t reg = BMP280_STATUS_UPDATE;
while(reg & BMP280_STATUS_UPDATE){ // wait while update is done
if(!i2c_read_reg8(BMP280_REG_STATUS, &reg)){
DBG("Can't read status");
return FALSE;
}
}
// allocate calibration and other data if need
if(!s->privdata){
s->privdata = calloc(1, sizeof(CaliData_t));
((CaliData_t*)s->privdata)->params = defparams; // and init default parameters
DBG("ALLOCA");
}else ((CaliData_t*)s->privdata)->calibrated = 0;
BPM280_params_t *params = &((CaliData_t*)s->privdata)->params;
params->ID = devid;
if(!readcompdata(s)){
DBG("Can't read calibration data\n");
return FALSE;
}else{
#ifdef EBUG
CaliData_t *CaliData = (CaliData_t*)s->privdata;
DBG("T: %d, %d, %d", CaliData->dig_T1, CaliData->dig_T2, CaliData->dig_T3);
DBG("\P: %d, %d, %d, %d, %d, %d, %d, %d, %d", CaliData->dig_P1, CaliData->dig_P2, CaliData->dig_P3,
CaliData->dig_P4, CaliData->dig_P5, CaliData->dig_P6, CaliData->dig_P7, CaliData->dig_P8, CaliData->dig_P9);
if(devid == BME280_CHIP_ID){ // H compensation
DBG("H: %d, %d, %d, %d, %d, %d", CaliData->dig_H1, CaliData->dig_H2, CaliData->dig_H3,
CaliData->dig_H4, CaliData->dig_H5, CaliData->dig_H6);
}
#endif
}
// write filter configuration
reg = params->filter << 2;
if(!i2c_write_reg8(BMP280_REG_CONFIG, reg)){
DBG("Can't save filter settings\n");
return FALSE;
}
reg = (params->t_os << 5) | (params->p_os << 2); // oversampling for P/T, sleep mode
if(!i2c_write_reg8(BMP280_REG_CTRL, reg)){
DBG("Can't write settings for P/T\n");
return FALSE;
}
params->regctl = reg;
if(devid == BME280_CHIP_ID){ // write CTRL_HUM only AFTER CTRL!
reg = params->h_os;
if(!i2c_write_reg8(BMP280_REG_CTRL_HUM, reg)){
DBG("Can't write settings for H\n");
return FALSE;
}
}
DBG("OK, inited");
s->status = SENS_RELAX;
return TRUE;
}
// start measurement, @return 1 if all OK
static int s_start(sensor_t *s){
if(!s->privdata || s->status == SENS_BUSY || ((CaliData_t*)s->privdata)->calibrated == 0) return FALSE;
uint8_t reg = ((CaliData_t*)s->privdata)->params.regctl | BMP280_MODE_FORSED; // start single measurement
if(!i2c_write_reg8(BMP280_REG_CTRL, reg)){
DBG("Can't write CTRL reg\n");
return FALSE;
}
s->status = SENS_BUSY;
return TRUE;
}
// return T in degC
static inline float compTemp(sensor_t *s, int32_t adc_temp, int32_t *t_fine){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
int32_t var1, var2;
var1 = ((((adc_temp >> 3) - ((int32_t) CaliData->dig_T1 << 1)))
* (int32_t) CaliData->dig_T2) >> 11;
var2 = (((((adc_temp >> 4) - (int32_t) CaliData->dig_T1)
* ((adc_temp >> 4) - (int32_t) CaliData->dig_T1)) >> 12)
* (int32_t) CaliData->dig_T3) >> 14;
*t_fine = var1 + var2;
return ((*t_fine * 5 + 128) >> 8) / 100.;
}
// return P in Pa
static inline double compPres(sensor_t *s, int32_t adc_press, int32_t fine_temp){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
int64_t var1, var2, p;
var1 = (int64_t) fine_temp - 128000;
var2 = var1 * var1 * (int64_t) CaliData->dig_P6;
var2 = var2 + ((var1 * (int64_t) CaliData->dig_P5) << 17);
var2 = var2 + (((int64_t) CaliData->dig_P4) << 35);
var1 = ((var1 * var1 * (int64_t) CaliData->dig_P3) >> 8)
+ ((var1 * (int64_t) CaliData->dig_P2) << 12);
var1 = (((int64_t) 1 << 47) + var1) * ((int64_t) CaliData->dig_P1) >> 33;
if (var1 == 0){
return 0; // avoid exception caused by division by zero
}
p = 1048576 - adc_press;
p = (((p << 31) - var2) * 3125) / var1;
var1 = ((int64_t) CaliData->dig_P9 * (p >> 13) * (p >> 13)) >> 25;
var2 = ((int64_t) CaliData->dig_P8 * p) >> 19;
p = ((p + var1 + var2) >> 8) + ((int64_t) CaliData->dig_P7 << 4);
return p/25600.; // hPa
}
// return H in percents
static inline double compHum(sensor_t *s, int32_t adc_hum, int32_t fine_temp){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
int32_t v_x1_u32r;
v_x1_u32r = fine_temp - (int32_t) 76800;
v_x1_u32r = ((((adc_hum << 14) - (((int32_t)CaliData->dig_H4) << 20)
- (((int32_t)CaliData->dig_H5) * v_x1_u32r)) + (int32_t)16384) >> 15)
* (((((((v_x1_u32r * ((int32_t)CaliData->dig_H6)) >> 10)
* (((v_x1_u32r * ((int32_t)CaliData->dig_H3)) >> 11)
+ (int32_t)32768)) >> 10) + (int32_t)2097152)
* ((int32_t)CaliData->dig_H2) + 8192) >> 14);
v_x1_u32r = v_x1_u32r
- (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7)
* ((int32_t)CaliData->dig_H1)) >> 4);
v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
return (v_x1_u32r >> 12)/1024.;
}
static sensor_status_t s_process(sensor_t *s){
uint8_t reg;
if(s->status != SENS_BUSY) goto ret;
if(!i2c_read_reg8(BMP280_REG_STATUS, &reg)) return (s->status = SENS_ERR);
DBG("stat=0x%02X", reg);
if(reg & BMP280_STATUS_MSRNG) goto ret;
// OK, measurements done -> get and calculate data
CaliData_t *CaliData = (CaliData_t*)s->privdata;
uint8_t ID = CaliData->params.ID;
uint8_t datasz = 8; // amount of bytes to read
uint8_t data[8];
if(ID == BMP280_CHIP_ID) datasz = 6; // no humidity
if(!i2c_read_data8(BMP280_REG_ALLDATA, datasz, data)){
DBG("Can't read data");
return (s->status = SENS_ERR);
}
int32_t p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
DBG("puncomp = %d", p);
int32_t t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
DBG("tuncomp = %d", t);
int32_t t_fine;
s->data.T = compTemp(s, t, &t_fine);
DBG("tfine = %d", t_fine);
s->data.P = compPres(s, p, t_fine);
if(ID == BME280_CHIP_ID){
int32_t h = (data[6] << 8) | data[7];
DBG("huncomp = %d", h);
s->data.H = compHum(s, h, t_fine);
}
s->status = SENS_RDY;
ret:
return s->status;
}
static sensor_props_t s_props(sensor_t *s){
sensor_props_t p = {.T = 1, .P = 1};
if(s && s->privdata){
if(((CaliData_t*)s->privdata)->params.ID == BME280_CHIP_ID) p.H = 1;
}
return p;
}
static int s_heater(sensor_t _U_ *s, int _U_ on){
return FALSE;
}
sensor_t BMP280 = {
.name = "BMP280",
.address = 0x76,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};
sensor_t BME280 = {
.name = "BME280",
.address = 0x76,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

22
I2Csensors/BMP280.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t BMP280;
extern sensor_t BME280;

513
I2Csensors/BMP580.c Normal file
View File

@@ -0,0 +1,513 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "BMP580.h"
#include "i2c.h"
#include "sensors_private.h"
/**
* BMP580 registers
*/
#define BMP5_REG_CHIP_ID (0x01)
#define BMP5_REG_REV_ID (0x02)
#define BMP5_REG_CHIP_STATUS (0x11)
#define BMP5_REG_DRIVE_CONFIG (0x13)
#define BMP5_REG_INT_CONFIG (0x14)
#define BMP5_REG_INT_SOURCE (0x15)
#define BMP5_REG_FIFO_CONFIG (0x16)
#define BMP5_REG_FIFO_COUNT (0x17)
#define BMP5_REG_FIFO_SEL (0x18)
#define BMP5_REG_TEMP_DATA_XLSB (0x1D)
#define BMP5_REG_TEMP_DATA_LSB (0x1E)
#define BMP5_REG_TEMP_DATA_MSB (0x1F)
#define BMP5_REG_PRESS_DATA_XLSB (0x20)
#define BMP5_REG_PRESS_DATA_LSB (0x21)
#define BMP5_REG_PRESS_DATA_MSB (0x22)
#define BMP5_REG_INT_STATUS (0x27)
#define BMP5_REG_STATUS (0x28)
#define BMP5_REG_FIFO_DATA (0x29)
#define BMP5_REG_NVM_ADDR (0x2B)
#define BMP5_REG_NVM_DATA_LSB (0x2C)
#define BMP5_REG_NVM_DATA_MSB (0x2D)
#define BMP5_REG_DSP_CONFIG (0x30)
#define BMP5_REG_DSP_IIR (0x31)
#define BMP5_REG_OOR_THR_P_LSB (0x32)
#define BMP5_REG_OOR_THR_P_MSB (0x33)
#define BMP5_REG_OOR_RANGE (0x34)
#define BMP5_REG_OOR_CONFIG (0x35)
#define BMP5_REG_OSR_CONFIG (0x36)
#define BMP5_REG_ODR_CONFIG (0x37)
#define BMP5_REG_OSR_EFF (0x38)
#define BMP5_REG_CMD (0x7E)
#define BMP5_CMD_NVMEN (0x5D)
#define BMP5_CMD_NVMWRITE (0xA0)
#define BMP5_CMD_NVMREAD (0xA5)
#define BMP5_CMD_RESET (0xB6)
#define BMP5_OSR_P_ENABLE (1<<6)
#define BMP5_CHIP_ID (0x50)
#define BMP585_CHIP_ID (0x51)
// ODR settings
#define BMP5_ODR_240_HZ (0x00)
#define BMP5_ODR_218_5_HZ (0x01)
#define BMP5_ODR_199_1_HZ (0x02)
#define BMP5_ODR_179_2_HZ (0x03)
#define BMP5_ODR_160_HZ (0x04)
#define BMP5_ODR_149_3_HZ (0x05)
#define BMP5_ODR_140_HZ (0x06)
#define BMP5_ODR_129_8_HZ (0x07)
#define BMP5_ODR_120_HZ (0x08)
#define BMP5_ODR_110_1_HZ (0x09)
#define BMP5_ODR_100_2_HZ (0x0A)
#define BMP5_ODR_89_6_HZ (0x0B)
#define BMP5_ODR_80_HZ (0x0C)
#define BMP5_ODR_70_HZ (0x0D)
#define BMP5_ODR_60_HZ (0x0E)
#define BMP5_ODR_50_HZ (0x0F)
#define BMP5_ODR_45_HZ (0x10)
#define BMP5_ODR_40_HZ (0x11)
#define BMP5_ODR_35_HZ (0x12)
#define BMP5_ODR_30_HZ (0x13)
#define BMP5_ODR_25_HZ (0x14)
#define BMP5_ODR_20_HZ (0x15)
#define BMP5_ODR_15_HZ (0x16)
#define BMP5_ODR_10_HZ (0x17)
#define BMP5_ODR_05_HZ (0x18)
#define BMP5_ODR_04_HZ (0x19)
#define BMP5_ODR_03_HZ (0x1A)
#define BMP5_ODR_02_HZ (0x1B)
#define BMP5_ODR_01_HZ (0x1C)
#define BMP5_ODR_0_5_HZ (0x1D)
#define BMP5_ODR_0_250_HZ (0x1E)
#define BMP5_ODR_0_125_HZ (0x1F)
#define BMP5_FIFO_EMPTY (0X7F)
#define BMP5_FIFO_MAX_THRESHOLD_P_T_MODE (0x0F)
#define BMP5_FIFO_MAX_THRESHOLD_P_MODE (0x1F)
// bypass both iir_t and iir_p
#define BMP5_IIR_BYPASS (0xC0)
// Pressure Out-of-range count limit
#define BMP5_OOR_COUNT_LIMIT_1 (0x00)
#define BMP5_OOR_COUNT_LIMIT_3 (0x01)
#define BMP5_OOR_COUNT_LIMIT_7 (0x02)
#define BMP5_OOR_COUNT_LIMIT_15 (0x03)
typedef enum{ // K for filtering: next = [prev*(k-1) + data_ADC]/k
BMP580_FILTER_OFF = 0,
BMP580_FILTER_1 = 1,
BMP580_FILTER_3 = 2,
BMP580_FILTER_7 = 3,
BMP580_FILTER_15 = 4,
BMP580_FILTER_31 = 5,
BMP580_FILTER_63 = 6,
BMP580_FILTER_127 = 7,
} BMP580_Filter;
typedef enum{ // Number of oversampling
BMP580_OVERS1 = 1,
BMP580_OVERS2 = 2,
BMP580_OVERS4 = 3,
BMP580_OVERS8 = 4,
BMP580_OVERS16 = 5,
BMP580_OVERS32 = 5,
BMP580_OVERS64 = 5,
BMP580_OVERS128 = 5,
} BMP580_Oversampling;
typedef enum{
BMP580_POW_STANDBY = 0,
BMP580_POW_NORMAL = 1,
BMP580_POW_FORCED = 2,
BMP580_POW_NONSTOP = 3
} BMP580_Powermode;
typedef struct{
BMP580_Filter filter; // filtering
BMP580_Oversampling p_os; // oversampling for pressure
BMP580_Oversampling t_os; // -//- temperature
BMP580_Powermode pmode; // power mode
uint8_t odr; // oversampling data rage
uint8_t ID; // identificator
} BPM580_params_t;
// default parameters for initialization
static const BPM580_params_t defparams = {
.filter = BMP580_FILTER_7,
.p_os = BMP580_OVERS128,
.t_os = BMP580_OVERS128,
.pmode = BMP580_POW_FORCED,
.odr = BMP5_ODR_01_HZ,
.ID = 0
};
// do a soft-reset procedure
static int s_reset(){
if(!i2c_write_reg8(BMP5_REG_CMD, BMP5_CMD_RESET)){
DBG("Can't reset\n");
return FALSE;
}
return TRUE;
}
static int s_init(sensor_t *s){
if(!s) return FALSE;
s->status = SENS_NOTINIT;
uint8_t devid;
DBG("HERE");
if(!i2c_read_reg8(BMP5_REG_CHIP_ID, &devid)){
DBG("Can't read BMP280_REG_ID");
return FALSE;
}
DBG("Got device ID: 0x%02x", devid);
if(devid != BMP5_CHIP_ID && devid != BMP585_CHIP_ID){
WARNX("Not BMP58x\n");
return FALSE;
}
if(!s_reset()) return FALSE;
// allocate calibration and other data if need
if(!s->privdata){
s->privdata = calloc(1, sizeof(BPM580_params_t));
DBG("ALLOCA");
}
BPM580_params_t *params = (BPM580_params_t*)s->privdata;
*params = defparams;
params->ID = devid;
if(!i2c_write_reg8(BMP5_REG_DSP_IIR, params->filter << 3 | params->filter)){
DBG("Can't set filter");
}
if(!i2c_write_reg8(BMP5_REG_OSR_CONFIG, BMP5_OSR_P_ENABLE | params->p_os << 3 | params->t_os)){
DBG("Can't set oversampling");
}
if(!i2c_write_reg8(BMP5_REG_ODR_CONFIG, params->odr << 2 | params->pmode)){
DBG("Can't set ODR");
}
if(!i2c_write_reg8(BMP5_REG_INT_SOURCE, 1)){
DBG("Can't setup interrupt on data ready");
}
DBG("OK, inited");
s->status = SENS_RELAX;
return TRUE;
}
static int s_start(sensor_t *s){
if(!s || !s->privdata) return FALSE;
BPM580_params_t *params = (BPM580_params_t*)s->privdata;
if(params->pmode == BMP580_POW_STANDBY || params->pmode == BMP580_POW_FORCED){
if(!i2c_write_reg8(BMP5_REG_ODR_CONFIG, params->odr << 2 | BMP580_POW_FORCED)){
WARNX("Can't set ODR");
s->status = SENS_RELAX;
return FALSE;
}
}
s->status = SENS_BUSY;
return TRUE;
}
// Tdeg = MSB|LSB|XLSB / 2^16
// Ppa = MSB|LSB|XLSB / 2^6
static sensor_status_t s_process(sensor_t *s){
if(!s) return SENS_NOTINIT;
uint8_t reg;
if(s->status != SENS_BUSY) goto ret;
if(!i2c_read_reg8(BMP5_REG_INT_STATUS, &reg)) return (s->status = SENS_ERR);
DBG("int=0x%02X", reg);
if(0 == (reg & 1)) goto ret;
// OK, measurements done -> get and calculate data
uint8_t rawdata[6];
if(!i2c_read_data8(BMP5_REG_TEMP_DATA_XLSB, 6, rawdata)){
WARNX("Can't read data");
return (s->status = SENS_ERR);
}
uint32_t T = rawdata[0] | rawdata[1] << 8 | rawdata[2] << 16;
uint32_t P = rawdata[3] | rawdata[4] << 8 | rawdata[5] << 16;
s->data.T = (double) T / (double)(1<<16);
s->data.P = (double) P / (double)(1<<6) / 100.; // hPa
s->status = SENS_RDY;
ret:
return s->status;
}
#if 0
typedef struct {
// temperature
uint16_t dig_T1; // 0x88 (LSB), 0x98 (MSB)
int16_t dig_T2; // ...
int16_t dig_T3;
// pressure
uint16_t dig_P1;
int16_t dig_P2;
int16_t dig_P3;
int16_t dig_P4;
int16_t dig_P5;
int16_t dig_P6;
int16_t dig_P7;
int16_t dig_P8;
int16_t dig_P9; // 0x9e, 0x9f
// humidity (partially calculated from EEE struct)
uint8_t unused; // 0xA0
uint8_t dig_H1; // 0xA1
int16_t dig_H2; // 0xE1...
uint8_t dig_H3; // only from EEE
uint16_t dig_H4;
uint16_t dig_H5;
int8_t dig_H6;
// calibration done
uint8_t calibrated;
// parameters
BPM280_params_t params;
} __attribute__ ((packed)) CaliData_t;
// get compensation data, return 1 if OK
static int readcompdata(sensor_t *s){
FNAME();
CaliData_t *CaliData = (CaliData_t*)s->privdata;
if(!i2c_read_data8(BMP280_REG_CALIBA, BMP280_CALIBA_SIZE, (uint8_t*)CaliData)){
DBG("Can't read calibration A data");
return FALSE;
}
if(CaliData->params.ID == BME280_CHIP_ID){
uint8_t EEE[BMP280_CALIBB_SIZE] = {0};
if(!i2c_read_reg8(BMP280_REG_CALIB_H1, &CaliData->dig_H1)){
WARNX("Can't read dig_H1");
return FALSE;
}
if(!i2c_read_data8(BMP280_REG_CALIBB, BMP280_CALIBB_SIZE, EEE)){
WARNX("Can't read rest of dig_Hx");
return FALSE;
}
// E5 is divided by two parts so we need this sex
CaliData->dig_H2 = (EEE[1] << 8) | EEE[0];
CaliData->dig_H3 = EEE[2];
CaliData->dig_H4 = (EEE[3] << 4) | (EEE[4] & 0x0f);
CaliData->dig_H5 = (EEE[5] << 4) | (EEE[4] >> 4);
CaliData->dig_H6 = EEE[6];
}
CaliData->calibrated = 1;
DBG("Calibration rdy");
return TRUE;
}
// do a soft-reset procedure
static int s_reset(){
if(!i2c_write_reg8(BMP280_REG_RESET, BMP280_RESET_VALUE)){
DBG("Can't reset\n");
return FALSE;
}
return TRUE;
}
// read compensation data & write registers
static int s_init(sensor_t *s){
s->status = SENS_NOTINIT;
uint8_t devid;
if(!i2c_read_reg8(BMP280_REG_ID, &devid)){
DBG("Can't read BMP280_REG_ID");
return FALSE;
}
DBG("Got device ID: 0x%02x", devid);
if(devid != BMP280_CHIP_ID && devid != BME280_CHIP_ID){
WARNX("Not BM[P/E]280\n");
return FALSE;
}
if(!s_reset()) return FALSE;
// wait whlie update done
uint8_t reg = BMP280_STATUS_UPDATE;
while(reg & BMP280_STATUS_UPDATE){ // wait while update is done
if(!i2c_read_reg8(BMP280_REG_STATUS, &reg)){
DBG("Can't read status");
return FALSE;
}
}
// allocate calibration and other data if need
if(!s->privdata){
s->privdata = calloc(1, sizeof(CaliData_t));
((CaliData_t*)s->privdata)->params = defparams; // and init default parameters
DBG("ALLOCA");
}else ((CaliData_t*)s->privdata)->calibrated = 0;
BPM280_params_t *params = &((CaliData_t*)s->privdata)->params;
params->ID = devid;
if(!readcompdata(s)){
DBG("Can't read calibration data\n");
return FALSE;
}else{
#ifdef EBUG
CaliData_t *CaliData = (CaliData_t*)s->privdata;
DBG("T: %d, %d, %d", CaliData->dig_T1, CaliData->dig_T2, CaliData->dig_T3);
DBG("\P: %d, %d, %d, %d, %d, %d, %d, %d, %d", CaliData->dig_P1, CaliData->dig_P2, CaliData->dig_P3,
CaliData->dig_P4, CaliData->dig_P5, CaliData->dig_P6, CaliData->dig_P7, CaliData->dig_P8, CaliData->dig_P9);
if(devid == BME280_CHIP_ID){ // H compensation
DBG("H: %d, %d, %d, %d, %d, %d", CaliData->dig_H1, CaliData->dig_H2, CaliData->dig_H3,
CaliData->dig_H4, CaliData->dig_H5, CaliData->dig_H6);
}
#endif
}
// write filter configuration
reg = params->filter << 2;
if(!i2c_write_reg8(BMP280_REG_CONFIG, reg)){
DBG("Can't save filter settings\n");
return FALSE;
}
reg = (params->t_os << 5) | (params->p_os << 2); // oversampling for P/T, sleep mode
if(!i2c_write_reg8(BMP280_REG_CTRL, reg)){
DBG("Can't write settings for P/T\n");
return FALSE;
}
params->regctl = reg;
if(devid == BME280_CHIP_ID){ // write CTRL_HUM only AFTER CTRL!
reg = params->h_os;
if(!i2c_write_reg8(BMP280_REG_CTRL_HUM, reg)){
DBG("Can't write settings for H\n");
return FALSE;
}
}
DBG("OK, inited");
s->status = SENS_RELAX;
return TRUE;
}
// start measurement, @return 1 if all OK
static int s_start(sensor_t *s){
if(!s->privdata || s->status == SENS_BUSY || ((CaliData_t*)s->privdata)->calibrated == 0) return FALSE;
uint8_t reg = ((CaliData_t*)s->privdata)->params.regctl | BMP280_MODE_FORSED; // start single measurement
if(!i2c_write_reg8(BMP280_REG_CTRL, reg)){
DBG("Can't write CTRL reg\n");
return FALSE;
}
s->status = SENS_BUSY;
return TRUE;
}
// return T in degC
static inline float compTemp(sensor_t *s, int32_t adc_temp, int32_t *t_fine){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
int32_t var1, var2;
var1 = ((((adc_temp >> 3) - ((int32_t) CaliData->dig_T1 << 1)))
* (int32_t) CaliData->dig_T2) >> 11;
var2 = (((((adc_temp >> 4) - (int32_t) CaliData->dig_T1)
* ((adc_temp >> 4) - (int32_t) CaliData->dig_T1)) >> 12)
* (int32_t) CaliData->dig_T3) >> 14;
*t_fine = var1 + var2;
return ((*t_fine * 5 + 128) >> 8) / 100.;
}
// return P in Pa
static inline double compPres(sensor_t *s, int32_t adc_press, int32_t fine_temp){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
int64_t var1, var2, p;
var1 = (int64_t) fine_temp - 128000;
var2 = var1 * var1 * (int64_t) CaliData->dig_P6;
var2 = var2 + ((var1 * (int64_t) CaliData->dig_P5) << 17);
var2 = var2 + (((int64_t) CaliData->dig_P4) << 35);
var1 = ((var1 * var1 * (int64_t) CaliData->dig_P3) >> 8)
+ ((var1 * (int64_t) CaliData->dig_P2) << 12);
var1 = (((int64_t) 1 << 47) + var1) * ((int64_t) CaliData->dig_P1) >> 33;
if (var1 == 0){
return 0; // avoid exception caused by division by zero
}
p = 1048576 - adc_press;
p = (((p << 31) - var2) * 3125) / var1;
var1 = ((int64_t) CaliData->dig_P9 * (p >> 13) * (p >> 13)) >> 25;
var2 = ((int64_t) CaliData->dig_P8 * p) >> 19;
p = ((p + var1 + var2) >> 8) + ((int64_t) CaliData->dig_P7 << 4);
return p/25600.; // hPa
}
// return H in percents
static inline double compHum(sensor_t *s, int32_t adc_hum, int32_t fine_temp){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
int32_t v_x1_u32r;
v_x1_u32r = fine_temp - (int32_t) 76800;
v_x1_u32r = ((((adc_hum << 14) - (((int32_t)CaliData->dig_H4) << 20)
- (((int32_t)CaliData->dig_H5) * v_x1_u32r)) + (int32_t)16384) >> 15)
* (((((((v_x1_u32r * ((int32_t)CaliData->dig_H6)) >> 10)
* (((v_x1_u32r * ((int32_t)CaliData->dig_H3)) >> 11)
+ (int32_t)32768)) >> 10) + (int32_t)2097152)
* ((int32_t)CaliData->dig_H2) + 8192) >> 14);
v_x1_u32r = v_x1_u32r
- (((((v_x1_u32r >> 15) * (v_x1_u32r >> 15)) >> 7)
* ((int32_t)CaliData->dig_H1)) >> 4);
v_x1_u32r = v_x1_u32r < 0 ? 0 : v_x1_u32r;
v_x1_u32r = v_x1_u32r > 419430400 ? 419430400 : v_x1_u32r;
return (v_x1_u32r >> 12)/1024.;
}
static sensor_status_t s_process(sensor_t *s){
uint8_t reg;
if(s->status != SENS_BUSY) goto ret;
if(!i2c_read_reg8(BMP280_REG_STATUS, &reg)) return (s->status = SENS_ERR);
DBG("stat=0x%02X", reg);
if(reg & BMP280_STATUS_MSRNG) goto ret;
// OK, measurements done -> get and calculate data
CaliData_t *CaliData = (CaliData_t*)s->privdata;
uint8_t ID = CaliData->params.ID;
uint8_t datasz = 8; // amount of bytes to read
uint8_t data[8];
if(ID == BMP280_CHIP_ID) datasz = 6; // no humidity
if(!i2c_read_data8(BMP280_REG_ALLDATA, datasz, data)){
DBG("Can't read data");
return (s->status = SENS_ERR);
}
int32_t p = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4);
DBG("puncomp = %d", p);
int32_t t = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4);
DBG("tuncomp = %d", t);
int32_t t_fine;
s->data.T = compTemp(s, t, &t_fine);
DBG("tfine = %d", t_fine);
s->data.P = compPres(s, p, t_fine);
if(ID == BME280_CHIP_ID){
int32_t h = (data[6] << 8) | data[7];
DBG("huncomp = %d", h);
s->data.H = compHum(s, h, t_fine);
}
s->status = SENS_RDY;
ret:
return s->status;
}
#endif
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .P = 1};
return p;
}
static int s_heater(sensor_t _U_ *s, int _U_ on){
return FALSE;
}
sensor_t BMP580 = {
.name = "BMP580",
.address = 0x47,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

21
I2Csensors/BMP580.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t BMP580;

104
I2Csensors/CMakeLists.txt Normal file
View File

@@ -0,0 +1,104 @@
cmake_minimum_required(VERSION 3.9)
set(PROJ i2csensorsPTH)
set(MINOR_VERSION "1")
set(MID_VERSION "1")
set(MAJOR_VERSION "0")
set(VERSION "${MAJOR_VERSION}.${MID_VERSION}.${MINOR_VERSION}")
project(${PROJ} VERSION ${VERSION} LANGUAGES C)
# default flags
set(CMAKE_C_FLAGS "${CFLAGS} -O2 -Wno-address-of-packed-member")
set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS}")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS} -Wextra -Wall -Werror -W")
set(CMAKE_COLOR_MAKEFILE ON)
option(DEBUG "Compile in debug mode" OFF)
option(EXAMPLES "Build examples" ON)
if(DEBUG)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Og -g3 -ggdb -Werror")
add_definitions(-DEBUG)
set(CMAKE_BUILD_TYPE DEBUG)
set(CMAKE_VERBOSE_MAKEFILE "ON")
else()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -O3 -march=native -fdata-sections -ffunction-sections -flto=auto")
# add_definitions(-DEBUG)
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections -flto=auto")
set(CMAKE_BUILD_TYPE RELEASE)
endif()
message("Build type: ${CMAKE_BUILD_TYPE}")
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SOURCES)
find_package(PkgConfig REQUIRED)
pkg_check_modules(${PROJ} REQUIRED usefull_macros)
# library
add_library(${PROJ} SHARED ${SOURCES})
# library header files
set(LIBHEADER "i2csensorsPTH.h")
# -I
include_directories(${${PROJ}_INCLUDE_DIRS})
# -L
link_directories(${${PROJ}_LIBRARY_DIRS})
# -D
add_definitions(-DPACKAGE_VERSION=\"${VERSION}\" -DMINOR_VERSION=\"${MINOR_VERSION}\"
-DMID_VERSION=\"${MID_VERSION}\" -DMAJOR_VERSION=\"${MAJOR_VESION}\")
# -l
target_link_libraries(${PROJ} ${${PROJ}_LIBRARIES})
set(PCFILE "${CMAKE_BINARY_DIR}/${PROJ}.pc")
configure_file("${PROJ}.pc.in" ${PCFILE} @ONLY)
set_target_properties(${PROJ} PROPERTIES VERSION ${VERSION})
set_target_properties(${PROJ} PROPERTIES PUBLIC_HEADER ${LIBHEADER})
# Installation of the program
include(GNUInstallDirs)
install(TARGETS ${PROJ} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR})
install(FILES ${PCFILE} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig)
# EXAMPLES
if(EXAMPLES)
add_subdirectory(examples)
endif()
###### gettext ######
if(NOT DEFINED NOGETTEXT)
add_definitions(-DGETTEXT)
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
message("Generate locale files @ make")
find_package(Gettext REQUIRED)
find_program(GETTEXT_XGETTEXT_EXECUTABLE xgettext)
if(NOT GETTEXT_XGETTEXT_EXECUTABLE OR NOT GETTEXT_MSGFMT_EXECUTABLE)
message(FATAL_ERROR "xgettext not found")
endif()
file(MAKE_DIRECTORY ${LCPATH})
file(MAKE_DIRECTORY ${LCPATH}/LC_MESSAGES)
add_custom_command(
OUTPUT ${PO_FILE}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND ${GETTEXT_XGETTEXT_EXECUTABLE} --from-code=koi8-r ${SOURCES} -c -k_ -kN_ -o ${PO_FILE}
COMMAND sed -i 's/charset=.*\\\\n/charset=koi8-r\\\\n/' ${PO_FILE}
COMMAND enconv ${PO_FILE}
DEPENDS ${SOURCES}
)
# we need this to prevent ru.po & .mo from deleting by make clean
add_custom_target(
RU_FILE
COMMAND [ -f ${RU_FILE} ] && ${GETTEXT_MSGMERGE_EXECUTABLE} -Uis ${RU_FILE} ${PO_FILE} || cp ${PO_FILE} ${RU_FILE}
DEPENDS ${PO_FILE} ${SOURCES}
)
add_custom_target(
MO_FILE
COMMAND make RU_FILE && ${GETTEXT_MSGFMT_EXECUTABLE} ${RU_FILE} -o ${MO_FILE}
DEPENDS ${RU_FILE}
)
add_dependencies(${PROJ} MO_FILE)
else() # install .mo file
install(FILES ${MO_FILE} DESTINATION "${LOCALEDIR}/ru/LC_MESSAGES")
endif()
endif(NOT DEFINED NOGETTEXT)

14
I2Csensors/PCA9548 Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
if [[ $# != 1 || $((-(-$1))) != $1 ]]; then
echo "Usage: $0 cnannel [!0-7 == all off]" >&2
exit 1
fi
CMD="0"
[[ $1 -ge 0 && $1 -le 7 ]] && CMD=$((1 << $1))
echo "send $CMD"
i2ctransfer -y 1 w1@0x70 $CMD
i2cdetect -y -r 1

158
I2Csensors/Readme.md Normal file
View File

@@ -0,0 +1,158 @@
Reading information from different T/P/H sensors
================================================
Supported sensors: AHT10, AHT15, AHT21b, BMP180, BMP280, BME280, SHT3x, SI7005
| Sensor | Precision H/T/P | Address | Max speed | Comment |
|---------|-----------------|---------------|-----------|---------|
| AHT10 | 2%/0.3°/- | 0x38/0x39 | 400k | ADDR selects lowest address bit |
| AHT15 | 2%/0.3°/- | 0x38 | 400k | what is the difference from AHT10? |
| AHT21b | 3%/0.5°/- | 0x38 | 400k | |
| BMP180 | -/1°/12Pa | 0x77 | 3.4M | could also works by SPI |
| BME280 | 3%/1°/0.2Pa | 0x76/77 | 3.4M | SDO allows to select lowest I2C address bit; supports SPI |
| SHT30 | 3%/0.3°/- | 0x44/0x45 | 1M | SHT31 have higher humidity precision (2%); ADDR selects address lowest bit; hav ALERT pin |
| SI7005 | 4.5%/1°/- | 0x40 | 400k | ~CS can select sensor if several are on bus |
-------------------------------------------------------------------
## Install library
1. Download: `git clone` or other way.
2. Create building directory: `mkdir mk`. Go into it: `cd mk`.
3. Run `cmake`: `cmake ..`.
4. Build and install: `make && su -c "make install"`.
### Cmake options
Marked options are ON by default:
- [ ] DEBUG - compile in debug mode;
- [x] EXAMPLES - build also examples (they won't be installed, you can use them just in build dir).
## How to use
After installing library you can use it including `i2csensorsPTH.h` into your code and linking with `-l i2csensorsPTH`.
Also you can use `pkg-config` after installing library:
```
pkg-config --libs --cflags i2csensorsPTH
```
### Base types
#### `sensor_status_t`
Status of given sensor. `SENS_NOTINIT` means that you should init device; also if you get `SENS_ERR` you should try to reinit it.
Receiving error on init function means that there's troubles on the bus or with sensor.
```
typedef enum{
SENS_NOTINIT, // wasn't inited
SENS_BUSY, // measurement in progress
SENS_ERR, // error occured
SENS_RELAX, // do nothing
SENS_RDY, // data ready - can get it
} sensor_status_t;
```
#### `sensor_props_t`
Properties: if the corresponding field sets, the device have this ability. `flags` allows to use all together as bit fields.
```
typedef union{
struct{
uint8_t T : 1; // can temperature (degC)
uint8_t H : 1; // can humidity (percent)
uint8_t P : 1; // can pressure (hPa)
uint8_t htr : 1; // have heater
};
uint32_t flags;
} sensor_props_t;
```
#### `sensor_data_t`
Gathered data. The fields that are zeroed in sensor's properties are undefined.
```
typedef struct{
double T; // temperature, degC
double H; // humidity, percents
double P; // pressure, hPa
} sensor_data_t;
```
### Functions
#### `int sensors_open(const char *dev)`
Open I2C device by path `dev`. Returns `TRUE` if all OK.
#### `void sensors_close()`
Close I2C device.
#### `char *sensors_list()`
Returns allocated string with comma-separated names of all supported sensors. Don't forget to `free` it later.
#### `sensor_t* sensor_new(const char *name)`
Search `name` in list of supported sensors and, if found, returns pointer to sensors structure. Returns `NULL` if sensor not failed or some error oqqured.
#### `void sensor_delete(sensor_t **s)`
Delete all memory, allocated for given sensor.
#### `sensor_props_t sensor_properties(sensor_t *s)`
Allows to check sensor's properties.
#### `int sensor_init(sensor_t *s, uint8_t address)`
Try to find given sensor on the bus and run initial procedures (like calibration and so on). The `address` argument shoul be zero to use default I2C address or non-zero for custom.
Returns `TRUE` if all OK.
#### `int sensor_heater(sensor_t *s, int on)`
Turn on (`on == 1`) or off (`on == 0`) sensor's heater (if sensor supported it). Returns `FALSE` if sensor don't support heater or some error occured during operations.
#### `int sensor_start(sensor_t *s)`
Start measurement process. While measuring, you should poll sensor until data would be ready (or you get timeout error).
#### `sensor_status_t sensor_process(sensor_t *s)`
Polling sensor and gathering all data in simple finite-state machine. Checks if sensor is still busy and asks for data portion on each measuring stage.
Returns current sensor's state. If you get `SENS_RDY`, you can ask for data.
#### `int sensor_getdata(sensor_t *s, sensor_data_t *d)`
Get data into your variable `d`. Returns `FALSE` if data isn't ready (e.g. you didn't run `start` or sensor is still measuring).
### I2C functions
Of course, you can wish to work with I2C directly (e.g. to switch multiplexer's channel and so on), so here are some usefull functions.
#### `int sensor_writeI2C(uint8_t addr, uint8_t *data, int len)`
Write `data` array with len of `len` bytes with device address `addr`. Returns `FALSE` if failed.
#### `int sensor_readI2C(uint8_t addr, uint8_t *data, int len)`
Read `len` bytes of `data` from address `addr`. Returns `FALSE` if failed.
#### `int sensor_readI2Cregs(uint8_t addr, uint8_t regaddr, uint16_t N, uint8_t *data)`
Read content of `N` 8-bit registers starting from `regaddr` to array `data`. Returns `FALSE` if failed.
#### `int sensor_writeI2Creg(uint8_t addr, uint8_t regaddr, uint8_t data)`
Write `data` to single register `regaddr`. Returns `FALSE` if failed.

121
I2Csensors/SHT3x.c Normal file
View File

@@ -0,0 +1,121 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "SHT3x.h"
#include "i2c.h"
#include "sensors_private.h"
// use single mode with high repeatability
static uint8_t cmd_measure[] = { 0x24, 0x00 };
static uint8_t cmd_reset[] = { 0x30, 0xa2 };
static uint8_t cmd_break[] = { 0x30, 0x93 };
static uint8_t cmd_heater_on[] = { 0x30, 0x6d };
static uint8_t cmd_heater_off[] = { 0x30, 0x66 };
//static uint8_t cmd_read_status_reg[] = { 0xf3, 0x2d };
static uint8_t cmd_clear_status_reg[] = { 0x30, 0x41 };
// maybe usefull to read heater status
//#define SHT31_REG_HEATER_BIT 0x0d
// documented timeout is 15ms, so let's wait 20
#define MEASUREMENT_TIMEOUT (0.02)
static int s_init(sensor_t *s){
s->status = SENS_NOTINIT;
if(!i2c_write_raw(cmd_break, 2)){
DBG("Can't break old measurements");
return FALSE;
}
if(!i2c_write_raw(cmd_reset, 2)){
DBG("Can't make soft reset");
return FALSE;
}
if(!i2c_write_raw(cmd_clear_status_reg, 2)){
DBG("Can't clear status bits");
return FALSE;
}
if(!s->privdata) s->privdata = calloc(1, sizeof(double)); // used for start measurement time
s->status = SENS_RELAX;
return TRUE;
}
static int s_start(sensor_t *s){
if(s->status != SENS_RELAX) return FALSE;
s->status = SENS_BUSY;
if(!i2c_write_raw(cmd_measure, 2)){
DBG("Can't write start Tmeas");
s->status = SENS_ERR;
return FALSE;
}
*((double*)s->privdata) = sl_dtime();
return TRUE;
}
static uint8_t crc8(const uint8_t *data, int len){
uint8_t POLYNOMIAL = 0x31;
uint8_t crc = 0xFF;
for(int j = len; j; --j){
crc ^= *data++;
for(int i = 8; i; --i) crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
}
return crc;
}
static sensor_status_t s_process(sensor_t *s){
if(s->status != SENS_BUSY) return s->status;
uint8_t data[6];
int ans = i2c_read_raw(data, 6);
if(sl_dtime() - *((double*)s->privdata) < MEASUREMENT_TIMEOUT){
if(!ans) return s->status; // poll ACK
}
if(!ans) return (s->status = SENS_ERR); // timeout!
i2c_write_raw(cmd_clear_status_reg, 2); // need to write any command or sensor will widthraw into itself
if(data[2] != crc8(data, 2) || data[5] != crc8(data + 3, 2)) return (s->status = SENS_ERR); // CRC error
int32_t stemp = (int32_t)(((uint32_t)data[0] << 8) | data[1]);
stemp = ((4375 * stemp) >> 14) - 4500;
s->data.T = stemp / 100.;
uint32_t shum = ((uint32_t)data[3] << 8) | data[4];
shum = (625 * shum) >> 12;
s->data.H = shum / 100.0;
return (s->status = SENS_RDY);
}
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .H = 1};
return p;
}
static int s_heater(sensor_t _U_ *s, int on){
uint8_t *cmd = (on) ? cmd_heater_on : cmd_heater_off;
if(!i2c_write_raw(cmd, 2)) return FALSE;
return TRUE;
}
sensor_t SHT3x = {
.name = "SHT3x",
.address = 0x44,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

22
I2Csensors/SHT3x.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t SHT3x;

139
I2Csensors/SI7005.c Normal file
View File

@@ -0,0 +1,139 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "i2c.h"
#include "sensors_private.h"
#include "SI7005.h"
#define SI7005_REGSTATUS 0
#define SI7005_STATUSNRDY 1
#define SI7005_REGDATA 1
#define SI7005_REGCONFIG 3
#define SI7005_CONFFAST (1<<5)
#define SI7005_CONFTEMP (1<<4)
#define SI7005_CONFHEAT (1<<1)
#define SI7005_CONFSTART (1<<0)
#define SI7005_REGID 0x11
#define SI7005_ID 0x50
static int s_init(sensor_t *s){
uint8_t ID;
s->status = SENS_NOTINIT;
if(!i2c_read_reg8(SI7005_REGID, &ID)){
DBG("Can't read SI_REG_ID");
return FALSE;
}
DBG("SI, device ID: 0x%02x", ID);
if(ID != SI7005_ID){
DBG("Not SI7005\n");
return FALSE;
}
s->status = SENS_RELAX;
return TRUE;
}
static int s_start(sensor_t *s){
if(s->status != SENS_RELAX) return FALSE;
s->status = SENS_BUSY;
if(!i2c_write_reg8(SI7005_REGCONFIG, SI7005_CONFTEMP | SI7005_CONFSTART)){
DBG("Can't write start Tmeas");
s->status = SENS_ERR;
return FALSE;
}
DBG("Wait for T\n");
return TRUE;
}
// start humidity measurement
static sensor_status_t si7005_cmdH(sensor_t *s){
s->status = SENS_BUSY;
if(!i2c_write_reg8(SI7005_REGCONFIG, SI7005_CONFSTART)){
DBG("Can't write start Hmeas");
return (s->status = SENS_ERR);
}
DBG("Wait for H");
return s->status;
}
static sensor_status_t s_process(sensor_t *s){
uint8_t c, d[3];
if(s->status != SENS_BUSY) return s->status;
if(!i2c_read_raw(d, 3)){
DBG("Can't read status");
return (s->status = SENS_ERR);
}
//DBG("Status: 0x%02x, H: 0x%02x, L: 0x%02x", d[0], d[1], d[2]);
if(!i2c_read_reg8(SI7005_REGCONFIG, &c)){
DBG("Can't read config");
return (s->status = SENS_ERR);
}
//DBG("Config: 0x%02x", c);
if(d[0] & SI7005_STATUSNRDY){ // not ready yet
return s->status;
}
uint16_t TH = (uint16_t)((d[1]<<8) | d[2]);
if(c & SI7005_CONFTEMP){ // temperature measured
TH >>= 2;
double Tmeasured = TH/32. - 50.;
DBG("T=%.2f", Tmeasured);
s->data.T = Tmeasured;
return si7005_cmdH(s);
}else{ // humidity measured
// correct T/H
#define A0 (-4.7844)
#define A1 (0.4008)
#define A2 (-0.00393)
TH >>= 4;
double Hmeasured = TH/16.f - 24.f;
DBG("H=%.1f", Hmeasured);
s->data.H = Hmeasured - (A2*Hmeasured*Hmeasured + A1*Hmeasured + A0);
s->status = SENS_RDY;
}
return s->status;
}
// turn heater on/off (1/0)
static int s_heater(sensor_t *s, int on){
DBG("status=%d", s->status);
if(s->status != SENS_RELAX) return FALSE;
uint8_t reg = (on) ? SI7005_CONFHEAT : 0;
if(!i2c_write_reg8(SI7005_REGCONFIG, reg)){
DBG("Can't write regconfig");
return FALSE;
}
return TRUE;
}
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .H = 1, .htr = 1};
return p;
}
sensor_t SI7005 = {
.name = "SI7005",
.address = 0x40,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

21
I2Csensors/SI7005.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t SI7005;

191
I2Csensors/aht.c Normal file
View File

@@ -0,0 +1,191 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "aht.h"
#include "i2c.h"
#include "sensors_private.h"
enum{
ISAHT1x,
ISAHT2x
};
#define AHT_CMD_INITIALIZE 0xE1
#define AHT_CMD_MEASURE 0xAC
#define AHT_CMD_SOFT_RESET 0xBA
// status - for AHT21
#define AHT_CMD_STATUS 0x71
// init command bits:
// normal/cycle/command modes (bits 6:5) [non-documented!]:
#define AHT_INIT_NORMAL_MODE 0x00
#define AHT_INIT_CYCLE_MODE 0x20
#define AHT_INIT_CMD_MODE 0x40
// run calibration
#define AHT_INIT_CAL_ON 0x08
// zero byte for INIT/START cmd
#define AHT_NOP 0
// measurement control [non-documented!]
#define AHT_MEAS_CTRL 0x33
// status bits
#define AHT_STATUS_BUSY 0x80
#define AHT_STATUS_NORMAL_MODE 0x00
#define AHT_STATUS_CYCLE_MODE 0x20
#define AHT_STATUS_CMD_MODE 0x40
#define AHT_STATUS_CAL_ON 0x08
// status bits for AHT2x (both should be ones, or init again)
#define AHT_STATUS_CHK 0x18
// max reset time
#define RST_TIME (20e-3)
// max data waiting time
#define DATA_TIME (75e-3)
static sensor_status_t s_poll(){
uint8_t b;
if(!i2c_read_raw(&b, 1)) return SENS_ERR;
#ifdef EBUG
if(b & AHT_STATUS_BUSY) printf("BUSY ");
static const char *modes[] = {"NOR", "CYC", "CMD", "CMD"};
printf("MODE=%s ", modes[(b >> 6)&3]);
printf("%sCALIBRATED\n", b & AHT_STATUS_CAL_ON ? "" : "NOT ");
#endif
if(b & AHT_STATUS_BUSY) return SENS_BUSY;
return SENS_RELAX;
}
static int s_init(sensor_t *s){
s->status = SENS_NOTINIT;
if(!i2c_write_reg8(AHT_CMD_SOFT_RESET, 0)){
DBG("Can't reset");
return FALSE;
}
double t0 = sl_dtime(), t;
while((t = sl_dtime()) - t0 < RST_TIME){
if(SENS_RELAX == s_poll()) break;
usleep(1000);
}
if(t - t0 > RST_TIME) return SENS_ERR;
DBG("Reseted");
uint8_t data[3] = {AHT_CMD_INITIALIZE, AHT_INIT_CAL_ON, AHT_NOP};
if(!i2c_write_raw(data, 3)){
DBG("Can't init");
return FALSE;
}
t0 = sl_dtime();
while((t = sl_dtime()) - t0 < RST_TIME){
if(SENS_RELAX == s_poll()) break;
usleep(1000);
}
if(t - t0 > RST_TIME) return SENS_ERR;
DBG("Inited");
s->status = SENS_RELAX;
return TRUE;
}
static int s_start(sensor_t *s){
if(s->status != SENS_RELAX) return FALSE;
uint8_t data[3] = {AHT_CMD_MEASURE, AHT_MEAS_CTRL, AHT_NOP};
// the only difference between AHT1x and AHT2x
if(s->private == ISAHT2x){ // check status
uint8_t b;
if(!i2c_read_reg8(AHT_CMD_STATUS, &b)) return FALSE;
if((b & AHT_STATUS_CHK) != AHT_STATUS_CHK){
DBG("need init");
if(!s->init(s)) return FALSE;
}
}
if(!i2c_write_raw(data, 3)){
DBG("Can't start measuring");
return FALSE;
}
DBG("Start @ %.3f", sl_dtime());
return TRUE;
}
static sensor_status_t s_process(sensor_t *s){
sensor_status_t st = s_poll();
if(st != SENS_RELAX) return (s->status = st);
uint8_t data[6];
if(!i2c_read_raw(data, 6)) return (s->status = SENS_ERR);
DBG("Got @ %.3f", sl_dtime());
uint32_t rawH = ((uint32_t)data[1] << 12) | ((uint32_t)data[2] << 4) | (data[3] >> 4);
uint32_t rawT = ((uint32_t)(data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5];
DBG("rawH=%d, rawT=%d", rawH, rawT);
s->data.T = rawT * 200.0 / 1048576.0 - 50.0;
s->data.H = rawH * 100.0 / 1048576.0;
return (s->status = SENS_RDY);
}
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .H = 1};
return p;
}
static int s_heater(sensor_t _U_ *s, int _U_ on){
return FALSE;
}
sensor_t AHT10 = {
.name = "AHT10",
.private = ISAHT1x,
.address = 0x38,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};
sensor_t AHT15 = {
.name = "AHT15",
.private = ISAHT1x,
.address = 0x38,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};
sensor_t AHT20 = {
.name = "AHT20",
.private = ISAHT2x,
.address = 0x38,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};
sensor_t AHT21 = {
.name = "AHT21",
.private = ISAHT2x,
.address = 0x38,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

25
I2Csensors/aht.h Normal file
View File

@@ -0,0 +1,25 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t AHT10;
extern sensor_t AHT15;
extern sensor_t AHT20;
extern sensor_t AHT21;

16
I2Csensors/common_table Normal file
View File

@@ -0,0 +1,16 @@
Датчики влажности[-температуры[-давления]]
# датчик = паспортная точность по влажности/температуре/давлению = доступные ID = максимальная скорость = примечание
AM2320 = 3%/0.5°/- = 0x5C = 100k* = (*) модбас (!!!) поверх I2C; желательно питать 5В, хоть вроде должен и от 3.3
AHT10 = 2%/0.3°/- = 0x38/0x39 = 400k = ADDR выбирает младший бит
AHT15 = 2%/0.3°/- = 0x38 = 400k = непонятно, чем от 10 отличается
AHT21b = 3%/0.5°/- = 0x38 = 400k =
BMP180 = -/1°/12Pa = 0x77 = 3.4M = есть вариант с SPI, но не в общем случае
BME280 = 3%/1°/0.2Pa = 0x76/77 = 3.4M = выбор младшего бита ногой SDO, есть SPI
HTU21d = 3-5%/0.3°/- = 0x40 = 400k = в зависимости от маркировки точность 3-5%RH, бывают ШИМ и др. интерфейсы
HTU32d = 2%/0.2°/- = 0x40/0x41 = 10M = младший бит адреса выбирается ногой ADDR
SHT30 = 3%/0.3°/- = 0x44/0x45 = 1M = у SHT31 заявленная точность по влажности: 2%; младший бит адреса выбирается ногой ADDR; есть программируемая нога ALERT!
SHT4x = * = 0x44/0x45** (*) 40: 2-4%/>0.2-0.4°, 41: 2-2.5%/0.2-0.4°, 45: 1-2%/0.1-0.3°; (**) адрес зависит от маркировки (A/B); есть нагреватель; есть команда reset (0x06) по адресу 0
SI7005 = 4.5%/1°/- = 0x40 = 400k = возможен выбор с помощью ноги ~CS
SHT85?

View File

@@ -0,0 +1,8 @@
cmake_minimum_required(VERSION 3.9)
include_directories(..)
link_libraries(i2csensorsPTH usefull_macros -lm)
add_executable(single single_sensor.c)
add_executable(log logmany.c)
#target_link_libraries(single i2csensorsPTH usefull_macros)

8
I2Csensors/examples/MAP Normal file
View File

@@ -0,0 +1,8 @@
0 - AHT15 (38), SI7005 (40)
1 - AHT10 (38), BMP180 (77), BME280 (76)
2 - BME280 (76), AHT21b (38), SHT30 (44)
3 - AHT+BMP (38, 77), /* HTU21d (40) */
4 - AHT+BMP (38, 77), /* SHT40 (44) */
5 - AHT+BMP (38, 77), BMP580 (47)
6 - MTU31 (40), BMP580 (47), BME280 (76)
7 - AM2320 (56)

View File

@@ -0,0 +1,35 @@
# Examples of library usage
### logmany.c
Creates executable `log`. The purpose of this is to show how you can use library wit lots of sensors (even with fully similar)
divided by groups (to prevent same addresses on one bus) switching by PCA9548A.
Usage:
```
-H, --hlog=arg humidity logging file
-P, --plog=arg pressure logging file
-T, --tlog=arg temperature logging file
-a, --muladdr=arg multiplexer I2C address
-d, --device=arg I2C device path
-h, --help show this help
-i, --interval=arg logging interval, seconds (default: 10)
-m, --presmm pressure in mmHg instead of hPa
```
### single_sensor.c
Creates executable `single`. Open single sensor and show its parameters.
Usage:
```
-H, --heater=arg turn on/off heater (if present)
-a, --address=arg sensor's address (if not default)
-d, --device=arg I2C device path
-h, --help show this help
-l, --list list all supported sensors
-m, --presmm pressure in mmHg instead of hPa
-s, --sensor=arg sensor's name
```

View File

@@ -0,0 +1,240 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <math.h> // for NaN
#include <signal.h>
#include <stdio.h>
#include <strings.h>
#include <unistd.h>
#include <usefull_macros.h>
#include "i2csensorsPTH.h"
typedef struct{
char *device;
char *Tlog;
char *Hlog;
char *Plog;
double interval;
int mul_addr;
int presmm; // pressure in mm instead of hPa
int help;
} glob_pars;
static glob_pars G = {
.device = "/dev/i2c-1",
.mul_addr = 0x70,
.interval = 10.,
};
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), "I2C device path"},
{"presmm", NO_ARGS, NULL, 'm', arg_int, APTR(&G.presmm), "pressure in mmHg instead of hPa"},
{"tlog", NEED_ARG, NULL, 'T', arg_string, APTR(&G.Tlog), "temperature logging file"},
{"hlog", NEED_ARG, NULL, 'H', arg_string, APTR(&G.Hlog), "humidity logging file"},
{"plog", NEED_ARG, NULL, 'P', arg_string, APTR(&G.Plog), "pressure logging file"},
{"interval",NEED_ARG, NULL, 'i', arg_double, APTR(&G.interval), "logging interval, seconds (default: 10)"},
{"muladdr", NEED_ARG, NULL, 'a', arg_int, APTR(&G.mul_addr), "multiplexer I2C address"},
end_option
};
static FILE *tlogf = NULL, *hlogf = NULL, *plogf = NULL;
static FILE *openlog(const char *name){
FILE *l = fopen(name, "w");
if(!l) ERR("Can't open %s", name);
return l;
}
void signals(int s){
DBG("Got sig %d", s);
sensors_close();
if(tlogf != stdout) fclose(tlogf);
if(hlogf != stdout) fclose(hlogf);
if(plogf != stdout) fclose(plogf);
exit(s);
}
typedef struct{
const char *name; // name - for header in log
const char *type; // sensor's name for `sensor_new`
uint8_t nch; // channel number
uint8_t address; // address (0 for default)
sensor_t *sensor; // pointer to sensor itself
} sd_t;
// amount of all sensors connected
#define SENSORS_AMOUNT 17
// list of sensors - must be sorted by channel number
static sd_t all_sensors[SENSORS_AMOUNT] = {
{.name = "AHT15", .type = "AHT15", .nch = 0},
{.name = "SI7005", .type = "SI7005", .nch = 0},
{.name = "AHT10", .type = "AHT10", .nch = 1},
{.name = "BMP180", .type = "BMP180", .nch = 1},
{.name = "BME280A", .type = "BME280", .nch = 1},
{.name = "BME280B", .type = "BME280", .nch = 2},
{.name = "AHT21", .type = "AHT21", .nch = 2},
{.name = "SHT30", .type = "SHT3x", .nch = 2},
{.name = "AHT20A", .type = "AHT21", .nch = 3},
{.name = "BMP280A", .type = "BMP280", .nch = 3, .address = 0x77},
{.name = "AHT20B", .type = "AHT20", .nch = 4},
{.name = "BMP280B", .type = "BMP280", .nch = 4, .address = 0x77},
{.name = "AHT20C", .type = "AHT20", .nch = 5},
{.name = "BMP280C", .type = "BMP280", .nch = 5, .address = 0x77},
{.name = "BMP580A", .type = "BMP580", .nch = 5},
// {.name = "MTU31", .type = "MTU31", .nch = 6},
{.name = "BMP580B", .type = "BMP580", .nch = 6},
{.name = "BME280C", .type = "BME280", .nch = 6},
// {.name = "AM2320", .type = "AM2320", .nch = 7},
};
/*
static int chsort(const void *v1, const void *v2){
const sd_t *s1 = (const sd_t*)v1;
const sd_t *s2 = (const sd_t*)v2;
return s1->nch - s2->nch;
}*/
static int setchan(uint8_t N){
if(N > 7){ WARNX("Wrong channel number: %d", N); return FALSE; }
N = 1<<N;
int r = sensor_writeI2C(G.mul_addr, &N, 1);
if(!r) return FALSE;
usleep(100); // wait for commutation
return TRUE;
}
static void wrnames(FILE*f, uint32_t p){
for(int i = 0; i < SENSORS_AMOUNT; ++i){
sensor_props_t sp = sensor_properties(all_sensors[i].sensor);
if((sp.flags & p) == 0) continue;
fprintf(f, "%s\t", all_sensors[i].name);
}
fprintf(f, "\n");
}
static void writeheader(){
sensor_props_t p;
fprintf(tlogf, "# Temperature, degC\n");
p.flags = 0; p.T = 1; wrnames(tlogf, p.flags);
fprintf(hlogf, "# Humidity, percent\n");
p.flags = 0; p.H = 1; wrnames(hlogf, p.flags);
fprintf(plogf, "# Pressure, %s\n", G.presmm ? "mmHg" : "hPa");
p.flags = 0; p.P = 1; wrnames(plogf, p.flags);
}
static void initsensors(){
uint8_t curch = 8;
for(int i = 0; i < SENSORS_AMOUNT; ++i){
uint8_t ch = all_sensors[i].nch;
if(ch != curch){
if(!setchan(ch)) ERRX("Error selecting channel %d", ch);
curch = ch;
}
if(!(all_sensors[i].sensor = sensor_new(all_sensors[i].type))) ERRX("Can't connect %s", all_sensors[i].name);
if(!sensor_init(all_sensors[i].sensor, all_sensors[i].address)) ERRX("Can't init %s", all_sensors[i].name);
}
}
static void writedata(uint8_t *got){
if(!got) return;
int NT = 0, NH = 0, NP = 0;
static const double nan = NAN;
for(int i = 0; i < SENSORS_AMOUNT; ++i){
sensor_props_t sp = sensor_properties(all_sensors[i].sensor);
sensor_data_t D;
if(got[i] && !sensor_getdata(all_sensors[i].sensor, &D)) got[i] = 0;
if(sp.T){ ++NT; fprintf(tlogf, "%.2f\t", got[i] ? D.T : nan); }
if(sp.H){ ++NH; fprintf(hlogf, "%.2f\t", got[i] ? D.H : nan); }
if(sp.P){ ++NP; fprintf(plogf, "%.2f\t", got[i] ? (G.presmm ? D.P * 0.750062 : D.P) : nan); }
}
DBG("Measured: %d T, %d H and %d P", NT, NH, NP);
if(NT){ fprintf(tlogf, "\n"); fflush(tlogf); }
if(NH){ fprintf(hlogf, "\n"); fflush(hlogf); }
if(NP){ fprintf(plogf, "\n"); fflush(plogf); }
}
static void startlogs(){
double t0 = sl_dtime();
uint8_t *started = MALLOC(uint8_t, SENSORS_AMOUNT);
uint8_t *got = MALLOC(uint8_t, SENSORS_AMOUNT);
uint8_t curch = 8;
while(1){
bzero(started, SENSORS_AMOUNT);
bzero(got, SENSORS_AMOUNT);
int Ngot = 0;
double t;
do{
for(int i = 0; i < SENSORS_AMOUNT; ++i){
if(got[i]) continue;
uint8_t ch = all_sensors[i].nch;
if(ch != curch){
if(!setchan(ch)){
WARNX("Error selecting channel %d", ch);
break;
}
else curch = ch;
}
if(!started[i]){
if(sensor_start(all_sensors[i].sensor)) started[i] = 1;
else WARNX("Can't start %s", all_sensors[i].name);
}
sensor_status_t sstat = sensor_process(all_sensors[i].sensor);
if(sstat == SENS_RDY){ got[i] = 1; ++Ngot; }
}
}while(Ngot != SENSORS_AMOUNT && sl_dtime() - t0 < G.interval);
if(Ngot != SENSORS_AMOUNT){ // try to reset bad sensors
for(int i = 0; i < SENSORS_AMOUNT; ++i){
if(got[i]) continue;
DBG("TRY TO INIT bad sensor #%d (%s)", i, all_sensors[i].name);
sensor_init(all_sensors[i].sensor, all_sensors[i].address);
}
}
if(Ngot) writedata(got);
while((t = sl_dtime()) - t0 < G.interval) usleep(1000);
t0 = t;
}
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) sl_showhelp(-1, cmdlnopts);
if(G.interval < 1.) ERRX("Interval should be >=1s");
if(G.mul_addr < 1 || G.mul_addr > 0x7f) ERRX("Wrong multiplexer address");
if(G.Tlog) tlogf = openlog(G.Tlog);
else tlogf = stdout;
if(G.Hlog) hlogf = openlog(G.Hlog);
else hlogf = stdout;
if(G.Plog) plogf = openlog(G.Plog);
else plogf = stdout;
if(!sensors_open(G.device)) ERRX("Can't open device %s", G.device);
signal(SIGINT, signals);
signal(SIGQUIT, signals);
signal(SIGABRT, signals);
signal(SIGTERM, signals);
signal(SIGHUP, SIG_IGN);
signal(SIGTSTP, SIG_IGN);
initsensors();
writeheader();
startlogs();
signals(0);
return 0; // never reached
}

View File

@@ -0,0 +1,121 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <unistd.h>
#include <usefull_macros.h>
#include "i2csensorsPTH.h"
typedef struct{
char *device;
char *sensor;
int slaveaddr;
int list;
int presmm; // pressure in mm instead of hPa
int help;
int heater; // turn on/off heater (if present)
} glob_pars;
static glob_pars G = {
.device = "/dev/i2c-6",
.heater = -1,
};
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), "I2C device path"},
{"address", NEED_ARG, NULL, 'a', arg_int, APTR(&G.slaveaddr), "sensor's address (if not default)"},
{"sensor", NEED_ARG, NULL, 's', arg_string, APTR(&G.sensor), "sensor's name"},
{"list", NO_ARGS, NULL, 'l', arg_int, APTR(&G.list), "list all supported sensors"},
{"presmm", NO_ARGS, NULL, 'm', arg_int, APTR(&G.presmm), "pressure in mmHg instead of hPa"},
{"heater", NEED_ARG, NULL, 'H', arg_int, APTR(&G.heater), "turn on/off heater (if present)"},
end_option
};
static int start(sensor_t *s, uint8_t addr){
if(!sensor_init(s, addr)){
WARNX("Can't init sensor");
return FALSE;
}
if(!sensor_start(s)){
WARNX("Can't start measurements");
return FALSE;
}
return TRUE;
}
static int printdata(sensor_t *s){
sensor_data_t D;
if(!sensor_getdata(s, &D)){
WARNX("Can't read data, try again");
if(!sensor_start(s)) WARNX("Oops: can't start");
return FALSE;
}
sensor_props_t props = sensor_properties(s);
if(props.T) printf("T=%.2f\n", D.T);
if(props.H) printf("H=%.2f\n", D.H);
if(props.P){
if(G.presmm) D.P *= 0.750062;
printf("P=%.1f\n", D.P);
}
return TRUE;
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) sl_showhelp(-1, cmdlnopts);
if(G.list){
char *l = sensors_list();
green("\nSupported sensors:\n");
printf("%s\n\n", l);
FREE(l);
return 0;
}
if(!G.sensor) ERRX("Point sensor's name");
if(G.slaveaddr && (G.slaveaddr < 8 || G.slaveaddr > 0x77)) ERRX("I2C address should be 7-bit and not forbidden");
if(!sensors_open(G.device)) ERR("Can't open %s", G.device);
sensor_t* s = sensor_new(G.sensor);
if(!s){ WARNX("Can't find sensor `%s` in supported list", G.sensor); goto clo; }
if(G.heater > -1){
sensor_props_t props = sensor_properties(s);
if(props.htr){
if(!sensor_init(s, G.slaveaddr)) ERRX("Can't init device");
if(!sensor_heater(s, G.heater)) WARNX("Cant run heater command");
else green("Heater is %s\n", G.heater ? "on" : "off");
}else ERRX("The sensor have no heater");
return 0;
}
if(!start(s, G.slaveaddr)) goto clo;
while(1){
sensor_status_t status = sensor_process(s);
if(status == SENS_RDY){ // data ready - get it
if(!printdata(s)) continue;
break;
}else if(status == SENS_ERR){
WARNX("Error in measurement, try again");
if(!start(s, G.slaveaddr)) break;
}
usleep(10000);
}
sensor_delete(&s);
clo:
sensors_close();
return 0;
}

233
I2Csensors/i2c.c Normal file
View File

@@ -0,0 +1,233 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <asm/ioctl.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <usefull_macros.h>
static uint8_t lastaddr = 0;
static int I2Cfd = -1;
// common function for raw read or write
static int i2c_rw(uint8_t *data, int len, uint16_t flags){
struct i2c_msg m;
struct i2c_rdwr_ioctl_data x = {.msgs = &m, .nmsgs = 1};
m.addr = lastaddr;
m.flags = flags; // 0 for w and I2C_M_RD for read
m.len = len;
m.buf = data;
DBG("write %d with len %d", *data, len);
if(ioctl(I2Cfd, I2C_RDWR, &x) < 0){
DBG("i2c_rw, ioctl()");
return FALSE;
}
return TRUE;
}
int i2c_write_raw(uint8_t *data, int len){
if(!data || I2Cfd < 1 || len < 1) return FALSE;
return i2c_rw(data, len, 0);
}
int i2c_read_raw(uint8_t *data, int len){
if(!data || I2Cfd < 1 || len < 1) return FALSE;
return i2c_rw(data, len, I2C_M_RD);
}
/**
* @brief i2c_read_reg8 - read 8-bit addressed register (8 bit)
* @param regaddr - register address
* @param data - data read
* @return state
*/
int i2c_read_reg8(uint8_t regaddr, uint8_t *data){
if(I2Cfd < 1) return FALSE;
struct i2c_smbus_ioctl_data args;
union i2c_smbus_data sd;
args.read_write = I2C_SMBUS_READ;
args.command = regaddr;
args.size = I2C_SMBUS_BYTE_DATA;
args.data = &sd;
if(ioctl(I2Cfd, I2C_SMBUS, &args) < 0){
WARN("i2c_read_reg8, ioctl()");
return FALSE;
}
if(data) *data = sd.byte;
return TRUE;
}
/**
* @brief i2c_write_reg8 - write to 8-bit addressed register
* @param regaddr - address
* @param data - data
* @return state
*/
int i2c_write_reg8(uint8_t regaddr, uint8_t data){
if(I2Cfd < 1) return FALSE;
struct i2c_smbus_ioctl_data args;
union i2c_smbus_data sd;
sd.byte = data;
args.read_write = I2C_SMBUS_WRITE;
args.command = regaddr;
args.size = I2C_SMBUS_BYTE_DATA;
args.data = &sd;
if(ioctl(I2Cfd, I2C_SMBUS, &args) < 0){
WARN("i2c_write_reg8, ioctl()");
return FALSE;
}
return TRUE;
}
/**
* @brief i2c_read_reg16 - read 16-bit addressed register (to 16-bit data)
* @param regaddr - address
* @param data - data
* @return state
*/
int i2c_read_reg16(uint16_t regaddr, uint16_t *data){
if(I2Cfd < 1) return FALSE;
struct i2c_msg m[2];
struct i2c_rdwr_ioctl_data x = {.msgs = m, .nmsgs = 2};
m[0].addr = lastaddr; m[1].addr = lastaddr;
m[0].flags = 0;
m[1].flags = I2C_M_RD;
m[0].len = 2; m[1].len = 2;
uint8_t a[2], d[2] = {0};
a[0] = regaddr >> 8;
a[1] = regaddr & 0xff;
m[0].buf = a; m[1].buf = d;
if(ioctl(I2Cfd, I2C_RDWR, &x) < 0){
WARN("i2c_read_reg16, ioctl()");
return FALSE;
}
if(data) *data = (uint16_t)((d[0] << 8) | (d[1]));
return TRUE;
}
/**
* @brief i2c_write_reg16 - write 16-bit data value to 16-bit addressed register
* @param regaddr - address
* @param data - data to write
* @return state
*/
int i2c_write_reg16(uint16_t regaddr, uint16_t data){
if(I2Cfd < 1) return FALSE;
union i2c_smbus_data d;
d.block[0] = 3;
d.block[1] = regaddr & 0xff;
d.block[2] = data >> 8;
d.block[3] = data & 0xff;
struct i2c_smbus_ioctl_data args;
args.read_write = I2C_SMBUS_WRITE;
args.command = regaddr >> 8;
args.size = I2C_SMBUS_I2C_BLOCK_DATA;
args.data = &d;
if(ioctl(I2Cfd, I2C_SMBUS, &args) < 0){
WARN("i2c_write_reg16, ioctl()");
return FALSE;
}
return TRUE;
}
/**
* @brief i2c_set_slave_address - set current slave address
* @param addr - address
* @return state
*/
int i2c_set_slave_address(uint8_t addr){
if(I2Cfd < 1) return FALSE;
DBG("Try to set slave addr 0x%02X", addr);
if(ioctl(I2Cfd, I2C_SLAVE, addr) < 0){
WARN("i2c_set_slave_address, ioctl()");
return FALSE;
}
lastaddr = addr;
return TRUE;
}
/**
* @brief i2c_open - open I2C device
* @param path - full path to device
* @return state
*/
int i2c_open(const char *path){
if(I2Cfd > 0) close(I2Cfd);
I2Cfd = open(path, O_RDWR);
if(I2Cfd < 1){
WARN("i2c_open, open()");
return FALSE;
}
return TRUE;
}
void i2c_close(){
if(I2Cfd > 0) close(I2Cfd);
}
/**
* @brief read_data16 - read data from 16-bit addressed register
* @param regaddr - address
* @param N - amount of BYTES!!!
* @param array - data read
* @return state
*/
int i2c_read_data16(uint16_t regaddr, uint16_t N, uint8_t *array){
if(I2Cfd < 1 || N == 0 || !array) return FALSE;
struct i2c_msg m[2];
struct i2c_rdwr_ioctl_data x = {.msgs = m, .nmsgs = 2};
m[0].addr = lastaddr; m[1].addr = lastaddr;
m[0].flags = 0;
m[1].flags = I2C_M_RD;
m[0].len = 2; m[1].len = N;
uint8_t a[2];
a[0] = regaddr >> 8;
a[1] = regaddr & 0xff;
m[0].buf = a; m[1].buf = array;
if(ioctl(I2Cfd, I2C_RDWR, &x) < 0){
WARN("i2c_read_data16, ioctl()");
return FALSE;
}
return TRUE;
}
/**
* @brief read_data8 - read data from 8-bit addressed register
* @param regaddr - address
* @param N - amount of bytes
* @param array - data read
* @return state
*/
int i2c_read_data8(uint8_t regaddr, uint16_t N, uint8_t *array){
if(I2Cfd < 1 || N < 1 || N+regaddr > 0xff || !array) return FALSE;
struct i2c_msg m[2];
struct i2c_rdwr_ioctl_data x = {.msgs = m, .nmsgs = 2};
m[0].addr = lastaddr; m[1].addr = lastaddr;
m[0].flags = 0; // write register address
m[1].flags = I2C_M_RD; // read contents
m[0].len = 1; m[1].len = N;
m[0].buf = &regaddr; m[1].buf = array;
if(ioctl(I2Cfd, I2C_RDWR, &x) < 0){
WARN("i2c_read_data8, ioctl()");
return FALSE;
}
return TRUE;
}

40
I2Csensors/i2c.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
#ifndef FALSE
#define FALSE 0
#endif
#ifndef TRUE
#define TRUE 1
#endif
int i2c_open(const char *path);
void i2c_close();
int i2c_set_slave_address(uint8_t addr);
int i2c_write_raw(uint8_t *data, int len);
int i2c_read_raw(uint8_t *data, int len);
int i2c_read_reg8(uint8_t regaddr, uint8_t *data);
int i2c_write_reg8(uint8_t regaddr, uint8_t data);
int i2c_read_data8(uint8_t regaddr, uint16_t N, uint8_t *array);
int i2c_read_reg16(uint16_t regaddr, uint16_t *data);
int i2c_write_reg16(uint16_t regaddr, uint16_t data);
int i2c_read_data16(uint16_t regaddr, uint16_t N, uint8_t *array);

View File

@@ -0,0 +1,68 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// timeout of i2c waiting
#define I2C_TIMEOUT (.5)
typedef enum{
SENS_NOTINIT, // wasn't inited
SENS_BUSY, // measurement in progress
SENS_ERR, // error occured
SENS_RELAX, // do nothing
SENS_RDY, // data ready - can get it
} sensor_status_t;
typedef union{
struct{
uint8_t T : 1; // can temperature (degC)
uint8_t H : 1; // can humidity (percent)
uint8_t P : 1; // can pressure (hPa)
uint8_t htr : 1; // have heater
};
uint32_t flags;
} sensor_props_t;
typedef struct{
double T;
double H;
double P;
} sensor_data_t;
//struct sensor_struct;
typedef struct sensor_struct sensor_t;
int sensors_open(const char *dev);
void sensors_close();
char *sensors_list();
sensor_t* sensor_new(const char *name);
void sensor_delete(sensor_t **s);
sensor_props_t sensor_properties(sensor_t *s);
int sensor_init(sensor_t *s, uint8_t address);
int sensor_heater(sensor_t *s, int on);
int sensor_start(sensor_t *s);
sensor_status_t sensor_process(sensor_t *s);
int sensor_getdata(sensor_t *s, sensor_data_t *d);
// I2C functions for client usage
int sensor_writeI2C(uint8_t addr, uint8_t *data, int len);
int sensor_readI2C(uint8_t addr, uint8_t *data, int len);
int sensor_readI2Cregs(uint8_t addr, uint8_t regaddr, uint16_t N, uint8_t *data);
int sensor_writeI2Creg(uint8_t addr, uint8_t regaddr, uint8_t data);

View File

@@ -0,0 +1,10 @@
prefix=@CMAKE_INSTALL_PREFIX@
exec_prefix=${prefix}
libdir=${exec_prefix}/lib
includedir=${prefix}/include
Name: @PROJ@
Description: I2C library working with pressure/temperature/humidity sensors
Version: @VERSION@
Libs: -L${libdir} -l@PROJ@
Cflags: -I${includedir}

186
I2Csensors/sensor.c Normal file
View File

@@ -0,0 +1,186 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "aht.h"
#include "BMP180.h"
#include "BMP280.h"
#include "BMP580.h"
#include "i2c.h"
#include "sensors_private.h"
#include "SHT3x.h"
#include "SI7005.h"
// NULL-terminated list of all supported sensors
static const sensor_t* supported_sensors[] = {&AHT10, &AHT15, &AHT20, &AHT21, &BME280, &BMP180, &BMP280, &BMP580, &SHT3x, &SI7005, NULL};
// just two stupid wrappers
int sensors_open(const char *dev){
return i2c_open(dev);
}
void sensors_close(){
i2c_close();
}
// init sensor with optional new address
int sensor_init(sensor_t *s, uint8_t address){
if(!s) return FALSE;
if(address) s->address = address;
else address = s->address; // default
if(!i2c_set_slave_address(address)){
DBG("Can't set slave address 0x%02x", address);
return FALSE;
}
if(!i2c_read_reg8(0, NULL)){
DBG("Can't connect!");
return FALSE;
}
double t0 = sl_dtime();
int result = FALSE;
while(sl_dtime() - t0 < I2C_TIMEOUT && !(result = s->init(s))) usleep(10000);
DBG("INIT: %d", result);
return result;
}
// find supported sensor by name and return allocated struct
sensor_t *sensor_new(const char *name){
if(!name || !*name) return NULL;
const sensor_t **p = supported_sensors;
while(*p){
if(0 == strcmp((*p)->name, name)){
sensor_t *n = MALLOC(sensor_t, 1);
memcpy(n, *p, sizeof(sensor_t));
return n;
}
++p;
}
return NULL;
}
void sensor_delete(sensor_t **s){
if(!s || !*s) return;
if((*s)->privdata) FREE((*s)->privdata);
// here could be additional free's
FREE((*s));
}
// list all supported sensors, return allocated string - should be free'd later
char *sensors_list(){
const sensor_t **p = supported_sensors;
int L = 0, rest = 0, idx = 0;
char *list = NULL;
#define PORTIONSZ 256
while(*p){
int l = strlen((*p)->name);
if(rest < l+2){
int add = PORTIONSZ * ((PORTIONSZ + 2 + l - rest) / PORTIONSZ);
rest += add; L += add;
list = realloc(list, L);
}
if(idx == 0) l = sprintf(list, "%s", (*p)->name);
else l = sprintf(list+idx, ",%s", (*p)->name);
rest -= l; idx += l;
//DBG("L=%d, rest=%d, idx=%d, list='%s'", L, rest, idx, list);
++p;
}
#undef PORTIONSZ
return list;
}
// wrapper with timeout
int sensor_start(sensor_t *s){
if(!s) return FALSE;
DBG("Start");
if(!i2c_set_slave_address(s->address)){
DBG("Can't set slave address 0x%02x", s->address);
return FALSE;
}
double t0 = sl_dtime();
int result = FALSE;
while(sl_dtime() - t0 < I2C_TIMEOUT && !(result = s->start(s))) usleep(10000);
DBG("result: %d", result);
return result;
}
int sensor_getdata(sensor_t *s, sensor_data_t *d){
if(!s || !d) return FALSE;
if(s->status != SENS_RDY) return FALSE;
*d = s->data;
s->status = SENS_RELAX;
return TRUE;
}
sensor_status_t sensor_process(sensor_t *s){
if(!s) return FALSE;
DBG("Process");
if(!i2c_set_slave_address(s->address)){
DBG("Can't set slave address 0x%02x", s->address);
return FALSE;
}
return s->process(s);
}
sensor_props_t sensor_properties(sensor_t *s){
sensor_props_t def = {0};
if(!s) return def;
return s->properties(s);
}
int sensor_heater(sensor_t *s, int on){
if(!s || !s->properties(s).htr || !s->heater) return FALSE;
if(!i2c_set_slave_address(s->address)){
DBG("Can't set slave address 0x%02x", s->address);
return FALSE;
}
return s->heater(s, on);
}
int sensor_writeI2C(uint8_t addr, uint8_t *data, int len){
if(!data || len < 1) return FALSE;
if(!i2c_set_slave_address(addr)){
DBG("Can't set slave address 0x%02x", addr);
return FALSE;
}
return i2c_write_raw(data, len);
}
int sensor_readI2C(uint8_t addr, uint8_t *data, int len){
if(!data || len < 1) return FALSE;
if(!i2c_set_slave_address(addr)){
DBG("Can't set slave address 0x%02x", addr);
return FALSE;
}
return i2c_read_raw(data, len);
}
int sensor_readI2Cregs(uint8_t addr, uint8_t regaddr, uint16_t N, uint8_t *data){
if(!data || N < 1) return FALSE;
if(!i2c_set_slave_address(addr)){
DBG("Can't set slave address 0x%02x", addr);
return FALSE;
}
return i2c_read_data8(regaddr, N, data);
}
int sensor_writeI2Creg(uint8_t addr, uint8_t regaddr, uint8_t data){
if(!i2c_set_slave_address(addr)){
DBG("Can't set slave address 0x%02x", addr);
return FALSE;
}
return i2c_write_reg8(regaddr, data);
}

1
I2Csensors/sensor.cflags Normal file
View File

@@ -0,0 +1 @@
-std=c17

2
I2Csensors/sensor.config Normal file
View File

@@ -0,0 +1,2 @@
#define _XOPEN_SOURCE 9999
#define _POSIX_C_SOURCE 333333L

View File

@@ -0,0 +1 @@
[General]

View File

@@ -0,0 +1,217 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 18.0.0, 2025-12-08T20:56:31. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
<value type="QString" key="ClangCodeModel.WarningConfigId">Builtin.BuildSystem</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">2</value>
<value type="bool" key="ClangTools.PreferConfigFile">false</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
<value type="int" key="RcSync">0</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/1/home/eddy/BMP180</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.UniqueId"></value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1 @@
-std=c++17

22
I2Csensors/sensor.files Normal file
View File

@@ -0,0 +1,22 @@
BMP180.c
BMP180.h
BMP280.c
BMP280.h
BMP580.c
BMP580.h
SHT3x.c
SHT3x.h
SI7005.c
SI7005.h
aht.c
aht.h
examples/CMakeLists.txt
examples/logmany.c
examples/single_sensor.c
i2c.c
i2c.h
i2csensorsPTH.h
main.c
sensor.c
sensor.h
sensors_private.h

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -0,0 +1 @@
.

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include "i2csensorsPTH.h"
// unfortunately, we have no "self" pointer in C, so we should add this struct calling to each function for further purposes
struct sensor_struct{
const char *name; // name
uint8_t address; // sensor's address
uint32_t private; // private information (e.g. for almost similar sensors with some slight differences)
void *privdata; // some private data for calibration etc
sensor_status_t status; // status of sensor
sensor_data_t data; // measured data
int (*init)(struct sensor_struct*); // init device - @ start after POR or in case of errors
int (*start)(struct sensor_struct*); // start measuring
int (*heater)(struct sensor_struct *, int); // turn heater on/off (1/0)
sensor_status_t (*process)(struct sensor_struct*); // main polling process
sensor_props_t (*properties)(struct sensor_struct*); // get properties
};

58
MLX90640_test/Makefile Normal file
View File

@@ -0,0 +1,58 @@
# run `make DEF=...` to add extra defines
PROGRAM := mlx
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros -L/usr/local/lib -lm -flto
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines -std=gnu99 -flto
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
DEPS := $(OBJS:.o=.d)
TARGFILE := $(OBJDIR)/TARGET
CC = gcc
#TARGET := RELEASE
ifeq ($(shell test -e $(TARGFILE) && echo -n yes),yes)
TARGET := $(file < $(TARGFILE))
else
TARGET := RELEASE
endif
ifeq ($(TARGET), DEBUG)
.DEFAULT_GOAL := debug
endif
release: $(PROGRAM)
debug: CFLAGS += -DEBUG -Werror
debug: TARGET := DEBUG
debug: $(PROGRAM)
$(TARGFILE): $(OBJDIR)
@echo -e "\t\tTARGET: $(TARGET)"
@echo "$(TARGET)" > $(TARGFILE)
$(PROGRAM) : $(TARGFILE) $(OBJS)
@echo -e "\t\tLD $(PROGRAM)"
$(CC) $(OBJS) $(LDFLAGS) -o $(PROGRAM)
$(OBJDIR):
@mkdir $(OBJDIR)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR)/%.o: %.c
@echo -e "\t\tCC $<"
$(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $<
clean:
@echo -e "\t\tCLEAN"
@rm -rf $(OBJDIR) 2>/dev/null || true
xclean: clean
@rm -f $(PROGRAM)
.PHONY: clean xclean

2
MLX90640_test/Readme Normal file
View File

@@ -0,0 +1,2 @@
device-independent MLX90640 processing & test
based on data example from melexis

1
MLX90640_test/alpha.csv Normal file

File diff suppressed because one or more lines are too long

1
MLX90640_test/eeprom.csv Normal file

File diff suppressed because one or more lines are too long

1
MLX90640_test/frame0.csv Normal file

File diff suppressed because one or more lines are too long

1
MLX90640_test/frame1.csv Normal file

File diff suppressed because one or more lines are too long

1
MLX90640_test/kta.csv Normal file

File diff suppressed because one or more lines are too long

1
MLX90640_test/kv.csv Normal file

File diff suppressed because one or more lines are too long

43
MLX90640_test/main.c Normal file
View File

@@ -0,0 +1,43 @@
/*
* This file is part of the mlxtest project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "mlx90640.h"
#include "testdata.h"
int main (int _U_ argc, char _U_ **argv){
sl_init();
MLX90640_params p;
if(!get_parameters(EEPROM, &p)) ERRX("Can't get parameters from test data");
dump_parameters(&p, &extracted_parameters);
fp_t *sp;
for(int i = 0; i < 2; ++i){
printf("Process subpage %d\n", i);
sp = process_subpage(&p, DataFrame[i], i, 2);
if(!sp) ERRX("WTF?");
dumpIma(sp);
chkImage(sp, ToFrame[i]);
}
drawIma(sp);
return 0;
}

516
MLX90640_test/mlx90640.c Normal file
View File

@@ -0,0 +1,516 @@
/*
* This file is part of the mlxtest project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "mlx90640.h"
#include "mlx90640_regs.h"
// tolerance of floating point comparison
#define FP_TOLERANCE (1e-3)
static fp_t mlx_image[MLX_PIXNO] = {0}; // ready image
#ifdef EBUG
static double Tlast = 0.;
#define chstate() do{Tlast = sl_dtime(); DBG("chstate()");}while(0)
#endif
void dumpIma(const fp_t im[MLX_PIXNO]){
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
printf("%5.1f ", *im++);
}
printf("\n");
}
}
#define GRAY_LEVELS (16)
// 16-level character set ordered by fill percentage (provided by user)
static const char* CHARS_16 = " .':;+*oxX#&%B$@";
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;
if(fabs(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-1) + 0.5);
// Ensure we stay within bounds
if(index < 0) index = 0;
else if(index > (GRAY_LEVELS-1)) index = (GRAY_LEVELS-1);
putchar(CHARS_16[index]);
}
putchar('\n');
}
printf("\nTemperature range: %.2f to %.2f\n", min_val, max_val);
}
static void chki(const char *name, int16_t param, int16_t standard){
printf("%*s | %-16d | %-16d - ", -16, name, param, standard);
if(param != standard){
red("NOT equal!\n"); exit(1);
}
printf("OK\n");
}
static void chkf(const char *name, fp_t param, fp_t standard){
printf("%*s | %-16g | %-16g - ", -16, name, param, standard);
fp_t diff = (fabs(param) + fabs(standard)) * FP_TOLERANCE;
if(fabs(param - standard) > diff){
// DBG("diff = %g", diff);
red("NOT equal!\n"); exit(1);
}
printf("OK\n");
}
static void chkfa(const char *name, const fp_t *ap, const fp_t *as, int n){
char buf[16];
snprintf(buf, 15, "(size %d)", n);
printf("%*s | %-16s | %-16s - ", -16, name, "(array)", buf);
for(int i = 0; i < n; ++i){
fp_t diff = (fabs(as[i]) + fabs(ap[i])) * FP_TOLERANCE;
if(fabs(ap[i] - as[i]) > diff){
// DBG("diff = %g", diff);
red("NOT equal on index %d (%g and %g)\n", i, ap[i], as[i]);
exit(1);
}
}
printf("OK");
if(n < 10){
for(int i = 0; i < n; ++i) printf(" %g", as[i]);
}
printf("\n");
}
static void chku8a(const char *name, const uint8_t *ap, const uint8_t *as, int n){
char buf[16];
snprintf(buf, 15, "(size %d)", n);
printf("%*s | %-16s | %-16s - ", -16, name, "(array)", buf);
for(int i = 0; i < n; ++i){
if(ap[i] != as[i]){
red("NOT equal on index %d (%d and %d)\n", i, ap[i], as[i]);
exit(1);
}
}
printf("OK");
if(n < 10){
for(int i = 0; i < n; ++i) printf(" %d", as[i]);
}
printf("\n");
}
void chkImage(const fp_t Image[MLX_PIXNO], const fp_t ToFrame[MLX_PIXNO]){
chkfa("Image", Image, ToFrame, MLX_PIXNO);
}
void dump_parameters(MLX90640_params *params, const MLX90640_params *standard){
printf("###########################################################\n");
printf("%*s | %*s | %*s\n###########################################################\n",
-16, "# name", -16, "value", -16, "standard");
#define CHKI(f) do{chki(#f, params->f, standard->f);}while(0)
#define CHKF(f) do{chkf(#f, params->f, standard->f);}while(0)
#define CHKFA(f, n) do{chkfa(#f, params->f, standard->f, n);}while(0)
#define CHKU8A(f, n) do{chku8a(#f, params->f, standard->f, n);}while(0)
CHKI(kVdd);
CHKI(vdd25);
CHKF(KvPTAT);
CHKI(vPTAT25);
CHKF(alphaPTAT);
CHKI(gainEE);
CHKF(tgc);
CHKF(cpKv);
CHKF(cpKta);
CHKF(KsTa);
CHKFA(CT, 3);
CHKFA(KsTo, 4);
CHKFA(alpha, MLX_PIXNO);
CHKFA(offset, MLX_PIXNO);
CHKFA(kta, MLX_PIXNO);
CHKFA(kv, 4);
CHKFA(cpAlpha, 2);
CHKI(resolEE);
CHKI(cpOffset[0]); CHKI(cpOffset[1]);
CHKU8A(outliers, MLX_PIXNO);
#if 0
printf("kVdd=%d\nvdd25=%d\nKvPTAT=%g\nKtPTAT=%g\nvPTAT25=%d\n", params->kVdd, params->vdd25, params->KvPTAT, params->KtPTAT, params->vPTAT25);
printf("alphaPTAT=%g\ngainEE=%d\ntgc=%g\ncpKv=%g\ncpKta=%g\n", params->alphaPTAT, params->gainEE, params->tgc, params->cpKv, params->cpKta);
printf("KsTa=%g\nCT[]={%g, %g, %g}\n", params->KsTa, params->CT[0], params->CT[1], params->CT[2]);
printf("KsTo[]={"); for(int i = 0; i < 4; ++i) printf("%s%g", (i) ? ", " : "", params->KsTo[i]); printf("}\n");
printf("alphacorr[]={"); for(int i = 0; i < 4; ++i) printf("%s%g", (i) ? ", " : "", params->alphacorr[i]); printf("}\n");
printf("alpha[]=\n"); dumpIma(params->alpha);
printf("offset[]=\n"); dumpIma(params->offset);
printf("kta[]=\n"); dumpIma(params->kta);
printf("kv[]={"); for(int i = 0; i < 4; ++i) printf("%s%g", (i) ? ", " : "", params->kv[i]); printf("}\n");
printf("cpAlpha[]={%g, %g}\n", params->cpAlpha[0], params->cpAlpha[1]);
printf("cpOffset[]={%d, %d}\n", params->cpOffset[0], params->cpOffset[1]);
printf("outliers[]=\n");
uint8_t *o = params->outliers;
for(int row = 0; row < MLX_H; ++row){
for(int col = 0; col < MLX_W; ++col){
printf("%d ", *o++);
}
printf("\n");
}
#endif
#undef CHKI
#undef CHKF
}
/*****************************************************************************
Calculate parameters & values
*****************************************************************************/
// fill OCC/ACC row/col arrays
static void occacc(int8_t *arr, int l, const 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
int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *params){
#define CREG_VAL(reg) dataarray[CREG_IDX(reg)]
int8_t i8;
int16_t i16;
uint16_t *pu16;
uint16_t val = CREG_VAL(REG_VDD);
i8 = (int8_t) (val >> 8);
params->kVdd = i8 * 32; // keep sign
if(params->kVdd == 0) return FALSE;
i16 = val & 0xFF;
params->vdd25 = ((i16 - 0x100) * 32) - (1<<13);
val = CREG_VAL(REG_KVTPTAT);
i16 = (val & 0xFC00) >> 10;
if(i16 > 0x1F) i16 -= 0x40;
params->KvPTAT = (fp_t)i16 / (1<<12);
i16 = (val & 0x03FF);
if(i16 > 0x1FF) i16 -= 0x400;
params->KtPTAT = (fp_t)i16 / 8.;
params->vPTAT25 = (int16_t) CREG_VAL(REG_PTAT);
val = CREG_VAL(REG_APTATOCCS) >> 12;
params->alphaPTAT = val / 4. + 8.;
params->gainEE = (int16_t)CREG_VAL(REG_GAIN);
if(params->gainEE == 0) return FALSE;
int8_t occRow[MLX_H];
int8_t occColumn[MLX_W];
occacc(occRow, MLX_H, &CREG_VAL(REG_OCCROW14));
occacc(occColumn, MLX_W, &CREG_VAL(REG_OCCCOL14));
int8_t accRow[MLX_H];
int8_t accColumn[MLX_W];
occacc(accRow, MLX_H, &CREG_VAL(REG_ACCROW14));
occacc(accColumn, MLX_W, &CREG_VAL(REG_ACCCOL14));
val = CREG_VAL(REG_APTATOCCS);
// need to do multiplication instead of bitshift, so:
fp_t 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 should 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 (1,3,..), even row (2,4,..) -> 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;
fp_t mul = (fp_t)(1<<scale2), div = (fp_t)(1<<scale1); // kta_scales
uint16_t a_r = CREG_VAL(REG_SENSIVITY); // alpha_ref
val = CREG_VAL(REG_SCALEACC);
fp_t *a = params->alpha;
uint32_t diva32 = 1 << (val >> 12);
fp_t diva = (fp_t)(diva32);
diva *= (fp_t)(1<<30); // alpha_scale
DBG("diva: %g", diva);
fp_t accRowScale = 1<<((val & 0x0f00)>>8),
accColumnScale = 1<<((val & 0x00f0)>>4),
accRemScale = 1<<(val & 0x0f);
pu16 = (uint16_t*)&CREG_VAL(REG_OFFAK1);
fp_t *kta = params->kta, *offset = params->offset;
uint8_t *ol = params->outliers;
for(int row = 0; row < MLX_H; ++row){
int idx = (row&1)<<1;
for(int col = 0; col < MLX_W; ++col){
// offset
register uint16_t rv = *pu16++;
i16 = (rv & 0xFC00) >> 10;
if(i16 > 0x1F) i16 -= 0x40;
*offset++ = (fp_t)offavg + (fp_t)occRow[row]*occRowScale + (fp_t)occColumn[col]*occColumnScale + (fp_t)i16*occRemScale;
// kta
i16 = (rv & 0xF) >> 1;
if(i16 > 0x03) i16 -= 0x08;
*kta++ = (ktaavg[idx|(col&1)] + i16*mul) / div;
// alpha
i16 = (rv & 0x3F0) >> 4;
if(i16 > 0x1F) i16 -= 0x40;
fp_t oft = (fp_t)a_r + accRow[row]*accRowScale + accColumn[col]*accColumnScale +i16*accRemScale;
*a++ = oft / diva;
*ol++ = (rv&1) ? 1 : 0;
}
}
scale1 = (CREG_VAL(REG_KTAVSCALE) >> 8) & 0xF; // kvscale
div = (fp_t)(1<<scale1);
val = CREG_VAL(REG_KVAVG);
// kv indexes: +2 for odd (ÎÅÞÅÔÎÙÈ) rows, +1 for odd columns, so:
// [ 3, 2; 1, 0] for left upper corner (because datashit counts from 1, not from 0!)
i16 = val >> 12; if(i16 > 0x07) i16 -= 0x10;
ktaavg[0] = (int8_t)i16; // odd col, odd row
i16 = (val & 0xF0) >> 4; if(i16 > 0x07) i16 -= 0x10;
ktaavg[1] = (int8_t)i16; // even col, odd row
i16 = (val & 0x0F00) >> 8; if(i16 > 0x07) i16 -= 0x10;
ktaavg[2] = (int8_t)i16; // odd col, even row
i16 = val & 0x0F; if(i16 > 0x07) i16 -= 0x10;
ktaavg[3] = (int8_t)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 = (fp_t)i8 / (1<<val);
val = (CREG_VAL(REG_KTAVSCALE) & 0x0F00) >> 8;
i16 = CREG_VAL(REG_KVTACP) >> 8;
if(i16 > 0x7F) i16 -= 0x100;
params->cpKv = (fp_t)i16 / (1<<val);
i16 = CREG_VAL(REG_KSTATGC) & 0xFF;
if(i16 > 0x7F) i16 -= 0x100;
params->tgc = (fp_t)i16;
params->tgc /= 32.;
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 = (fp_t)(1<<val);
div *= (fp_t)(1<<27);
params->cpAlpha[0] = (fp_t)(CREG_VAL(REG_ALPHA) & 0x03FF) / div;
div = (fp_t)(1<<7);
params->cpAlpha[1] = params->cpAlpha[0] * (1. + (fp_t)i16/div);
i8 = (int8_t)(CREG_VAL(REG_KSTATGC) >> 8);
params->KsTa = (fp_t)i8/(1<<13);
div = 1<<((CREG_VAL(REG_CT34) & 0x0F) + 8); // kstoscale
DBG("kstoscale=%g (regct34=0x%04x)", div, CREG_VAL(REG_CT34));
val = CREG_VAL(REG_KSTO12);
DBG("ksto12=0x%04x", val);
i8 = (int8_t)(val & 0xFF);
DBG("To1ee=%d", i8);
params->KsTo[0] = i8 / div;
i8 = (int8_t)(val >> 8);
DBG("To2ee=%d", i8);
params->KsTo[1] = i8 / div;
val = CREG_VAL(REG_KSTO34);
DBG("ksto34=0x%04x", val);
i8 = (int8_t)(val & 0xFF);
DBG("To3ee=%d", i8);
params->KsTo[2] = i8 / div;
i8 = (int8_t)(val >> 8);
DBG("To4ee=%d", i8);
params->KsTo[3] = i8 / div;
// CT1 = -40, CT2 = 0 -> start from zero index, so CT[0] is CT2, CT[1] is CT3, CT[2] is CT4
params->CT[0] = 0.; // 0degr - between ranges 1 and 2
val = CREG_VAL(REG_CT34);
mul = ((val & 0x3000)>>12)*10.; // 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
// alphacorr for each range: 11.1.11
params->alphacorr[0] = 1./(1. + params->KsTo[0] * 40.);
params->alphacorr[1] = 1.;
params->alphacorr[2] = (1. + params->KsTo[1] * params->CT[1]);
params->alphacorr[3] = (1. + params->KsTo[2] * (params->CT[2] - params->CT[1])) * params->alphacorr[2];
params->resolEE = (uint8_t)((CREG_VAL(REG_KTAVSCALE) & 0x3000) >> 12);
// Don't forget to check 'outlier' flags for wide purpose
return TRUE;
#undef CREG_VAL
}
/**
* @brief process_subpage - calculate all parameters from `dataarray` into `mlx_image`
* @param subpageno - number of subpage
* @param simpleimage == 0 - simplest, 1 - narrow range, 2 - extended range
*/
fp_t *process_subpage(MLX90640_params *params, const int16_t Frame[MLX_DMA_MAXLEN], int subpageno, int simpleimage){
#define IMD_VAL(reg) Frame[IMD_IDX(reg)]
DBG("\nprocess_subpage(%d)", subpageno);
#ifdef EBUG
chstate();
#endif
// 11.2.2.1. Resolution restore
// temporary:
fp_t resol_corr = (fp_t)(1<<params->resolEE) / (1<<2); // calibrated resol/current resol
DBG("resolEE=%d, resolCur=%d", params->resolEE, 2);
//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));
// 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;
dvdd /= params->kVdd;
fp_t dV = i16a - params->vdd25; // for next step
dV /= params->kVdd;
DBG("ram=%d, vdd25=%d, dvdd=%g, resol=%g", i16a, params->vdd25, dvdd, resol_corr);
DBG("Vd=%g", dvdd+3.3);
// 11.2.2.3. Ambient temperature calculation
i16a = (int16_t)IMD_VAL(REG_ITAPTAT);
int16_t i16b = (int16_t)IMD_VAL(REG_ITAVBE);
fp_t dTa = (fp_t)i16a / (i16a * params->alphaPTAT + i16b); // vptatart
dTa *= (fp_t)(1<<18);
dTa = (dTa / (1. + params->KvPTAT*dV)) - params->vPTAT25;
dTa = dTa / params->KtPTAT; // without 25degr - Ta0
DBG("Ta=%g", dTa+25.);
// 11.2.2.4. Gain parameter calculation
i16a = (int16_t)IMD_VAL(REG_IGAIN);
fp_t Kgain = params->gainEE / (fp_t)i16a;
DBG("Kgain=%g", Kgain);
fp_t pixOS[2]; // pix_gain_CP_SPx
// 11.2.2.6.1
pixOS[0] = ((int16_t)IMD_VAL(REG_ICPSP0))*Kgain; // pix_OS_CP_SPx
pixOS[1] = ((int16_t)IMD_VAL(REG_ICPSP1))*Kgain;
DBG("pixGain: %g/%g", pixOS[0], pixOS[1]);
for(int i = 0; i < 2; ++i){ // calc pixOS by gain
// 11.2.2.6.2
pixOS[i] -= params->cpOffset[i]*(1. + params->cpKta*dTa)*(1. + params->cpKv*dvdd);
}
// now make first approximation to image
uint16_t pixno = 0; // current pixel number - for indexing in parameters etc
for(int row = 0, rowidx = 0; row < MLX_H; ++row, rowidx ^= 2){
for(int col = 0, idx = rowidx; col < MLX_W; ++col, ++pixno, idx ^= 1){
uint8_t sp = (row&1)^(col&1); // subpage of current pixel
if(sp != subpageno) continue;
// 11.2.2.5.1
fp_t curval = (fp_t)(Frame[pixno]) * Kgain; // gain compensation
// 11.2.2.5.3
curval -= params->offset[pixno] * (1. + params->kta[pixno]*dTa) *
(1. + params->kv[idx]*dvdd); // add offset
// now `curval` is pix_OS == V_IR_emiss_comp (we can divide it by `emissivity` to compensate for it)
// 11.2.2.7: 'Pattern' is just subpage number!
fp_t IRcompens = curval - params->tgc * pixOS[subpageno]; // 11.2.2.8. Normalizing to sensitivity
if(simpleimage == 0){ // 13.3. Using the device in ?image mode?
curval = IRcompens;
}else{
// 11.2.2.8
fp_t alphaComp = params->alpha[pixno] - params->tgc * params->cpAlpha[subpageno];
alphaComp *= 1. + params->KsTa * dTa;
// 11.2.2.9: calculate To for basic range
fp_t Tar = dTa + 273.15 + 25.; // Ta+273.15
Tar = Tar*Tar*Tar*Tar; // T_aK4 (when \epsilon==1 this is T_{a-r} too)
fp_t ac3 = alphaComp*alphaComp*alphaComp;
fp_t Sx = ac3*IRcompens + alphaComp*ac3*Tar;
Sx = params->KsTo[1] * SQRT(SQRT(Sx));
fp_t To4 = IRcompens / (alphaComp * (1. - 273.15*params->KsTo[1]) + Sx) + Tar;
curval = SQRT(SQRT(To4)) - 273.15;
if(simpleimage == 2){ // 11.2.2.9.1.3. Extended To range calculation
int idx = 0; // range 1 by default
fp_t ctx = -40.;
if(curval > params->CT[2]){ // range 4
idx = 3; ctx = params->CT[2];
}else if(curval > params->CT[1]){ // range 3
idx = 2; ctx = params->CT[1];
}else if(curval > params->CT[0]){ // range 2, default
idx = 1; ctx = params->CT[0];
}
if(idx != 1){ // recalculate for extended range if we are out of standard range
To4 = IRcompens / (alphaComp * params->alphacorr[idx] * (1. + params->KsTo[idx]*(curval - ctx))) + Tar;
curval = SQRT(SQRT(To4)) - 273.15;
}
}
}
mlx_image[pixno] = curval;
}
}
DBG("Time: %g", sl_dtime()-Tlast);
return mlx_image;
#undef IMD_VAL
}
#if 0
// start image acquiring for next subpage
static int process_startima(int subpageno){
chstate();
DBG("startima(%d)", subpageno);
uint16_t reg, N;
while(1){
// write `overwrite` flag twice
if(!write_reg(REG_CONTROL, reg_control_val[subpageno]) ||
!write_reg(REG_STATUS, REG_STATUS_OVWEN) ||
!write_reg(REG_STATUS, REG_STATUS_OVWEN)) chkerr();
while(1){
if(read_reg(REG_STATUS, &reg)){
if(reg & REG_STATUS_NEWDATA){
DBG("got newdata: %g", sl_dtime() - Tlast);
if(subpageno != (reg & REG_STATUS_SPNO)){
DBG("wrong subpage number -> M_ERROR");
return FALSE;
}else{ // all OK, run image reading
chstate();
write_reg(REG_STATUS, 0); // clear rdy bit
N = MLX_PIXARRSZ;
if(read_data(REG_IMAGEDATA, &N) && N == MLX_PIXARRSZ){
DBG("got readoutm N=%d: %g", N, sl_dtime() - Tlast);
return TRUE;
}else chkerr();
}
}else chktmout();
}else chkerr();
}
}
return FALSE;
}
// 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, fp_t **image){
if(I2Cfd < 1) return FALSE;
if(params->kVdd == 0){ // no parameters -> make first run
if(!process_firstrun()) return FALSE;
}
DBG("\n\n\n-> M_STARTIMA");
for(int sp = 0; sp < 2; ++sp){
if(!process_startima(sp)) return FALSE; // get first subpage
process_subpage(sp, simple);
}
if(image) *image = mlx_image;
return TRUE;
}
#endif

67
MLX90640_test/mlx90640.h Normal file
View File

@@ -0,0 +1,67 @@
/*
* This file is part of the mlxtest project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stdint.h>
// floating type & sqrt operator
typedef double fp_t;
#define SQRT(x) sqrt((x))
// amount of pixels
#define MLX_W (32)
#define MLX_H (24)
#define MLX_PIXNO (MLX_W*MLX_H)
// pixels + service data
#define MLX_PIXARRSZ (MLX_PIXNO + 64)
typedef struct{
int16_t kVdd;
int16_t vdd25;
fp_t KvPTAT;
fp_t KtPTAT;
int16_t vPTAT25;
fp_t alphaPTAT;
int16_t gainEE;
fp_t tgc;
fp_t cpKv; // K_V_CP
fp_t cpKta; // K_Ta_CP
fp_t KsTa;
fp_t CT[3]; // range borders (0, 160, 320 degrC?)
fp_t KsTo[4]; // K_S_To for each range * 273.15
fp_t alphacorr[4]; // Alpha_corr for each range
fp_t alpha[MLX_PIXNO]; // full - with alpha_scale
fp_t offset[MLX_PIXNO];
fp_t kta[MLX_PIXNO]; // full K_ta - with scale1&2
fp_t 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
fp_t cpAlpha[2]; // alpha_CP_subpage 0 and 1
uint8_t resolEE; // resolution_EE
int16_t cpOffset[2];
uint8_t outliers[MLX_PIXNO]; // outliers - bad pixels (if == 1)
} MLX90640_params;
// full amount of IMAGE data + EXTRA data (counts of uint16_t!)
#define MLX_DMA_MAXLEN (834)
int get_parameters(const uint16_t dataarray[MLX_DMA_MAXLEN], MLX90640_params *params);
void dump_parameters(MLX90640_params *params, const MLX90640_params *standard);
fp_t *process_subpage(MLX90640_params *params, const int16_t Frame[MLX_DMA_MAXLEN], int subpageno, int simpleimage);
void chkImage(const fp_t Image[MLX_PIXNO], const fp_t ToFrame[MLX_PIXNO]);
void dumpIma(const fp_t im[MLX_PIXNO]);
void drawIma(const fp_t im[MLX_PIXNO]);

View File

@@ -0,0 +1,90 @@
/*
* This file is part of the mlxtest project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#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_RES16 (0<<10)
#define REG_CONTROL_RES17 (1<<10)
#define REG_CONTROL_RES18 (2<<10)
#define REG_CONTROL_RES19 (3<<10)
#define REG_CONTROL_RESMASK (3<<10)
#define REG_CONTROL_REFR_05HZ (0<<7)
#define REG_CONTROL_REFR_1HZ (1<<7)
#define REG_CONTROL_REFR_2HZ (2<<7)
#define REG_CONTROL_REFR_4HZ (3<<7)
#define REG_CONTROL_REFR_8HZ (4<<7)
#define REG_CONTROL_REFR_16HZ (5<<7)
#define REG_CONTROL_REFR_32HZ (6<<7)
#define REG_CONTROL_REFR_64HZ (7<<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)
// default value
#define REG_CONTROL_DEFAULT (REG_CONTROL_CHESS|REG_CONTROL_RES18|REG_CONTROL_REFR_2HZ|REG_CONTROL_SUBPEN)
// calibration data start & len
#define REG_CALIDATA 0x2400
#define REG_CALIDATA_LEN 832
#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
// index of register in array (from REG_IMAGEDATA)
#define IMD_IDX(addr) ((addr)-REG_IMAGEDATA)

View File

@@ -0,0 +1 @@
-std=c17

View File

@@ -0,0 +1,4 @@
#define GNU_SOURCE 1
#define _XOPEN_SOURCE 1111
#define EBUG

View File

@@ -0,0 +1 @@
[General]

View File

@@ -0,0 +1,218 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.1, 2025-09-09T23:05:00. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
<value type="QByteArray">{7bd84e39-ca37-46d3-be9d-99ebea85bc0d}</value>
</data>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="qlonglong">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value type="bool" key="EditorConfiguration.AutoDetect">true</value>
<value type="bool" key="EditorConfiguration.AutoIndent">true</value>
<value type="bool" key="EditorConfiguration.CamelCaseNavigation">true</value>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.0">
<value type="QString" key="language">Cpp</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">CppGlobal</value>
</valuemap>
</valuemap>
<valuemap type="QVariantMap" key="EditorConfiguration.CodeStyle.1">
<value type="QString" key="language">QmlJS</value>
<valuemap type="QVariantMap" key="value">
<value type="QByteArray" key="CurrentPreferences">QmlJSGlobal</value>
</valuemap>
</valuemap>
<value type="qlonglong" key="EditorConfiguration.CodeStyle.Count">2</value>
<value type="QByteArray" key="EditorConfiguration.Codec">KOI8-R</value>
<value type="bool" key="EditorConfiguration.ConstrainTooltips">false</value>
<value type="int" key="EditorConfiguration.IndentSize">4</value>
<value type="bool" key="EditorConfiguration.KeyboardTooltips">false</value>
<value type="int" key="EditorConfiguration.LineEndingBehavior">0</value>
<value type="int" key="EditorConfiguration.MarginColumn">80</value>
<value type="bool" key="EditorConfiguration.MouseHiding">true</value>
<value type="bool" key="EditorConfiguration.MouseNavigation">true</value>
<value type="int" key="EditorConfiguration.PaddingMode">1</value>
<value type="int" key="EditorConfiguration.PreferAfterWhitespaceComments">0</value>
<value type="bool" key="EditorConfiguration.PreferSingleLineComments">false</value>
<value type="bool" key="EditorConfiguration.ScrollWheelZooming">true</value>
<value type="bool" key="EditorConfiguration.ShowMargin">false</value>
<value type="int" key="EditorConfiguration.SmartBackspaceBehavior">0</value>
<value type="bool" key="EditorConfiguration.SmartSelectionChanging">true</value>
<value type="bool" key="EditorConfiguration.SpacesForTabs">true</value>
<value type="int" key="EditorConfiguration.TabKeyBehavior">0</value>
<value type="int" key="EditorConfiguration.TabSize">8</value>
<value type="bool" key="EditorConfiguration.UseGlobal">true</value>
<value type="bool" key="EditorConfiguration.UseIndenter">false</value>
<value type="int" key="EditorConfiguration.Utf8BomBehavior">1</value>
<value type="bool" key="EditorConfiguration.addFinalNewLine">true</value>
<value type="bool" key="EditorConfiguration.cleanIndentation">false</value>
<value type="bool" key="EditorConfiguration.cleanWhitespace">true</value>
<value type="QString" key="EditorConfiguration.ignoreFileTypes">*.md, *.MD, Makefile</value>
<value type="bool" key="EditorConfiguration.inEntireDocument">false</value>
<value type="bool" key="EditorConfiguration.skipTrailingWhitespace">true</value>
<value type="bool" key="EditorConfiguration.tintMarginArea">true</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.PluginSettings</variable>
<valuemap type="QVariantMap">
<valuemap type="QVariantMap" key="AutoTest.ActiveFrameworks">
<value type="bool" key="AutoTest.Framework.Boost">true</value>
<value type="bool" key="AutoTest.Framework.CTest">false</value>
<value type="bool" key="AutoTest.Framework.Catch">true</value>
<value type="bool" key="AutoTest.Framework.GTest">true</value>
<value type="bool" key="AutoTest.Framework.QtQuickTest">true</value>
<value type="bool" key="AutoTest.Framework.QtTest">true</value>
</valuemap>
<value type="bool" key="AutoTest.ApplyFilter">false</value>
<valuemap type="QVariantMap" key="AutoTest.CheckStates"/>
<valuelist type="QVariantList" key="AutoTest.PathFilters"/>
<value type="int" key="AutoTest.RunAfterBuild">0</value>
<value type="bool" key="AutoTest.UseGlobal">true</value>
<valuelist type="QVariantList" key="ClangCodeModel.CustomCommandLineKey"/>
<value type="bool" key="ClangCodeModel.UseGlobalConfig">true</value>
<value type="QString" key="ClangCodeModel.WarningConfigId">Builtin.BuildSystem</value>
<valuemap type="QVariantMap" key="ClangTools">
<value type="bool" key="ClangTools.AnalyzeOpenFiles">true</value>
<value type="bool" key="ClangTools.BuildBeforeAnalysis">true</value>
<value type="QString" key="ClangTools.DiagnosticConfig">Builtin.DefaultTidyAndClazy</value>
<value type="int" key="ClangTools.ParallelJobs">2</value>
<value type="bool" key="ClangTools.PreferConfigFile">false</value>
<valuelist type="QVariantList" key="ClangTools.SelectedDirs"/>
<valuelist type="QVariantList" key="ClangTools.SelectedFiles"/>
<valuelist type="QVariantList" key="ClangTools.SuppressedDiagnostics"/>
<value type="bool" key="ClangTools.UseGlobalSettings">true</value>
</valuemap>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value type="QString" key="DeviceType">Desktop</value>
<value type="bool" key="HasPerBcDcs">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Desktop</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">{65a14f9e-e008-4c1b-89df-4eaa4774b6e3}</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveBuildConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.BuildConfiguration.0">
<value type="QString" key="ProjectExplorer.BuildConfiguration.BuildDirectory">/tmp/1/home/eddy/MLX90640_wiringPi</value>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">all</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Build</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Build</value>
</valuemap>
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.1">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildStepList.Step.0">
<valuelist type="QVariantList" key="GenericProjectManager.GenericMakeStep.BuildTargets">
<value type="QString">clean</value>
</valuelist>
<value type="bool" key="ProjectExplorer.BuildStep.Enabled">true</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericMakeStep</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">1</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Clean</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Clean</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">2</value>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.CustomParsers"/>
<value type="bool" key="ProjectExplorer.BuildConfiguration.ParseStandardOutput">false</value>
<valuelist type="QVariantList" key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges"/>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">По умолчанию</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">GenericProjectManager.GenericBuildConfiguration</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveDeployConfiguration">0</value>
<value type="qlonglong" key="ProjectExplorer.Target.ActiveRunConfiguration">0</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.BuildConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.DeployConfiguration.0">
<valuemap type="QVariantMap" key="ProjectExplorer.BuildConfiguration.BuildStepList.0">
<value type="qlonglong" key="ProjectExplorer.BuildStepList.StepsCount">0</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DefaultDisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName">Deploy</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.BuildSteps.Deploy</value>
</valuemap>
<value type="int" key="ProjectExplorer.BuildConfiguration.BuildStepListCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.DeployConfiguration.CustomData"/>
<value type="bool" key="ProjectExplorer.DeployConfiguration.CustomDataEnabled">false</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.DefaultDeployConfiguration</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.DeployConfigurationCount">1</value>
<valuemap type="QVariantMap" key="ProjectExplorer.Target.RunConfiguration.0">
<value type="bool" key="Analyzer.Perf.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.QmlProfiler.Settings.UseGlobalSettings">true</value>
<value type="bool" key="Analyzer.Valgrind.Settings.UseGlobalSettings">true</value>
<valuelist type="QVariantList" key="CustomOutputParsers"/>
<value type="int" key="PE.EnvironmentAspect.Base">2</value>
<valuelist type="QVariantList" key="PE.EnvironmentAspect.Changes"/>
<value type="bool" key="PE.EnvironmentAspect.PrintOnRun">false</value>
<value type="QString" key="PerfRecordArgsId">-e cpu-cycles --call-graph dwarf,4096 -F 250</value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.DisplayName"></value>
<value type="QString" key="ProjectExplorer.ProjectConfiguration.Id">ProjectExplorer.CustomExecutableRunConfiguration</value>
<value type="QString" key="ProjectExplorer.RunConfiguration.BuildKey"></value>
<value type="bool" key="ProjectExplorer.RunConfiguration.Customized">false</value>
<value type="bool" key="RunConfiguration.UseCppDebuggerAuto">true</value>
<value type="bool" key="RunConfiguration.UseQmlDebuggerAuto">true</value>
</valuemap>
<value type="qlonglong" key="ProjectExplorer.Target.RunConfigurationCount">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="qlonglong">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">22</value>
</data>
<data>
<variable>Version</variable>
<value type="int">22</value>
</data>
</qtcreator>

View File

@@ -0,0 +1 @@
-std=c++17

View File

@@ -0,0 +1,5 @@
main.c
mlx90640.c
mlx90640.h
mlx90640_regs.h
testdata.h

View File

@@ -0,0 +1,2 @@
/usr/local/include
.

1
MLX90640_test/offset.csv Normal file
View File

@@ -0,0 +1 @@
-54,-56,-50,-60,-50,-56,-48,-60,-49,-57,-48,-61,-49,-59,-48,-63,-46,-59,-48,-65,-49,-61,-49,-66,-47,-64,-50,-69,-53,-66,-51,-75,-61,-64,-65,-61,-57,-63,-63,-61,-56,-63,-63,-62,-56,-65,-63,-64,-53,-65,-63,-65,-55,-66,-63,-66,-53,-69,-63,-69,-58,-70,-64,-74,-53,-56,-50,-60,-51,-55,-48,-60,-48,-56,-47,-61,-47,-58,-47,-62,-45,-58,-48,-64,-47,-59,-47,-67,-46,-62,-49,-68,-52,-65,-50,-75,-60,-62,-65,-61,-58,-61,-63,-61,-55,-62,-62,-61,-54,-64,-62,-62,-51,-64,-62,-63,-53,-66,-62,-66,-52,-67,-63,-67,-59,-71,-64,-75,-50,-54,-49,-58,-48,-54,-48,-59,-46,-55,-47,-60,-47,-55,-47,-61,-43,-57,-46,-62,-46,-59,-47,-65,-46,-61,-48,-67,-52,-65,-50,-74,-57,-63,-65,-60,-56,-62,-64,-61,-54,-63,-62,-62,-54,-62,-62,-63,-51,-64,-62,-63,-52,-66,-62,-65,-53,-69,-62,-69,-59,-71,-64,-75,-53,-54,-49,-59,-49,-54,-47,-58,-45,-54,-47,-60,-47,-55,-46,-62,-46,-57,-47,-62,-47,-58,-47,-64,-46,-61,-48,-66,-50,-64,-49,-73,-62,-64,-66,-62,-59,-63,-64,-61,-54,-63,-64,-62,-55,-63,-63,-63,-54,-65,-63,-64,-55,-65,-63,-65,-54,-68,-64,-68,-58,-72,-64,-75,-52,-53,-49,-58,-50,-53,-47,-58,-48,-54,-47,-60,-47,-55,-46,-61,-45,-57,-47,-63,-46,-58,-48,-65,-46,-61,-49,-67,-50,-64,-50,-73,-62,-65,-67,-63,-59,-63,-65,-63,-57,-64,-64,-63,-56,-65,-64,-64,-54,-66,-64,-65,-55,-68,-64,-66,-54,-69,-65,-69,-58,-72,-66,-75,-50,-54,-49,-58,-47,-53,-49,-59,-45,-55,-48,-60,-45,-55,-49,-62,-44,-57,-47,-63,-45,-58,-48,-65,-47,-62,-51,-69,-52,-65,-52,-75,-61,-66,-68,-64,-58,-65,-67,-64,-56,-66,-66,-65,-55,-66,-65,-65,-54,-67,-64,-66,-55,-68,-65,-68,-54,-71,-66,-71,-60,-73,-67,-77,-54,-54,-52,-59,-51,-54,-50,-59,-49,-55,-49,-60,-48,-55,-49,-62,-46,-58,-49,-65,-48,-60,-49,-66,-46,-63,-50,-68,-52,-64,-52,-75,-66,-69,-73,-66,-62,-68,-69,-65,-60,-67,-68,-65,-59,-67,-69,-66,-56,-69,-67,-68,-57,-69,-66,-69,-54,-71,-66,-70,-60,-73,-69,-77,-56,-54,-52,-59,-53,-54,-51,-60,-50,-56,-51,-61,-49,-56,-50,-63,-47,-58,-50,-65,-48,-59,-51,-66,-47,-62,-52,-68,-54,-66,-53,-76,-69,-71,-73,-69,-66,-69,-72,-68,-62,-69,-71,-67,-61,-69,-70,-68,-58,-70,-68,-70,-58,-70,-68,-69,-56,-71,-68,-71,-62,-74,-69,-79,-54,-55,-53,-60,-52,-55,-53,-61,-51,-57,-54,-63,-50,-57,-53,-64,-49,-59,-51,-65,-50,-60,-53,-66,-49,-63,-54,-71,-55,-66,-55,-76,-69,-73,-76,-70,-66,-71,-75,-69,-65,-72,-74,-71,-63,-71,-71,-71,-60,-71,-70,-70,-61,-71,-70,-70,-59,-74,-70,-75,-63,-76,-70,-80,-62,-57,-58,-62,-59,-57,-56,-62,-56,-59,-54,-64,-55,-60,-54,-65,-52,-61,-54,-67,-53,-62,-54,-68,-53,-64,-56,-71,-55,-67,-56,-75,-77,-77,-83,-74,-74,-74,-79,-73,-70,-74,-76,-73,-68,-74,-75,-73,-64,-75,-74,-73,-64,-74,-73,-74,-63,-76,-74,-75,-64,-78,-74,-80,-66,-58,-61,-65,-61,-59,-57,-64,-57,-60,-56,-66,-55,-60,-56,-67,-55,-63,-57,-69,-54,-63,-56,-69,-54,-64,-57,-72,-59,-67,-58,-77,-82,-80,-86,-78,-77,-78,-81,-76,-73,-78,-79,-76,-69,-76,-78,-75,-68,-78,-78,-76,-67,-76,-77,-76,-66,-78,-77,-77,-70,-80,-77,-84,-67,-60,-65,-66,-62,-61,-63,-68,-62,-62,-62,-67,-60,-64,-61,-70,-57,-65,-59,-69,-56,-65,-59,-71,-58,-67,-62,-74,-63,-69,-63,-78,-94,-91,-100,-88,-88,-88,-95,-87,-85,-87,-92,-86,-82,-88,-90,-88,-78,-87,-88,-84,-77,-87,-87,-86,-76,-89,-87,-90,-80,-90,-87,-93
1 -54 -56 -50 -60 -50 -56 -48 -60 -49 -57 -48 -61 -49 -59 -48 -63 -46 -59 -48 -65 -49 -61 -49 -66 -47 -64 -50 -69 -53 -66 -51 -75 -61 -64 -65 -61 -57 -63 -63 -61 -56 -63 -63 -62 -56 -65 -63 -64 -53 -65 -63 -65 -55 -66 -63 -66 -53 -69 -63 -69 -58 -70 -64 -74 -53 -56 -50 -60 -51 -55 -48 -60 -48 -56 -47 -61 -47 -58 -47 -62 -45 -58 -48 -64 -47 -59 -47 -67 -46 -62 -49 -68 -52 -65 -50 -75 -60 -62 -65 -61 -58 -61 -63 -61 -55 -62 -62 -61 -54 -64 -62 -62 -51 -64 -62 -63 -53 -66 -62 -66 -52 -67 -63 -67 -59 -71 -64 -75 -50 -54 -49 -58 -48 -54 -48 -59 -46 -55 -47 -60 -47 -55 -47 -61 -43 -57 -46 -62 -46 -59 -47 -65 -46 -61 -48 -67 -52 -65 -50 -74 -57 -63 -65 -60 -56 -62 -64 -61 -54 -63 -62 -62 -54 -62 -62 -63 -51 -64 -62 -63 -52 -66 -62 -65 -53 -69 -62 -69 -59 -71 -64 -75 -53 -54 -49 -59 -49 -54 -47 -58 -45 -54 -47 -60 -47 -55 -46 -62 -46 -57 -47 -62 -47 -58 -47 -64 -46 -61 -48 -66 -50 -64 -49 -73 -62 -64 -66 -62 -59 -63 -64 -61 -54 -63 -64 -62 -55 -63 -63 -63 -54 -65 -63 -64 -55 -65 -63 -65 -54 -68 -64 -68 -58 -72 -64 -75 -52 -53 -49 -58 -50 -53 -47 -58 -48 -54 -47 -60 -47 -55 -46 -61 -45 -57 -47 -63 -46 -58 -48 -65 -46 -61 -49 -67 -50 -64 -50 -73 -62 -65 -67 -63 -59 -63 -65 -63 -57 -64 -64 -63 -56 -65 -64 -64 -54 -66 -64 -65 -55 -68 -64 -66 -54 -69 -65 -69 -58 -72 -66 -75 -50 -54 -49 -58 -47 -53 -49 -59 -45 -55 -48 -60 -45 -55 -49 -62 -44 -57 -47 -63 -45 -58 -48 -65 -47 -62 -51 -69 -52 -65 -52 -75 -61 -66 -68 -64 -58 -65 -67 -64 -56 -66 -66 -65 -55 -66 -65 -65 -54 -67 -64 -66 -55 -68 -65 -68 -54 -71 -66 -71 -60 -73 -67 -77 -54 -54 -52 -59 -51 -54 -50 -59 -49 -55 -49 -60 -48 -55 -49 -62 -46 -58 -49 -65 -48 -60 -49 -66 -46 -63 -50 -68 -52 -64 -52 -75 -66 -69 -73 -66 -62 -68 -69 -65 -60 -67 -68 -65 -59 -67 -69 -66 -56 -69 -67 -68 -57 -69 -66 -69 -54 -71 -66 -70 -60 -73 -69 -77 -56 -54 -52 -59 -53 -54 -51 -60 -50 -56 -51 -61 -49 -56 -50 -63 -47 -58 -50 -65 -48 -59 -51 -66 -47 -62 -52 -68 -54 -66 -53 -76 -69 -71 -73 -69 -66 -69 -72 -68 -62 -69 -71 -67 -61 -69 -70 -68 -58 -70 -68 -70 -58 -70 -68 -69 -56 -71 -68 -71 -62 -74 -69 -79 -54 -55 -53 -60 -52 -55 -53 -61 -51 -57 -54 -63 -50 -57 -53 -64 -49 -59 -51 -65 -50 -60 -53 -66 -49 -63 -54 -71 -55 -66 -55 -76 -69 -73 -76 -70 -66 -71 -75 -69 -65 -72 -74 -71 -63 -71 -71 -71 -60 -71 -70 -70 -61 -71 -70 -70 -59 -74 -70 -75 -63 -76 -70 -80 -62 -57 -58 -62 -59 -57 -56 -62 -56 -59 -54 -64 -55 -60 -54 -65 -52 -61 -54 -67 -53 -62 -54 -68 -53 -64 -56 -71 -55 -67 -56 -75 -77 -77 -83 -74 -74 -74 -79 -73 -70 -74 -76 -73 -68 -74 -75 -73 -64 -75 -74 -73 -64 -74 -73 -74 -63 -76 -74 -75 -64 -78 -74 -80 -66 -58 -61 -65 -61 -59 -57 -64 -57 -60 -56 -66 -55 -60 -56 -67 -55 -63 -57 -69 -54 -63 -56 -69 -54 -64 -57 -72 -59 -67 -58 -77 -82 -80 -86 -78 -77 -78 -81 -76 -73 -78 -79 -76 -69 -76 -78 -75 -68 -78 -78 -76 -67 -76 -77 -76 -66 -78 -77 -77 -70 -80 -77 -84 -67 -60 -65 -66 -62 -61 -63 -68 -62 -62 -62 -67 -60 -64 -61 -70 -57 -65 -59 -69 -56 -65 -59 -71 -58 -67 -62 -74 -63 -69 -63 -78 -94 -91 -100 -88 -88 -88 -95 -87 -85 -87 -92 -86 -82 -88 -90 -88 -78 -87 -88 -84 -77 -87 -87 -86 -76 -89 -87 -90 -80 -90 -87 -93

82
MLX90640_test/testdata.h Normal file
View File

@@ -0,0 +1,82 @@
/*
* This file is part of the mlxtest project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
// data from MLX documentation for algorithms testing
// this file should be included only once!
#ifdef _TESTDATA_H__
#error "Don't include this file several times!"
#endif
#define _TESTDATA_H__
#include "mlx90640.h"
static const uint16_t EEPROM[MLX_DMA_MAXLEN] = {
#include "eeprom.csv"
};
static const int16_t DataFrame[2][MLX_DMA_MAXLEN] = {
{
#include "frame0.csv"
},
{
#include "frame1.csv"
}
};
static const MLX90640_params extracted_parameters = {
.kVdd = -3200
,.vdd25 = -12544
,.KvPTAT = 0.002197
,.KtPTAT = 42.625000
,.vPTAT25 = 12196
,.alphaPTAT = 9.0
,.gainEE = 5580
,.tgc = 0.000000
,.cpKv = 0.3750
,.cpKta = 0.004272
// ,.calibrationModeEE = 128
,.KsTa = -0.002441
,.CT = {0., 300., 500.}
,.KsTo = {-0.0002, -0.0002, -0.0002, -0.0002}
,.alphacorr = {}
,.alpha = {
#include "alpha.csv"
}
,.offset = {
#include "offset.csv"
}
,.kta = {
#include "kta.csv"
}
,.kv = {0.4375, 0.3750, 0.3750, 0.3750}
,.cpAlpha = {0.0000000028812792152, 0.0000000029037892091}
,.resolEE = 2
,.cpOffset = {-69, -65}
,.outliers = {0}
};
static const fp_t ToFrame[2][MLX_PIXNO] = {
{
#include "to_frame0.csv"
},
{
#include "to_frame1.csv"
}
};

View File

@@ -0,0 +1 @@
28.711,0.000,28.178,0.000,28.313,0.000,28.631,0.000,28.295,0.000,28.586,0.000,28.336,0.000,28.683,0.000,28.248,0.000,28.573,0.000,29.127,0.000,28.629,0.000,29.070,0.000,28.894,0.000,28.883,0.000,29.687,0.000,0.000,28.618,0.000,28.134,0.000,28.517,0.000,27.982,0.000,28.092,0.000,27.914,0.000,28.156,0.000,28.261,0.000,28.198,0.000,28.528,0.000,28.837,0.000,28.730,0.000,28.814,0.000,28.558,0.000,28.605,0.000,29.005,28.513,0.000,28.482,0.000,28.428,0.000,27.946,0.000,28.248,0.000,28.387,0.000,28.340,0.000,28.128,0.000,28.544,0.000,28.321,0.000,28.986,0.000,28.445,0.000,28.728,0.000,28.778,0.000,29.035,0.000,28.639,0.000,0.000,28.116,0.000,28.237,0.000,28.173,0.000,28.519,0.000,28.303,0.000,28.049,0.000,28.246,0.000,28.466,0.000,28.247,0.000,28.378,0.000,28.835,0.000,28.480,0.000,28.828,0.000,28.428,0.000,28.528,0.000,29.030,28.163,0.000,28.719,0.000,28.268,0.000,28.439,0.000,28.607,0.000,28.199,0.000,28.440,0.000,28.730,0.000,28.361,0.000,28.256,0.000,28.253,0.000,28.563,0.000,28.593,0.000,28.962,0.000,28.746,0.000,28.917,0.000,0.000,28.112,0.000,27.955,0.000,28.365,0.000,28.291,0.000,28.335,0.000,28.610,0.000,28.281,0.000,28.303,0.000,28.246,0.000,28.710,0.000,28.723,0.000,28.532,0.000,28.493,0.000,28.686,0.000,28.565,0.000,28.197,28.489,0.000,28.295,0.000,28.610,0.000,28.497,0.000,29.183,0.000,29.365,0.000,28.763,0.000,28.320,0.000,28.594,0.000,28.641,0.000,28.629,0.000,28.950,0.000,27.980,0.000,28.349,0.000,28.453,0.000,28.852,0.000,0.000,28.364,0.000,28.244,0.000,28.023,0.000,28.247,0.000,29.572,0.000,30.034,0.000,28.667,0.000,28.642,0.000,28.316,0.000,28.311,0.000,28.444,0.000,28.168,0.000,28.403,0.000,28.438,0.000,28.800,0.000,27.913,28.091,0.000,28.598,0.000,28.313,0.000,28.813,0.000,30.533,0.000,32.362,0.000,31.328,0.000,29.314,0.000,28.126,0.000,28.517,0.000,28.644,0.000,28.679,0.000,28.485,0.000,28.714,0.000,28.549,0.000,28.739,0.000,0.000,28.275,0.000,27.955,0.000,28.448,0.000,28.965,0.000,31.879,0.000,32.094,0.000,31.286,0.000,29.814,0.000,28.593,0.000,28.303,0.000,28.808,0.000,28.596,0.000,28.363,0.000,28.585,0.000,28.420,0.000,28.341,28.595,0.000,28.668,0.000,28.724,0.000,29.526,0.000,32.332,0.000,32.878,0.000,32.587,0.000,32.080,0.000,30.319,0.000,29.259,0.000,28.347,0.000,28.371,0.000,28.591,0.000,28.779,0.000,28.650,0.000,28.577,0.000,0.000,28.330,0.000,28.480,0.000,28.753,0.000,30.412,0.000,33.225,0.000,33.175,0.000,32.848,0.000,31.967,0.000,30.751,0.000,29.090,0.000,28.461,0.000,28.636,0.000,28.437,0.000,28.707,0.000,28.479,0.000,28.493,28.450,0.000,28.205,0.000,28.945,0.000,30.841,0.000,33.628,0.000,34.155,0.000,33.454,0.000,32.621,0.000,32.219,0.000,31.181,0.000,28.945,0.000,28.535,0.000,28.354,0.000,28.338,0.000,28.487,0.000,28.645,0.000,0.000,28.420,0.000,28.235,0.000,29.189,0.000,32.258,0.000,34.054,0.000,34.386,0.000,33.366,0.000,32.502,0.000,32.534,0.000,30.553,0.000,28.703,0.000,28.123,0.000,28.465,0.000,28.435,0.000,28.666,0.000,28.458,28.412,0.000,28.618,0.000,29.437,0.000,32.515,0.000,33.633,0.000,34.348,0.000,33.460,0.000,33.297,0.000,32.441,0.000,30.583,0.000,28.901,0.000,28.751,0.000,28.455,0.000,28.551,0.000,28.674,0.000,28.816,0.000,0.000,28.379,0.000,28.929,0.000,30.135,0.000,32.781,0.000,33.364,0.000,33.703,0.000,33.035,0.000,32.681,0.000,32.508,0.000,29.271,0.000,28.813,0.000,28.176,0.000,28.281,0.000,28.308,0.000,28.441,0.000,28.851,28.266,0.000,28.759,0.000,30.103,0.000,32.329,0.000,32.560,0.000,32.963,0.000,32.587,0.000,32.781,0.000,32.408,0.000,29.431,0.000,28.536,0.000,28.849,0.000,28.187,0.000,28.597,0.000,29.227,0.000,28.880,0.000,0.000,28.259,0.000,28.992,0.000,30.442,0.000,32.244,0.000,32.250,0.000,32.312,0.000,32.854,0.000,32.680,0.000,31.065,0.000,28.621,0.000,28.344,0.000,28.447,0.000,28.667,0.000,28.698,0.000,29.718,0.000,28.963,28.429,0.000,28.570,0.000,29.163,0.000,29.828,0.000,31.671,0.000,32.379,0.000,32.265,0.000,32.592,0.000,31.175,0.000,29.218,0.000,28.587,0.000,28.508,0.000,28.957,0.000,29.226,0.000,31.199,0.000,31.547,0.000,0.000,28.137,0.000,28.463,0.000,28.557,0.000,29.704,0.000,30.460,0.000,31.603,0.000,31.990,0.000,32.004,0.000,29.626,0.000,28.473,0.000,28.220,0.000,28.694,0.000,28.703,0.000,29.831,0.000,32.548,0.000,32.176,28.473,0.000,28.264,0.000,28.662,0.000,28.372,0.000,29.225,0.000,29.797,0.000,30.999,0.000,31.244,0.000,29.575,0.000,28.510,0.000,28.530,0.000,28.449,0.000,28.642,0.000,30.602,0.000,32.418,0.000,34.239,0.000,0.000,28.322,0.000,28.078,0.000,28.183,0.000,28.517,0.000,28.593,0.000,29.005,0.000,30.394,0.000,30.257,0.000,28.746,0.000,28.295,0.000,28.561,0.000,28.250,0.000,28.593,0.000,31.333,0.000,33.254,0.000,33.224,28.516,0.000,28.317,0.000,28.465,0.000,28.845,0.000,28.642,0.000,28.464,0.000,28.858,0.000,28.979,0.000,29.039,0.000,28.832,0.000,28.280,0.000,28.837,0.000,29.056,0.000,31.946,0.000,33.101,0.000,32.988,0.000,0.000,28.161,0.000,28.257,0.000,28.346,0.000,28.301,0.000,28.101,0.000,28.288,0.000,28.615,0.000,28.926,0.000,28.512,0.000,28.178,0.000,28.392,0.000,28.412,0.000,30.166,0.000,32.614,0.000,32.530,0.000,32.028
1 28.711 0.000 28.178 0.000 28.313 0.000 28.631 0.000 28.295 0.000 28.586 0.000 28.336 0.000 28.683 0.000 28.248 0.000 28.573 0.000 29.127 0.000 28.629 0.000 29.070 0.000 28.894 0.000 28.883 0.000 29.687 0.000 0.000 28.618 0.000 28.134 0.000 28.517 0.000 27.982 0.000 28.092 0.000 27.914 0.000 28.156 0.000 28.261 0.000 28.198 0.000 28.528 0.000 28.837 0.000 28.730 0.000 28.814 0.000 28.558 0.000 28.605 0.000 29.005 28.513 0.000 28.482 0.000 28.428 0.000 27.946 0.000 28.248 0.000 28.387 0.000 28.340 0.000 28.128 0.000 28.544 0.000 28.321 0.000 28.986 0.000 28.445 0.000 28.728 0.000 28.778 0.000 29.035 0.000 28.639 0.000 0.000 28.116 0.000 28.237 0.000 28.173 0.000 28.519 0.000 28.303 0.000 28.049 0.000 28.246 0.000 28.466 0.000 28.247 0.000 28.378 0.000 28.835 0.000 28.480 0.000 28.828 0.000 28.428 0.000 28.528 0.000 29.030 28.163 0.000 28.719 0.000 28.268 0.000 28.439 0.000 28.607 0.000 28.199 0.000 28.440 0.000 28.730 0.000 28.361 0.000 28.256 0.000 28.253 0.000 28.563 0.000 28.593 0.000 28.962 0.000 28.746 0.000 28.917 0.000 0.000 28.112 0.000 27.955 0.000 28.365 0.000 28.291 0.000 28.335 0.000 28.610 0.000 28.281 0.000 28.303 0.000 28.246 0.000 28.710 0.000 28.723 0.000 28.532 0.000 28.493 0.000 28.686 0.000 28.565 0.000 28.197 28.489 0.000 28.295 0.000 28.610 0.000 28.497 0.000 29.183 0.000 29.365 0.000 28.763 0.000 28.320 0.000 28.594 0.000 28.641 0.000 28.629 0.000 28.950 0.000 27.980 0.000 28.349 0.000 28.453 0.000 28.852 0.000 0.000 28.364 0.000 28.244 0.000 28.023 0.000 28.247 0.000 29.572 0.000 30.034 0.000 28.667 0.000 28.642 0.000 28.316 0.000 28.311 0.000 28.444 0.000 28.168 0.000 28.403 0.000 28.438 0.000 28.800 0.000 27.913 28.091 0.000 28.598 0.000 28.313 0.000 28.813 0.000 30.533 0.000 32.362 0.000 31.328 0.000 29.314 0.000 28.126 0.000 28.517 0.000 28.644 0.000 28.679 0.000 28.485 0.000 28.714 0.000 28.549 0.000 28.739 0.000 0.000 28.275 0.000 27.955 0.000 28.448 0.000 28.965 0.000 31.879 0.000 32.094 0.000 31.286 0.000 29.814 0.000 28.593 0.000 28.303 0.000 28.808 0.000 28.596 0.000 28.363 0.000 28.585 0.000 28.420 0.000 28.341 28.595 0.000 28.668 0.000 28.724 0.000 29.526 0.000 32.332 0.000 32.878 0.000 32.587 0.000 32.080 0.000 30.319 0.000 29.259 0.000 28.347 0.000 28.371 0.000 28.591 0.000 28.779 0.000 28.650 0.000 28.577 0.000 0.000 28.330 0.000 28.480 0.000 28.753 0.000 30.412 0.000 33.225 0.000 33.175 0.000 32.848 0.000 31.967 0.000 30.751 0.000 29.090 0.000 28.461 0.000 28.636 0.000 28.437 0.000 28.707 0.000 28.479 0.000 28.493 28.450 0.000 28.205 0.000 28.945 0.000 30.841 0.000 33.628 0.000 34.155 0.000 33.454 0.000 32.621 0.000 32.219 0.000 31.181 0.000 28.945 0.000 28.535 0.000 28.354 0.000 28.338 0.000 28.487 0.000 28.645 0.000 0.000 28.420 0.000 28.235 0.000 29.189 0.000 32.258 0.000 34.054 0.000 34.386 0.000 33.366 0.000 32.502 0.000 32.534 0.000 30.553 0.000 28.703 0.000 28.123 0.000 28.465 0.000 28.435 0.000 28.666 0.000 28.458 28.412 0.000 28.618 0.000 29.437 0.000 32.515 0.000 33.633 0.000 34.348 0.000 33.460 0.000 33.297 0.000 32.441 0.000 30.583 0.000 28.901 0.000 28.751 0.000 28.455 0.000 28.551 0.000 28.674 0.000 28.816 0.000 0.000 28.379 0.000 28.929 0.000 30.135 0.000 32.781 0.000 33.364 0.000 33.703 0.000 33.035 0.000 32.681 0.000 32.508 0.000 29.271 0.000 28.813 0.000 28.176 0.000 28.281 0.000 28.308 0.000 28.441 0.000 28.851 28.266 0.000 28.759 0.000 30.103 0.000 32.329 0.000 32.560 0.000 32.963 0.000 32.587 0.000 32.781 0.000 32.408 0.000 29.431 0.000 28.536 0.000 28.849 0.000 28.187 0.000 28.597 0.000 29.227 0.000 28.880 0.000 0.000 28.259 0.000 28.992 0.000 30.442 0.000 32.244 0.000 32.250 0.000 32.312 0.000 32.854 0.000 32.680 0.000 31.065 0.000 28.621 0.000 28.344 0.000 28.447 0.000 28.667 0.000 28.698 0.000 29.718 0.000 28.963 28.429 0.000 28.570 0.000 29.163 0.000 29.828 0.000 31.671 0.000 32.379 0.000 32.265 0.000 32.592 0.000 31.175 0.000 29.218 0.000 28.587 0.000 28.508 0.000 28.957 0.000 29.226 0.000 31.199 0.000 31.547 0.000 0.000 28.137 0.000 28.463 0.000 28.557 0.000 29.704 0.000 30.460 0.000 31.603 0.000 31.990 0.000 32.004 0.000 29.626 0.000 28.473 0.000 28.220 0.000 28.694 0.000 28.703 0.000 29.831 0.000 32.548 0.000 32.176 28.473 0.000 28.264 0.000 28.662 0.000 28.372 0.000 29.225 0.000 29.797 0.000 30.999 0.000 31.244 0.000 29.575 0.000 28.510 0.000 28.530 0.000 28.449 0.000 28.642 0.000 30.602 0.000 32.418 0.000 34.239 0.000 0.000 28.322 0.000 28.078 0.000 28.183 0.000 28.517 0.000 28.593 0.000 29.005 0.000 30.394 0.000 30.257 0.000 28.746 0.000 28.295 0.000 28.561 0.000 28.250 0.000 28.593 0.000 31.333 0.000 33.254 0.000 33.224 28.516 0.000 28.317 0.000 28.465 0.000 28.845 0.000 28.642 0.000 28.464 0.000 28.858 0.000 28.979 0.000 29.039 0.000 28.832 0.000 28.280 0.000 28.837 0.000 29.056 0.000 31.946 0.000 33.101 0.000 32.988 0.000 0.000 28.161 0.000 28.257 0.000 28.346 0.000 28.301 0.000 28.101 0.000 28.288 0.000 28.615 0.000 28.926 0.000 28.512 0.000 28.178 0.000 28.392 0.000 28.412 0.000 30.166 0.000 32.614 0.000 32.530 0.000 32.028

File diff suppressed because one or more lines are too long

4
deepseek/Readme Normal file
View File

@@ -0,0 +1,4 @@
Here are some files generated by deepseek and fixed by me:
hashreplace.c - replase words in text by replacement table using hashing and binary search
thermal.c - ASSCII-set for thermal data 16-levels mapping

185
deepseek/hashreplace.c Normal file
View File

@@ -0,0 +1,185 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>
#include <unistd.h>
// Hash table entry structure
typedef struct {
uint32_t hash;
const char *key;
const char *value;
size_t key_len;
} HashEntry;
// Substitution table structure
typedef struct {
HashEntry *entries;
size_t size;
size_t capacity;
} SubstitutionTable;
// DJB2 hash function
uint32_t hash_function(const char *str, size_t len) {
uint32_t hash = 5381;
for (size_t i = 0; i < len; i++) {
hash = ((hash << 5) + hash) + (uint8_t)str[i];
}
return hash;
}
// Initialize substitution table
void init_table(SubstitutionTable *table, size_t initial_capacity) {
table->entries = malloc(initial_capacity * sizeof(HashEntry));
table->size = 0;
table->capacity = initial_capacity;
}
// Add a substitution pair to the table
void add_substitution(SubstitutionTable *table, const char *key, const char *value) {
if (table->size >= table->capacity) {
// Resize table if needed
table->capacity *= 2;
table->entries = realloc(table->entries, table->capacity * sizeof(HashEntry));
}
size_t key_len = strlen(key);
uint32_t hash = hash_function(key, key_len);
table->entries[table->size].hash = hash;
table->entries[table->size].key = key;
table->entries[table->size].value = value;
table->entries[table->size].key_len = key_len;
table->size++;
}
// Compare function for binary search
int compare_entries(const void *a, const void *b) {
const HashEntry *entry_a = (const HashEntry *)a;
const HashEntry *entry_b = (const HashEntry *)b;
if (entry_a->hash < entry_b->hash) return -1;
if (entry_a->hash > entry_b->hash) return 1;
return 0;
}
// Sort the table by hash for binary search
void sort_table(SubstitutionTable *table) {
printf("sort\n");
qsort(table->entries, table->size, sizeof(HashEntry), compare_entries);
for(int i = 0; i < table->size; ++i){
HashEntry *e = &table->entries[i];
printf("hash: %d, key: %s, val: %s, len: %zd\n", e->hash, e->key, e->value, e->key_len);
}
}
// Find a substitution using binary search
const char *find_substitution(SubstitutionTable *table, const char *word, size_t word_len) {
uint32_t hash = hash_function(word, word_len);
write(1, word, word_len);
fprintf(stderr, " - search this with hash %d\n", hash);
// Binary search for the hash
size_t low = 0, high = table->size - 1;
while (low <= high) {
size_t mid = (low + high) / 2;
printf("\t\tmid=%zd, low=%zd, high=%zd\n", mid, low, high);
if (table->entries[mid].hash == hash) {
// Check all entries with the same hash for exact match
size_t left = mid;
while (left > 0 && table->entries[left - 1].hash == hash) left--;
for (size_t i = left; i < table->size && table->entries[i].hash == hash; i++) {
if (table->entries[i].key_len == word_len &&
strncmp(table->entries[i].key, word, word_len) == 0) {
printf("FOUND: %s\n", table->entries[i].value);
return table->entries[i].value;
}
}
break;
} else if (table->entries[mid].hash < hash) {
low = mid + 1;
} else {
if(mid > 0) high = mid - 1;
else break;
}
}
return NULL; // Not found
}
// Check if a character is a word boundary
int is_word_boundary(char c) {
return !isalnum(c) && c != '_';
}
// Perform substitution on the input string
char *substitute_words(const char *input, SubstitutionTable *table) {
size_t input_len = strlen(input);
// Allocate enough space (input length * 2 for worst-case expansion)
char *result = malloc(input_len * 2 + 1);
if (!result) return NULL;
size_t result_idx = 0;
const char *p = input;
while (*p) {
// Skip non-word characters
if (is_word_boundary(*p)) {
result[result_idx++] = *p++;
continue;
}
// Find the current word boundaries
const char *word_start = p;
while (*p && !is_word_boundary(*p)) p++;
size_t word_len = p - word_start;
// Look up the word in the substitution table
const char *replacement = find_substitution(table, word_start, word_len);
if (replacement) {
// Copy the replacement word
size_t replacement_len = strlen(replacement);
strcpy(result + result_idx, replacement);
result_idx += replacement_len;
} else {
// Copy the original word
strncpy(result + result_idx, word_start, word_len);
result_idx += word_len;
}
}
result[result_idx] = '\0';
return result;
}
// Free the substitution table
void free_table(SubstitutionTable *table) {
free(table->entries);
table->entries = NULL;
table->size = table->capacity = 0;
}
int main() {
// Example usage
SubstitutionTable table;
init_table(&table, 10);
// Add substitution pairs
add_substitution(&table, "hello", "hi");
add_substitution(&table, "world", "earth");
add_substitution(&table, "foo", "bar");
add_substitution(&table, "test", "example");
// Sort the table for binary search
sort_table(&table);
const char *input = "hello world, this is a test foo bar!";
char *output = substitute_words(input, &table);
printf("Input: %s\n", input);
printf("Output: %s\n", output);
free(output);
free_table(&table);
return 0;
}

72
deepseek/thermal.c Normal file
View File

@@ -0,0 +1,72 @@
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// 16-level character set ordered by fill percentage (provided by user)
const char* CHARS_16 = " .':;+*oxX#&%B$@";
void print_thermal_ascii(float data[24][32]) {
// Find min and max values
float min_val = data[0][0];
float max_val = data[0][0];
for (int i = 0; i < 24; i++) {
for (int j = 0; j < 32; j++) {
if (data[i][j] < min_val) min_val = data[i][j];
if (data[i][j] > max_val) max_val = data[i][j];
}
}
// Handle case where all values are the same
if (max_val - min_val < 0.001) {
min_val -= 1.0;
max_val += 1.0;
}
// Generate and print ASCII art
for (int i = 0; i < 24; i++) {
for (int j = 0; j < 32; j++) {
// Normalize value to [0, 1] range
float normalized = (data[i][j] - min_val) / (max_val - min_val);
// Map to character index (0 to 15)
int index = (int)(normalized * 15 + 0.5);
// Ensure we stay within bounds
if (index < 0) index = 0;
if (index > 15) index = 15;
putchar(CHARS_16[index]);
}
putchar('\n');
}
// Print the temperature range for reference
printf("\nTemperature range: %.2f to %.2f\n", min_val, max_val);
}
// Helper function to generate sample data
void generate_sample_data(float data[24][32]) {
for (int i = 0; i < 24; i++) {
for (int j = 0; j < 32; j++) {
// Create a gradient with a hot spot in the center
float dx = j - 16;
float dy = i - 12;
float distance = sqrt(dx*dx + dy*dy);
data[i][j] = 20.0 + 30.0 * exp(-distance/8.0);
}
}
}
int main() {
float thermal_data[24][32];
// Generate sample thermal data
generate_sample_data(thermal_data);
// Print thermal image
printf("Thermal image (16 levels):\n");
print_thermal_ascii(thermal_data);
return 0;
}

197
deepseek/transformator Normal file
View File

@@ -0,0 +1,197 @@
Да, конечно! Написать программу на C для этой задачи вполне возможно. Это典型的 задача инженерного расчета, которая хорошо ложится на процедурный подход языка C.
Вот пример структуры программы на C:
## Структура программы
```c
#include <stdio.h>
#include <math.h>
#include <float.h>
// Константы
#define PI 3.141592653589793
#define MU_0 (4.0 * PI * 1e-7)
#define DENSITY_CU 8900.0 // плотность меди, кг/м³
#define PRICE_CU 8.5 // цена меди, у.е./кг
#define K_FILL 0.3 // коэффициент заполнения окна
#define EFFICIENCY 0.95 // КПД трансформатора
// Структура для описания сердечника
typedef struct {
char name[20]; // название сердечника
double S_fe; // сечение магнитопровода, м²
double S_window; // площадь окна, м²
double price_fe; // стоимость сердечника, у.е.
double B_max; // максимальная индукция, Тл
double k_fe; // коэффициент формы
} Core;
// Структура для параметров трансформатора
typedef struct {
double P_out; // выходная мощность, Вт
double U_in; // входное напряжение, В
double U_out; // выходное напряжение, В
double f; // частота, Гц
double J; // плотность тока, А/мм²
} TransformerParams;
// Структура для результатов расчета
typedef struct {
Core core;
double N1, N2; // число витков
double d_wire1, d_wire2; // диаметры проводов, мм
double cost_total; // общая стоимость
double cost_cu; // стоимость меди
int is_valid; // флаг валидности расчета
} CalculationResult;
// Функция расчета одного варианта
CalculationResult calculate_transformer(const TransformerParams* params, const Core* core) {
CalculationResult result = {0};
result.core = *core;
// Расчет числа витков
result.N1 = (params->U_in * 1.05) / (4.44 * params->f * core->B_max * core->S_fe);
result.N2 = (params->U_out * 1.05) / (4.44 * params->f * core->B_max * core->S_fe);
// Расчет токов
double I1 = params->P_out / (params->U_in * EFFICIENCY);
double I2 = params->P_out / params->U_out;
// Расчет сечений проводов
double s_wire1 = I1 / (params->J * 1e6); // м²
double s_wire2 = I2 / (params->J * 1e6); // м²
result.d_wire1 = sqrt(s_wire1 * 4 / PI) * 1000; // мм
result.d_wire2 = sqrt(s_wire2 * 4 / PI) * 1000; // мм
// Проверка заполнения окна
double occupied_area = (result.N1 * s_wire1 + result.N2 * s_wire2) / K_FILL;
result.is_valid = (occupied_area <= core->S_window);
if (result.is_valid) {
// Оценка стоимости меди (упрощенно)
// Предполагаем среднюю длину витка пропорциональной корню из площади окна
double mean_length_turn = 4 * sqrt(core->S_window); // м
double length_wire1 = result.N1 * mean_length_turn;
double length_wire2 = result.N2 * mean_length_turn;
double volume_cu = length_wire1 * s_wire1 + length_wire2 * s_wire2;
double mass_cu = volume_cu * DENSITY_CU;
result.cost_cu = mass_cu * PRICE_CU;
result.cost_total = core->price_fe + result.cost_cu;
}
return result;
}
// Основная функция оптимизации
Core find_optimal_core(const TransformerParams* params, Core* cores, int num_cores) {
CalculationResult best_result = {0};
best_result.cost_total = DBL_MAX;
int best_index = -1;
printf("Расчет трансформатора:\n");
printf("P = %.1f Вт, Uin = %.1f В, Uout = %.1f В, f = %.1f Гц\n\n",
params->P_out, params->U_in, params->U_out, params->f);
for (int i = 0; i < num_cores; i++) {
CalculationResult result = calculate_transformer(params, &cores[i]);
printf("Сердечник: %s\n", cores[i].name);
printf(" N1 = %.0f, N2 = %.0f, d1 = %.2f мм, d2 = %.2f мм\n",
result.N1, result.N2, result.d_wire1, result.d_wire2);
printf(" Стоимость: сердечник = %.2f, медь = %.2f, общая = %.2f\n",
cores[i].price_fe, result.cost_cu, result.cost_total);
printf(" Статус: %s\n\n", result.is_valid ? "ПОДХОДИТ" : "НЕ ПОДХОДИТ");
if (result.is_valid && result.cost_total < best_result.cost_total) {
best_result = result;
best_index = i;
}
}
if (best_index >= 0) {
printf("ОПТИМАЛЬНЫЙ ВАРИАНТ:\n");
printf("Сердечник: %s\n", cores[best_index].name);
printf("Общая стоимость: %.2f у.е.\n", best_result.cost_total);
return cores[best_index];
} else {
printf("НЕ УДАЛОСЬ НАЙТИ ПОДХОДЯЩИЙ СЕРДЕЧНИК!\n");
Core empty = {0};
return empty;
}
}
// Пример использования
int main() {
// Параметры трансформатора
TransformerParams params = {
.P_out = 100.0, // 100 Вт
.U_in = 220.0, // 220 В
.U_out = 12.0, // 12 В
.f = 50.0, // 50 Гц
.J = 3.0 // 3 А/мм²
};
// База данных стандартных сердечников
Core cores[] = {
{"ШЛ20х25", 5.0e-4, 4.0e-4, 15.0, 1.2, 1.2},
{"ШЛ25х32", 8.0e-4, 6.4e-4, 22.0, 1.2, 1.2},
{"ШЛ32х40", 12.8e-4, 10.0e-4, 35.0, 1.2, 1.2},
{"Тороид_40", 12.0e-4, 8.0e-4, 28.0, 1.3, 1.6},
{"Тороид_50", 15.0e-4, 12.0e-4, 42.0, 1.3, 1.6}
};
int num_cores = sizeof(cores) / sizeof(cores[0]);
// Поиск оптимального сердечника
Core optimal = find_optimal_core(&params, cores, num_cores);
return 0;
}
```
## Ключевые особенности реализации:
1. **Модульная структура** - разделение на логические блоки
2. **Структуры данных** для параметров, сердечников и результатов
3. **Итерационный поиск** по базе сердечников
4. **Проверка ограничений** (заполнение окна)
5. **Критерий оптимизации** - минимальная стоимость
## Дополнительные улучшения, которые можно добавить:
```c
// Функция для итерационного подбора плотности тока
void optimize_current_density(TransformerParams* params, Core* cores, int num_cores) {
double best_J = 2.0;
double best_cost = DBL_MAX;
for (double J = 2.0; J <= 4.0; J += 0.5) {
params->J = J;
// ... расчет для каждой плотности тока
}
}
// Чтение базы сердечников из файла
int read_cores_from_file(const char* filename, Core** cores) {
// ... реализация чтения из CSV или текстового файла
}
// Расчет габаритной мощности
double calculate_gabarit_power(const Core* core, const TransformerParams* params) {
return core->S_fe * core->S_window * core->k_fe *
params->J * 1e6 * core->B_max * params->f;
}
```
Такой подход позволяет:
- Легко добавлять новые типы сердечников
- Менять критерии оптимизации
- Интегрироваться с другими системами
- Проводить параметрические исследования
Программа будет эффективно работать даже с большими базами сердечников и может быть легко расширена для дополнительных расчетов.

59
modbus_params/Makefile Normal file
View File

@@ -0,0 +1,59 @@
# run `make DEF=...` to add extra defines
PROGRAM := modbus_par
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros -lmodbus
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
DEPS := $(OBJS:.o=.d)
TARGFILE := $(OBJDIR)/TARGET
CC = gcc
#TARGET := RELEASE
ifeq ($(shell test -e $(TARGFILE) && echo -n yes),yes)
TARGET := $(file < $(TARGFILE))
else
TARGET := RELEASE
endif
ifeq ($(TARGET), DEBUG)
.DEFAULT_GOAL := debug
endif
release: CFLAGS += -flto
release: LDFLAGS += -flto
release: $(PROGRAM)
debug: CFLAGS += -DEBUG -Werror
debug: TARGET := DEBUG
debug: $(PROGRAM)
$(TARGFILE): $(OBJDIR)
@echo -e "\t\tTARGET: $(TARGET)"
@echo "$(TARGET)" > $(TARGFILE)
$(PROGRAM) : $(TARGFILE) $(OBJS)
@echo -e "\t\tLD $(PROGRAM)"
$(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM)
$(OBJDIR):
@mkdir $(OBJDIR)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR)/%.o: %.c
@echo -e "\t\tCC $<"
$(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $<
clean:
@echo -e "\t\tCLEAN"
@rm -rf $(OBJDIR) 2>/dev/null || true
xclean: clean
@rm -f $(PROGRAM)
.PHONY: clean xclean

21
modbus_params/Readme Normal file
View File

@@ -0,0 +1,21 @@
modbus_par: reading/writing/dumping modbus registers using `dictionary`
-D, --dictionary=arg file with dictionary (format: code register value writeable)
-N, --node=arg node "IP", or path (could be "\0path" for anonymous UNIX-socket)
-O, --outdic=arg output dictionary for full device dump by input dictionary registers
-R, --readc registers (by keycodes, checked by dictionary) to read; multiply parameter
-U, --unixsock UNIX socket instead of INET
-W, --writec write new value to register by keycode (format: keycode=val); multiply parameter
-a, --alias=arg file with aliases in format 'name : command to run'
-b, --baudrate=arg modbus baudrate (default: 9600)
-d, --device=arg modbus device (default: /dev/ttyUSB0)
-h, --help show this help
-k, --dumpkey dump entry with this keycode; multiply parameter
-o, --outfile=arg file with parameter's dump
-r, --readr registers (by address) to read; multiply parameter
-s, --slave=arg slave ID (default: 1)
-t, --dumptime=arg dumping time interval (seconds, default: 0.1)
-v, --verbose verbose level (each -v adds 1)
-w, --writer write new value to register (format: reg=val); multiply parameter

510
modbus_params/dictionary.c Normal file
View File

@@ -0,0 +1,510 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
/*****************************************************************************
* WARNING!!!! *
* Be carefull: do not call funtions working with dictionary from several *
* threads if you're planning to open/close dictionaries "on-the-fly" *
*****************************************************************************/
#include <ctype.h>
#include <inttypes.h>
#include <stdatomic.h>
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "dictionary.h"
#include "modbus.h"
#include "verbose.h"
// main dictionary and arrays of pointers: sorted by code and by register value
static dicentry_t *dictionary = NULL, **dictbycode = NULL, **dictbyreg = NULL;
// size of opened dictionary
static size_t dictsize = 0;
size_t get_dictsize(){ return dictsize; }
/* dump */
static FILE *dumpfile = NULL; // file for dump output
static char *dumpname = NULL; // it's name
static dicentry_t *dumppars = NULL; // array with parameters to dump
static int dumpsize = 0; // it's size
static double dumpTime = 0.1; // period to dump
static atomic_int stopdump = FALSE, isstopped = TRUE; // flags
/* aliases */
// list of aliases sorted by name
static alias_t *aliases = NULL;
static size_t aliasessize = 0;
size_t get_aliasessize(){ return aliasessize; }
// functions for `qsort` (`needle` and `straw` are pointers to pointers)
static int sort_by_code(const void *needle, const void *straw){
const char *c1 = (*(dicentry_t**)needle)->code, *c2 = (*(dicentry_t**)straw)->code;
return strcmp(c1, c2);
}
static int sort_by_reg(const void *needle, const void *straw){
const int r1 = (int)(*(dicentry_t**)needle)->reg, r2 = (int)(*(dicentry_t**)straw)->reg;
return r1 - r2;
}
// functions for `binsearch`
static int search_by_code(const void *code, const void *straw){
const char *c1 = (const char*)code, *c2 = ((dicentry_t*)straw)->code;
return strcmp(c1, c2);
}
static int search_by_reg(const void *reg, const void *straw){
const int r1 = (int)(*(uint16_t*)reg), r2 = (int)((dicentry_t*)straw)->reg;
return r1 - r2;
}
// find comment in `str`; substitute '#' in `str` by 0; remove '\n' from comment
// @return pointer to comment start in `str` or NULL if absent
static char *getcomment(char *str){
char *comment = strchr(str, '#');
if(comment){
*comment++ = 0;
while(isspace(*comment)) ++comment;
if(*comment && *comment != '\n'){
char *nl = strchr(comment, '\n');
if(nl) *nl = 0;
return comment;
}
}
return NULL;
}
// open dictionary file and check it; return TRUE if all OK
// all after "#" is comment;
// dictionary format: "'code' 'register' 'value' 'readonly flag'\n", e.g.
// "F00.09 61444 5000 1"
int opendict(const char *dic){
closedict(); // close early opened dictionary to prevent problems
FILE *f = fopen(dic, "r");
if(!f){
WARN("Can't open %s", dic);
return FALSE;
}
size_t dicsz = 0;
size_t linesz = BUFSIZ;
char *line = MALLOC(char, linesz);
dicentry_t curentry;
curentry.code = MALLOC(char, BUFSIZ);
// `help` field of `curentry` isn't used here
int retcode = TRUE;
while(1){
if(getline(&line, &linesz, f) < 0) break;
// DBG("Original LINE: '%s'", line);
char *comment = getcomment(line);
char *newline = strchr(line, '\n');
if(newline) *newline = 0;
// DBG("LINE: '%s'", line);
if(*line == 0) continue;
if(4 != sscanf(line, "%s %" SCNu16 " %" SCNu16 " %" SCNu8, curentry.code, &curentry.reg, &curentry.value, &curentry.readonly)){
WARNX("Can't understand this line: '%s'", line);
continue;
}
// DBG("Got line: '%s %" PRIu16 " %" PRIu16 " %" PRIu8, curentry.code, curentry.reg, curentry.value, curentry.readonly);
if(++dictsize >= dicsz){
dicsz += 50;
dictionary = realloc(dictionary, sizeof(dicentry_t) * dicsz);
if(!dictionary){
WARN("Can't allocate memory for dictionary");
retcode = FALSE;
goto ret;
}
}
dicentry_t *entry = &dictionary[dictsize-1];
entry->code = strdup(curentry.code);
entry->reg = curentry.reg;
entry->value = curentry.value;
entry->readonly = curentry.readonly;
if(comment) entry->help = strdup(comment);
else entry->help = NULL;
//DBG("Add entry; now dictsize is %d", dictsize);
}
// init dictionaries for sort
dictbycode = MALLOC(dicentry_t*, dictsize);
dictbyreg = MALLOC(dicentry_t*, dictsize);
for(size_t i = 0; i < dictsize; ++i)
dictbyreg[i] = dictbycode[i] = &dictionary[i];
qsort(dictbycode, dictsize, sizeof(dicentry_t*), sort_by_code);
qsort(dictbyreg, dictsize, sizeof(dicentry_t*), sort_by_reg);
ret:
fclose(f);
FREE(curentry.code);
FREE(line);
return retcode;
}
/**
* @brief chkdict - check if dictionary is opened
* @return TRUE if dictionary opened
*/
int chkdict(){
if(dictsize < 1){
WARNX("Init dictionary first");
return FALSE;
}
return TRUE;
}
void closedict(){
if(!dictsize) return;
FREE(dictbycode);
FREE(dictbyreg);
for(size_t i = 0; i < dictsize; ++i){
FREE(dictionary[i].code);
FREE(dictionary[i].help);
}
FREE(dictionary);
dictsize = 0;
}
dicentry_t *binsearch(dicentry_t **base, const void* needle, int(*compar)(const void*, const void*)){
size_t low = 0, high = dictsize;
//DBG("search");
while(low < high){
size_t mid = (high + low) / 2;
//DBG("low=%zd, high=%zd, mid=%zd", low, high, mid);
int cmp = compar(needle, base[mid]);
if(cmp < 0){
high = mid;
}else if(cmp > 0){
low = mid + 1;
}else{
return base[mid];
}
}
//DBG("not found");
return NULL;
}
// find dictionary entry
dicentry_t *findentry_by_code(const char *code){
if(!chkdict()) return NULL;
return binsearch(dictbycode, (const void*)code, search_by_code);
}
dicentry_t *findentry_by_reg(uint16_t reg){
if(!chkdict()) return NULL;
return binsearch(dictbyreg, (const void*)(&reg), search_by_reg);
return NULL;
}
/** dump functions **/
// prepare a list with dump parameters (new call will rewrite previous list)
int setdumppars(char **pars){
if(!pars || !*pars) return FALSE;
if(!chkdict()) return FALSE;
FNAME();
char **p = pars;
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static int cursz = -1; // current allocated size
int N = 0;
pthread_mutex_lock(&mutex);
while(*p){ // count parameters and check them
dicentry_t *e = findentry_by_code(*p);
if(!e){
WARNX("Can't find entry with code %s", *p);
pthread_mutex_unlock(&mutex);
return FALSE;
}
DBG("found entry do dump, reg=%d", e->reg);
if(cursz <= N){
cursz += 50;
DBG("realloc list to %d", cursz);
dumppars = realloc(dumppars, sizeof(dicentry_t) * (cursz));
DBG("zero mem");
bzero(&dumppars[N], sizeof(dicentry_t)*(cursz-N));
}
FREE(dumppars[N].code);
dumppars[N] = *e;
dumppars[N].code = strdup(e->code);
DBG("Add %s", e->code);
++N; ++p;
}
dumpsize = N;
pthread_mutex_unlock(&mutex);
return TRUE;
}
// open dump file and add header; return FALSE if failed
int opendumpfile(const char *name){
if(!chkdict()) return FALSE;
if(dumpsize < 1){
WARNX("Set dump parameters first");
return FALSE;
}
if(!name) return FALSE;
closedumpfile();
dumpfile = fopen(name, "w+");
if(!dumpfile){
WARN("Can't open %s", name);
return FALSE;
}
dumpname = strdup(name);
fprintf(dumpfile, "# time,s ");
for(int i = 0; i < dumpsize; ++i){
fprintf(dumpfile, "%s ", dumppars[i].code);
}
fprintf(dumpfile, "\n");
return TRUE;
}
char *getdumpname(){ return dumpname;}
void closedumpfile(){
if(dumpfile && !isstopped){
if(!isstopped){
stopdump = TRUE;
while(!isstopped);
}
fclose(dumpfile);
FREE(dumpname);
}
}
static void *dumpthread(void *p){
isstopped = FALSE;
stopdump = FALSE;
double dT = *(double*)p;
DBG("Dump thread started. Period: %gs", dT);
double startT = sl_dtime();
while(!stopdump){
double t0 = sl_dtime();
fprintf(dumpfile, "%10.3f ", t0 - startT);
for(int i = 0; i < dumpsize; ++i){
if(!read_entry(&dumppars[i])) fprintf(dumpfile, "---- ");
else fprintf(dumpfile, "%4d ", dumppars[i].value);
}
fprintf(dumpfile, "\n");
while(sl_dtime() - t0 < dT) usleep(100);
}
isstopped = TRUE;
return NULL;
}
int setDumpT(double dT){
if(dT < 0.){
WARNX("Time interval should be > 0");
return FALSE;
}
dumpTime = dT;
DBG("user give dT: %g", dT);
return TRUE;
}
int rundump(){
if(!dumpfile){
WARNX("Open dump file first");
return FALSE;
}
pthread_t thread;
if(pthread_create(&thread, NULL, dumpthread, (void*)&dumpTime)){
WARN("Can't create dumping thread");
return FALSE;
}
DBG("Thread created, detach");
pthread_detach(thread);
return TRUE;
}
/**
* @brief dicentry_descr/dicentry_descrN - form string with dictionary entry (common function for dictionary dump)
* @param entry - dictionary entry
* @param N - entry number
* @param buf - outbuf
* @param bufsize - it's size
* @return NULL if error or pointer to `buf` (include '\n' terminating)
*/
char *dicentry_descr(dicentry_t *entry, char *buf, size_t bufsize){
//DBG("descr of %s", entry->code);
if(!entry || !buf || !bufsize) return NULL;
if(entry->help){
snprintf(buf, bufsize-1, "%s %4" PRIu16 " %4" PRIu16 " %" PRIu8 " # %s\n",
entry->code, entry->reg, entry->value, entry->readonly, entry->help);
}else{
snprintf(buf, bufsize-1, "%s %4" PRIu16 " %4" PRIu16 " %" PRIu8 "\n",
entry->code, entry->reg, entry->value, entry->readonly);
}
return buf;
}
char *dicentry_descrN(size_t N, char *buf, size_t bufsize){
if(N >= dictsize) return NULL;
return dicentry_descr(&dictionary[N], buf, bufsize);
}
// dump all registers by input dictionary into output; also modify values of registers in dictionary
int read_dict_entries(const char *outdic){
if(!chkdict()) return -1;
int got = 0;
FILE *o = fopen(outdic, "w");
if(!o){
WARN("Can't open %s", outdic);
return -1;
}
char buf[BUFSIZ];
for(size_t i = 0; i < dictsize; ++i){
if(read_entry(&dictionary[i])){
verbose(LOGLEVEL_MSG, "Read register %d, value: %d\n", dictionary[i].reg, dictionary[i].value);
if(dicentry_descrN(i, buf, BUFSIZ)){
++got;
fprintf(o, "%s\n", buf);
}
}else verbose(LOGLEVEL_WARN, "Can't read value of register %d\n", dictionary[i].reg);
}
fclose(o);
return got;
}
/********** Aliases **********/
// omit leading and trainling spaces
static char *omitspaces(char *ori){
char *name = sl_omitspaces(ori);
if(!name || !*name) return NULL;
char *e = sl_omitspacesr(name);
if(e) *e = 0;
if(!*name) return NULL;
return name;
}
static int sortalias(const void *l, const void *r){
const char *cl = ((alias_t*)l)->name, *cr = ((alias_t*)r)->name;
return strcmp(cl, cr);
}
// open file with aliases and fill structure
int openaliases(const char *filename){
closealiases();
FILE *f = fopen(filename, "r");
if(!f){
WARN("Can't open %s", filename);
return FALSE;
}
int retcode = TRUE;
size_t linesz = BUFSIZ, asz = 0;
char *line = MALLOC(char, linesz);
while(1){
if(getline(&line, &linesz, f) < 0) break;
DBG("aliases line %zd: '%s'", aliasessize, line);
char *comment = getcomment(line);
char *newline = strchr(line, '\n');
if(newline) *newline = 0;
if(*line == 0) continue;
char *_2run = strchr(line, ':');
if(!_2run){
WARNX("Bad string for alias: '%s'", line);
continue;
}
*_2run++ = 0;
_2run = omitspaces(_2run);
if(!_2run){
WARNX("Empty alias: '%s'", line);
continue;
}
char *name = omitspaces(line);
if(!name || !*name){
WARNX("Empty `name` field of alias: '%s'", _2run);
continue;
}
if(strchr(name, ' ')){
WARNX("Space in alias name: '%s'", name);
continue;
}
if(++aliasessize >= asz){
asz += 50;
aliases = realloc(aliases, sizeof(alias_t) * asz);
if(!aliases){
WARNX("Can't allocate memory for aliases list");
retcode = FALSE;
goto ret;
}
}
alias_t *cur = &aliases[aliasessize - 1];
cur->name = strdup(name);
cur->expr = strdup(_2run);
if(comment) cur->help = strdup(comment);
else cur->help = NULL;
DBG("Read alias; name='%s', expr='%s', comment='%s'", cur->name, cur->expr, cur->help);
}
if(aliasessize == 0) retcode = FALSE;
qsort(aliases, aliasessize, sizeof(alias_t), sortalias);
ret:
fclose(f);
FREE(line);
return retcode;
}
// return TRUE if aliases list inited
int chkaliases(){
if(aliasessize < 1){
WARNX("Init aliases first");
return FALSE;
}
return TRUE;
}
// remove aliases
void closealiases(){
if(!aliasessize) return;
for(size_t i = 0; i < aliasessize; ++i){
FREE(aliases[i].name);
FREE(aliases[i].expr);
FREE(aliases[i].help);
}
FREE(aliases);
aliasessize = 0;
}
// find alias by name
alias_t *find_alias(const char *name){
if(!chkaliases()) return NULL;
size_t low = 0, high = aliasessize;
DBG("find alias %s", name);
while(low < high){
size_t mid = (high + low) / 2;
int cmp = strcmp(name, aliases[mid].name);
DBG("cmp %s and %s give %d", name, aliases[mid].name, cmp);
if(cmp < 0){
high = mid;
}else if(cmp > 0){
low = mid + 1;
}else{
return &aliases[mid];
}
}
DBG("not found");
return NULL;
}
// describe alias by entry
char *alias_descr(alias_t *entry, char *buf, size_t bufsize){
if(!entry || !buf || !bufsize) return NULL;
if(entry->help){
snprintf(buf, bufsize-1, "%s : %s # %s\n", entry->name, entry->expr, entry->help);
}else{
snprintf(buf, bufsize-1, "%s : %s\n", entry->name, entry->expr);
}
return buf;
}
// describe alias by number
char *alias_descrN(size_t N, char *buf, size_t bufsize){
if(N >= aliasessize) return NULL;
return alias_descr(&aliases[N], buf, bufsize);
}

View File

@@ -0,0 +1,522 @@
# awk '{printf "%s\t%s\t%s\t0\t# ", $1, $2, $3; $1=""; $2=""; $3=""; print $0}' orig > dictionary.dic
F00.00 61440 1 0 # Управление двигателем (V/F)
F00.01 61441 0 0 # Выбор задания команды ПУСК [0-с клавиатуры; 3,4 Modbus RTU]
F00.02 61442 0 0 # Тип связи [0 - MODbus RTU]
F00.03 61443 5000 0 # Максимальная частота для П.Ч. х100, Гц [от F00.04 до 500.00 Гц] (750 rpm)
F00.04 61444 5000 0 # Верхний предел рабочей частоты х100, Гц [F00.05 - F00.03] (610 rpm)
F00.05 61445 0 0 # Минимальная частота х100, Гц [от 0.00 до F00.04 Гц]
F00.06 61446 0 0 # Источник частоты А от … [0 — 9] [9 Modbus RTU]
F00.07 61447 0 0 # Источник частоты B от … [0 — 9] (3)
F00.08 61448 0 0 # Задание частоты по каналу B [0 макс.частота; 1-частота канала A]
F00.09 61449 1000 0 # Максимальная частота по каналу В х10 в % (100 rpm)
F00.10 61450 0 0 # Комбинированный режим регулирования [от 0 до 5] (0 => только А)
F00.11 61451 5000 0 # Частота комбинированного режима х100, Гц [от 0.00 до F00.03] (600 rpm)
F00.12 61452 100 0 # Время разгона 1 х 10, сек. (30s)
F00.13 61453 100 0 # Время торможения 1 х 10, сек. (30s)
F00.14 61454 0 0 # Направление вращения [от 0 CW; 1 CCW; 2 запрет CCW]
F00.15 61455 50 0 # Несущая частота модуляции 1 х 10 [от 2.0 до 10.0 кГц] (5 kHz)
F00.16 61456 0 0 # Отслеживание скорости [от 0 до 2]
F00.17 61457 0 0 # Автонастройка двигателя [0 off;1 on with run; 2 - on without run;]
F00.18 61458 0 0 # Сброс параметров [от 0 до 3; 0 - без сброса]
F01.00 61696 0 0 # Тип запуска: 0 — запуск со стартовой частоты; 1 — запуск после торможения; 2 — запуск с поиском скорости
F01.01 61697 50 0 # Стартовая частота х100, [от 0 до 10 Гц] (7,5/101.48 = 0,074 rpm каток)
F01.02 61698 0 0 # Время удержания стартовой частоты х10, [от 0.0 до 100.0 сек.] (тест 1 с.)
F01.03 61699 0 0 # Ток торможения перед стартом х10, % [от 00 до 150.0%]
F01.04 61700 0 0 # Время торможения перед стартом х10, сек. [от 0.0 до 100.0 сек]
F01.05 61701 0 0 # Тип разгона/торможения [0 — линейный; 1 — S кривая;]
F01.06 61702 300 0 # Стартовая фаза S-кривой х10, % [от 0.0 — 50.0% времени разг./торм.]
F01.07 61703 300 0 # Конечная фаза S-кривой х10, % [от 0.0 — 50.0% времени разг./торм.]
F01.08 61704 0 0 # Метод остановки [0 — замедление до остан.; 1 — свободн. выбег]
F01.09 61705 0 0 # Частота включения торможения постоянным током х100, Гц
F01.10 61706 0 0 # Время ожидания торможения постоянным током х10, сек. [0 до100]
F01.11 61707 0 0 # Постоянный ток торможения во время остановки х10, % [0 до100]
F01.12 61708 0 0 # Время торможения пост. током во время остановки х10, сек.
F01.13 61709 0 0 # Время бездействия между сменой направлений вращения х10, сек.
F01.14 61710 50 0 # Частота остановки х10, Гц
F01.15 61711 0 1 # Зарезервировано
F01.16 61712 0 1 # Зарезервировано
F01.17 61713 0 0 # Перезапуск с клемм после включения [0 — не действ. 1 - действует]
F01.18 61714 0 0 # Перезапуск после потери напряжения [0 — запрет; 1 - перезапуск]
F01.19 61715 10 0 # Задержка перезапуска х10, сек. [0.0 до 6000.0] (Только если F1.18 = 1)
F01.20 61716 0 1 # Зарезервировано
F01.21 61717 0 0 # Действие, если выходная частота меньше минимальной; 0 — работа на мин.частоте; 1 - остановка; 2 - работа на нулевой скорости вращения.
F01.22 61718 5000 0 # Задание частоты при работе в пожарном режиме (х100), Гц.
F02.00 61952 0 0 # Тип нагрузки [0 — тип G; 1 тип P;]
F02.01 61953 0 0 # Тип двигателя [0 - АСИНХРОННЫЙ]
F02.02 61954 15 0 # Мощность двигателя х10, кВт (5.5 кВт)
F02.03 61955 380 0 # Напряжение двигателя, В [0 — 1000.0] (380 В)
F02.04 61956 65 0 # Номинальный ток х10, А (14.8А)
F02.05 61957 5000 0 # Номинальная частота для двигателя х100, Гц
F02.06 61958 1450 0 # Номинальные обороты двигателя по паспорту, об/мин
F02.07 61959 1414 0 # Сопротивление статора х100, Ом (Значение после автонастройки!)
F02.08 61960 1264 0 # Сопротивление ротора х100, Ом (Значение после автонастройки!)
F02.09 61961 67 0 # Индуктивность рассеивания х10, мГн (Значение после автонастройки!)
F02.10 61962 956 0 # Индуктивность двигателя х10, мГн (Значение после автонастройки!)
F02.11 61963 33 0 # Ток холостого хода х10, А (Значение после автонастройки!)
F02.12 61964 880 1 # Зарезервировано
F02.13 61965 810 1 # Зарезервировано
F02.14 61966 750 1 # Зарезервировано
F02.15 61967 0 0 # Выбор типа энкодера
F02.16 61968 1024 0 # Число импульсов энкодера на оборот
F02.17 61969 0 0 # Последовательность фаз энкодера ABZ
F02.18 61970 1 0 # Количество пар полюсов резольвера
F02.19 61971 1 1 # Зарезервировано
F02.20 61972 3 1 # Зарезервировано
F02.21 61973 3 1 # Зарезервировано
F02.22 61974 10 1 # Зарезервировано
F02.23 61975 10 1 # Зарезервировано
F02.24 61976 1000 1 # Зарезервировано
F02.25 61977 1 0 # Защита от перегрузки [0 — не действует; 1 — действует;]
F02.26 61978 1000 0 # Коэффициент защиты от перегрузки х10, % [F02.26 x F02.04]
F03.00 62208 30 0 # Пропорциональный коэффициент контура скорости 1
F03.01 62209 50 0 # Время интегрирования контура скорости 1 х1000, сек
F03.02 62210 500 0 # Пороговое значение низкой скорости х100, Гц
F03.03 62211 20 0 # Пропорциональный коэффициент контура скорости 2
F03.04 62212 100 0 # Время интегрирования контура скорости 2 х1000, сек
F03.05 62213 1000 0 # Пороговое значение высокой скорости 2 х100, Гц
F03.06 62214 15 0 # Коэффициент фильтрации обратной связи х100, сек
F03.07 62215 0 0 # Пропорциональный коэффициент контура тока 1
F03.08 62216 0 0 # Время интегрирования контура тока 1
F03.09 62217 0 0 # Пропорциональный коэффициент контура тока 2
F03.10 62218 0 0 # Время интегрирования контура тока 2
F03.11 62219 0 0 # Выбор канала ограничения крутящего момента
F03.12 62220 0 0 # Выбор канала ограничения момента торможения
F03.13 62221 1500 0 # Цифровое задание крутящего момента х10, %
F03.14 62222 1500 0 # Цифровое задание момента торможения х10, %
F03.15 62223 100 0 # Коэффициент ограничения момента
F03.16 62224 100 0 # Коэффициент компенсации скольжения, %
F04.00 62464 0 0 # Выбор кривой V/F (линейная х-ка)
F04.01 62465 0 0 # Усиление момента х10, %
F04.02 62466 200 0 # Частота усиления момента х10, % ((300rpm/750rpm)*100)*10%
F04.03 62467 0 0 # V/F частота 1 х100, Гц
F04.04 62468 0 0 # V/F напряжение 1 х10, %
F04.05 62469 0 0 # V/F частота 2 х100, Гц
F04.06 62470 0 0 # V/F напряжение 2 х10, %
F04.07 62471 0 0 # V/F частота 3 х100, Гц
F04.08 62472 0 0 # V/F напряжение 3 х10, %
F04.09 62473 100 0 # Компенсация скольжения х10, %
F04.10 62474 2 0 # Контроль вибрации при низкой частоте
F04.11 62475 2 0 # Контроль вибрации при высокой частоте
F04.12 62476 3000 0 # Порог контроля вибрации х100, Гц
F04.13 62477 0 0 # Выбор кривой V/F Мотор 2
F04.14 62478 0 0 # Усиление момента х10, %
F04.15 62479 200 0 # Частота усиления момента х10, %
F04.16 62480 0 0 # V/F частота 1 х100, Гц
F04.17 62481 0 0 # V/F напряжение 1 х10, %
F04.18 62482 0 0 # V/F частота 2 х100, Гц
F04.19 62483 0 0 # V/F напряжение 2 х10, %
F04.20 62484 0 0 # V/F частота 3 х100, Гц
F04.21 62485 0 0 # V/F напряжение 3 х10, %
F04.22 62486 1000 0 # Компенсация скольжения х10, %
F04.23 62487 2 0 # Контроль вибрации при низкой частоте
F04.24 62488 2 0 # Контроль вибрации при высокой частоте
F04.25 62489 3000 0 # Порог контроля вибрации х100, Гц
F04.26 62490 0 0 # Энергосбережение
F04.27 62491 0 0 # Установка напряжения
F04.28 62492 1000 0 # Задание с клавиатуры х10, %
F04.29 62493 50 0 # Время повышения напряжения х10, сек.
F04.30 62494 50 0 # Время понижения напряжения х10, сек.
F04.31 62495 1000 0 # Максимальное выходное напряжение х10, %
F04.32 62496 0 0 # Минимальное выходное напряжение х10, %
F04.33 62497 1 0 # Выбор функции АВР
F04.34 62498 400 1 # Зарезервировано
F05.00 62720 0 0 # Выбор клеммы DI5
F05.01 62721 1 0 # Клемма DI1
F05.02 62722 4 0 # Клемма DI2
F05.03 62723 7 0 # Клемма DI3
F05.04 62724 0 0 # Клемма DI4
F05.05 62725 0 0 # Клемма DI5 (дискретный)
F05.06 62726 0 0 # Клемма DI6
F05.07 62727 0 0 # Клемма DI7 (опционально)
F05.08 62728 0 0 # Клемма DI8 (опционально)
F05.09 62729 0 0 # Выбор полярности входных клемм
F05.10 62730 10 0 # Фильтр дребезга входных клемм х1000, сек.
F05.11 62731 0 0 # Настройка управления входных клемм
F05.12 62732 0 0 # Настройка подключения входных клемм
F05.13 62733 0 0 # Задержка включения клеммы DI1 х1000, сек.
F05.14 62734 0 0 # Задержка выключения клеммы DI1 х1000, сек.
F05.15 62735 0 0 # Задержка включения клеммы DI2 х1000, сек.
F05.16 62736 0 0 # Задержка выключения клеммы DI2 х1000, сек.
F05.17 62737 0 0 # Задержка включения клеммы DI3 х1000, сек.
F05.18 62738 0 0 # Задержка выключения клеммы DI3 х1000, сек.
F05.19 62739 0 0 # Задержка включения клеммы DI4 х1000, сек.
F05.20 62740 0 0 # Задержка выключения клеммы DI4 х1000, сек.
F05.21 62741 0 0 # Задержка включения клеммы DI5 х1000, сек.
F05.22 62742 0 0 # Задержка выключения клеммы DI5 х1000, сек.
F05.23 62743 0 0 # Задержка включения клеммы DI6 х1000, сек.
F05.24 62744 0 0 # Задержка выключения клеммы DI6 х1000, сек.
F05.25 62745 0 0 # Задержка включения клеммы DI7 х1000, сек.
F05.26 62746 0 0 # Задержка выключения клеммы DI7 х1000, сек.
F05.27 62747 0 0 # Задержка включения клеммы DI8 х1000, сек.
F05.28 62748 0 0 # Задержка выключения клеммы DI8 х1000, сек.
F05.29 62749 0 1 # Зарезервировано
F05.30 62750 0 0 # Нижний предел частоты DI5 х100, КГц
F05.31 62751 0 0 # Настройка нижнего предела частоты DI5 х10, %
F05.32 62752 5000 0 # Верхний предел частоты DI5 х100, КГц
F05.33 62753 1000 0 # Настройка верхнего предела частоты DI5 х10, %
F05.34 62754 100 0 # Время фильтрации DI5 х1000, сек.
F05.35 62755 0 0 # Режим работы клеммы пожарного режима
F06.00 62976 0 0 # Конфигурация клеммы HDO1
F06.01 62977 0 0 # Функция клеммы DO1
F06.02 62978 0 0 # Функция клеммы HDO1
F06.03 62979 1 0 # Функция реле Т1
F06.04 62980 5 0 # Функция реле Т2
F06.05 62981 0 0 # Выбор полярности выходных клемм
F06.06 62982 0 0 # Задержка включения клеммы DO1 х1000, сек.
F06.07 62983 0 0 # Задержка выключения клеммы DO1 х1000, сек.
F06.08 62984 0 0 # Задержка включения клеммы НDO1 х1000, сек.
F06.09 62985 0 0 # Задержка выключения клеммы НDO1 х1000, сек.
F06.10 62986 0 0 # Задержка включения реле Т1 х1000, сек.
F06.11 62987 0 0 # Задержка выключения реле Т1 х1000, сек.
F06.12 62988 0 0 # Задержка включения реле Т2 х1000, сек.
F06.13 62989 0 0 # Задержка выключения реле Т2 х1000, сек.
F06.14 62990 0 0 # Выход АО1
F06.15 62991 0 0 # Выход АО2
F06.16 62992 0 0 # Выход HDO1 (высокоскоростной выход)
F06.17 62993 0 0 # Нижний предел АО1 х10. %
F06.18 62994 200 0 # Значение нижнего предела АО1 х100, В
F06.19 62995 1000 0 # Верхний предел АО1 х10, %
F06.20 62996 1000 0 # Значение верхнего предела АО1 х100, В
F06.21 62997 0 0 # Время фильтрации выхода АО1 х1000, сек.
F06.22 62998 0 0 # Нижний предел АО2 х10, %
F06.23 62999 0 0 # Значение нижнего предела АО2 х100, В
F06.24 63000 1000 0 # Верхний предел АО2 х10, %
F06.25 63001 1000 0 # Значение верхнего предела АО2 х100, В
F06.26 63002 0 0 # Время фильтрации выхода АО2 х1000, сек.
F06.27 63003 0 0 # Нижний предел HDO1 х10, %
F06.28 63004 0 0 # Значение нижнего предела HDO1 х100, КГц
F06.29 63005 1000 0 # Верхний предел HDO1 х10, %
F06.30 63006 5000 0 # Значение верхнего предела HDO1 х100, КГц
F06.31 63007 0 0 # Время фильтрации выхода HDO1 х1000, сек.
F06.32 63008 1 0 # Функция управления тормозом
F06.33 63009 500 0 # Частота отпускания тормоза при подъеме х100, Гц
F06.34 63010 20 0 # Ток отпускания тормоза при подъеме, % от ном.тока
F06.35 63011 600 0 # Частота задержки отпускания тормоза при подъеме х100, Гц
F06.36 63012 2 0 # Время задержки отпускания тормоза при подъеме х10, сек.
F06.37 63013 500 0 # Частота включения тормоза при подъеме х100, Гц
F06.38 63014 450 0 # Частота задержки включения тормоза при подъеме х100, Гц
F06.39 63015 2 0 # Время задержки включения тормоза при подъеме х10, сек.
F06.40 63016 500 0 # Частота отпускания тормоза при спуске х100, Гц
F06.41 63017 20 0 # Ток отпускания тормоза при спуске, % от ном.тока
F06.42 63018 600 0 # Частота задержки отпускания тормоза при спуске х100, Гц
F06.43 63019 2 0 # Время задержки отпускания тормоза при спуске х100, сек.
F06.44 63020 500 0 # Частота включения тормоза при спуске х100, Гц
F06.45 63021 450 0 # Частота задержки включения тормоза при спуске х100, сек.
F06.46 63022 2 0 # Время задержки включения тормоза при спуске х10, сек.
F06.47 63023 1000 0 # Ограничение момента при включенном тормозе х10, %
F07.00 63232 0 0 # Пароль пользователя
F07.01 63233 0 0 # Выбор функции кнопки S
F07.02 63234 255 0 # Параметры состояния 1
F07.03 63235 255 0 # Параметры состояния 2
F07.04 63236 255 0 # Параметры состояния при остановке
F07.05 63237 100 0 # Коэффициент частоты х100
F07.06 63238 1000 0 # Скорость вращения х10, %
F07.07 63239 10 0 # Коэффициент линейной скорости х10, %
F07.08 63240 36 1 # Температура выпрямителя, С
F07.09 63241 36 1 # Температура инвертора, С
F07.10 63242 60770 1 # Версия ПО
F07.11 63243 0 1 # Время наработки, час.
F07.12 63244 0 1 # Нижний предел сигнализации AI1, В
F07.13 63245 680 1 # Верхний предел сигнализации AI1, В
F07.14 63246 0 1 # Выбор канала ограничения верхнего предела выхода ПИД-регулятора
F07.15 63247 15 1 # Номинальная мощность ПЧ х10, КВт
F07.16 63248 380 1 # Номинальное напряжение, В
F07.17 63249 75 1 # Номинальный ток х10, А
F07.18 63250 0 1 # Текущая ошибка
F07.19 63251 0 1 # Ошибка при предыдущем отключении
F07.20 63252 0 1 # Ошибка при 2-м отключении
F07.21 63253 0 1 # Ошибка при 3-м отключении
F07.22 63254 0 1 # Ошибка при 4-м отключении
F07.23 63255 0 1 # Ошибка при 5-м отключении
F07.24 63256 0 1 # Частота в момент текущей аварии х100, Гц
F07.25 63257 0 1 # Опорная частота в момент текущей аварии х100, Гц
F07.26 63258 0 1 # Выходное напряжение в момент текущей аварии, В
F07.27 63259 0 1 # Выходной ток в момент текущей аварии х10, В
F07.28 63260 0 1 # Напряжение звена постоянного тока в момент текущей аварии х10, В
F07.29 63261 0 1 # Максимальная температура ПЧ в момент текущей аварии х10, С
F07.30 63262 0 1 # Состояние входных клемм в момент текущей аварии
F07.31 63263 0 1 # Состояние выходных клемм в момент текущей аварии
F07.32 63264 0 1 # Выходная частота в момент предыдущей аварии х100, Гц
F07.33 63265 0 1 # Опорная частота в момент предыдущей аварии х100, Гц
F07.34 63266 0 1 # Выходное напряжение в момент предыдущей аварии, В
F07.35 63267 0 1 # Выходной ток в момент предыдущей аварии х10, А
F07.36 63268 0 1 # Напряжение звена постоянного тока в момент предыдущей аварии х10, В
F07.37 63269 0 1 # Максимальная температура ПЧ в момент предыдущей аварии х10, С
F07.38 63270 0 1 # Состояние входных клемм в момент предыдущей аварии
F07.39 63271 0 1 # Состояние выходных клемм в момент предыдущей аварии
F07.40 63272 0 1 # Частота в момент 2-й аварии х100, Гц
F07.41 63273 0 1 # Опорная частота в момент 2-й аварии х100, Гц
F07.42 63274 0 1 # Выходное напряжение в момент 2-й аварии, В
F07.43 63275 0 1 # Выходной ток в момент 2-й аварии х10, А
F07.44 63276 0 1 # Напряжение звена постоянного тока в момент 2-й аварии, В
F07.45 63277 0 1 # Максимальная температура ПЧ в момент 2-й аварии х10, С
F07.46 63278 0 1 # Состояние входных клемм в момент 2-й аварии
F07.47 63279 0 1 # Состояние выходных клемм в момент 2-й аварии
F08.00 63488 100 0 # Время разгона 2 х10, сек.
F08.01 63489 100 0 # Время торможения 2 х10, сек.
F08.02 63490 100 0 # Время разгона 3 х10, сек.
F08.03 63491 100 0 # Время торможения 3 х10, сек.
F08.04 63492 100 0 # Время разгона 4 х10, сек.
F08.05 63493 100 0 # Время торможения 4 х10, сек.
F08.06 63494 500 0 # Толчковая частота х100, Гц
F08.07 63495 100 0 # Время разгона толчкового режима х10, сек.
F08.08 63496 100 0 # Время торможения толчкового режима х10, сек.
F08.09 63497 0 0 # Количество сбросов аварии
F08.10 63498 10 0 # Время задержки автоматического сброса х10, сек.
F08.11 63499 0 0 # Контроль снижения частоты х100, Гц
F08.12 63500 0 0 # Переключение двигателя
F08.13 63501 5000 0 # Уровень достижения частоты FDT1 х100, Гц
F08.14 63502 50 0 # Удержание FDT1 х10, %
F08.15 63503 5000 0 # Уровень достижения частоты FDT1 х100, Гц
F08.16 63504 50 0 # Удержание FDT2 х10, %
F08.17 63505 0 0 # Ширина обнаружения частоты х100, Гц
F08.18 63506 1 0 # Тормозной прерыватель
F08.19 63507 3800 0 # Уровень напряжения срабатывания х10, В
F08.20 63508 0 0 # Компенсация ШИМ
F08.21 63509 0 0 # Функция кнопок пульта
F08.22 63510 10 0 # Время задержки кнопок 'ВВЕРХ'/'ВНИЗ х10, сек.'
F08.23 63511 0 0 # Задание кнопок 'ВВЕРХ'/'ВНИЗ'
F08.24 63512 50 0 # Время задержки кнопки 'ВВЕРХ' х100, сек.
F08.25 63513 50 0 # Время задержки кнопки 'ВНИЗ' х100, сек.
F08.26 63514 0 0 # Частота при потере питания
F08.27 63515 50 0 # Торможение магнитным потоком х100, сек.
F08.28 63516 1 0 # Вспомогательный мониторинг
F08.29 63517 0 0 # Копирование параметров (только для панели)
F08.30 63518 0 0 # Дополнительная плата входов/выходов
F08.31 63519 0 0 # Торможение при потере питания
F08.32 63520 0 0 # Время торможения при потере питания х10, сек.
F08.33 63521 2000 0 # Напряжение срабатывания, В
F08.34 63522 5000 0 # Произвольная частота 1 х100, Гц
F08.35 63523 0 0 # Ширина обнаружения произвольной частоты 1
F08.36 63524 5000 0 # Произвольная частота 2 х100, Гц
F08.37 63525 0 0 # Ширина обнаружения произвольной частоты 2
F08.38 63526 0 0 # Толчковый режим во время работы
F09.00 63744 0 0 # Источник задания уставки ПИД
F09.01 63745 500 0 # Уставка ПИД х10, %
F09.02 63746 0 0 # Обратная связь ПИД
F09.03 63747 0 0 # Тип обратной связи
F09.04 63748 200 0 # Пропорциональная составляющая (Кр) х10
F09.05 63749 20 0 # Интегральная составляющая (Ti) х10
F09.06 63750 0 0 # Дифференциальная составляющая (Td) х100
F09.07 63751 10 0 # Цикл выборки(T) х100, сек.
F09.08 63752 2 0 # Предел отклонения ПИД х10, %
F09.09 63753 1000 0 # Верхний предел ПИД х10, %
F09.10 63754 0 0 # Нижний предел ПИД х10, %
F09.11 63755 0 0 # Величина сигнала обратной связи, активирущая ошибку пропажи сигнала х10, %
F09.12 63756 10 0 # Задержка включения ошибки пропажи сигнала х10, сек.
F09.13 63757 0 1 # Зарезервировано
F09.14 63758 50 0 # Пропорциональная составляющая 2 (Кр2) х100
F09.15 63759 20 0 # Интегральная составляющая 2 (Ti2) х100
F09.16 63760 0 0 # Дифференциальная составляющая 2 (Td2) х100
F09.17 63761 0 0 # Переключение ПИД параметров
F09.18 63762 200 0 # Пороговое отклонение ПИД х10, %
F09.19 63763 600 0 # Частота засыпания х100, %
F09.20 63764 0 0 # Задержка засыпания х10, сек.
F09.21 63765 0 0 # Коэффициент пробуждения х10, %
F09.22 63766 5 0 # Задержка пробуждения х10, сек
F09.23 63767 0 0 # Предустановленное значение ПИД х10, %
F09.24 63768 0 0 # Время удержания предустановленного значения ПИД х10, сек.
F0A.00 64000 0 0 # Частота колебаний х10, %
F0A.01 64001 0 0 # Амплитуда частоты толчка х10, сек.
F0A.02 64002 0 0 # Время увеличения частоты колебаний х10, сек.
F0A.03 64003 0 0 # Время снижения частоты колебаний х10, сек.
F0A.04 64004 0 0 # Установка длины, м
F0A.05 64005 0 1 # Заданная длина, м
F0A.06 64006 1 0 # Количество импульсов на оборот
F0A.07 64007 1000 0 # Длина окружности вала х100, м
F0A.08 64008 1000 0 # Множитель х1000
F0A.09 64009 1000 0 # Корректирующий коэффициент х1000
F0A.10 64010 0 0 # Установленное значение счетчика
F0A.11 64011 0 0 # Определяемое значение счетчика
F0A.12 64012 0 0 # Время работы, мин.
F0A.13 64013 0 0 # Метод остановки
F0A.14 64014 0 1 # Зарезервировано
F0B.00 64256 0 0 # Режим простого ПЛК
F0B.01 64257 0 0 # Режим работы ПЛК при перебое с питанием
F0B.02 64258 0 0 # Многоскоростной режим 0 х10, %
F0B.03 64259 0 0 # Время работы в режиме 0 х10, сек.
F0B.04 64260 0 0 # Многоскоростной режим 1 х10, %
F0B.05 64261 0 0 # Время работы в режиме 1 х10, сек.
F0B.06 64262 0 0 # Многоскоростной режим 2 х10, %
F0B.07 64263 0 0 # Время работы в режиме 2 х10, сек.
F0B.08 64264 0 0 # Многоскоростной режим 3 х10, %
F0B.09 64265 0 0 # Время работы в режиме 3 х10, сек.
F0B.10 64266 0 0 # Многоскоростной режим 4 х10, %
F0B.11 64267 0 0 # Время работы в режиме 4 х10, сек.
F0B.12 64268 0 0 # Многоскоростной режим 5 х10, %
F0B.13 64269 0 0 # Время работы в режиме 5 х10, сек.
F0B.14 64270 0 0 # Многоскоростной режим 6 х10, %
F0B.15 64271 0 0 # Время работы в режиме 6 х10, сек.
F0B.16 64272 0 0 # Многоскоростной режим 7 х10, %
F0B.17 64273 0 0 # Время работы в режиме 7 х10, сек.
F0B.18 64274 0 0 # Многоскоростной режим 8 х10, %
F0B.19 64275 0 0 # Время работы в режиме 8 х10, сек.
F0B.20 64276 0 0 # Многоскоростной режим 9 х10, %
F0B.21 64277 0 0 # Время работы в режиме 9 х10, сек.
F0B.22 64278 0 0 # Многоскоростной режим 10 х10, %
F0B.23 64279 0 0 # Время работы в режиме 10 х10, сек.
F0B.24 64280 0 0 # Многоскоростной режим 11 х10, %
F0B.25 64281 0 0 # Время работы в режиме 11 х10, сек.
F0B.26 64282 0 0 # Многоскоростной режим 12 х10, %
F0B.27 64283 0 0 # Время работы в режиме 12 х10, сек.
F0B.28 64284 0 0 # Многоскоростной режим 13 х10, %
F0B.29 64285 0 0 # Время работы в режиме 13 х10, сек.
F0B.30 64286 0 0 # Многоскоростной режим 14 х10, %
F0B.31 64287 0 0 # Время работы в режиме 14 х10, сек.
F0B.32 64288 0 0 # Многоскоростной режим 15 х10, %
F0B.33 64289 0 0 # Время работы в режиме 15 х10, сек.
F0B.34 64290 0 0 # Значения разгона и торможения ПЛК (0 - 7)
F0B.35 64291 0 0 # Значения разгона и торможения ПЛК (8 - 15)
F0B.36 64292 0 0 # Перезапуск ПЛК
F0B.37 64293 0 0 # Режим перезапуска ПЛК
F0C.00 64512 11 0 # Защита от потери фазы
F0C.01 64513 0 0 # Снижение частоты при перебое c питанием х100, Гц/сек.
F0C.02 64514 1000 0 # Скорость снижения частоты при перебое с питанием х100, Гц/сек.
F0C.03 64515 0 0 # Превышение напряжения при блокировке вала
F0C.04 64516 140 0 # Уровень превышения напряжения при блокировке вала, %
F0C.05 64517 1 0 # Ограничение тока
F0C.06 64518 1660 0 # Автоматическое ограничение тока х10, %
F0C.07 64519 1000 0 # Шаг понижения частоты при ограничении тока х100, Гц/сек.
F0C.08 64520 0 0 # Защита от перегрузки
F0C.09 64521 166 0 # Предварительный сигнал обнаружения перегрузки, %
F0C.10 64522 0 0 # Время обнаружения перегрузки х100, сек.
F0C.11 64523 0 0 # Предварительный сигнал низкой нагрузки, %
F0C.12 64524 30 0 # Предварительный сигнал обнаружения низкой нагрузки, %
F0C.13 64525 10 0 # Время обнаружения низкой нагрузки х10, сек.
F0C.14 64526 0 0 # Режимы оповещения при обнаружении низкой нагрузки
F0C.15 64527 0 0 # Выбор регулировки несущей частоты
F0C.16 64528 1 0 # Режим ШИМ
F0C.17 64529 1 0 # Фильтр низких частот
F0C.18 64530 1 1 # Зарезервирован
F0D.00 64768 1 0 # Метод управления вторым двигателем
F0D.01 64769 1 0 # Тип нагрузки
F0D.02 64770 1 0 # Тип второго двигателя
F0D.03 64771 15 0 # Номинальная мощность двигателя 2 х10, КВт
F0D.04 64772 380 0 # Номинальное напряжение двигателя 2, В
F0D.05 64773 65 0 # Номинальный ток двигателя 2 х10, А
F0D.06 64774 5000 0 # Номинальная чаота двигателя 2 х100, Гц
F0D.07 64775 1450 0 # Номинальные обороты двигателя 2 х10, об/мин.
F0D.08 64776 1414 0 # Сопротивление статора двигателя 2 х1000, Ом
F0D.09 64777 1264 0 # Сопротивление ротора двигателя 2 х1000, Ом
F0D.10 64778 67 0 # Индуктивность рассеяния двигателя 2 х10
F0D.11 64779 956 0 # Индуктивность двигателя 2 х10
F0D.12 64780 33 0 # Ток холостого хода двигателя 2 х10, А
F0D.13 64781 1 1 # Зарезервировано
F0D.14 64782 1 1 # Зарезервировано
F0D.15 64783 1 1 # Зарезервировано
F0D.16 64784 1 1 # Зарезервировано
F0D.17 64785 1 1 # Зарезервировано
F0D.18 64786 1 1 # Зарезервировано
F0D.19 64787 1 1 # Зарезервировано
F0D.20 64788 1 1 # Зарезервировано
F0D.21 64789 1 1 # Зарезервировано
F0D.22 64790 1 1 # Зарезервировано
F0D.23 64791 1 1 # Зарезервировано
F0D.24 64792 1 1 # Зарезервировано
F0D.25 64793 1 1 # Зарезервировано
F0D.26 64794 1 0 # Защита от перегрузки для двигателя 2
F0D.27 64795 1000 0 # Обнаружение перегрузки двигателя 2 х10, %
F0E.00 65024 1 0 # Сетевой адрес
F0E.01 65025 4 0 # Скорость передачи данных
F0E.02 65026 4 0 # Тип связи
F0E.03 65027 5 0 # Задержка ответа
F0E.04 65028 0 0 # Время обнаружения потери связи х10, сек
F0E.05 65029 0 0 # Ошибка передачи данных
F0E.06 65030 0 0 # Зарезервировано
F0E.07 65031 200 0 # Зарезервировано
F0E.08 65032 0 0 # Зарезервировано
A00.00 40960 0 0 # Управление скоростью/моментом
A00.01 40961 0 0 # Канал задания при выбранном управлении моментом
A00.02 40962 1500 0 # Цифровое задание момента х10, %
A00.03 40963 0 0 # Время разгона при управлении моментом х10, сек.
A00.04 40964 0 0 # Время замедления при управлении моментом х10, сек.
A00.05 40965 0 0 # Выбор канала ограничения частоты прямого вращения при управлении моментом
A00.06 40966 5000 0 # Цифровое ограничение частоты прямого вращения х100, Гц
A00.07 40967 0 0 # Выбор канала ограничения частоты обратного вращения при управлении моментом
A00.08 40968 5000 0 # Цифровое ограничение частоты обратного вращения х100, Гц
A00.09 40969 0 0 # Коэффициент компенсации момента нулевой скорости х10, %
A00.10 40970 300 0 # Уровень определения нулевой скорости х100, Гц
A00.11 40971 0 0 # Коэффициент компенсации трения скольжения х10, %
A01.00 41216 0 0 # Нижний предел входа AI1 х100, В, мА
A01.01 41217 0 0 # Соответствие нижнему пределу входа AI1 х10, %
A01.02 41218 1000 0 # Верхний предел входа AI1 х100, В, мА
A01.03 41219 1000 0 # Соответствие верхнему пределу входа AI1 х10, %
A01.04 41220 100 0 # Фильтр входа AI1 х1000, сек.
A01.05 41221 0 0 # Нижний предел входа AI2 х100, В, мА
A01.06 41222 0 0 # Соответствие нижнему пределу входа AI2 х10, %
A01.07 41223 1000 0 # Верхний предел входа AI2 х100, В, мА
A01.08 41224 1000 0 # Соответствие верхнему пределу входа AI2 х10, %
A01.09 41225 0 0 # Среднее значение входа AI2 х100, В
A01.10 41226 0 0 # Соответствие среднему значению входа AI2 х10, %
A01.11 41227 100 0 # Фильтр входа AI2 х1000, сек.
A01.12 41228 0 0 # Нижний предел входа AI3 х100, В
A01.13 41229 0 0 # Соответствие нижнему пределу входа AI3 х10, %
A01.14 41230 1000 0 # Верхний предел входа AI3 х100, В
A01.15 41231 1000 0 # Соответствие верхнему пределу входа AI3 х10, %
A01.16 41232 500 0 # Среднее значение входа AI3 х100, В
A01.17 41233 500 0 # Соответствие среднему значению входа AI3 х10, %
A01.18 41234 100 0 # Фильтр входа AI3 х1000, сек
A01.19 41235 50 0 # Фильтр дребезга контактов х1000, сек.
A02.00 41472 0 1 # Заданная частота х100, Гц
A02.01 41473 0 1 # Выходная частота х100, Гц
A02.02 41474 0 1 # Опорная частота разгона х100, Гц
A02.03 41475 0 1 # Выходное напряжение, В
A02.04 41476 0 1 # Выходной ток х10, А
A02.05 41477 0 1 # Обороты двигателя, об/мин
A02.06 41478 0 1 # Зарезервировано
A02.07 41479 0 1 # Зарезервировано
A02.08 41480 0 1 # Выходная мощность х10, %
A02.09 41481 0 1 # Выходной момент х10, %
A02.10 41482 0 1 # Оценка частоты двигателя х100, Гц
A02.11 41483 0 1 # Напряжение звена постоянного тока, В
A02.12 41484 0 1 # Состояние дискретных входов
A02.13 41485 0 1 # Состояние дискретных выходов
A02.14 41486 0 1 # Цифровое задание частоты х100, Гц
A02.15 41487 0 1 # Зарезервировано
A02.16 41488 0 1 # Линейная скорость
A02.17 41489 0 1 # Длина импульсов
A02.18 41490 0 1 # Счетчик значения
A02.19 41491 0 1 # Напряжение на входе AI1 х100, В
A02.20 41492 0 1 # Напряжение на входе AI2 х100, В
A02.21 41493 0 1 # Напряжение на входе AI3 х100, В
A02.22 41494 0 1 # Частота на входе HDI1 х100, КГц
A02.23 41495 0 1 # Уставка ПИД х10, %
A02.24 41496 0 1 # Обратная связь ПИД х10, %
A02.25 41497 0 1 # Выходной сигнал ПИД х100, %
A02.26 41498 0 1 # Коэффициент мощности х10, %
A02.27 41499 0 1 # Время работы, мин.
A02.28 41500 0 1 # Шаг работы ПЛК
A02.29 41501 0 1 # Выходной сигнал регулятора ASR х10, %
A02.30 41502 0 1 # Напряжение на выходе A01 х100, В
A02.31 41503 0 1 # Напряжение на выходе A02 х100, В
A02.32 41504 0 1 # Ток ПЧ х10, А
A02.33 41505 0 1 # Момент х10
A02.34 41506 0 1 # Счетчик перегрузок двигателя
A02.35 41507 0 1 # Зарезервировано
A02.36 41508 1 1 # Текущий выбор двигателя
A03.00 41728 0 0 # Тип телеграммы
A03.01 41729 1 0 # Адрес устройства
A03.02 41730 0 0 # Параметр в телеграмме PZD3 (запись в ПЧ - DateOutput, %QW)
A03.03 41731 0 0 # Параметр в телеграмме PZD4 (запись в ПЧ - DateOutput, %QW)
A03.04 41732 0 0 # Параметр в телеграмме PZD5 (запись в ПЧ - DateOutput, %QW)
A03.05 41733 0 0 # Параметр в телеграмме PZD6 (запись в ПЧ - DateOutput, %QW)
A03.06 41734 0 0 # Параметр в телеграмме PZD7 (запись в ПЧ - DateOutput, %QW)
A03.07 41735 0 0 # Параметр в телеграмме PZD8 (запись в ПЧ - DateOutput, %QW)
A03.08 41736 0 0 # Параметр в телеграмме PZD9 (запись в ПЧ - DateOutput, %QW)
A03.09 41737 0 0 # Параметр в телеграмме PZD10 (запись в ПЧ - DateOutput, %QW)
A03.10 41738 0 0 # Параметр в телеграмме PZD11 (запись в ПЧ - DateOutput, %QW)
A03.11 41739 0 0 # Параметр в телеграмме PZD12 (запись в ПЧ - DateOutput, %QW)
A03.12 41740 0 0 # Зарезервировано
A03.13 41741 0 0 # Зарезервировано
A03.14 41742 0 0 # Параметр в телеграмме PZD3 (чтение из ПЧ - DateInput, %IW)
A03.15 41743 0 0 # Параметр в телеграмме PZD4 (чтение из ПЧ - DateInput, %IW)
A03.16 41744 0 0 # Параметр в телеграмме PZD5 (чтение из ПЧ - DateInput, %IW)
A03.17 41745 0 0 # Параметр в телеграмме PZD6 (чтение из ПЧ - DateInput, %IW)
A03.18 41746 0 0 # Параметр в телеграмме PZD7 (чтение из ПЧ - DateInput, %IW)
A03.19 41747 0 0 # Параметр в телеграмме PZD8 (чтение из ПЧ - DateInput, %IW)
A03.20 41748 0 0 # Параметр в телеграмме PZD9 (чтение из ПЧ - DateInput, %IW)
A03.21 41749 0 0 # Параметр в телеграмме PZD10 (чтение из ПЧ - DateInput, %IW)
A03.22 41750 0 0 # Параметр в телеграмме PZD11 (чтение из ПЧ - DateInput, %IW)
A03.23 41751 0 0 # Параметр в телеграмме PZD12 (чтение из ПЧ - DateInput, %IW)
A03.24 41752 0 0 # Зарезервировано
A03.25 41753 0 0 # Зарезервировано

View File

@@ -0,0 +1,63 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef struct{
char *code; // mnemonic code
char *help; // help message (all after '#')
uint16_t value; // value
uint16_t reg; // register number
uint8_t readonly; // ==1 if can't be changed
} dicentry_t;
typedef struct{
char *name; // alias name
char *expr; // expression to run
char *help; // help message
} alias_t;
int opendict(const char *dic);
void closedict();
int chkdict();
size_t get_dictsize();
char *dicentry_descr(dicentry_t *entry, char *buf, size_t bufsize);
char *dicentry_descrN(size_t N, char *buf, size_t bufsize);
dicentry_t *findentry_by_code(const char *code);
dicentry_t *findentry_by_reg(uint16_t reg);
int setdumppars(char **pars);
int opendumpfile(const char *name);
void closedumpfile();
int setDumpT(double dT);
int rundump();
char *getdumpname();
int read_dict_entries(const char *outdic);
int openaliases(const char *filename);
int chkaliases();
void closealiases();
size_t get_aliasessize();
alias_t *find_alias(const char *name);
char *alias_descr(alias_t *entry, char *buf, size_t bufsize);
char *alias_descrN(size_t N, char *buf, size_t bufsize);

140
modbus_params/main.c Normal file
View File

@@ -0,0 +1,140 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <signal.h>
#include <stdio.h>
#include <usefull_macros.h>
#include "dictionary.h"
#include "modbus.h"
#include "server.h"
#include "verbose.h"
typedef struct{
int help; // help
int verbose; // verbose level (not used yet)
int slave; // slave ID
int isunix; // Unix-socket
char **read_keycodes; // keycodes to dump into file
char **writeregs; // reg=val to write
char **writecodes; // keycode=val to write
int **readregs; // regs to write
char **readcodes; // keycodes to write
char *dumpfile; // dump file name
char *outdic; // output dictionary to save everything read from slave
char *dicfile; // file with dictionary
char *aliasesfile; // file with aliases
char *device; // serial device
char *node; // server port or path
int baudrate; // baudrate
double dTdump; // dumping time interval (s)
} parameters;
static parameters G = {
.slave = 1,
.device = "/dev/ttyUSB0",
.baudrate = 9600,
.dTdump = 0.1,
};
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&G.help), "show this help"},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&G.verbose), "verbose level (each -v adds 1)"},
{"outfile", NEED_ARG, NULL, 'o', arg_string, APTR(&G.dumpfile), "file with parameter's dump"},
{"dumpkey", MULT_PAR, NULL, 'k', arg_string, APTR(&G.read_keycodes), "dump entry with this keycode; multiply parameter"},
{"dumptime", NEED_ARG, NULL, 't', arg_double, APTR(&G.dTdump), "dumping time interval (seconds, default: 0.1)"},
{"dictionary", NEED_ARG, NULL, 'D', arg_string, APTR(&G.dicfile), "file with dictionary (format: code register value writeable)"},
{"slave", NEED_ARG, NULL, 's', arg_int, APTR(&G.slave), "slave ID (default: 1)"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&G.device), "modbus device (default: /dev/ttyUSB0)"},
{"baudrate", NEED_ARG, NULL, 'b', arg_int, APTR(&G.baudrate), "modbus baudrate (default: 9600)"},
{"writer", MULT_PAR, NULL, 'w', arg_string, APTR(&G.writeregs), "write new value to register (format: reg=val); multiply parameter"},
{"writec", MULT_PAR, NULL, 'W', arg_string, APTR(&G.writecodes),"write new value to register by keycode (format: keycode=val); multiply parameter"},
{"outdic", NEED_ARG, NULL, 'O', arg_string, APTR(&G.outdic), "output dictionary for full device dump by input dictionary registers"},
{"readr", MULT_PAR, NULL, 'r', arg_int, APTR(&G.readregs), "registers (by address) to read; multiply parameter"},
{"readc", MULT_PAR, NULL, 'R', arg_string, APTR(&G.readcodes), "registers (by keycodes, checked by dictionary) to read; multiply parameter"},
{"node", NEED_ARG, NULL, 'N', arg_string, APTR(&G.node), "node \"IP\", or path (could be \"\\0path\" for anonymous UNIX-socket)"},
{"unixsock", NO_ARGS, NULL, 'U', arg_int, APTR(&G.isunix), "UNIX socket instead of INET"},
{"alias", NEED_ARG, NULL, 'a', arg_string, APTR(&G.aliasesfile),"file with aliases in format 'name : command to run'"},
end_option
};
void signals(int sig){
if(sig > 0) WARNX("Exig with signal %d", sig);
close_modbus();
closedict();
closealiases();
exit(sig);
}
int main(int argc, char **argv){
sl_init();
sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) sl_showhelp(-1, cmdlnopts);
sl_loglevel_e lvl = G.verbose + LOGLEVEL_ERR;
set_verbose_level(lvl);
if(lvl >= LOGLEVEL_AMOUNT) lvl = LOGLEVEL_AMOUNT - 1;
if(!G.dicfile) WARNX("Dictionary is absent");
else if(!opendict(G.dicfile)) signals(-1);
if(G.aliasesfile){
if(!openaliases(G.aliasesfile)) WARNX("No aliases found in '%s'", G.aliasesfile);
}
if(G.read_keycodes && !setdumppars(G.read_keycodes)) signals(-1);
if(G.dumpfile && !opendumpfile(G.dumpfile)) signals(-1);
if(!open_modbus(G.device, G.baudrate)) signals(-1);
if(!set_slave(G.slave)) signals(-1);
signal(SIGTERM, signals); // kill (-15) - quit
signal(SIGHUP, SIG_IGN); // hup - ignore
signal(SIGINT, signals); // ctrl+C - quit
signal(SIGQUIT, signals); // ctrl+\ - quit
signal(SIGTSTP, SIG_IGN); // ignore ctrl+Z
if(G.writeregs){
DBG("writeregs");
green("Write user registers to slave %d\n", G.slave);
green("Total: %d written successfully", write_regval(G.writeregs));
fflush(stdout);
}
if(G.writecodes){
DBG("writecodes");
green("Write user registers coded by keycode to slave %d\n", G.slave);
green("Total: %d written successfully", write_codeval(G.writecodes));
fflush(stdout);
}
if(G.outdic){
DBG("outdic");
int N = read_dict_entries(G.outdic);
if(N < 1) WARNX("Dump full dictionary failed");
else green("Read %N registers, dump to %s\n", N, G.outdic);
fflush(stdout);
}
if(G.readregs) read_registers(G.readregs);
if(G.readcodes) read_keycodes(G.readcodes);
if(G.dumpfile){
if(!setDumpT(G.dTdump)) ERRX("Can't set dumptime %g", G.dTdump);
DBG("dumpfile");
if(!rundump()) signals(-1);
}
if(G.node){
DBG("Create server");
if(!runserver(G.node, G.isunix)) signals(-1); // this function exits only after server death
}
if(G.dumpfile){
DBG("Done, wait for ctrl+C");
while(1);
}
return 0;
}

204
modbus_params/modbus.c Normal file
View File

@@ -0,0 +1,204 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <inttypes.h>
#include <modbus/modbus.h>
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "dictionary.h"
#include "modbus.h"
#include "verbose.h"
static modbus_t *modbus_ctx = NULL;
static pthread_mutex_t modbus_mutex = PTHREAD_MUTEX_INITIALIZER;
void close_modbus(){
if(modbus_ctx){
closedumpfile();
modbus_close(modbus_ctx);
modbus_free(modbus_ctx);
}
}
// read register and modify entry->reg; return FALSE if failed
int read_entry(dicentry_t *entry){
if(!entry){
WARNX("NULL instead of entry");
return FALSE;
}
int ret = TRUE;
pthread_mutex_lock(&modbus_mutex);
if(modbus_read_registers(modbus_ctx, entry->reg, 1, &entry->value) < 0){
WARNX("Can't read entry by reg %u", entry->reg);
ret = FALSE;
}
pthread_mutex_unlock(&modbus_mutex);
return ret;
}
// write register value; FALSE - if failed or read-only
int write_entry(dicentry_t *entry){
if(!entry || entry->readonly){
if(!entry) WARNX("NULL instead of entry");
else WARNX("Can't write readonly entry %u", entry->reg);
return FALSE;
}
int ret = TRUE;
pthread_mutex_lock(&modbus_mutex);
if(modbus_write_register(modbus_ctx, entry->reg, entry->value) < 0){
WARNX("Error writing %u to %u", entry->value, entry->reg);
ret = FALSE;
}
pthread_mutex_unlock(&modbus_mutex);
return ret;
}
// write multiply regs (without checking by dict) by NULL-terminated array "reg=val"; return amount of items written
int write_regval(char **regval){
int written = 0;
dicentry_t entry = {0};
while(*regval){
DBG("Parse %s", *regval);
char key[SL_KEY_LEN], value[SL_VAL_LEN];
if(2 != sl_get_keyval(*regval, key, value)){
WARNX("%s isn't in format reg=val", *regval);
}else{
DBG("key: %s, val: %s", key, value);
int r=-1, v=-1;
if(!sl_str2i(&r, key) || !sl_str2i(&v, value) || r < 0 || v < 0 || r > UINT16_MAX || v > UINT16_MAX){
WARNX("Wrong register number or value: %d=%d; should be uint16_t", r, v);
}else{
entry.value = (uint16_t)v;
entry.reg = (uint16_t)r;
if(write_entry(&entry)){
++written;
verbose(LOGLEVEL_MSG, "Written %u to register %u", entry.value, entry.reg);
}else verbose(LOGLEVEL_WARN, "Can't write %u to register %u", entry.value, entry.reg);
}
}
++regval;
}
return written;
}
// write multiply regs by NULL-terminated array "keycode=val" (checking `keycode`); return amount of items written
int write_codeval(char **codeval){
if(!chkdict()) return FALSE;
int written = 0;
dicentry_t entry = {0};
while(*codeval){
char key[SL_KEY_LEN], value[SL_VAL_LEN];
if(2 != sl_get_keyval(*codeval, key, value)){
WARNX("%s isn't in format keycode=val");
}else{
int v=-1;
if(!sl_str2i(&v, value) || v < 0 || v > UINT16_MAX){
WARNX("Wrong value: %d; should be uint16_t", v);
}else{
dicentry_t *de = findentry_by_code(key);
if(!de){
WARNX("Keycode %s not found in dictionary", key);
}else{
entry.readonly = de->readonly;
entry.reg = de->reg;
if(entry.readonly){
verbose(LOGLEVEL_WARN, "Can't change readonly register %d", entry.reg);
}else{
entry.value = (uint16_t)v;
if(write_entry(&entry)){
++written;
verbose(LOGLEVEL_MSG, "Written %u to register %u", entry.value, entry.reg);
}else verbose(LOGLEVEL_WARN, "Can't write %u to register %u", entry.value, entry.reg);
}
}
}
}
++codeval;
}
return written;
}
int open_modbus(const char *path, int baudrate){
int ret = FALSE;
if(modbus_ctx) close_modbus();
pthread_mutex_lock(&modbus_mutex);
modbus_ctx = modbus_new_rtu(path, baudrate, 'N', 8, 1);
if(!modbus_ctx){
WARNX("Can't open device %s @ %d", path, baudrate);
goto rtn;
}
modbus_set_response_timeout(modbus_ctx, 0, 100000);
if(modbus_connect(modbus_ctx) < 0){
WARNX("Can't connect to device %s", path);
modbus_free(modbus_ctx);
modbus_ctx = NULL;
goto rtn;
}
ret = TRUE;
rtn:
pthread_mutex_unlock(&modbus_mutex);
return ret;
}
int set_slave(int ID){
int ret = TRUE;
pthread_mutex_lock(&modbus_mutex);
if(modbus_set_slave(modbus_ctx, ID)){
WARNX("Can't set slave ID to %d", ID);
ret = FALSE;
}
pthread_mutex_unlock(&modbus_mutex);
return ret;
}
// read dump register values (not checking in dict) and dump to stdout (not modifying dictionary)
void read_registers(int **addresses){
dicentry_t entry = {0};
green("Dump registers: (reg val)\n");
while(*addresses){
int addr = **addresses;
if(addr < 0 || addr > UINT16_MAX){
WARNX("Wrong register number: %d", addr);
}else{
entry.reg = (uint16_t) addr;
if(!read_entry(&entry)) WARNX("Can't read register %d", addr);
else printf("%4d %4d\n", entry.reg, entry.value);
}
++addresses;
}
printf("\n");
}
// `read_registers` but by keycodes (not modifying dictionary)
void read_keycodes(char **keycodes){
if(!chkdict()) return;
dicentry_t entry = {0};
green("Dump registers: (code (reg) val)\n");
while(*keycodes){
dicentry_t *de = findentry_by_code(*keycodes);
if(!de){
WARNX("Can't find keycode %s in dictionary", *keycodes);
}else{
entry.reg = de->reg;
if(!read_entry(&entry)) WARNX("Can't read register %s (%d)", *keycodes, entry.reg);
else printf("%s (%4d) %4d\n", de->code, entry.reg, entry.value);
}
++keycodes;
}
printf("\n");
}

33
modbus_params/modbus.h Normal file
View File

@@ -0,0 +1,33 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "dictionary.h"
int open_modbus(const char *path, int baudrate);
void close_modbus();
int set_slave(int ID);
int read_entry(dicentry_t *entry);
int write_entry(dicentry_t *entry);
int write_regval(char **regval);
int write_codeval(char **codeval);
void read_registers(int **addresses);
void read_keycodes(char **keycodes);

View File

@@ -0,0 +1 @@
-std=c17

View File

@@ -0,0 +1,2 @@
#define _XOPEN_SOURCE 1111
#define _GNU_SOURCE

View File

@@ -0,0 +1 @@
[General]

View File

@@ -0,0 +1 @@
-std=c++17

View File

@@ -0,0 +1,9 @@
dictionary.c
dictionary.h
main.c
modbus.c
modbus.h
server.c
server.h
verbose.c
verbose.h

View File

View File

@@ -0,0 +1,15 @@
# aliases for test purposes
allon : id = 5 # turn all on
alloff: id = 0 # turn all off
get : id
#wrong:
: test3 = stop
help :
some wrong
# good but with space inside
space test : test3 = 0
# newer call
test1 : test2
# no newline
got : test1

4
modbus_params/relay.dic Normal file
View File

@@ -0,0 +1,4 @@
id 0000 1 0 # relay ID
test1 1 0 0 # test stuff
test2 2 0 0
test3 3 0 0

177
modbus_params/server.c Normal file
View File

@@ -0,0 +1,177 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include "dictionary.h"
#include "modbus.h"
#include "server.h"
static sl_sock_t *s = NULL;
// stop dump and close dump file
static sl_sock_hresult_e closedump(_U_ sl_sock_t *client, _U_ sl_sock_hitem_t *item, _U_ const char *req){
closedumpfile();
return RESULT_OK;
}
// open new dump file
static sl_sock_hresult_e newdump(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
char buf[BUFSIZ];
if(!req){ // getter
snprintf(buf, BUFSIZ-1, "%s = %s\n", item->key, getdumpname());
sl_sock_sendstrmessage(client, buf);
return RESULT_SILENCE;
}else{
if(!opendumpfile(req) || !rundump()) return RESULT_FAIL;
}
return RESULT_OK;
}
// list all dictionary or only given
static sl_sock_hresult_e listdict(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
char buf[BUFSIZ];
size_t N = get_dictsize();
if(!N) return RESULT_FAIL;
if(!req){ // getter - list all
DBG("list all dict");
for(size_t i = 0; i < N; ++i){
if(dicentry_descrN(i, buf, BUFSIZ)){
sl_sock_sendstrmessage(client, buf);
}
}
}else{ // setter - list by code/reg
DBG("list %s", req);
dicentry_t *e = findentry_by_code(req);
if(!e){
DBG("User wants number?");
int x;
if(!sl_str2i(&x, req) || (x < 0 || x > UINT16_MAX)) return RESULT_BADVAL;
DBG("REG: %d", x);
e = findentry_by_reg((uint16_t)x);
if(!e) return RESULT_BADVAL;
}
if(!dicentry_descr(e, buf, BUFSIZ)) return RESULT_FAIL;
sl_sock_sendstrmessage(client, buf);
}
return RESULT_SILENCE;
}
// list all aliases
static sl_sock_hresult_e listaliases(sl_sock_t *client, _U_ sl_sock_hitem_t *item, const char *req){
char buf[BUFSIZ];
size_t N = get_aliasessize();
if(!N) return RESULT_FAIL;
if(!req){ // all
for(size_t i = 0; i < N; ++i){
if(alias_descrN(i, buf, BUFSIZ)){
sl_sock_sendstrmessage(client, buf);
}
}
}else{
alias_t *a = find_alias(req);
if(!a || !alias_descr(a, buf, BUFSIZ)) return RESULT_FAIL;
sl_sock_sendstrmessage(client, buf);
}
return RESULT_SILENCE;
}
static sl_sock_hitem_t handlers[] = {
{closedump, "clodump", "stop dump and close current dump file", NULL},
{newdump, "newdump", "open new dump file or get name of current", NULL},
{listdict, "list", "list all dictionary (as getter) or given register (as setter: by codename or value)", NULL},
{listaliases, "alias", "list all of aliases (as getter) or with given name (by setter)", NULL},
{NULL, NULL, NULL, NULL}
};
// new connections handler (return FALSE to reject client)
static int connected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("New client fd=%d connected", c->fd);
else LOGMSG("New client fd=%d, IP=%s connected", c->fd, c->IP);
return TRUE;
}
// disconnected handler
static void disconnected(sl_sock_t *c){
if(c->type == SOCKT_UNIX) LOGMSG("Disconnected client fd=%d", c->fd);
else LOGMSG("Disconnected client fd=%d, IP=%s", c->fd, c->IP);
}
static sl_sock_hresult_e defhandler(sl_sock_t *s, const char *str){
if(!s || !str) return RESULT_FAIL;
char key[SL_KEY_LEN], value[SL_VAL_LEN];
int n = sl_get_keyval(str, key, value);
if(n == 0){
WARNX("Can't parse `%s` as reg[=val]", str);
return RESULT_BADKEY;
}
dicentry_t *entry = NULL;
int N;
if(sl_str2i(&N, key)){ // `key` is register number
entry = findentry_by_reg((uint16_t) N);
}else{ // `key` is register name
entry = findentry_by_code(key);
}
if(!entry){
// check alias - should be non-setter!!!
if(n == 1){
alias_t *alias = find_alias(key);
if(alias) // recoursive call of defhandler
return defhandler(s, alias->expr);
}
WARNX("Entry %s not found", key);
return RESULT_BADKEY;
}
if(n == 1){ // getter
if(!read_entry(entry)) return RESULT_FAIL;
snprintf(value, SL_VAL_LEN-1, "%s=%u\n", key, entry->value);
}else{ // setter
if(!sl_str2i(&N, value)){
WARNX("%s isn't a value of register", value);
return RESULT_BADVAL;
}
entry->value = (uint16_t)N;
if(!write_entry(entry)) return RESULT_FAIL;
return RESULT_OK;
}
sl_sock_sendstrmessage(s, value);
return RESULT_SILENCE;
}
int runserver(const char *node, int isunix){
if(!node){
WARNX("Point node");
return FALSE;
}
sl_socktype_e type = (isunix) ? SOCKT_UNIX : SOCKT_NET;
if(s) sl_sock_delete(&s);
s = sl_sock_run_server(type, node, -1, handlers);
if(!s){
WARNX("Can't run server");
return FALSE;
}
sl_sock_connhandler(s, connected);
sl_sock_dischandler(s, disconnected);
sl_sock_defmsghandler(s, defhandler);
while(s && s->connected){
if(!s->rthread){
WARNX("Server handlers thread is dead");
break;
}
}
DBG("Close");
sl_sock_delete(&s);
return TRUE;
}

23
modbus_params/server.h Normal file
View File

@@ -0,0 +1,23 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <usefull_macros.h>
int runserver(const char *node, int isunix);

40
modbus_params/verbose.c Normal file
View File

@@ -0,0 +1,40 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <stdarg.h>
#include <stdio.h>
#include "verbose.h"
static sl_loglevel_e verblvl = LOGLEVEL_WARN;
void set_verbose_level(sl_loglevel_e level){
if(level >= LOGLEVEL_AMOUNT) level = LOGLEVEL_AMOUNT - 1;
else if(level < 0) level = 0;
verblvl = level;
}
void verbose(sl_loglevel_e lvl, const char *fmt, ...){
if(lvl > verblvl) return;
va_list ar;
va_start(ar, fmt);
vprintf(fmt, ar);
va_end(ar);
fflush(stdout);
}

24
modbus_params/verbose.h Normal file
View File

@@ -0,0 +1,24 @@
/*
* This file is part of the modbus_param project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <usefull_macros.h>
void set_verbose_level(sl_loglevel_e level);
void verbose(sl_loglevel_e lvl, const char *fmt, ...);

View File

@@ -57,6 +57,7 @@ static sl_option_t cmdlnopts[] = {
{"node", NEED_ARG, NULL, 'n', arg_int, APTR(&GP.nodenum), "node number (default: 1)"},
{"readN", NO_ARGS, NULL, 0, arg_int, APTR(&GP.readn), "read node number"},
{"setN", NEED_ARG, NULL, 0, arg_int, APTR(&GP.setn), "change node number"},
{"type", NEED_ARG, NULL, 't', arg_int, APTR(&GP.relaytype), "type of relay: 0 - standard 2relay, 1 - non-standard 4relay"},
end_option
};

View File

@@ -31,6 +31,7 @@ typedef struct{
int nodenum; // node number (default: 1)
int readn; // read node number
int setn; // set node number
int relaytype; // type of relay
int **setrelay; // set relay number N
int **resetrelay; // reset relay number N
int **getinput; // read Nth input

View File

@@ -46,13 +46,27 @@ int main(int argc, char **argv){
modbus_set_response_timeout(ctx, 0, 100000);
if(modbus_set_slave(ctx, GP.nodenum)) ERRX("Can't set modbus slave");
if(modbus_connect(ctx) < 0) ERR("Can't open device %s", GP.device);
if(GP.relaytype < 0 || GP.relaytype > 1) ERRX("Type should be 0 or 1");
VMSG("OK!\n");
uint8_t dest8[8] = {0};
int result = 0;
int ninputs = 8, noutputs = 8;
if(GP.relaytype == 1){ ninputs = 4; noutputs = 4;}
if(GP.setall){
memset(dest8, 1, 8);
if(modbus_write_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't set all relays");
if(GP.relaytype == 0){
memset(dest8, 1, noutputs);
result = modbus_write_bits(ctx, 0, noutputs, dest8);
}else{
result = modbus_write_bit(ctx, 0xff, 0xff00);
}
if(result < 0) WARNX("Can't set all relays");
}else if(GP.resetall){
if(modbus_write_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't clear all relays");
if(GP.relaytype == 0){
result = modbus_write_bits(ctx, 0, noutputs, dest8);
}else{
result = modbus_write_bit(ctx, 0xff, 0);
}
if(result < 0) WARNX("Can't clear all relays");
}else{
if(GP.resetrelay){
int **p = GP.resetrelay;
@@ -80,24 +94,24 @@ int main(int argc, char **argv){
}
}
if(GP.getinput){
if(modbus_read_input_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't read inputs");
if(modbus_read_input_bits(ctx, 0, ninputs, dest8) < 0) WARNX("Can't read inputs");
else{
int **p = GP.getinput;
while(*p){
int n = **p;
if(n > 7 || n < 0) WARNX("Input number should be in [0, 7]");
if(n > ninputs-1 || n < 0) WARNX("Input number should be in [0, %d]", ninputs-1);
else printf("INPUT%d=%u\n", n, dest8[n]);
++p;
}
}
}
if(GP.getrelay){
if(modbus_read_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't read relays");
if(modbus_read_bits(ctx, 0, noutputs, dest8) < 0) WARNX("Can't read relays");
else{
int **p = GP.getrelay;
while(*p){
int n = **p;
if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]");
if(n > noutputs-1 || n < 0) WARNX("Relay number should be in [0, %d]", noutputs-1);
else printf("RELAY%d=%u\n", n, dest8[n]);
++p;
}
@@ -108,7 +122,11 @@ int main(int argc, char **argv){
}
if(GP.setn){
uint16_t nodeN = (uint16_t) GP.setn;
modbus_write_registers(ctx, 0, 1, &nodeN);
if(GP.relaytype == 0){
modbus_write_registers(ctx, 0, 1, &nodeN);
}else{
modbus_write_register(ctx, 0x4000, nodeN);
}
modbus_set_slave(ctx, nodeN);
getN(ctx);
}

Binary file not shown.

View File

@@ -0,0 +1,96 @@
<!DOCTYPE html>
<html>
<head>
<title>Relay Control</title>
<style>
.container {
display: flex;
flex-direction: column;
gap: 10px;
width: 200px;
margin: 20px;
}
button {
padding: 10px;
font-size: 16px;
}
.indicator {
width: 100%;
height: 30px;
border: 1px solid black;
border-radius: 5px;
}
.red {
background-color: #ff9999;
}
.green {
background-color: #99ff99;
}
</style>
</head>
<body>
<div class="container">
<button id="leftBtn" onclick="sendCommand('open')">Open</button>
<button id="rightBtn" onclick="sendCommand('close')">Close</button>
<button onclick="sendCommand('stop')">Stop</button>
<div class="indicator red" id="leftInd">Left</div>
<div class="indicator red" id="rightInd">Right</div>
</div>
<script>
async function updateStatus() {
try {
const response = await fetch('http://ishtar.sao.ru:9000/status', {
method: 'POST',
body: 'status\n'
});
const text = await response.text();
const status = parseStatus(text);
// Update buttons
document.getElementById('leftBtn').disabled = status.relay0 === '1';
document.getElementById('rightBtn').disabled = status.relay1 === '1';
// Update indicators
document.getElementById('leftInd').className =
`indicator ${status.in0 === '1' ? 'green' : 'red'}`;
document.getElementById('rightInd').className =
`indicator ${status.in1 === '1' ? 'green' : 'red'}`;
} catch (error) {
console.error('Status update failed:', error);
}
}
function parseStatus(text) {
const lines = text.split('\n');
const status = {};
lines.forEach(line => {
const [key, value] = line.split('=');
if (key && value !== undefined) {
status[key] = value.replace('\n', '').trim();
}
});
return status;
}
async function sendCommand(cmd) {
try {
await fetch(`http://ishtar.sao.ru:9000/${cmd}`, {
method: 'POST',
body: cmd
});
// Update status immediately after command
await updateStatus();
} catch (error) {
console.error('Command failed:', error);
}
}
// Update status every second
setInterval(updateStatus, 3000);
// Initial status update
updateStatus();
</script>
</body>
</html>

View File

@@ -0,0 +1,59 @@
# run `make DEF=...` to add extra defines
PROGRAM := modbus_relay
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros -lmodbus
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines
OBJS := $(addprefix $(OBJDIR)/, $(SRCS:%.c=%.o))
DEPS := $(OBJS:.o=.d)
TARGFILE := $(OBJDIR)/TARGET
CC = gcc
#TARGET := RELEASE
ifeq ($(shell test -e $(TARGFILE) && echo -n yes),yes)
TARGET := $(file < $(TARGFILE))
else
TARGET := RELEASE
endif
ifeq ($(TARGET), DEBUG)
.DEFAULT_GOAL := debug
endif
release: CFLAGS += -flto
release: LDFLAGS += -flto
release: $(PROGRAM)
debug: CFLAGS += -DEBUG -Werror
debug: TARGET := DEBUG
debug: $(PROGRAM)
$(TARGFILE): $(OBJDIR)
@echo -e "\t\tTARGET: $(TARGET)"
@echo "$(TARGET)" > $(TARGFILE)
$(PROGRAM) : $(TARGFILE) $(OBJS)
@echo -e "\t\tLD $(PROGRAM)"
$(CC) $(LDFLAGS) $(OBJS) -o $(PROGRAM)
$(OBJDIR):
@mkdir $(OBJDIR)
ifneq ($(MAKECMDGOALS),clean)
-include $(DEPS)
endif
$(OBJDIR)/%.o: %.c
@echo -e "\t\tCC $<"
$(CC) -MD -c $(LDFLAGS) $(CFLAGS) $(DEFINES) -o $@ $<
clean:
@echo -e "\t\tCLEAN"
@rm -rf $(OBJDIR) 2>/dev/null || true
xclean: clean
@rm -f $(PROGRAM)
.PHONY: clean xclean

View File

@@ -0,0 +1,83 @@
/* geany_encoding=koi8-r
* cmdlnopts.c - the only function that parse cmdln args and returns glob parameters
*
* Copyright 2013 Edward V. Emelianoff <eddy@sao.ru>
*
* 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 2 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, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include <assert.h>
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include "cmdlnopts.h"
#include "usefull_macros.h"
/*
* here are global parameters initialisation
*/
static int help;
sl_loglevel_e verblvl = LOGLEVEL_WARN;
glob_pars GP = {
.baudrate = 9600,
.device = "/dev/ttyUSB0",
.nodenum = 1,
.port = "9000",
};
/*
* Define command line options by filling structure:
* name has_arg flag val type argptr help
*/
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
{"verbose", NO_ARGS, NULL, 'v', arg_none, APTR(&GP.verbose), "verbose level for (each `-v` increase it)"},
{"logfile", NEED_ARG, NULL, 'l', arg_string, APTR(&GP.logfile), "logging file name"},
{"baudrate",NEED_ARG, NULL, 'b', arg_int, APTR(&GP.baudrate), "interface baudrate (default: 9600)"},
{"device", NEED_ARG, NULL, 'd', arg_string, APTR(&GP.device), "serial device path (default: /dev/ttyUSB0)"},
{"node", NEED_ARG, NULL, 'n', arg_int, APTR(&GP.nodenum), "node number (default: 1)"},
{"port", NEED_ARG, NULL, 'p', arg_string, APTR(&GP.port), "socket port (default: 9000)"},
end_option
};
/**
* Parse command line options and return dynamically allocated structure
* to global parameters
* @param argc - copy of argc from main
* @param argv - copy of argv from main
* @return allocated structure with global parameters
*/
void parse_args(int argc, char **argv){
size_t hlen = 1024;
char helpstring[1024], *hptr = helpstring;
snprintf(hptr, hlen, "Usage: %%s [args]\n\n\tWhere args are:\n");
// format of help: "Usage: progname [args]\n"
sl_helpstring(helpstring);
// parse arguments
sl_parseargs(&argc, &argv, cmdlnopts);
if(help) sl_showhelp(-1, cmdlnopts);
if(argc > 0) WARNX("Omit %d unexpected arguments", argc);
}
void verbose(sl_loglevel_e lvl, const char *fmt, ...){
if(lvl > verblvl) return;
va_list ar;
va_start(ar, fmt);
vprintf(fmt, ar);
va_end(ar);
fflush(stdout);
}

View File

@@ -0,0 +1,42 @@
/*
* This file is part of the modbus_relay project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <usefull_macros.h>
/*
* here are some typedef's for global data
*/
typedef struct{
int verbose; // verbose level
int baudrate; // interface baudrate (default: 9600)
int nodenum; // node number (default: 1)
char *port; // socket port (default: 9000)
char *logfile; // logfile name
char *device; // serial device path (default: /dev/ttyUSB0)
} glob_pars;
extern glob_pars GP;
extern sl_loglevel_e verblvl;
void parse_args(int argc, char **argv);
void verbose(sl_loglevel_e lvl, const char *fmt, ...);
#define VMSG(...) verbose(LOGLEVEL_MSG, __VA_ARGS__)
#define VDBG(...) verbose(LOGLEVEL_DBG, __VA_ARGS__)
#define VANY(...) verbose(LOGLEVEL_ANY, __VA_ARGS__)

View File

@@ -0,0 +1,134 @@
/*
* This file is part of the modbus_relay project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include <modbus/modbus.h>
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
#include <sys/wait.h>
#include "cmdlnopts.h"
#include "server.h"
void signals(int sig){
if(sig){
signal(sig, SIG_IGN);
DBG("Get signal %d, quit.\n", sig);
LOGERR("Exit with status %d", sig);
}else LOGERR("Exit");
exit(sig);
}
int main(int argc, char **argv){
sl_init();
parse_args(argc, argv);
verblvl = GP.verbose + LOGLEVEL_WARN;
if(verblvl >= LOGLEVEL_AMOUNT) verblvl = LOGLEVEL_AMOUNT - 1;
if(GP.logfile) OPENLOG(GP.logfile, verblvl, 1);
LOGMSG("Started");
signal(SIGTERM, signals);
signal(SIGINT, signals);
signal(SIGQUIT, signals);
signal(SIGTSTP, SIG_IGN);
signal(SIGHUP, signals);
VMSG("Try to open %s @%d ... ", GP.device, GP.baudrate);
modbus_t *ctx = modbus_new_rtu(GP.device, GP.baudrate, 'N', 8, 1);
modbus_set_response_timeout(ctx, 0, 100000);
if(modbus_set_slave(ctx, GP.nodenum)) ERRX("Can't set modbus slave");
if(modbus_connect(ctx) < 0) ERR("Can't open device %s", GP.device);
VMSG("OK!\n");
#ifndef EBUG
while(1){
pid_t childpid = fork();
if(childpid){ // master
LOGMSG("Created child with pid %d", childpid);
wait(NULL);
LOGWARN("Child %d died", childpid);
sleep(5); // wait a little before respawn
}else{ // slave
prctl(PR_SET_PDEATHSIG, SIGTERM); // send SIGTERM to child when parent dies
break;
}
}
#endif
runserver(GP.port, ctx);
#if 0
uint8_t dest8[8] = {0};
if(GP.setall){
memset(dest8, 1, 8);
if(modbus_write_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't set all relays");
}else if(GP.resetall){
if(modbus_write_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't clear all relays");
}else{
if(GP.resetrelay){
int **p = GP.resetrelay;
while(*p){
int n = **p;
if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]");
else{
if(modbus_write_bit(ctx, n, 0) < 0) WARNX("Can't reset relay #%d", n);
else VMSG("RELAY%d=0\n", n);
}
++p;
}
}
if(GP.setrelay){
int **p = GP.setrelay;
while(*p){
int n = **p;
if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]");
else{
if(modbus_write_bit(ctx, n, 1) < 0) WARNX("Can't set relay #%d", n);
else VMSG("RELAY%d=1\n", n);
}
++p;
}
}
}
if(GP.getinput){
if(modbus_read_input_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't read inputs");
else{
int **p = GP.getinput;
while(*p){
int n = **p;
if(n > 7 || n < 0) WARNX("Input number should be in [0, 7]");
else printf("INPUT%d=%u\n", n, dest8[n]);
++p;
}
}
}
if(GP.getrelay){
if(modbus_read_bits(ctx, 0, 8, dest8) < 0) WARNX("Can't read relays");
else{
int **p = GP.getrelay;
while(*p){
int n = **p;
if(n > 7 || n < 0) WARNX("Relay number should be in [0, 7]");
else printf("RELAY%d=%u\n", n, dest8[n]);
++p;
}
}
}
#endif
LOGMSG("End");
VMSG("End\n");
modbus_close(ctx);
modbus_free(ctx);
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1 @@
-std=c17

View File

@@ -0,0 +1,3 @@
// Add predefined macros for your project here. For example:
// #define THE_ANSWER 42
#define _DEFAULT_SOURCE

Some files were not shown because too many files have changed in this diff Show More