...

понедельник, 11 июня 2018 г.

Power monitor для встраиваемых систем (Linux)

Потребовалось измерить ток потребления одного устройства
+ хранить полученные значения в таблице БД (PostgreSQL)

Первые 5 минут гугления показали — почти все производители полупроводников, имеют красивые решения, на одном кристалле
Сложные схемы на ОУ, остались далеко в прошлом

Выбор пал на INA260
Напряжение до 36v, простой для монтажа корпус, компромиссная стоимость
Но самый решающий аргумент, он уже валялся в тумбе :D Среди прочих образцов
Настало время его задействовать

Включаемая схема ничем не отличается от приведенной в даташите
Ее не высокая сложность, позволяет все собрать на коленке
image
Современный Current/Power Monitor ток измерит, напряжение отобразит
Если потребуется, даже Alert-ом сообщит о превышении порогового значения!

Для сборки потребуется минимальное количество компонентов (5 R + 2 C)
Можно поискать готовые модули на азиатских площадках, например на INA219
Тогда вероятно потребуется несложная доработка функции пересчета
image
Написал класс опроса

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
}



Запускать файл вручную не интересно, это может привести к дублированию процессов
Правильнее использовать демон
После прочтения поста @shevmax, смысл написания собственной реализации fork отпал
Ограничился небольшим рефакторингом
Создаем поток и помещаем в него i2cPoller (часть 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));
        }
}



После добавления в проект fork, нужен скрипт init.d
Сохраним его как /etc/init.d/i2c_poller.sh
Осталось написать скрипт запуска или взять готовый и подправить
#!/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


Проверим, что запускается
После выполнения /etc/init.d/i2c_poller.sh start

Тут же видим данные в базе, значит опрос работает

Проект на git
https://github.com/khomin/i2c_poller

TODO: использование i2cget — не самое рациональное решение
В зависимости от типа одноплатника, есть смысл включить драйвер i2c при сборке ядра

Let's block ads! (Why?)

Комментариев нет:

Отправить комментарий