mirror of
https://github.com/eddyem/eddys_snippets.git
synced 2026-03-20 08:41:02 +03:00
Compare commits
23 Commits
ce15889295
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb5cce6ee | ||
|
|
055d7f7f59 | ||
|
|
d18ab25440 | ||
|
|
524a39b8a2 | ||
|
|
4a09776d27 | ||
|
|
ebc8144271 | ||
|
|
ca7a53ee1c | ||
|
|
19fbf250a9 | ||
|
|
ea0de3c904 | ||
|
|
7a37dc0d2f | ||
|
|
b0097d5ee6 | ||
|
|
7f85861d6c | ||
|
|
675b02f1a3 | ||
|
|
5c424fc617 | ||
|
|
a893589a9c | ||
|
|
dfdc4222fd | ||
|
|
664566aa47 | ||
|
|
9211cfaf43 | ||
| 5f4e06bb7b | |||
| 0074eac5d8 | |||
| c35f38ccd6 | |||
| 01abbf4a25 | |||
| 1a3dba4676 |
34
ESP8266/CMakeLists.txt
Normal file
34
ESP8266/CMakeLists.txt
Normal 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
2
ESP8266/Readme
Normal 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
170
ESP8266/esp8266.c
Normal 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
53
ESP8266/esp8266.h
Normal 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
182
ESP8266/main.c
Normal 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
178
ESP8266/serial.c
Normal 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
40
ESP8266/serial.h
Normal 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
247
I2Csensors/BMP180.c
Normal 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
21
I2Csensors/BMP180.h
Normal 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
385
I2Csensors/BMP280.c
Normal 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, ®)){
|
||||
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, ®)) 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
22
I2Csensors/BMP280.h
Normal 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
513
I2Csensors/BMP580.c
Normal 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, ®)) 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, ®)){
|
||||
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, ®)) 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
21
I2Csensors/BMP580.h
Normal 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
104
I2Csensors/CMakeLists.txt
Normal 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
14
I2Csensors/PCA9548
Executable 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
158
I2Csensors/Readme.md
Normal 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
121
I2Csensors/SHT3x.c
Normal 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
22
I2Csensors/SHT3x.h
Normal 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
139
I2Csensors/SI7005.c
Normal 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
21
I2Csensors/SI7005.h
Normal 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
191
I2Csensors/aht.c
Normal 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
25
I2Csensors/aht.h
Normal 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
16
I2Csensors/common_table
Normal 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?
|
||||
8
I2Csensors/examples/CMakeLists.txt
Normal file
8
I2Csensors/examples/CMakeLists.txt
Normal 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
8
I2Csensors/examples/MAP
Normal 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)
|
||||
35
I2Csensors/examples/Readme.md
Normal file
35
I2Csensors/examples/Readme.md
Normal 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
|
||||
|
||||
```
|
||||
240
I2Csensors/examples/logmany.c
Normal file
240
I2Csensors/examples/logmany.c
Normal 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
|
||||
}
|
||||
121
I2Csensors/examples/single_sensor.c
Normal file
121
I2Csensors/examples/single_sensor.c
Normal 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
233
I2Csensors/i2c.c
Normal 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 = ®addr; 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
40
I2Csensors/i2c.h
Normal 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);
|
||||
|
||||
68
I2Csensors/i2csensorsPTH.h
Normal file
68
I2Csensors/i2csensorsPTH.h
Normal 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);
|
||||
10
I2Csensors/i2csensorsPTH.pc.in
Normal file
10
I2Csensors/i2csensorsPTH.pc.in
Normal 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
186
I2Csensors/sensor.c
Normal 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
1
I2Csensors/sensor.cflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c17
|
||||
2
I2Csensors/sensor.config
Normal file
2
I2Csensors/sensor.config
Normal file
@@ -0,0 +1,2 @@
|
||||
#define _XOPEN_SOURCE 9999
|
||||
#define _POSIX_C_SOURCE 333333L
|
||||
1
I2Csensors/sensor.creator
Normal file
1
I2Csensors/sensor.creator
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
217
I2Csensors/sensor.creator.user
Normal file
217
I2Csensors/sensor.creator.user
Normal 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>
|
||||
1
I2Csensors/sensor.cxxflags
Normal file
1
I2Csensors/sensor.cxxflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c++17
|
||||
22
I2Csensors/sensor.files
Normal file
22
I2Csensors/sensor.files
Normal 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 |
1
I2Csensors/sensor.includes
Normal file
1
I2Csensors/sensor.includes
Normal file
@@ -0,0 +1 @@
|
||||
.
|
||||
36
I2Csensors/sensors_private.h
Normal file
36
I2Csensors/sensors_private.h
Normal 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
58
MLX90640_test/Makefile
Normal 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
2
MLX90640_test/Readme
Normal file
@@ -0,0 +1,2 @@
|
||||
device-independent MLX90640 processing & test
|
||||
based on data example from melexis
|
||||
1
MLX90640_test/alpha.csv
Normal file
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
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
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
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
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
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
43
MLX90640_test/main.c
Normal 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
516
MLX90640_test/mlx90640.c
Normal 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, ®)){
|
||||
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
67
MLX90640_test/mlx90640.h
Normal 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]);
|
||||
90
MLX90640_test/mlx90640_regs.h
Normal file
90
MLX90640_test/mlx90640_regs.h
Normal 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)
|
||||
1
MLX90640_test/mlxtest.cflags
Normal file
1
MLX90640_test/mlxtest.cflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c17
|
||||
4
MLX90640_test/mlxtest.config
Normal file
4
MLX90640_test/mlxtest.config
Normal file
@@ -0,0 +1,4 @@
|
||||
#define GNU_SOURCE 1
|
||||
#define _XOPEN_SOURCE 1111
|
||||
#define EBUG
|
||||
|
||||
1
MLX90640_test/mlxtest.creator
Normal file
1
MLX90640_test/mlxtest.creator
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
218
MLX90640_test/mlxtest.creator.user
Normal file
218
MLX90640_test/mlxtest.creator.user
Normal 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>
|
||||
1
MLX90640_test/mlxtest.cxxflags
Normal file
1
MLX90640_test/mlxtest.cxxflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c++17
|
||||
5
MLX90640_test/mlxtest.files
Normal file
5
MLX90640_test/mlxtest.files
Normal file
@@ -0,0 +1,5 @@
|
||||
main.c
|
||||
mlx90640.c
|
||||
mlx90640.h
|
||||
mlx90640_regs.h
|
||||
testdata.h
|
||||
2
MLX90640_test/mlxtest.includes
Normal file
2
MLX90640_test/mlxtest.includes
Normal file
@@ -0,0 +1,2 @@
|
||||
/usr/local/include
|
||||
.
|
||||
1
MLX90640_test/offset.csv
Normal file
1
MLX90640_test/offset.csv
Normal 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
|
||||
|
82
MLX90640_test/testdata.h
Normal file
82
MLX90640_test/testdata.h
Normal 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"
|
||||
}
|
||||
};
|
||||
|
||||
1
MLX90640_test/to_frame0.csv
Normal file
1
MLX90640_test/to_frame0.csv
Normal 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
MLX90640_test/to_frame1.csv
Normal file
1
MLX90640_test/to_frame1.csv
Normal file
File diff suppressed because one or more lines are too long
4
deepseek/Readme
Normal file
4
deepseek/Readme
Normal 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
185
deepseek/hashreplace.c
Normal 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
72
deepseek/thermal.c
Normal 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
197
deepseek/transformator
Normal 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(¶ms, 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
59
modbus_params/Makefile
Normal 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
21
modbus_params/Readme
Normal 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
510
modbus_params/dictionary.c
Normal 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*)(®), 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);
|
||||
}
|
||||
522
modbus_params/dictionary.dic
Normal file
522
modbus_params/dictionary.dic
Normal 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 # Номинальная чаcтота двигателя 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 # Зарезервировано
|
||||
63
modbus_params/dictionary.h
Normal file
63
modbus_params/dictionary.h
Normal 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
140
modbus_params/main.c
Normal 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
204
modbus_params/modbus.c
Normal 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
33
modbus_params/modbus.h
Normal 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);
|
||||
1
modbus_params/modbus_param.cflags
Normal file
1
modbus_params/modbus_param.cflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c17
|
||||
2
modbus_params/modbus_param.config
Normal file
2
modbus_params/modbus_param.config
Normal file
@@ -0,0 +1,2 @@
|
||||
#define _XOPEN_SOURCE 1111
|
||||
#define _GNU_SOURCE
|
||||
1
modbus_params/modbus_param.creator
Normal file
1
modbus_params/modbus_param.creator
Normal file
@@ -0,0 +1 @@
|
||||
[General]
|
||||
1
modbus_params/modbus_param.cxxflags
Normal file
1
modbus_params/modbus_param.cxxflags
Normal file
@@ -0,0 +1 @@
|
||||
-std=c++17
|
||||
9
modbus_params/modbus_param.files
Normal file
9
modbus_params/modbus_param.files
Normal file
@@ -0,0 +1,9 @@
|
||||
dictionary.c
|
||||
dictionary.h
|
||||
main.c
|
||||
modbus.c
|
||||
modbus.h
|
||||
server.c
|
||||
server.h
|
||||
verbose.c
|
||||
verbose.h
|
||||
0
modbus_params/modbus_param.includes
Normal file
0
modbus_params/modbus_param.includes
Normal file
15
modbus_params/relay.aliases
Normal file
15
modbus_params/relay.aliases
Normal 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
4
modbus_params/relay.dic
Normal 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
177
modbus_params/server.c
Normal 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
23
modbus_params/server.h
Normal 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
40
modbus_params/verbose.c
Normal 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
24
modbus_params/verbose.h
Normal 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, ...);
|
||||
@@ -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
|
||||
};
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
96
modbus_relay/web_management/index.html
Normal file
96
modbus_relay/web_management/index.html
Normal 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>
|
||||
59
modbus_relay/web_management/relay_daemon/Makefile
Normal file
59
modbus_relay/web_management/relay_daemon/Makefile
Normal 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
|
||||
83
modbus_relay/web_management/relay_daemon/cmdlnopts.c
Normal file
83
modbus_relay/web_management/relay_daemon/cmdlnopts.c
Normal 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);
|
||||
}
|
||||
42
modbus_relay/web_management/relay_daemon/cmdlnopts.h
Normal file
42
modbus_relay/web_management/relay_daemon/cmdlnopts.h
Normal 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__)
|
||||
134
modbus_relay/web_management/relay_daemon/main.c
Normal file
134
modbus_relay/web_management/relay_daemon/main.c
Normal 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;
|
||||
}
|
||||
BIN
modbus_relay/web_management/relay_daemon/modbus_relay
Executable file
BIN
modbus_relay/web_management/relay_daemon/modbus_relay
Executable file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
-std=c17
|
||||
@@ -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
Reference in New Issue
Block a user