Compare commits

...

12 Commits

Author SHA1 Message Date
Edward Emelianov
aeb5cce6ee refresh my hash 2026-03-07 18:23:37 +03:00
Edward Emelianov
055d7f7f59 fixed, tested 2026-03-07 17:45:48 +03:00
Edward Emelianov
d18ab25440 example of helpcmds.in 2026-03-07 15:12:20 +03:00
Edward Emelianov
524a39b8a2 fixed for new libusefull_macros 2026-03-07 15:08:35 +03:00
Edward Emelianov
4a09776d27 ADD BMP580 2025-12-08 20:57:40 +03:00
Edward Emelianov
ebc8144271 some little joke 2025-10-29 23:39:05 +03:00
Edward Emelianov
ca7a53ee1c ESP simplest server 2025-10-17 00:32:56 +03:00
Edward Emelianov
19fbf250a9 Add readme 2025-10-14 17:50:53 +03:00
Edward Emelianov
ea0de3c904 create library and make two examples of usage 2025-10-14 00:17:07 +03:00
Edward Emelianov
7a37dc0d2f Add BMP280/BME280 (tested only on BME); add SHT3x (bug: reads only once) 2025-10-13 00:46:21 +03:00
Edward Emelianov
b0097d5ee6 Made refactoring for multi-sensor ability (N same sensors); check AHT21. I cry: I need this pointer in C!!! 2025-10-12 01:49:06 +03:00
Edward Emelianov
7f85861d6c Add SI7005 2025-10-11 21:10:55 +03:00
45 changed files with 3323 additions and 509 deletions

34
ESP8266/CMakeLists.txt Normal file
View File

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

2
ESP8266/Readme Normal file
View File

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

170
ESP8266/esp8266.c Normal file
View File

@@ -0,0 +1,170 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <usefull_macros.h>
#include <stdio.h>
#include "esp8266.h"
#include "serial.h"
#define ESPMSG(x) do{if(!serial_send_msg(x)) return FALSE;}while(0)
// sometimes ESP hangs with message "WIFI GOT IP" and I can do nothing except waiting
const char *msgptr = NULL; // pointer to received message
static int receivedlen = 0;
// check module working
int esp_check(){
// try simplest check for three times
for(int i = 0; i < 3; ++i) if( ANS_OK == serial_sendwans("AT")) break;
//esp_close();
// now try next even if no answer for "AT"
if( ANS_OK == serial_sendwans("ATE0") // echo off
&& ANS_OK == serial_sendwans("AT+CWMODE_CUR=1") // station mode
) return TRUE;
return FALSE;
}
// check established wifi connection
esp_cipstatus_t esp_cipstatus(){
if(ANS_OK != serial_sendwans("AT+CIPSTATUS")) return ESP_CIP_FAILED;
char *l = serial_getline(NULL, NULL);
const char *n = NULL;
if(!l || !(n = serial_tidx(l, "STATUS:"))) return ESP_CIP_FAILED;
return (esp_cipstatus_t)serial_s2i(n);
}
// connect to AP
int esp_connect(const char *SSID, const char *pass){
ESPMSG("AT+CWJAP_CUR=\"");
ESPMSG(SSID);
ESPMSG("\",\"");
ESPMSG(pass);
if(ANS_OK != serial_sendwans("\"")) return FALSE;
return TRUE;
}
// just send AT+CIFSR
int esp_myip(){
return (ANS_OK == serial_sendwans("AT+CIFSR"));
}
// start server on given port
int esp_start_server(const char *port){
if(ANS_OK != serial_sendwans("AT+CIPMUX=1")){ // can't start server without mux
// what if already ready?
char *x = serial_getline(NULL, NULL);
if(!x || !serial_tidx(x, "link is builded")) return FALSE;
}
ESPMSG("AT+CIPSERVER=1,");
if(ANS_OK != serial_sendwans(port)) return FALSE;
return TRUE;
}
// stop server
int ep_stop_server(){
return (ANS_OK == serial_sendwans("AT+CIPSERVER=0"));
}
// next (or only) line of received data
const char *esp_msgline(){
DBG("receivedlen=%d", receivedlen);
if(msgptr){
const char *p = msgptr;
msgptr = NULL;
return p;
}
if(receivedlen < 1) return NULL;
int l, d;
const char *got = serial_getline(&l, &d);
receivedlen -= l + d;
return got;
}
// process connection/disconnection/messages
// fd - file descriptor of opened/closed connections
esp_clientstat_t esp_process(int *fd){
msgptr = NULL;
receivedlen = 0;
int l, d;
char *got = serial_getline(&l, &d);
if(!got) return ESP_CLT_IDLE;
const char *x = serial_tidx(got, "+IPD,");
if(x){
if(fd) *fd = serial_s2i(x);
x = serial_tidx(x, ",");
if(!x) return ESP_CLT_ERROR;
int r = serial_s2i(x);
x = serial_tidx(x, ":");
if(!x) return ESP_CLT_ERROR;
receivedlen = r - d - (l - (x-got)); // this is a rest of data (if any)
msgptr = x;
return ESP_CLT_GETMESSAGE;
}
// check for CONNECT/CLOSE
if((x = serial_tidx(got, ",CONNECT"))){
if(fd) *fd = serial_s2i(got);
return ESP_CLT_CONNECTED;
}
if((x = serial_tidx(got, ",CLOSED"))){
if(fd) *fd = serial_s2i(got);
return ESP_CLT_DISCONNECTED;
}
DBG("Unknown message: '%s'", got);
return ESP_CLT_IDLE;
}
int esp_send(int fd, const char *msg){
DBG("send '%s' to %d", msg, fd);
ESPMSG("AT+CIPSENDEX=");
if(!serial_putchar('0' + fd)) return FALSE;
if(ANS_OK != serial_sendwans(",2048")) return FALSE;
int got = 0;
// try several times
for(int i = 0; i < 10; ++i){
got = serial_getch();
if(got == '>') break;
}
if(got != '>'){
DBG("Didn't found '>'");
serial_send_msg("\\0"); // terminate message
serial_clr();
return FALSE; // go into terminal mode
}
serial_clr(); // remove space after '>'
ESPMSG(msg);
if(ANS_OK == serial_sendwans("\\0")) return TRUE;
DBG("Didn't sent");
return FALSE;
}
void esp_reset(){
serial_sendwans("AT+RST");
}
void esp_close(){
serial_sendwans("AT+CIPMUX=0");
serial_sendwans("AT+CIPCLOSE");
}
int esp_listAP(){
if(ANS_OK == serial_sendwans("AT+CWLAP")) return TRUE;
return FALSE;
}

53
ESP8266/esp8266.h Normal file
View File

@@ -0,0 +1,53 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
// really only 5 clients allowed
#define ESP_MAX_CLT_NUMBER 8
typedef enum{
ESP_CIP_0,
ESP_CIP_1,
ESP_CIP_GOTIP,
ESP_CIP_CONNECTED,
ESP_CIP_DISCONNECTED,
ESP_CIP_FAILED
} esp_cipstatus_t;
typedef enum{
ESP_CLT_IDLE, // nothing happened
ESP_CLT_CONNECTED, // new client connected
ESP_CLT_DISCONNECTED, // disconnected
ESP_CLT_ERROR, // error writing or other
ESP_CLT_GETMESSAGE, // receive message from client
ESP_CLT_OK, // sent OK
} esp_clientstat_t;
int esp_check();
void esp_close();
void esp_reset();
esp_cipstatus_t esp_cipstatus();
int esp_connect(const char *SSID, const char *pass);
int esp_myip();
int esp_start_server(const char *port);
int ep_stop_server();
esp_clientstat_t esp_process(int *fd);
int esp_send(int fd, const char *msg);
const char *esp_msgline();
int esp_listAP();

182
ESP8266/main.c Normal file
View File

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

178
ESP8266/serial.c Normal file
View File

@@ -0,0 +1,178 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <string.h>
#include <usefull_macros.h>
#include "serial.h"
// ALL functions here aren't thread-independent, as you can't use the same line simultaneously
static sl_tty_t *device = NULL;
static double timeout = 30.; // timeout, s
static sl_ringbuffer_t *rbin = NULL; // input ring buffer
// read all incoming
void serial_clr(){
if(!device) return;
while(sl_tty_read(device) > 0);
}
int serial_init(char *path, int speed){
device = sl_tty_new(path, speed, 256);
if(!device) return FALSE;
device = sl_tty_open(device, 1);
if(!device) return FALSE;
rbin = sl_RB_new(4096);
if(!rbin){
sl_tty_close(&device);
return FALSE;
}
sl_tty_tmout(1000); // set select() timeout to 1ms
// clear buffer
serial_clr();
return TRUE;
}
void serial_close(){
if(device) sl_tty_close(&device);
if(rbin) sl_RB_delete(&rbin);
}
int serial_set_timeout(double tms){
if(tms < 0.1) return FALSE;
timeout = tms;
return TRUE;
}
// send messages over serial,
// without EOL:
int serial_send_msg(const char *msg){
if(!msg || !device) return FALSE;
int l = strlen(msg);
DBG("Write message `%s` (%d bytes)", msg, l);
if(sl_tty_write(device->comfd, msg, l)) return FALSE;
return TRUE;
}
// and with:
int serial_send_cmd(const char *msg){
if(!msg || !device) return FALSE;
if(!serial_send_msg(msg)) return FALSE;
DBG("Write EOL");
if(sl_tty_write(device->comfd, "\r\n", 2)) return FALSE;
return TRUE;
}
int serial_putchar(char ch){
if(!device) return FALSE;
if(sl_tty_write(device->comfd, &ch, 1)) return FALSE;
return TRUE;
}
static void fillinbuff(){
ssize_t got;
while((got = sl_tty_read(device))){
if(got < 0){
WARNX("Serial device disconnected!");
serial_close();
}else if(got){
if((size_t)got != sl_RB_write(rbin, (const uint8_t*)device->buf, got)){
WARNX("Rinbguffer overflow?");
sl_RB_clearbuf(rbin);
return;
}
}
}
}
// read one string line from serial
// @arg deleted - N symbols deleted from rest of string (1 in case of '\n' and 2 in case of "\r\n")
// @arg len - strlen of data
char *serial_getline(int *len, int *deleted){
if(!device) return NULL;
static char buf[BUFSIZ];
fillinbuff();
// read old records
if(!sl_RB_readline(rbin, buf, BUFSIZ-1)) return NULL;
// remove trailing '\r'
int l = strlen(buf), d = 1;
if(l > -1 && buf[l - 1] == '\r'){
++d;
buf[--l] = 0;
}
if(deleted) *deleted = d;
if(len) *len = l;
DBG("read: '%s'", buf);
return buf;
}
// get symbol
int serial_getch(){
if(!device) return -1;
fillinbuff();
char C;
DBG("rb size: %zd", sl_RB_datalen(rbin));
size_t rd = sl_RB_read(rbin, (uint8_t*)&C, 1);
DBG("got %zd : '%c'", rd, C);
if(rd != 1) return -1;
//if(1 != sl_RB_read(rbin, (uint8_t*)&C, 1)) return -1;
return (int) C;
}
serial_ans_t serial_sendwans(const char *msg){
if(!msg || !device) return ANS_FAILED;
if(!serial_send_cmd(msg)) return ANS_FAILED;
double t0 = sl_dtime();
int ret = ANS_FAILED;
while(sl_dtime() - t0 < timeout && device){
char *ans = NULL;
if(!(ans = serial_getline(NULL, NULL))){ usleep(500); continue; }
DBG("Get line: '%s' (%zd bytes)", ans, strlen(ans));
if(!*ans) continue; // empty string
if(strcmp(ans, "OK") == 0 || strcmp(ans, "SEND OK") == 0){ ret = ANS_OK; goto rtn; }
if(strcmp(ans, "ERROR") == 0){ ret = ANS_ERR; goto rtn; }
if(strcmp(ans, "FAIL") == 0){ ret = ANS_FAILED; goto rtn; }
DBG("Return '%s' into buff", ans);
sl_RB_writestr(rbin, ans); // put other data into ringbuffer for further processing
sl_RB_putbyte(rbin, '\n');
}
rtn:
DBG("proc time: %g", sl_dtime() - t0);
return ret;
}
// return NULL if `s` don't contain `t`, else return next symbol in `s`
const char *serial_tidx(const char *s, const char *t){
if(!s) return NULL;
DBG("check '%s' for '%s'", s, t);
int pos = 0;
if(t){
const char *sub = strstr(s, t);
if(!sub) return NULL;
int l = strlen(t);
pos = (sub - s) + l;
}
DBG("pos = %d", pos);
return s + pos;
}
int serial_s2i(const char *s){
if(!s) return -1;
DBG("conv '%s' to %d", s, atoi(s));
return atoi(s);
}

40
ESP8266/serial.h Normal file
View File

@@ -0,0 +1,40 @@
/*
* This file is part of the esp8266 project.
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
typedef enum{
ANS_FAILED,
ANS_OK,
ANS_ERR
} serial_ans_t;
void serial_clr();
int serial_set_timeout(double tms);
int serial_init(char *path, int speed);
void serial_close();
int serial_send_msg(const char *msg);
int serial_send_cmd(const char *msg);
int serial_putchar(char ch);
serial_ans_t serial_sendwans(const char *msg);
char *serial_getline(int *len, int *deleted);
int serial_getch();
// conversion functions for ESP
const char *serial_tidx(const char *s, const char *t);
int serial_s2i(const char *s);

View File

@@ -1,5 +1,4 @@
/*
* This file is part of the bmp180 project.
* Copyright 2022 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
@@ -19,18 +18,18 @@
#include <stdio.h>
#include <usefull_macros.h>
#include "i2c.h"
#include "BMP180.h"
#include "i2c.h"
#include "sensors_private.h"
static uint8_t addr = 0x77;
typedef enum{
enum{
BMP180_OVERS_1 = 0, // oversampling is off
BMP180_OVERS_2 = 1,
BMP180_OVERS_4 = 2,
BMP180_OVERS_8 = 3,
BMP180_OVERSMAX = 4
} BMP180_oversampling;
BMP180_OVERSMAX = 3
};
#define BMP180_CHIP_ID 0x55
@@ -50,23 +49,18 @@ typedef enum{
#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)
typedef enum{
WAIT_T,
WAIT_P,
WAIT_NONE
} waitmsr_t;
// mind that user can't change this
static const uint8_t bmp180_os = BMP180_OVERSMAX;
static waitmsr_t wait4 = WAIT_NONE;
static BMP180_oversampling bmp180_os = BMP180_OVERSMAX;
static struct {
typedef struct {
int16_t AC1;
int16_t AC2;
int16_t AC3;
@@ -80,45 +74,36 @@ static struct {
int16_t MD;
int32_t MCfix;
int32_t AC1_fix;
} __attribute__ ((packed)) CaliData = {0};
static sensor_status_t bmpstatus = SENS_NOTINIT;
static uint8_t calidata_rdy = 0;
//static uint32_t milliseconds_start = 0; // time of measurement start
//static uint32_t p_delay = 8; // delay for P measurement
static uint8_t uncomp_data[3]; // raw uncompensated data
static int32_t Tval; // uncompensated T value
// compensated values:
static uint32_t Pmeasured; // Pa
static float Tmeasured; // degC
static uint8_t devID = 0;
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(){
static int readcompdata(sensor_t *s){
FNAME();
if(!i2c_read_data8(BMP180_REG_CALIB, sizeof(CaliData), (uint8_t*)&CaliData)) return FALSE;
// convert big-endian into little-endian
uint8_t *arr = (uint8_t*)&CaliData;
for(int i = 0; i < (int)sizeof(CaliData); i+=2){
register uint8_t val = arr[i];
arr[i] = arr[i+1];
arr[i+1] = val;
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_rdy = 1;
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(){
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;
@@ -127,9 +112,10 @@ static int BMP180_reset(){
}
// read compensation data & write registers
static int BMP180_init(){
bmpstatus = SENS_NOTINIT ;
if(!BMP180_reset()) return FALSE;
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;
@@ -139,50 +125,53 @@ static int BMP180_init(){
DBG("Not BMP180\n");
return FALSE;
}
if(!readcompdata()){
if(!readcompdata(s)){
DBG("Can't read calibration data\n");
return FALSE;
}else{
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);
#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);
}
bmpstatus = SENS_RELAX;
s->status = SENS_RELAX;
return TRUE;
}
// start measurement, @return 1 if all OK
static int BMP180_start(){
if(!calidata_rdy || bmpstatus == SENS_BUSY) return FALSE;
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)){
bmpstatus = SENS_ERR;
s->status = SENS_ERR;
DBG("Can't write CTRL reg\n");
return FALSE;
}
bmpstatus = SENS_BUSY;
wait4 = WAIT_T;
s->status = SENS_BUSY;
return TRUE;
}
// calculate T degC and P in Pa
static inline void compens(uint32_t Pval){
static inline void compens(sensor_t *s, uint32_t Pval){
CaliData_t *CaliData = (CaliData_t*)s->privdata;
// T:
int32_t X1 = ((Tval - CaliData.AC6)*CaliData.AC5) >> 15;
int32_t X2 = CaliData.MCfix / (X1 + CaliData.MD);
int32_t X1 = ((CaliData->Tuncomp - CaliData->AC6)*CaliData->AC5) >> 15;
int32_t X2 = CaliData->MCfix / (X1 + CaliData->MD);
int32_t B5 = X1 + X2;
Tmeasured = (B5 + 8.) / 160.;
s->data.T = (B5 + 8.) / 160.;
// P:
int32_t B6 = B5 - 4000;
X1 = (CaliData.B2 * ((B6*B6) >> 12)) >> 11;
X2 = (CaliData.AC2 * B6) >> 11;
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;
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 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){
@@ -194,81 +183,65 @@ static inline void compens(uint32_t Pval){
X1 *= X1;
X1 = (X1 * 3038) >> 16;
X2 = (-7357 * p) / 65536;
Pmeasured = p + ((X1 + X2 + 3791) / 16);
s->data.P = (p + ((X1 + X2 + 3791) / 16)) / 100.; // convert to hPa
}
static int still_measuring(){
uint8_t reg;
if(!i2c_read_reg8(BMP180_REG_CTRLMEAS, &reg)) return TRUE;
if(reg & BMP180_CTRLM_SCO){
return TRUE;
}
return FALSE;
}
static sensor_status_t BMP180_process(){
uint8_t reg;
if(bmpstatus != SENS_BUSY) goto ret;
if(wait4 == WAIT_T){ // wait for temperature
if(still_measuring()) goto ret;
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)){
bmpstatus = SENS_ERR;
s->status = SENS_ERR;
goto ret;
}
Tval = uncomp_data[0] << 8 | uncomp_data[1];
DBG("Start P measuring\n");
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)){
bmpstatus = SENS_ERR;
s->status = SENS_ERR;
goto ret;
}
wait4 = WAIT_P;
}else{ // wait for pressure
if(still_measuring()) goto ret;
DBG("Read uncompensated P\n");
if(!i2c_read_data8(BMP180_REG_OUT, 3, uncomp_data)){
bmpstatus = SENS_ERR;
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(Pval);
compens(s, Pval);
DBG("All data ready\n");
bmpstatus = SENS_RDY; // data ready
wait4 = WAIT_NONE;
s->status = SENS_RDY; // data ready
}
ret:
return bmpstatus;
return s->status;
}
// read data & convert it
static int BMP180_getdata(sensor_data_t *d){
if(!d || bmpstatus != SENS_RDY) return FALSE;
d->T = Tmeasured;
d->P = Pmeasured / 100.; // convert Pa to hPa
bmpstatus = SENS_RELAX;
return TRUE;
}
static sensor_props_t BMP180_props(){
sensor_props_t p = {.T = 1, .H = 0, .P = 1};
static sensor_props_t BMP180_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .P = 1};
return p;
}
static uint8_t address(uint8_t new){
if(new) addr = new;
return addr;
static int s_heater(sensor_t _U_ *s, int _U_ on){
return FALSE;
}
sensor_t BMP180 = {
.name = "BMP180",
.address = address,
.address = 0x77,
.status = SENS_NOTINIT,
.init = BMP180_init,
.start = BMP180_start,
.heater = s_heater,
.process = BMP180_process,
.properties = BMP180_props,
.get_data = BMP180_getdata
};

View File

@@ -1,5 +1,4 @@
/*
* This file is part of the BMP180 project.
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
@@ -17,6 +16,6 @@
*/
#pragma once
#include "sensor.h"
#include "i2csensorsPTH.h"
extern sensor_t BMP180;

385
I2Csensors/BMP280.c Normal file
View File

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

22
I2Csensors/BMP280.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* Copyright 2021 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t BMP280;
extern sensor_t BME280;

513
I2Csensors/BMP580.c Normal file
View File

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

21
I2Csensors/BMP580.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t BMP580;

104
I2Csensors/CMakeLists.txt Normal file
View File

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

View File

@@ -1,57 +0,0 @@
# run `make DEF=...` to add extra defines
PROGRAM := sensors
LDFLAGS := -fdata-sections -ffunction-sections -Wl,--gc-sections -Wl,--discard-all
LDFLAGS += -lusefull_macros -lm
SRCS := $(wildcard *.c)
DEFINES := $(DEF) -D_GNU_SOURCE -D_XOPEN_SOURCE=1111
OBJDIR := mk
CFLAGS += -O2 -Wall -Wextra -Wno-trampolines -std=gnu99
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

14
I2Csensors/PCA9548 Executable file
View File

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

View File

@@ -1,21 +1,158 @@
Reading information from different T/P/H sensors
================================================
Supported sensors:
Supported sensors: AHT10, AHT15, AHT21b, BMP180, BMP280, BME280, SHT3x, SI7005
- BMP180 - T/P sensor
| 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
sensors
```
-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
### Base types
#### `sensor_status_t`
Status of given sensor. `SENS_NOTINIT` means that you should init device; also if you get `SENS_ERR` you should try to reinit it.
Receiving error on init function means that there's troubles on the bus or with sensor.
```
typedef enum{
SENS_NOTINIT, // wasn't inited
SENS_BUSY, // measurement in progress
SENS_ERR, // error occured
SENS_RELAX, // do nothing
SENS_RDY, // data ready - can get it
} sensor_status_t;
```
#### `sensor_props_t`
Properties: if the corresponding field sets, the device have this ability. `flags` allows to use all together as bit fields.
```
typedef union{
struct{
uint8_t T : 1; // can temperature (degC)
uint8_t H : 1; // can humidity (percent)
uint8_t P : 1; // can pressure (hPa)
uint8_t htr : 1; // have heater
};
uint32_t flags;
} sensor_props_t;
```
#### `sensor_data_t`
Gathered data. The fields that are zeroed in sensor's properties are undefined.
```
typedef struct{
double T; // temperature, degC
double H; // humidity, percents
double P; // pressure, hPa
} sensor_data_t;
```
### Functions
#### `int sensors_open(const char *dev)`
Open I2C device by path `dev`. Returns `TRUE` if all OK.
#### `void sensors_close()`
Close I2C device.
#### `char *sensors_list()`
Returns allocated string with comma-separated names of all supported sensors. Don't forget to `free` it later.
#### `sensor_t* sensor_new(const char *name)`
Search `name` in list of supported sensors and, if found, returns pointer to sensors structure. Returns `NULL` if sensor not failed or some error oqqured.
#### `void sensor_delete(sensor_t **s)`
Delete all memory, allocated for given sensor.
#### `sensor_props_t sensor_properties(sensor_t *s)`
Allows to check sensor's properties.
#### `int sensor_init(sensor_t *s, uint8_t address)`
Try to find given sensor on the bus and run initial procedures (like calibration and so on). The `address` argument shoul be zero to use default I2C address or non-zero for custom.
Returns `TRUE` if all OK.
#### `int sensor_heater(sensor_t *s, int on)`
Turn on (`on == 1`) or off (`on == 0`) sensor's heater (if sensor supported it). Returns `FALSE` if sensor don't support heater or some error occured during operations.
#### `int sensor_start(sensor_t *s)`
Start measurement process. While measuring, you should poll sensor until data would be ready (or you get timeout error).
#### `sensor_status_t sensor_process(sensor_t *s)`
Polling sensor and gathering all data in simple finite-state machine. Checks if sensor is still busy and asks for data portion on each measuring stage.
Returns current sensor's state. If you get `SENS_RDY`, you can ask for data.
#### `int sensor_getdata(sensor_t *s, sensor_data_t *d)`
Get data into your variable `d`. Returns `FALSE` if data isn't ready (e.g. you didn't run `start` or sensor is still measuring).
### I2C functions
Of course, you can wish to work with I2C directly (e.g. to switch multiplexer's channel and so on), so here are some usefull functions.
#### `int sensor_writeI2C(uint8_t addr, uint8_t *data, int len)`
Write `data` array with len of `len` bytes with device address `addr`. Returns `FALSE` if failed.
#### `int sensor_readI2C(uint8_t addr, uint8_t *data, int len)`
Read `len` bytes of `data` from address `addr`. Returns `FALSE` if failed.
#### `int sensor_readI2Cregs(uint8_t addr, uint8_t regaddr, uint16_t N, uint8_t *data)`
Read content of `N` 8-bit registers starting from `regaddr` to array `data`. Returns `FALSE` if failed.
#### `int sensor_writeI2Creg(uint8_t addr, uint8_t regaddr, uint8_t data)`
Write `data` to single register `regaddr`. Returns `FALSE` if failed.

121
I2Csensors/SHT3x.c Normal file
View File

@@ -0,0 +1,121 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "SHT3x.h"
#include "i2c.h"
#include "sensors_private.h"
// use single mode with high repeatability
static uint8_t cmd_measure[] = { 0x24, 0x00 };
static uint8_t cmd_reset[] = { 0x30, 0xa2 };
static uint8_t cmd_break[] = { 0x30, 0x93 };
static uint8_t cmd_heater_on[] = { 0x30, 0x6d };
static uint8_t cmd_heater_off[] = { 0x30, 0x66 };
//static uint8_t cmd_read_status_reg[] = { 0xf3, 0x2d };
static uint8_t cmd_clear_status_reg[] = { 0x30, 0x41 };
// maybe usefull to read heater status
//#define SHT31_REG_HEATER_BIT 0x0d
// documented timeout is 15ms, so let's wait 20
#define MEASUREMENT_TIMEOUT (0.02)
static int s_init(sensor_t *s){
s->status = SENS_NOTINIT;
if(!i2c_write_raw(cmd_break, 2)){
DBG("Can't break old measurements");
return FALSE;
}
if(!i2c_write_raw(cmd_reset, 2)){
DBG("Can't make soft reset");
return FALSE;
}
if(!i2c_write_raw(cmd_clear_status_reg, 2)){
DBG("Can't clear status bits");
return FALSE;
}
if(!s->privdata) s->privdata = calloc(1, sizeof(double)); // used for start measurement time
s->status = SENS_RELAX;
return TRUE;
}
static int s_start(sensor_t *s){
if(s->status != SENS_RELAX) return FALSE;
s->status = SENS_BUSY;
if(!i2c_write_raw(cmd_measure, 2)){
DBG("Can't write start Tmeas");
s->status = SENS_ERR;
return FALSE;
}
*((double*)s->privdata) = sl_dtime();
return TRUE;
}
static uint8_t crc8(const uint8_t *data, int len){
uint8_t POLYNOMIAL = 0x31;
uint8_t crc = 0xFF;
for(int j = len; j; --j){
crc ^= *data++;
for(int i = 8; i; --i) crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
}
return crc;
}
static sensor_status_t s_process(sensor_t *s){
if(s->status != SENS_BUSY) return s->status;
uint8_t data[6];
int ans = i2c_read_raw(data, 6);
if(sl_dtime() - *((double*)s->privdata) < MEASUREMENT_TIMEOUT){
if(!ans) return s->status; // poll ACK
}
if(!ans) return (s->status = SENS_ERR); // timeout!
i2c_write_raw(cmd_clear_status_reg, 2); // need to write any command or sensor will widthraw into itself
if(data[2] != crc8(data, 2) || data[5] != crc8(data + 3, 2)) return (s->status = SENS_ERR); // CRC error
int32_t stemp = (int32_t)(((uint32_t)data[0] << 8) | data[1]);
stemp = ((4375 * stemp) >> 14) - 4500;
s->data.T = stemp / 100.;
uint32_t shum = ((uint32_t)data[3] << 8) | data[4];
shum = (625 * shum) >> 12;
s->data.H = shum / 100.0;
return (s->status = SENS_RDY);
}
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .H = 1};
return p;
}
static int s_heater(sensor_t _U_ *s, int on){
uint8_t *cmd = (on) ? cmd_heater_on : cmd_heater_off;
if(!i2c_write_raw(cmd, 2)) return FALSE;
return TRUE;
}
sensor_t SHT3x = {
.name = "SHT3x",
.address = 0x44,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

22
I2Csensors/SHT3x.h Normal file
View File

@@ -0,0 +1,22 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t SHT3x;

139
I2Csensors/SI7005.c Normal file
View File

@@ -0,0 +1,139 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdio.h>
#include <usefull_macros.h>
#include "i2c.h"
#include "sensors_private.h"
#include "SI7005.h"
#define SI7005_REGSTATUS 0
#define SI7005_STATUSNRDY 1
#define SI7005_REGDATA 1
#define SI7005_REGCONFIG 3
#define SI7005_CONFFAST (1<<5)
#define SI7005_CONFTEMP (1<<4)
#define SI7005_CONFHEAT (1<<1)
#define SI7005_CONFSTART (1<<0)
#define SI7005_REGID 0x11
#define SI7005_ID 0x50
static int s_init(sensor_t *s){
uint8_t ID;
s->status = SENS_NOTINIT;
if(!i2c_read_reg8(SI7005_REGID, &ID)){
DBG("Can't read SI_REG_ID");
return FALSE;
}
DBG("SI, device ID: 0x%02x", ID);
if(ID != SI7005_ID){
DBG("Not SI7005\n");
return FALSE;
}
s->status = SENS_RELAX;
return TRUE;
}
static int s_start(sensor_t *s){
if(s->status != SENS_RELAX) return FALSE;
s->status = SENS_BUSY;
if(!i2c_write_reg8(SI7005_REGCONFIG, SI7005_CONFTEMP | SI7005_CONFSTART)){
DBG("Can't write start Tmeas");
s->status = SENS_ERR;
return FALSE;
}
DBG("Wait for T\n");
return TRUE;
}
// start humidity measurement
static sensor_status_t si7005_cmdH(sensor_t *s){
s->status = SENS_BUSY;
if(!i2c_write_reg8(SI7005_REGCONFIG, SI7005_CONFSTART)){
DBG("Can't write start Hmeas");
return (s->status = SENS_ERR);
}
DBG("Wait for H");
return s->status;
}
static sensor_status_t s_process(sensor_t *s){
uint8_t c, d[3];
if(s->status != SENS_BUSY) return s->status;
if(!i2c_read_raw(d, 3)){
DBG("Can't read status");
return (s->status = SENS_ERR);
}
//DBG("Status: 0x%02x, H: 0x%02x, L: 0x%02x", d[0], d[1], d[2]);
if(!i2c_read_reg8(SI7005_REGCONFIG, &c)){
DBG("Can't read config");
return (s->status = SENS_ERR);
}
//DBG("Config: 0x%02x", c);
if(d[0] & SI7005_STATUSNRDY){ // not ready yet
return s->status;
}
uint16_t TH = (uint16_t)((d[1]<<8) | d[2]);
if(c & SI7005_CONFTEMP){ // temperature measured
TH >>= 2;
double Tmeasured = TH/32. - 50.;
DBG("T=%.2f", Tmeasured);
s->data.T = Tmeasured;
return si7005_cmdH(s);
}else{ // humidity measured
// correct T/H
#define A0 (-4.7844)
#define A1 (0.4008)
#define A2 (-0.00393)
TH >>= 4;
double Hmeasured = TH/16.f - 24.f;
DBG("H=%.1f", Hmeasured);
s->data.H = Hmeasured - (A2*Hmeasured*Hmeasured + A1*Hmeasured + A0);
s->status = SENS_RDY;
}
return s->status;
}
// turn heater on/off (1/0)
static int s_heater(sensor_t *s, int on){
DBG("status=%d", s->status);
if(s->status != SENS_RELAX) return FALSE;
uint8_t reg = (on) ? SI7005_CONFHEAT : 0;
if(!i2c_write_reg8(SI7005_REGCONFIG, reg)){
DBG("Can't write regconfig");
return FALSE;
}
return TRUE;
}
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .H = 1, .htr = 1};
return p;
}
sensor_t SI7005 = {
.name = "SI7005",
.address = 0x40,
.status = SENS_NOTINIT,
.init = s_init,
.start = s_start,
.heater = s_heater,
.process = s_process,
.properties = s_props,
};

21
I2Csensors/SI7005.h Normal file
View File

@@ -0,0 +1,21 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "i2csensorsPTH.h"
extern sensor_t SI7005;

View File

@@ -20,90 +20,172 @@
#include "aht.h"
#include "i2c.h"
static uint8_t addr = 0x38;
static sensor_status_t status = SENS_NOTINIT;
#include "sensors_private.h"
enum{
ISAHT10,
ISAHT15,
ISAHT21b
ISAHT1x,
ISAHT2x
};
static uint32_t rawH = 0, rawT = 0;
#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
#define AHT_CMD_INITIALIZE 0xE1
#define AHT_CMD_MEASURE 0xAC
#define AHT_CMD_SOFT_RESET 0xBA
// max reset time
#define RST_TIME (20e-3)
// max data waiting time
#define DATA_TIME (75e-3)
static int s_init(){
status = SENS_NOTINIT;
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;
}
uint8_t data[3] = {AHT_CMD_INITIALIZE, 0x08, 0};
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;
}
status = SENS_RELAX;
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(){
if(status != SENS_RELAX) return FALSE;
uint8_t data[3] = {AHT_CMD_MEASURE, 0x33, 0};
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(){
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_data8(0, 6, data)) return (status = SENS_ERR);
if(!i2c_read_raw(data, 6)) return (status = SENS_ERR);
#ifdef EBUG
printf("Got data: "); for(int i = 0; i < 6; ++i) printf(" %02X", data[i]); printf("\n");
if(data[0] & 0x80) printf("BUSY ");
static const char *modes[] = {"NOR", "CYC", "CMD", "CMD"};
printf("MODE=%s ", modes[(data[0] >> 6)&3]);
printf("%sCALIBRATED\n", data[0] & 8 ? "" : "NOT ");
#endif
if(data[0] & 0x80) return (status = SENS_BUSY); // still measuring
rawH = ((uint32_t)data[1] << 12) | ((uint32_t)data[2] << 4) | (data[3] >> 4);
rawT = ((uint32_t)(data[3] & 0x0F) << 16) | ((uint32_t)data[4] << 8) | data[5];
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);
return (status = SENS_RDY);
s->data.T = rawT * 200.0 / 1048576.0 - 50.0;
s->data.H = rawH * 100.0 / 1048576.0;
return (s->status = SENS_RDY);
}
static int s_getdata(sensor_data_t *d){
if(!d || status != SENS_RDY) return FALSE;
d->T = rawT * 200.0 / 1048576.0 - 50.0;
d->H = rawH * 100.0 / 1048576.0;
return TRUE;
}
static sensor_props_t s_props(){
sensor_props_t p = {.T = 1, .H = 1, .P = 0};
static sensor_props_t s_props(sensor_t _U_ *s){
sensor_props_t p = {.T = 1, .H = 1};
return p;
}
static uint8_t address(uint8_t new){
if(new) addr = new;
return addr;
static int s_heater(sensor_t _U_ *s, int _U_ on){
return FALSE;
}
sensor_t AHT10 = {
.name = "AHT10",
.private = ISAHT10,
.address = address,
.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,
.get_data = s_getdata
};

View File

@@ -17,6 +17,9 @@
#pragma once
#include "sensor.h"
#include "i2csensorsPTH.h"
extern sensor_t AHT10;
extern sensor_t AHT15;
extern sensor_t AHT20;
extern sensor_t AHT21;

View File

@@ -12,3 +12,5 @@ HTU32d = 2%/0.2
SHT30 = 3%/0.3°/- = 0x44/0x45 = 1M = у SHT31 заявленная точность по влажности: 2%; младший бит адреса выбирается ногой ADDR; есть программируемая нога ALERT!
SHT4x = * = 0x44/0x45** (*) 40: 2-4%/>0.2-0.4°, 41: 2-2.5%/0.2-0.4°, 45: 1-2%/0.1-0.3°; (**) адрес зависит от маркировки (A/B); есть нагреватель; есть команда reset (0x06) по адресу 0
SI7005 = 4.5%/1°/- = 0x40 = 400k = возможен выбор с помощью ноги ~CS
SHT85?

View File

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

8
I2Csensors/examples/MAP Normal file
View File

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

View File

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

View File

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

View File

@@ -19,7 +19,7 @@
#include <unistd.h>
#include <usefull_macros.h>
#include "sensor.h"
#include "i2csensorsPTH.h"
typedef struct{
char *device;
@@ -28,10 +28,12 @@ typedef struct{
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[] = {
@@ -41,10 +43,11 @@ static sl_option_t cmdlnopts[] = {
{"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(const sensor_t *s, uint8_t addr){
static int start(sensor_t *s, uint8_t addr){
if(!sensor_init(s, addr)){
WARNX("Can't init sensor");
return FALSE;
@@ -56,14 +59,14 @@ static int start(const sensor_t *s, uint8_t addr){
return TRUE;
}
static int printdata(const sensor_t *s){
static int printdata(sensor_t *s){
sensor_data_t D;
if(!s->get_data(&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 = s->properties();
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){
@@ -78,17 +81,29 @@ int main(int argc, char **argv){
sl_parseargs(&argc, &argv, cmdlnopts);
if(G.help) sl_showhelp(-1, cmdlnopts);
if(G.list){
sensors_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);
const sensor_t* s = sensor_find(G.sensor);
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 = s->process();
sensor_status_t status = sensor_process(s);
if(status == SENS_RDY){ // data ready - get it
if(!printdata(s)) continue;
break;
@@ -96,7 +111,9 @@ int main(int argc, char **argv){
WARNX("Error in measurement, try again");
if(!start(s, G.slaveaddr)) break;
}
usleep(10000);
}
sensor_delete(&s);
clo:
sensors_close();

View File

@@ -28,6 +28,7 @@
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};
@@ -35,8 +36,9 @@ static int i2c_rw(uint8_t *data, int len, uint16_t flags){
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){
WARN("i2c_read_reg16, ioctl()");
DBG("i2c_rw, ioctl()");
return FALSE;
}
return TRUE;
@@ -143,9 +145,6 @@ int i2c_write_reg16(uint16_t regaddr, uint16_t data){
WARN("i2c_write_reg16, ioctl()");
return FALSE;
}
/* printf("Block: ");
for(int i = 0; i < 4; ++i) printf("0x%02x ", d.block[i]);
printf("\n");*/
return TRUE;
}
@@ -156,6 +155,7 @@ int i2c_write_reg16(uint16_t regaddr, uint16_t data){
*/
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;
@@ -183,40 +183,10 @@ void i2c_close(){
if(I2Cfd > 0) close(I2Cfd);
}
#if 0
// Don't work :(
/**
* @brief read_regN8 - read up to I2C_SMBUS_BLOCK_MAX bytes from 8-bit addressed register
* @param regaddr - address
* @param data - data to read
* @param N - amount of bytes
* @return state
*/
static int read_regN8(uint8_t regaddr, uint8_t *data, uint16_t N){
if(I2Cfd < 1 || N > I2C_SMBUS_BLOCK_MAX || N == 0 || !data) return FALSE;
struct i2c_smbus_ioctl_data args = {0};
union i2c_smbus_data sd = {0};
sd.block[0] = N;
DBG("block: %d, %d, %d", sd.block[0], sd.block[1], sd.block[2]);
DBG("Try to get %d bytes from 0x%02x", N, regaddr);
args.read_write = I2C_SMBUS_READ;
args.command = regaddr;
args.size = I2C_SMBUS_BLOCK_DATA;
args.data = &sd;
if(ioctl(I2Cfd, I2C_SMBUS, &args) < 0){
WARN("read_regN8, ioctl()");
return FALSE;
}
DBG("block: %d, %d, %d", sd.block[0], sd.block[1], sd.block[2]);
memcpy(data, sd.block+1, N);
return TRUE;
}
#endif
/**
* @brief read_data16 - read data from 16-bit addressed register
* @param regaddr - address
* @param N - amount of bytes
* @param N - amount of BYTES!!!
* @param array - data read
* @return state
*/
@@ -248,24 +218,16 @@ int i2c_read_data16(uint16_t regaddr, uint16_t N, uint8_t *array){
*/
int i2c_read_data8(uint8_t regaddr, uint16_t N, uint8_t *array){
if(I2Cfd < 1 || N < 1 || N+regaddr > 0xff || !array) return FALSE;
#if 0
uint16_t rest = N;
do{
uint8_t l = (rest > I2C_SMBUS_BLOCK_MAX) ? I2C_SMBUS_BLOCK_MAX : (uint8_t)rest;
if(!read_regN8(regaddr, array, l)){
DBG("can't read");
return FALSE;
}
rest -= l;
regaddr += l;
array += l;
}while(rest);
#endif
for(uint16_t i = 0; i < N; ++i){
if(!i2c_read_reg8((uint8_t)(regaddr+i), array++)){
DBG("can't read @%dth byte", i);
return FALSE;
}
struct i2c_msg m[2];
struct i2c_rdwr_ioctl_data x = {.msgs = m, .nmsgs = 2};
m[0].addr = lastaddr; m[1].addr = lastaddr;
m[0].flags = 0; // write register address
m[1].flags = I2C_M_RD; // read contents
m[0].len = 1; m[1].len = N;
m[0].buf = &regaddr; m[1].buf = array;
if(ioctl(I2Cfd, I2C_RDWR, &x) < 0){
WARN("i2c_read_data8, ioctl()");
return FALSE;
}
return TRUE;
}

View File

@@ -20,7 +20,7 @@
#include <stdint.h>
// timeout of i2c waiting
#define SENS_TIMEOUT (2.)
#define I2C_TIMEOUT (.5)
typedef enum{
SENS_NOTINIT, // wasn't inited
@@ -30,10 +30,14 @@ typedef enum{
SENS_RDY, // data ready - can get it
} sensor_status_t;
typedef struct{
uint8_t T : 1; // can temperature (degC)
uint8_t H : 1; // can humidity (percent)
uint8_t P : 1; // can pressure (hPa)
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{
@@ -42,20 +46,23 @@ typedef struct{
double P;
} sensor_data_t;
typedef struct{
const char *name; // name
uint32_t private; // private information (e.g. for almost similar sensors with some slight differences)
uint8_t (*address)(uint8_t new);// set/get sensor's address (get - if `new`==0)
int (*init)(); // init device - only @ start after POR
int (*start)(); // start measuring
sensor_status_t (*process)(); // main polling process
sensor_props_t (*properties)(); // get properties
int (*get_data)(sensor_data_t*);// read data
} sensor_t;
//struct sensor_struct;
typedef struct sensor_struct sensor_t;
int sensors_open(const char *dev);
void sensors_close();
int sensor_init(const sensor_t *s, uint8_t address);
void sensors_list();
const sensor_t* sensor_find(const char *name);
int sensor_start(const sensor_t *s);
char *sensors_list();
sensor_t* sensor_new(const char *name);
void sensor_delete(sensor_t **s);
sensor_props_t sensor_properties(sensor_t *s);
int sensor_init(sensor_t *s, uint8_t address);
int sensor_heater(sensor_t *s, int on);
int sensor_start(sensor_t *s);
sensor_status_t sensor_process(sensor_t *s);
int sensor_getdata(sensor_t *s, sensor_data_t *d);
// I2C functions for client usage
int sensor_writeI2C(uint8_t addr, uint8_t *data, int len);
int sensor_readI2C(uint8_t addr, uint8_t *data, int len);
int sensor_readI2Cregs(uint8_t addr, uint8_t regaddr, uint16_t N, uint8_t *data);
int sensor_writeI2Creg(uint8_t addr, uint8_t regaddr, uint8_t data);

View File

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

View File

@@ -21,11 +21,15 @@
#include "aht.h"
#include "BMP180.h"
#include "BMP280.h"
#include "BMP580.h"
#include "i2c.h"
#include "sensor.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, &BMP180, NULL};
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){
@@ -36,9 +40,10 @@ void sensors_close(){
}
// init sensor with optional new address
int sensor_init(const sensor_t *s, uint8_t address){
int sensor_init(sensor_t *s, uint8_t address){
if(!s) return FALSE;
address = s->address(address);
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;
@@ -49,37 +54,133 @@ int sensor_init(const sensor_t *s, uint8_t address){
}
double t0 = sl_dtime();
int result = FALSE;
while(sl_dtime() - t0 < SENS_TIMEOUT && !(result = s->init())) usleep(10000);
while(sl_dtime() - t0 < I2C_TIMEOUT && !(result = s->init(s))) usleep(10000);
DBG("INIT: %d", result);
return result;
}
// find supported sensor by name
const sensor_t* sensor_find(const char *name){
// 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)) return *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;
}
// list all supported sensors
void sensors_list(){
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;
green("Supported sensors:\n");
int L = 0, rest = 0, idx = 0;
char *list = NULL;
#define PORTIONSZ 256
while(*p){
printf("%s", (*p)->name);
if(*(++p)) printf(", ");
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;
}
printf("\n");
#undef PORTIONSZ
return list;
}
// wrapper with timeout
int sensor_start(const sensor_t *s){
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 < SENS_TIMEOUT && !(result = s->start())) usleep(10000);
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);
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE QtCreatorProject>
<!-- Written by QtCreator 17.0.2, 2025-10-11T01:16:49. -->
<!-- Written by QtCreator 18.0.0, 2025-12-08T20:56:31. -->
<qtcreator>
<data>
<variable>EnvironmentId</variable>
@@ -89,6 +89,7 @@
<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>
@@ -165,6 +166,7 @@
<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>
@@ -197,6 +199,7 @@
<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>
@@ -207,10 +210,6 @@
<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>

View File

@@ -1,9 +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

Before

Width:  |  Height:  |  Size: 67 B

After

Width:  |  Height:  |  Size: 239 B

View File

@@ -0,0 +1,36 @@
/*
* Copyright 2025 Edward V. Emelianov <edward.emelianoff@gmail.com>.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <stdint.h>
#include "i2csensorsPTH.h"
// unfortunately, we have no "self" pointer in C, so we should add this struct calling to each function for further purposes
struct sensor_struct{
const char *name; // name
uint8_t address; // sensor's address
uint32_t private; // private information (e.g. for almost similar sensors with some slight differences)
void *privdata; // some private data for calibration etc
sensor_status_t status; // status of sensor
sensor_data_t data; // measured data
int (*init)(struct sensor_struct*); // init device - @ start after POR or in case of errors
int (*start)(struct sensor_struct*); // start measuring
int (*heater)(struct sensor_struct *, int); // turn heater on/off (1/0)
sensor_status_t (*process)(struct sensor_struct*); // main polling process
sensor_props_t (*properties)(struct sensor_struct*); // get properties
};

197
deepseek/transformator Normal file
View File

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

View File

@@ -5,3 +5,16 @@ another files used for test: use script ./run for it
Compile: gcc -lusefull_macro file.c -o file
gcc -lusefull_macro test.c hash.c -o test
file helpcmds.in should be included into proto.c as help list:
const char *helpstring =
"https://github.com/eddyem/stm32samples/tree/master/F3:F303/Multistepper build#" BUILD_NUMBER " @ " BUILD_DATE "\n"
"Format: cmd [N] - getter, cmd [N] = val - setter; N - optional index\n"
"* - command not supported yet, G - getter, S - setter\n\n"
#include "hashgen/helpcmds.in"
;
helpcmds.in format: each string should be in quotes with '\n' @ its end. First word - command, next text - help.

View File

@@ -20,6 +20,8 @@
#include <usefull_macros.h>
#include <ctype.h>
// maximal string length (n*4) without terminating '\0' - for buffers
#define MAXCMDLEN (128)
#define ALLOCSZ (5000)
typedef struct{
@@ -31,35 +33,44 @@ typedef struct{
static glob_pars G = {.headerfile = "hash.h", .sourcefile = "hash.c"};
static int help = 0;
static myoption cmdlnopts[] = {
static sl_option_t cmdlnopts[] = {
{"help", NO_ARGS, NULL, 'h', arg_int, APTR(&help), "show this help"},
{"dict", NEED_ARG, NULL, 'd', arg_string, APTR(&G.dict), "dictionary file"},
{"header", NEED_ARG, NULL, 'H', arg_string, APTR(&G.headerfile),"output header filename"},
{"source", NEED_ARG, NULL, 'S', arg_string, APTR(&G.sourcefile),"output source filename"},
{"genfunc", NO_ARGS, NULL, 'F', arg_int, APTR(&G.genfunc), "generate function bodys"},
{"genfunc", NO_ARGS, NULL, 'F', arg_int, APTR(&G.genfunc), "generate function bodies"},
end_option
};
static void parse_args(int argc, char **argv){
parseargs(&argc, &argv, cmdlnopts);
if(help) showhelp(-1, cmdlnopts);
sl_parseargs(&argc, &argv, cmdlnopts);
if(help) sl_showhelp(-1, cmdlnopts);
if(argc > 0){
red("Unused arguments:\n");
for(int i = 0; i < argc; ++i)
printf("%s ", argv[i]);
printf("\n");
showhelp(-1, cmdlnopts);
sl_showhelp(-1, cmdlnopts);
}
}
#define HASHFNO (3)
#define HASHFNO (4)
static uint32_t my(const char *str){
char buf[MAXCMDLEN+1];
uint32_t hash = 0, *c = (uint32_t*)buf;
bzero(buf, sizeof(buf));
int len = strlen(str);
memcpy(buf, str, len);
len = (len + 3) / 4;
for(int i = 0; i < len; ++i)
hash = (hash << 2) + c[i];
return hash;
}
// djb2 & sdbm: http://www.cse.yorku.ca/~oz/hash.html
static uint32_t djb2(const char *str){
uint32_t hash = 5381;
uint32_t c;
while((c = (uint32_t)*str++))
hash = ((hash << 7) + hash) + c;
//hash = hash * 31 + c;
//hash = hash * 33 + c;
return hash;
}
static uint32_t sdbm(const char *str){
@@ -84,6 +95,17 @@ static uint32_t jenkins(const char *str){
}
static const char *hashsources[HASHFNO] = {
"static uint32_t hashf(const char *str){\n\
char buf[CMD_MAXLEN+1];\n\
uint32_t hash = 0, *c = (uint32_t*)buf;\n\
bzero(buf, sizeof(buf));\n\
int len = strlen(str);\n\
memcpy(buf, str, len);\n\
len = (len + 3) / 4;\n\
for(int i = 0; i < len; ++i)\n\
hash = (hash << 2) + c[i];\n\
return hash;\n\
}\n",
"static uint32_t hashf(const char *str){\n\
uint32_t hash = 5381;\n\
uint32_t c;\n\
@@ -112,13 +134,14 @@ static const char *hashsources[HASHFNO] = {
}\n"
};
static uint32_t (*hash[HASHFNO])(const char *str) = {djb2, sdbm, jenkins};
static const char *hashnames[HASHFNO] = {"DJB2", "SDBM", "Jenkins"};
static uint32_t (*hash[HASHFNO])(const char *str) = {my, djb2, sdbm, jenkins};
static const char *hashnames[HASHFNO] = {"My", "DJB2", "SDBM", "Jenkins"};
typedef struct{
char str[32]; // string command
char fname[32]; // function namee
uint32_t hash; // command hash
char str[MAXCMDLEN+1]; // string command define (capital letters)
char fname[MAXCMDLEN+1]; // function name
char macroname[MAXCMDLEN+1];// macro name
uint32_t hash; // command hash
} strhash;
static int sorthashesH(const void *a, const void *b){ // sort by hash
@@ -143,11 +166,11 @@ static FILE *openoutp(const char *name){
}
static char *macroname(const char *cmd){
static char macro[32];
static char macro[MAXCMDLEN+1];
int i = 0;
while(i < 31 && *cmd){
while(i < MAXCMDLEN && *cmd){
char c = *cmd++;
if(!isalnum(c)) c = '_';
if(!isalpha(c)) c = '_';
if(islower(c)) c = toupper(c);
macro[i++] = c;
}
@@ -156,9 +179,9 @@ static char *macroname(const char *cmd){
}
static char *fnname(const char *cmd){
static char fn[32];
static char fn[MAXCMDLEN+1];
int i = 0;
while(i < 31 && *cmd){
while(i < MAXCMDLEN && *cmd){
char c = *cmd++;
if(!isalpha(c)) c = '_';
if(isupper(c)) c = tolower(c);
@@ -170,17 +193,14 @@ static char *fnname(const char *cmd){
static const char *fhdr =
"int parsecmd(const char *str){\n\
char cmd[CMD_MAXLEN + 1];\n\
if(!str || !*str) return RET_CMDNOTFOUND;\n\
int i = 0;\n\
while(*str > '@' && i < CMD_MAXLEN){ cmd[i++] = *str++; }\n\
cmd[i] = 0;\n\
if(*str){\n\
while(*str <= ' ') ++str;\n\
}\n\
char *args = (char*) str;\n\
uint32_t h = hashf(cmd);\n\
switch(h){\n\n"
if(!str || !*str) return RET_CMDNOTFOUND;\n\
bzero(lastcmd, sizeof(lastcmd));\n\
int i = 0;\n\
while(*str > '@' && i < CMD_MAXLEN){ lastcmd[i++] = *str++; }\n\
while(*str && *str <= ' ') ++str;\n\
char *args = (char*) str;\n\
uint32_t h = hashf(lastcmd);\n\
switch(h){\n"
;
static const char *ffooter =
" default: break;\n\
@@ -189,43 +209,58 @@ static const char *ffooter =
}\n\n"
;
static const char *fns =
"int fn_%s(_U_ uint32_t hash, _U_ char *args) WAL; // \"%s\" (%u)\n\n"
"int fn_%s(uint32_t _U_ hash, char _U_ *args) WAL; // \"%s\" (%u)\n"
;
static const char *headercontent = "#ifndef _U_\n\
static const char *fnsh =
"int fn_%s(uint32_t, char*); // \"%s\" (%u)\n"
;
static const char *headercontent =
"// Generated by HASHGEN (https://github.com/eddyem/eddys_snippets/tree/master/stringHash4MCU_)\n\
// Licensed by GPLv3\n\
#pragma once\n\
#ifndef _U_\n\
#define _U_ __attribute__((__unused__))\n\
#endif\n\n\
#define CMD_MAXLEN (32)\n\n\
#define CMD_MAXLEN (%d)\n\n\
enum{\n\
RET_HELP = -3,\n\
RET_CMDNOTFOUND = -2,\n\
RET_WRONGCMD = -1,\n\
RET_BAD = 0,\n\
RET_GOOD = 1\n\
RET_GOOD = 0,\n\
RET_BAD = 1\n\
};\n\n\
int parsecmd(const char *cmdwargs);\n\n";
int parsecmd(const char *cmdwargs);\n\n\
extern char lastcmd[];\n\n";
static const char *sw =
" case CMD_%s:\n\
return fn_%s(h, args);\n\
break;\n";
static const char *srchdr =
"#include <stdint.h>\n\
"// Generated by HASHGEN (https://github.com/eddyem/eddys_snippets/tree/master/stringHash4MCU_)\n\
// Licensed by GPLv3\n\
#include <stdint.h>\n\
#include <stddef.h>\n\
#include <string.h>\n\
#include \"%s\"\n\n\
#ifndef WAL\n\
#define WAL __attribute__ ((weak, alias (\"__f1\")))\n\
#endif\n\nstatic int __f1(_U_ uint32_t h, _U_ char *a){return 1;}\n\n"
#endif\n\nstatic int __f1(uint32_t _U_ h, char _U_ *a){return RET_BAD;}\n\n\
char lastcmd[CMD_MAXLEN + 1];\n\n"
;
static void build(strhash *H, int hno, int hlen){
green("Generate files for hash function '%s'\n", hashnames[hno]);
int lmax = 1;
for(int i = 0; i < hlen; ++i){
strcpy(H[i].macroname, macroname(H[i].str));
int l = strlen(H[i].str);
if(l > lmax){
lmax = l;
}
}
lmax = (lmax + 3)/4;
lmax *= 4;
lmax *= 4; // round by 4 bytes
// resort H by strings
qsort(H, hlen, sizeof(strhash), sorthashesS);
FILE *source = openoutp(G.sourcefile), *header = openoutp(G.headerfile);
@@ -235,26 +270,36 @@ static void build(strhash *H, int hno, int hlen){
fprintf(source, fns, H[i].fname, H[i].str, H[i].hash);
}
}
fprintf(header, "%s", headercontent);
fprintf(source, "%s\n", hashsources[hno]);
fprintf(header, headercontent, lmax);
fprintf(source, "\n%s\n", hashsources[hno]);
fprintf(source, "%s", fhdr);
for(int i = 0; i < hlen; ++i){
char *m = macroname(H[i].str);
fprintf(source, sw, m, H[i].fname);
fprintf(header, "#define CMD_%-*s (%u)\n", lmax, m, H[i].hash);
fprintf(source, sw, H[i].macroname, H[i].fname);
fprintf(header, "#define CMD_%-*s (%u)\n", lmax, H[i].macroname, H[i].hash);
}
fprintf(source, "%s", ffooter);
fclose(source);
fprintf(header, "\n");
for(int i = 0; i < hlen; ++i){
fprintf(header, "#define STR_%-*s \"%s\"\n", lmax, H[i].macroname, H[i].str);
}
if(G.genfunc){
fprintf(header, "\n");
for(int i = 0; i < hlen; ++i){
fprintf(header, fnsh, H[i].fname, H[i].str, H[i].hash);
}
}
fclose(header);
}
int main(int argc, char **argv){
initial_setup();
sl_init();
parse_args(argc, argv);
if(!G.dict) ERRX("point dictionary file");
if(!G.headerfile) ERRX("point header source file");
if(!G.sourcefile) ERRX("point c source file");
mmapbuf *b = My_mmap(G.dict);
sl_mmapbuf_t *b = sl_mmap(G.dict);
if(!b) ERRX("Can't open %s", G.dict);
char *word = b->data;
strhash *H = MALLOC(strhash, ALLOCSZ);
@@ -267,19 +312,26 @@ int main(int argc, char **argv){
}
while(*word && *word < 33) ++word;
if(!*word) break;
char *nxt = strchr(word, '\n');
char *eol = strchr(word, '\n');
char *sp = strchr(word, ' ');
char *nxt = eol;
if(sp){ // got space after command - truncate
if(eol){
if(eol > sp) nxt = sp; // space before strend
}else nxt = sp; // no strend, but have space
};
if(nxt){
int len = nxt - word;
if(len > 31) len = 31;
if(len > MAXCMDLEN) len = MAXCMDLEN;
strncpy(H[idx].str, word, len);
H[idx].str[len] = 0;
}else{
snprintf(H[idx].str, 31, "%s", word);
snprintf(H[idx].str, MAXCMDLEN, "%s", word);
}
snprintf(H[idx].fname, 31, "%s", fnname(H[idx].str));
snprintf(H[idx].fname, MAXCMDLEN, "%s", fnname(H[idx].str));
++idx;
if(!nxt) break;
word = nxt + 1;
if(!eol) break;
word = eol + 1;
}
// test fname matches
qsort(H, idx, sizeof(strhash), sorthashesF);
@@ -300,7 +352,11 @@ int main(int argc, char **argv){
strhash *p = H;
int nmatches = 0;
for(int i = 0; i < imax1; ++i, ++p){ // test hash matches
if(p->hash == p[1].hash) ++nmatches;
if(p->hash == p[1].hash){
red("Found matched pair:");
printf(" '%s' and '%s'\n", p->str, p[1].str);
++nmatches;
}
}
if(nmatches == 0){
build(H, hno, idx);
@@ -310,6 +366,6 @@ int main(int argc, char **argv){
}
if(hno == HASHFNO) WARNX("Can't find proper hash function");
FREE(H);
My_munmap(b);
sl_munmap(b);
return 0;
}

View File

@@ -1,117 +0,0 @@
/*
* 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 <string.h>
#include <usefull_macros.h>
#define ALLOCSZ (5000)
#define DJB2
#if defined DJB2
// djb2 http://www.cse.yorku.ca/~oz/hash.html
static uint32_t hash(const char *str){
uint32_t hash = 5381;
uint32_t c;
while((c = (uint32_t)*str++))
hash = ((hash << 5) + hash) + c;
//hash = hash * 31 + c;
//hash = hash * 33 + c;
return hash;
}
#elif defined SDBM
static uint32_t hash(const char *str){ // sdbm
uint32_t hash = 5381;
uint32_t c;
while((c = (uint32_t)*str++))
hash = c + (hash << 6) + (hash << 16) - hash;
return hash;
}
#elif defined JENKINS
uint32_t hash(const char *str){
uint32_t hash = 0, c;
while((c = (uint32_t)*str++)){
hash += c;
hash += (hash << 10);
hash ^= (hash >> 6);
}
hash += (hash << 3);
hash ^= (hash >> 11);
hash += (hash << 15);
return hash;
}
#endif
typedef struct{
char str[32];
uint32_t hash;
} strhash;
static int sorthashes(const void *a, const void *b){
register uint32_t h1 = ((strhash*)a)->hash, h2 = ((strhash*)b)->hash;
if(h2 > h1) return h2 - h1;
else return -((h1 - h2));
}
int main(int argc, char **argv){
//char buf[32];
initial_setup();
if(argc != 2) ERRX("Usage: %s dictionary_file", argv[0]);
mmapbuf *b = My_mmap(argv[1]);
if(!b) ERRX("Can't open %s", argv[1]);
char *word = b->data;
strhash *H = MALLOC(strhash, ALLOCSZ);
int l = ALLOCSZ, idx = 0;
while(*word){
if(idx >= l){
l += ALLOCSZ;
H = realloc(H, sizeof(strhash) * l);
if(!H) ERR("realloc()");
}
char *nxt = strchr(word, '\n');
if(nxt){
int len = nxt - word;
if(len > 31) len = 31;
strncpy(H[idx].str, word, len);
H[idx].str[len] = 0;
//strncpy(buf, word, len);
//buf[len] = 0;
}else{
//snprintf(buf, 31, "%s", word);
snprintf(H[idx].str, 31, "%s", word);
}
H[idx].hash = hash(H[idx].str);
//printf("word: %s\n", buf);
//printf("%u\t%s\n", hash(buf), buf);
//printf("%u\t%s\n", H[idx].hash, H[idx].str);
++idx;
if(!nxt) break;
word = nxt + 1;
}
qsort(H, idx, sizeof(strhash), sorthashes);
--idx;
strhash *p = H;
for(int i = 0; i < idx; ++i, ++p){
if(p->hash == p[1].hash){
printf("Words '%s' and '%s' have same hashes: %u\n", p->str, p[1].str, p->hash);
}
}
FREE(H);
My_munmap(b);
return 0;
}

View File

@@ -0,0 +1,61 @@
"absposN - GS absolute position (in steps, setter just changes current value)\n"
"accelN - GS accel/decel (steps/s^2)\n"
"adcN - G ADC value (N=0..3)\n"
"button[N] - G all or given (N=0..6) buttons' state\n"
"canerrcodes - G print last CAN errcodes\n"
"canfilter - GS can filters, format: bank# FIFO# mode(M/I) num0 [num1 [num2 [num3]]]\n"
"canflood - send/clear flood message: ID byte0 ... byteN\n"
"canfloodT - GS flood period (>=0ms)\n"
"canid - GS CAN ID of device\n"
"canignore - GS ignore list (max 10 IDs), negative to delete\n"
"canincrflood - send incremental flood message (ID is last for 'flood', stop by 'flood')\n"
"canpause - pause IN packets displaying\n"
"canreinit - reinit CAN\n"
"canresume - resume IN packets displaying\n"
"cansend - send data over CAN: send ID byte0 .. byteN (N<8)\n"
"canspeed - GS CAN speed (reinit if setter)\n"
"canstat - G CAN status\n"
"diagn[N] - G DIAG state of motor N (or all)\n"
"drvtypeN - GS driver type (0 - only step/dir, 1 - UART, 2 - SPI, 3 - reserved)\n"
"dumperr - dump error codes\n"
"dumpcmd - dump command codes\n"
"dumpconf - dump current configuration\n"
"dumpmotN - dump Nth motor configuration\n"
"dumpmotflags - dump motor flags' bits\n"
"dumpstates - dump motors' state codes\n"
"emstop[N] - emergency stop motor N or all\n"
"eraseflash [=N] - erase flash data storage (full or only N'th page of it)\n"
"esw[N] - G end-switches state\n"
"eswreactN - GS end-switches reaction (0 - ignore, 1 - ignore ESW1 and stop@0 only when moving negative, 2 - stop@any, 3 - stop@dir)\n"
"gotoN - GS move motor to given absolute position\n"
"gotozN - find zero position & refresh counters\n"
"gpioconfN* - GS GPIO configuration (0 - PUin, 1 - PPout, 2 - ODout), N=0..2\n"
"gpioN - GS GPIO values, N=0..2\n"
"help - print this help\n"
"maxspeedN - GS max speed (steps per sec)\n"
"maxstepsN - GS max steps (from zero ESW)\n"
"mcut - G MCU T\n"
"mcuvdd - G MCU Vdd\n"
"microstepsN - GS microsteps settings (2^0..2^9)\n"
"minspeedN - min speed (steps per sec)\n"
"motcurrentN - GS motor current (1..32 for 1/32..32/32 of max current)\n"
"motflagsN - motorN flags\n"
"motmul* - GS external multiplexer status (<0 - disable, 0..7 - enable and set address)\n"
"motno - GS motor number for next `pdn` commands\n"
"motreinit - re-init motors after configuration changed\n"
"pdnN - GS read/write TMC2209 registers over uart @ motor0\n"
"ping - echo given command back\n"
"relposN - GS relative move (get remaining)\n"
"relslowN - GS like 'relpos' but with slowest speed\n"
"reset - software reset\n"
"saveconf - save current configuration\n"
"screen* - GS screen enable (1) or disable (0)\n"
"speedlimit - G limiting speed for current microsteps setting\n"
"stateN - G motor state (0-relax, 1-accel, 2-move, 3-mvslow, 4-decel, 5-stall, 6-err)\n"
"stopN - stop motor with deceleration\n"
"time - G time from start (ms)\n"
"tmcbus* - GS TMC control bus (0 - USART, 1 - SPI)\n"
"udata* - GS data by usart in slave mode (text strings, '\\n'-terminated)\n"
"usartstatus* - GS status of USART1 (0 - off, 1 - master, 2 - slave)\n"
"vdrive - G approx voltage on Vdrive\n"
"vfive - G approx voltage on 5V bus\n"

4
stringHash4MCU_/mktestdic Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/bash
awk '{print $1}' helpcmds.in |sed -e 's/"//' -e 's/\*//' -e 's/\[.*//' -e 's/N//' > testdic
./hashgen -d testdic -H hdr.h -S hdr.c -F

View File

@@ -3,8 +3,12 @@
gcc hashgen.c -o hashgen -lusefull_macros
./hashgen -d testdic -H hdr.h -S hdr.c -F
gcc hdr.c test.c strfunc.c -o test
./test "wrongarg"
./test "Wrong Arg"
./test "time 54 = some"
./test "voltage12 more"
./test "esw45 = some text"
./test "goto 55 = "
./test "stop 3256"
./test "mcut"

View File

@@ -7,12 +7,12 @@
static int noargs(uint32_t hash){
switch(hash){
case CMD_REBOOT: printf("REBOOT\n"); break;
case CMD_RESET: printf("REBOOT\n"); break;
case CMD_TIME: printf("TIME!\n"); break;
case CMD_TEMP: printf("Temp\n"); break;
default: printf("Unknown hash 0x%x\n", hash); return 0;
case CMD_MCUT: printf("Temp\n"); break;
default: printf("Unknown hash 0x%x\n", hash); return RET_BAD;
}
return 1;
return RET_GOOD;
}
static int withparno(uint32_t hash, char *args){
uint32_t N;
@@ -30,7 +30,7 @@ static int withparno(uint32_t hash, char *args){
switch(hash){
case CMD_ESW: fname = "ESW"; break;
case CMD_GOTO: fname = "GOTO"; break;
case CMD_POS: fname = "POS"; break;
case CMD_ABSPOS: fname = "ABSPOS"; break;
case CMD_STOP: fname = "STOP"; break;
default: fname = "unknown";
}
@@ -42,12 +42,12 @@ static int withparno(uint32_t hash, char *args){
// these functions should be global to substitute weak aliases
int fn_esw(uint32_t hash, char *args){return withparno(hash, args);}
int fn_goto(uint32_t hash, char *args){return withparno(hash, args);}
int fn_pos(uint32_t hash, char *args){return withparno(hash, args);}
int fn_abspos(uint32_t hash, char *args){return withparno(hash, args);}
int fn_stop(uint32_t hash, char *args){return withparno(hash, args);}
int fn_voltage(uint32_t hash, char *args){return withparno(hash, args);}
int fn_reboot(uint32_t hash, _U_ char *args){return noargs(hash);}
int fn_reset(uint32_t hash, _U_ char *args){return noargs(hash);}
int fn_time(uint32_t hash, _U_ char *args){return noargs(hash);}
int fn_temp(uint32_t hash, _U_ char *args){return noargs(hash);}
int fn_mcut(uint32_t hash, _U_ char *args){return noargs(hash);}
int main(int argc, char **argv){
if(argc != 2) return 1;

View File

@@ -1,8 +1,8 @@
pos
temp
voltage
time
reboot
stop
goto
abspos
esw
goto
stop
voltage
reset
time
mcut