Power monitor for embedded systems (Linux)

It was necessary to measure the current consumption of a single device + store the values ​​obtained in the database table (PostgreSQL).

The first 5 minutes of googling showed - almost all semiconductor manufacturers have beautiful solutions on a single chip. Difficult schemes at the OS, were far in the past.

The choice fell on the INA260
Voltage up to 36v, easy to install chassis, a compromise cost.
But the most decisive argument, he was already lying in the drawer: D Among other samples.
It is time to use it.

The included circuit is no different from the one given in the datasheet.
Its not high complexity, it allows you to collect everything on your knee

image

Modern Current / Power Monitor current will measure, voltage will display. If necessary, even the Alert will report when the threshold is exceeded!

The assembly requires the minimum number of components (5 R + 2 C).
You can search for ready-made modules on Asian sites, for example, on INA219.
Then a simple refinement of the conversion function is probably needed:

image Wrote poll class:

I2cPoll
//     static logger& loggerInstance = logger::Instance(); I2cPoll::I2cPoll() { //    db = new DbConnection(); if(db->isConnecting()) { loggerInstance.appendToLog("I2cPoll: db-connected-OK\r\n"); } else { loggerInstance.appendToLog("I2cPoll: db opening -ERROR\r\n"); } //       //      this->currentDeviceType = startDeviceType; } //   void I2cPoll::pollExect() { uint8_t *p_data = NULL; uint16_t word = 0; bool res = false; char cmd_command[128] = {0}; S_insertData device_data; int device_addr = 0; //   switch(currentDeviceType) { case dev_i2c_ina260: { //    id  device_addr = db->getDeviceAddrFromName( i2c_Dev_typeName.ina260.name_text.c_str() ); //    if(device_addr != 0) { //  3  - ,    for(int i=0; i<3; i++) { switch(i) { // curent case 0: sprintf(cmd_command, "i2cget -y 0 0x%X 0x01 w\r\n", device_addr); //    res = readWord(cmd_command, &word); if(res) { //   p_data = (uint8_t*)&word; float raw_data; fprintf(stdout, "Raw u16 %x\r\n", word); //      raw_data = (0xFF & p_data[1]) | ((0xFF & (p_data[0])) << 8); device_data.parameter.power_monitor.currnetFlowing = raw_data * 1.25 / 1000; fprintf(stdout, "Current %4.2f\r\n", device_data.parameter.power_monitor.currnetFlowing ); } break; // voltage case 1: sprintf(cmd_command, "i2cget -y 0 0x%X 0x02 w", device_addr); //    res = readWord(cmd_command, &word); if(res) { //   p_data = (uint8_t*)&word; float raw_data; fprintf(stdout, "Raw u16 %x\r\n", word); //      raw_data = (0xFF & p_data[1]) | ((0xFF & (p_data[0])) << 8); device_data.parameter.power_monitor.voltage = raw_data * 1.25 / 1000; fprintf(stdout, "Volage %4.2f\r\n", device_data.parameter.power_monitor.voltage ); } break; case 2: // power sprintf(cmd_command, "i2cget -y 0 0x%X 0x03 w\r\n", device_addr); //    res = readWord(cmd_command, &word); if(res) { //   p_data = (uint8_t*)&word; float raw_data; fprintf(stdout, "Raw u16 %x\r\n", word); //      raw_data = (0xFF & p_data[1]) | ((0xFF & (p_data[0])) << 8); device_data.parameter.power_monitor.currentPower = raw_data * 1.25; fprintf(stdout, "Power %4.2f\r\n", device_data.parameter.power_monitor.currentPower ); } break; } } //   ,     if(res) { db->insertData(device_data); } } } break; default : //  -  ,     currentDeviceType = startDeviceType; break; } //   ,     if(currentDeviceType >= (E_I2c_device)endTypeDeviceType) { currentDeviceType = startDeviceType; fprintf(stdout, "I2c parce -endDev\r\n"); } else { //    currentDeviceType++; fprintf(stdout, "I2c parce -nextDev\r\n"); } } //     i2c //     bool I2cPoll::readWord(char *cmd_command, uint16_t *p_word) { int res = false; int word = 0; FILE *stream; //    stream = popen(cmd_command, "r"); fprintf(stdout, "cmd_command - %s\r\n", cmd_command); std::string data; if (stream) { char reply_buff[128] = {0}; while (!feof(stream)) if(fgets(reply_buff, sizeof(reply_buff), stream) != NULL) { data.append(reply_buff); } pclose(stream); } //   fprintf(stdout, "shell result :\r\n%s\r\n", data.c_str()); if(data.length() != 0) { char *p_start = strstr((char*)data.c_str(), "0x"); if(p_start != NULL) { res = sscanf(p_start, "%x", &word); if(res) { *p_word = (uint16_t)word; } fprintf(stdout, "getWord %x\r\n", *p_word); } } return res; } I2cPoll::~I2cPoll() { // TODO Auto-generated destructor stub } 


Running the file manually is not interesting, it can lead to duplication of processes.

Correct to use a demon. After reading the @shevmax post, the meaning of writing your own fork implementation was dropped. Limited to a little refactoring:

Create a stream and put i2cPoller into it (part of main.c)
 //      int initWorkThread() { loggerInstance.appendToLog("[DAEMON] Init...\r\n"); i2c_poller = new I2cPoll(); std::thread thr(thread_proc); threads.emplace_back(std::move(thr)); loggerInstance.appendToLog("[DAEMON] Init -Ok\r\n"); return 0; } void thread_proc(void) { for(;;) { { std::lock_guard<std::mutex> lock(mtx_thread_poll); i2c_poller->pollExect(); } std::this_thread::sleep_for(std::chrono::milliseconds(100)); } } 


After adding to the project fork, you need an init.d script

Save it as /etc/init.d/i2c_poller.sh

It remains to write a startup script or take ready and correct
 #!/bin/sh dir="/home/khomin/Project/i2c_poller/bin/" cmd="/home/khomin/Project/i2c_poller/bin/i2c_poller" user="root" name="i2c_poller" pid_file="/var/run/$name.pid" log_dir="/var/log/i2c_poller/" stdout_log="$log_dir/$name.log" stderr_log="$log_dir/$name.err" get_pid() { cat "$pid_file" } is_running() { [ -f "$pid_file" ] && ps -p `get_pid` > /dev/null 2>&1 } case "$1" in start) if is_running; then echo "Already started" else echo "Starting $name" rm -rf $log_dir mkdir $log_dir cd "$dir" if [ -z "$user" ]; then sudo $cmd >> "$stdout_log" 2>> "$stderr_log" & else sudo -u "$user" $cmd >> "$stdout_log" 2>> "$stderr_log" & fi echo $! > "$pid_file" if ! is_running; then echo "Unable to start, see $stdout_log and $stderr_log" exit 1 fi fi ;; stop) if is_running; then echo -n "Stopping $name.." kill `get_pid` for i in 1 2 3 4 5 6 7 8 9 10 # for i in `seq 10` do if ! is_running; then break fi echo -n "." sleep 1 done echo if is_running; then echo "Not stopped; may still be shutting down or shutdown may have failed" exit 1 else echo "Stopped" if [ -f "$pid_file" ]; then rm "$pid_file" fi fi else echo "Not running" fi ;; restart) $0 stop if is_running; then echo "Unable to stop, will not attempt to start" exit 1 fi $0 start ;; status) if is_running; then echo "Running" else echo "Stopped" exit 1 fi ;; *) echo "Usage: $0 {start|stop|restart|status}" exit 1 ;; esac exit 0 


Check what runs
After running /etc/init.d/i2c_poller.sh start



Immediately see the data in the database, then the poll works



Git project

TODO: using i2cget is not the most rational solution. Depending on the type of single-board device, it makes sense to enable the i2c driver when building the kernel.

Source: https://habr.com/ru/post/413835/


All Articles