diff --git a/src/collectors/collectors.c b/src/collectors/collectors.c index f818c1c..cf99aa9 100644 --- a/src/collectors/collectors.c +++ b/src/collectors/collectors.c @@ -4,7 +4,7 @@ static const clctr_entry_t COLLECTORS[] = { {.name = "SHT41", .collector = sht41_collector}, {.name = "SYSCLOCK", .collector = sysclock_collector}, {.name = "MS5611", .collector = ms5611_collector}, {.name = "LSM6DSO32", .collector = lsm6dso32_collector}, - {.name = "MAXM10S", .collector = m10spg_collector}, + {.name = "MAXM10S", .collector = m10spg_collector}, {.name = "PAC1952-2", .collector = pac1952_2_collector}, }; /** diff --git a/src/collectors/collectors.h b/src/collectors/collectors.h index d2b91f8..565e9fa 100644 --- a/src/collectors/collectors.h +++ b/src/collectors/collectors.h @@ -35,5 +35,6 @@ void *ms5611_collector(void *args); void *sht41_collector(void *args); void *lsm6dso32_collector(void *args); void *m10spg_collector(void *args); +void *pac1952_2_collector(void *args); #endif // _COLLECTORS_H_ diff --git a/src/collectors/pac195x_clctr.c b/src/collectors/pac195x_clctr.c new file mode 100644 index 0000000..bb2aebf --- /dev/null +++ b/src/collectors/pac195x_clctr.c @@ -0,0 +1,85 @@ +#include "collectors.h" +#include "drivers/pac195x/pac195x.h" +#include + +/** Macro to early return errors. */ +#define return_err(err) return (void *)((uint64_t)errno) + +/** The RSENSE value connected to the PAC1952-2 in milliohms. */ +#define RSENSE 18 + +typedef struct { + uint8_t type; + uint8_t id; + int16_t voltage; +} voltage_msg_t; + +void *pac1952_2_collector(void *args) { + + /* Open message queue. */ + mqd_t sensor_q = mq_open(SENSOR_QUEUE, O_WRONLY); + if (sensor_q == -1) { + fprintf(stderr, "PAC195X collector could not open message queue '%s': '%s' \n", SENSOR_QUEUE, strerror(errno)); + return_err(err); + } + + SensorLocation loc = { + .addr = {.addr = clctr_args(args)->addr, .fmt = I2C_ADDRFMT_7BIT}, + .bus = clctr_args(args)->bus, + }; + + int err = pac195x_set_sample_mode(&loc, SAMPLE_1024_SPS_AD); + if (err != EOK) { + fprintf(stderr, "Failed to set sampling mode on PAC195X: %s\n", strerror(err)); + return_err(err); + } + + err = pac195x_toggle_channel(&loc, CHANNEL1 | CHANNEL2, true); + if (err != EOK) { + fprintf(stderr, "Failed to enable all channels on PAC195X: %s\n", strerror(err)); + return_err(err); + } + + err = pac195x_refresh(&loc); // Refresh after all configuration to force changes into effect + usleep(1000); // 1ms after refresh until accumulator data can be read again. + if (err != EOK) { + fprintf(stderr, "Failed to refresh PAC195X: %s\n", strerror(err)); + return_err(err); + } + + uint16_t vbus[2]; + voltage_msg_t msg; + + for (;;) { + + for (int i = 0; i < 2; i++) { + err = pac195x_get_vbusn(&loc, i + 1, &vbus[i]); + if (err != EOK) { + fprintf(stderr, "PAC195X could not read VBUS_%d: %s\n", i - 1, strerror(err)); + break; + } + } + + // Calculate voltage on SENSE 1 + msg.type = TAG_VOLTAGE; + msg.id = 1; + msg.voltage = pac195x_calc_bus_voltage(32, vbus[0], false); + if (mq_send(sensor_q, (char *)&msg, sizeof(msg), 0) == -1) { + fprintf(stderr, "Could not send voltage measurement: %s\n", strerror(errno)); + } + + // Calculate voltage on SENSE 2 + msg.type = TAG_VOLTAGE; + msg.id = 2; + msg.voltage = pac195x_calc_bus_voltage(32, vbus[1], false); + if (mq_send(sensor_q, (char *)&msg, sizeof(msg), 0) == -1) { + fprintf(stderr, "Could not send voltage measurement: %s\n", strerror(errno)); + } + + // Get new measurements + pac195x_refresh_v(&loc); + usleep(1000); + } + + return_err(EOK); +} diff --git a/src/drivers/pac195x/pac195x.c b/src/drivers/pac195x/pac195x.c new file mode 100644 index 0000000..64dc65c --- /dev/null +++ b/src/drivers/pac195x/pac195x.c @@ -0,0 +1,434 @@ +/** + * @file pac195x.c + * @brief Includes a driver for the PAC195X family of power monitors. Currently tested on the PAC1952. + * Datasheet: + * https://ww1.microchip.com/downloads/aemDocuments/documents/MSLD/ProductDocuments/DataSheets/PAC195X-Family-Data-Sheet-DS20006539.pdf + */ + +#include "pac195x.h" +#include "sensor_api.h" +#include +#include +#include +#include + +/** Macro to early return error statuses. */ +#define return_err(err) \ + if (err != EOK) return err + +/** All the different registers/commands available in the PAC195X. */ +typedef enum { + REFRESH = 0x00, /**< Refreshes the PAC195X. */ + CTRL = 0x01, /**< Control register for configuring sampling mode and ALERT pins. */ + ACC_COUNT = 0x02, /**< Accumulator count for all channels. */ + VACCN = 0x03, /**< Accumulator outputs for channels 1-4. Spans until 0x06. */ + VBUSN = 0x07, /**< V_BUS measurements for channels 1-4. Spans until 0x0A. */ + VSENSEN = 0x0B, /**< V_SENSE measurements for channels 1-4. Spans until 0x0E. */ + VBUSN_AVG = 0x0F, /**< Rolling average of the 8 most recent V_BUS_n measurements, (n: 1-4). Spans until 0x12. */ + VSENSEN_AVG = 0x13, /**< Rolling average of the 8 most recent V_SENSE_n measurements, (n: 1-4). Spans until 0x16. */ + VPOWERN = 0x17, /**< V_SENSE * V_BUS for channels 1-4. Spans until 0x1A. */ + SMBUS_SETTINGS = 0x1C, /**< Activate SMBus functionality, I/O data for R/W on I/O pins. */ + NEG_PWR_FSR = 0x1D, /**< Configuration control for bidirectional current. */ + REFRESH_G = 0x1E, /**< Refresh response to General Call Adddress. */ + REFRESH_V = 0x1F, /**< Refreshes V_BUS and V_SENSE data only, no accumulator reset. */ + SLOW = 0x20, /**< Status and control for SLOW pin functions. */ + CTRL_ACT = 0x21, /**< Currently active value of CTRL register. */ + NEG_PWR_FSR_ACT = 0x22, /**< Currently active value of NEG_PWR register. */ + CTRL_LAT = 0x23, /**< Latched active value of CTRL register. */ + NWG_PWR_FSR_LAT = 0x24, /**< Latched active value of NEG_PWR register. */ + ACCUM_CONFIG = 0x25, /**< Enable V_SENSE and V_BUS accumulation. */ + ALERT_STATUS = 0x26, /**< Reads to see what triggered ALERT. */ + SLOW_ALERT1 = 0x27, /**< Assigns specific ALERT to ALERTn/SLOW. */ + GPIO_ALERT2 = 0x28, /**< Assigns specific ALERT to ALERTn/I/O. */ + ACC_FULLNESS_LIMITS = 0x29, /**< ACC and ACC Count fullness limits. */ + OC_LIMITN = 0x30, /**< OC limit for channels 1-4. Spans until 0x33. */ + UC_LIMITN = 0x34, /**< UC limit for channels 1-4. Spans until 0x37. */ + OP_LIMITN = 0x38, /**< OP limit for channels 1-4. Spans until 0x3B. */ + OV_LIMITN = 0x3C, /**< OV limit for channels 1-4. Spans until 0x3F. */ + UV_LIMITN = 0x40, /**< UV limit for channels 1-4. Spans until 0x43. */ + OC_LIMIT_NSAMPLES = 0x44, /**< Consecutive OC samples over threshold for ALERT. */ + UC_LIMIT_NSAMPLES = 0x45, /**< Consecutive UC samples over threshold for ALERT. */ + OP_LIMIT_NSAMPLES = 0x46, /**< Consecutive OP samples over threshold for ALERT. */ + OV_LIMIT_NSAMPLES = 0x47, /**< Consecutive OV samples over threshold for ALERT. */ + UV_LIMIT_NSAMPLES = 0x48, /**< Consecutive UV samples over threshold for ALERT. */ + ALERT_ENABLE = 0x49, /**< ALERT enable. */ + ACCUM_CONFIG_ACT = 0x50, /**< Currently active value of ACCUM_CONFIG register. */ + ACCUM_CONFIG_LAT = 0x51, /**< Currently latched value of ACCUM_CONFIG register. */ + PRODUCT_ID = 0xFD, /**< The register containing the product ID. */ + MANUFACTURER_ID = 0xFE, /**< The register containing the manufacturer ID of 0x54. */ + REVISION_ID = 0xFF, /**< The register containing the revision ID. Initial release is 0x02. */ +} pac195x_reg_t; + +/** + * Set the internal address pointer of the PAC195X to the specified address (in preparation for write/read). + * @param loc The location of the sensor on the I2C bus. + * @param addr The register address to set the address pointer to. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +static int pac195x_send_byte(SensorLocation const *loc, uint8_t addr) { + i2c_send_t hdr = {.len = 1, .stop = 1, .slave = loc->addr}; + uint8_t cmd[sizeof(hdr) + 1]; + memcpy(cmd, &hdr, sizeof(hdr)); + cmd[sizeof(hdr)] = addr; + + return devctl(loc->bus, DCMD_I2C_SEND, cmd, sizeof(cmd), NULL); +} + +/** + * Writes the byte to the specified register of the PAC195X. + * @param loc The location of the sensor on the I2C bus. + * @param addr The register address to write to. + * @param data The data to write. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +static int pac195x_write_byte(SensorLocation const *loc, uint8_t addr, uint8_t data) { + i2c_send_t hdr = {.len = 2, .stop = 1, .slave = loc->addr}; + uint8_t cmd[sizeof(hdr) + 2]; + memcpy(cmd, &hdr, sizeof(hdr)); + cmd[sizeof(hdr)] = addr; + cmd[sizeof(hdr) + 1] = data; + + return devctl(loc->bus, DCMD_I2C_SEND, cmd, sizeof(cmd), NULL); +} + +/** + * Reads a byte from the PAC195X, assuming the address pointer is already at the correct location. + * @param loc The location of the sensor on the I2C bus. + * @param data A pointer to where to store the byte just read. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +static int pac195x_receive_byte(SensorLocation const *loc, uint8_t *data) { + i2c_recv_t hdr = {.len = 1, .stop = 1, .slave = loc->addr}; + uint8_t cmd[sizeof(hdr) + 1]; + memcpy(cmd, &hdr, sizeof(hdr)); + + int err = devctl(loc->bus, DCMD_I2C_RECV, cmd, sizeof(cmd), NULL); + return_err(err); + *data = cmd[sizeof(hdr)]; + return err; +} + +/** + * Read a byte from a specific register address. + * @param loc The location of the sensor on the I2C bus. + * @param addr The register address to read from. + * @param data A pointer to where to store the byte just read. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +static int pac195x_read_byte(SensorLocation const *loc, uint8_t addr, uint8_t *data) { + i2c_sendrecv_t hdr = {.send_len = 1, .recv_len = 1, .stop = 1, .slave = loc->addr}; + uint8_t cmd[sizeof(hdr) + 1]; + memcpy(cmd, &hdr, sizeof(hdr)); + cmd[sizeof(hdr)] = addr; + + int err = devctl(loc->bus, DCMD_I2C_SENDRECV, cmd, sizeof(cmd), NULL); + return_err(err); + *data = cmd[sizeof(hdr)]; + return err; +} + +/** + * Read several bytes from the PAC195X starting at a specific address. After the call, data will be stored in `buf[20]` + * and onward (inclusive). + * @param loc The location of the sensor on the I2C bus. + * @param addr The register address to read from. + * @param nbytes The number of bytes to read. Cannot be 0. + * @param buf A pointer to where to store the bytes just read. Must have room for `nbytes + 20`. + * @return Any error which occurred while communicating with the sensor. EOK if successful, EINVAL if nbytes is 0. + */ +static int pac195x_block_read(SensorLocation const *loc, uint8_t addr, size_t nbytes, uint8_t *buf) { + + if (nbytes == 0) return EINVAL; + + i2c_sendrecv_t hdr = {.send_len = 1, .recv_len = nbytes, .stop = 1, .slave = loc->addr}; + memcpy(buf, &hdr, sizeof(hdr)); + buf[sizeof(hdr)] = addr; + + return devctl(loc->bus, DCMD_I2C_SENDRECV, buf, nbytes + sizeof(hdr), NULL); +} + +/** + * Write several bytes to the PAC195X starting at a specific address. + * @param loc The location of the sensor on the I2C bus. + * @param addr The register address to write to. + * @param nbytes The number of bytes to write. Cannot be 0. + * @param buf A pointer to where the data to be written is located. Data must be preceded with 17 bytes of empty space. + * @return Any error which occurred while communicating with the sensor. EOK if successful, EINVAL if nbytes is 0. + */ +static int pac195x_block_write(SensorLocation const *loc, uint8_t addr, size_t nbytes, uint8_t *buf) { + + if (nbytes == 0) return EINVAL; + + i2c_send_t hdr = {.len = nbytes + 1, .stop = 1, .slave = loc->addr}; + memcpy(buf, &hdr, sizeof(hdr)); + buf[sizeof(hdr)] = addr; // Immediately after this addr is where the caller should have put their data. + + return devctl(loc->bus, DCMD_I2C_SEND, buf, nbytes + sizeof(hdr) + 1, NULL); +} + +/** + * Reads the manufacturer ID from the PAC195X into `id`. Should always be 0x54. + * @param loc The location of the sensor on the I2C bus. + * @param id A pointer to where the ID returned by the sensor will be stored. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_get_manu_id(SensorLocation const *loc, uint8_t *id) { return pac195x_read_byte(loc, MANUFACTURER_ID, id); } + +/** + * Reads the product ID from the PAC195X into `id`. The value is chip model dependent. + * @param loc The location of the sensor on the I2C bus. + * @param id A pointer to where the ID returned by the sensor will be stored. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_get_prod_id(SensorLocation const *loc, uint8_t *id) { return pac195x_read_byte(loc, PRODUCT_ID, id); } + +/** + * Reads the revision ID from the PAC195X into `id`. Should be 0x02. + * @param loc The location of the sensor on the I2C bus. + * @param id A pointer to where the ID returned by the sensor will be stored. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_get_rev_id(SensorLocation const *loc, uint8_t *id) { return pac195x_read_byte(loc, REVISION_ID, id); } + +/** + * Sends the refresh command to the PAC195X sensor. + * @param loc The location of the sensor on the I2C bus. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_refresh(SensorLocation const *loc) { return pac195x_send_byte(loc, REFRESH); } + +/** + * Sends the refresh general call command to the PAC195X sensor. + * WARNING: This command is received by all I2C slave devices. They will interpret this as a software reset if + * implemented. This command may have unintended consequences. + * @param loc The location of the sensor on the I2C bus. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_refresh_g(SensorLocation const *loc) { + SensorLocation general_loc = { + .bus = loc->bus, + .addr = loc->addr, + }; + general_loc.addr.addr = 0x0; + return pac195x_send_byte(&general_loc, REFRESH_G); +} + +/** + * Sends the refresh v command to the PAC195X. Same as refresh except accumulators and accumulator count are not reset. + * @param loc The location of the sensor on the I2C bus. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_refresh_v(SensorLocation const *loc) { return pac195x_send_byte(loc, REFRESH_V); } + +/** + * Set the sampling mode for the PAC195x. + * @param loc The location of the sensor on the I2C bus. + * @param mode The sampling mode to set. + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_set_sample_mode(SensorLocation const *loc, pac195x_sm_e mode) { + uint8_t ctrl_reg; + int err = pac195x_read_byte(loc, CTRL, &ctrl_reg); + return_err(err); + + ctrl_reg &= ~(0xF0); // Clear upper 4 bits + ctrl_reg |= mode; // Set the mode + err = pac195x_write_byte(loc, CTRL, ctrl_reg); + return err; +} + +/** + * Enable/disable channels for sampling on the PAC195X. + * NOTE: Some models, like the PAC1952-2, physically do not have all channel pins. Trying to enable those channels will + * do nothing. + * @param loc The location of the sensor on the I2C bus. + * @param channel A channel or multiple channels to enable/disable. Multiple channels can be given by ORing the channels + * together. + * @param enable True to enable the channel(s), false to disable the channel(s). + * @return Any error which occurred while communicating with the sensor. EOK if successful. + */ +int pac195x_toggle_channel(SensorLocation const *loc, pac195x_channel_e channel, bool enable) { + + uint8_t buf[sizeof(i2c_sendrecv_t) + 2]; + int err = pac195x_block_read(loc, CTRL, 2, buf); + return_err(err); + if (enable) { + buf[sizeof(i2c_sendrecv_t) + 1] &= ~(channel << 4); // Set the channels on (0 enables) + } else { + buf[sizeof(i2c_sendrecv_t) + 1] |= (channel << 4); // Set the channels off (1 disables) + } + buf[sizeof(i2c_send_t) + 1] = buf[sizeof(i2c_sendrecv_t)]; // Move back because write header takes less space + buf[sizeof(i2c_send_t) + 2] = buf[sizeof(i2c_sendrecv_t) + 1]; + err = pac195x_block_write(loc, CTRL, 2, buf); + return err; +} + +/** + * Generic function for reading 2 bit values from a specific channel number. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param addr A pointer to where to store the value. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +static int pac195x_get_16b_channel(SensorLocation const *loc, uint8_t addr, uint8_t n, uint16_t *val) { + if (n > 4 || n < 1) return EINVAL; // Invalid channel number + + uint8_t buf[sizeof(i2c_sendrecv_t) + 2]; // Space for header and 16 bit response. + int err = pac195x_block_read(loc, addr + (n - 1), 2, buf); + return_err(err); + *val = 0; + *val |= (uint32_t)(buf[sizeof(i2c_sendrecv_t)] << 8); + *val |= (uint32_t)buf[sizeof(i2c_sendrecv_t) + 1]; + return err; +} + +/** + * Get the V_SENSE measurements for channels 1-4. + * NOTE: If SKIP is enabled and the caller attempts to read from a channel that is disabled, an I/O error will be + * returned. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +int pac195x_get_vsensen(SensorLocation const *loc, uint8_t n, uint16_t *val) { + return pac195x_get_16b_channel(loc, VSENSEN, n, val); +} + +/** + * Get the V_BUS measurements for channels 1-4. + * NOTE: If SKIP is enabled and the caller attempts to read from a channel that is disabled, an I/O error will be + * returned. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +int pac195x_get_vbusn(SensorLocation const *loc, uint8_t n, uint16_t *val) { + return pac195x_get_16b_channel(loc, VBUSN, n, val); +} + +/** + * Get the V_BUS_AVG measurements for channels 1-4. + * NOTE: If SKIP is enabled and the caller attempts to read from a channel that is disabled, an I/O error will be + * returned. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +int pac195x_get_vbusnavg(SensorLocation const *loc, uint8_t n, uint16_t *val) { + return pac195x_get_16b_channel(loc, VBUSN_AVG, n, val); +} + +/** + * Get the V_SENSE_AVG measurements for channels 1-4. + * NOTE: If SKIP is enabled and the caller attempts to read from a channel that is disabled, an I/O error will be + * returned. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +int pac195x_get_vsensenavg(SensorLocation const *loc, uint8_t n, uint16_t *val) { + return pac195x_get_16b_channel(loc, VSENSEN_AVG, n, val); +} + +/** + * Get the V_POWER measurements for channels 1-4. + * NOTE: If SKIP is enabled and the caller attempts to read from a channel that is disabled, an I/O error will be + * returned. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +int pac195x_get_vpowern(SensorLocation const *loc, uint8_t n, uint32_t *val) { + if (n > 4 || n < 1) return EINVAL; // Invalid channel number + + uint8_t buf[sizeof(i2c_sendrecv_t) + 4]; // Space for header and 32 bit response. + int err = pac195x_block_read(loc, VPOWERN + (n - 1), 4, buf); + return_err(err); + *val = *(uint32_t *)(&buf[sizeof(i2c_sendrecv_t)]); // TODO: fix byte ordering + return err; +} + +/** + * Get the V_ACCN measurements for channels 1-4. + * NOTE: If SKIP is enabled and the caller attempts to read from a channel that is disabled, an I/O error will be + * returned. + * @param loc The location of the sensor on the I2C bus. + * @param n The channel number (1-4, inclusive) to get the measurement from. + * @param val A pointer to where to store the value. + * @return Any error which occurred while communicating with the sensor. EOK if successful. EINVAL if `n` is an invalid + * channel number. + */ +int pac195x_get_vaccn(SensorLocation const *loc, uint8_t n, uintptr64_t *val) { + if (n > 4 || n < 1) return EINVAL; // Invalid channel number + + uint8_t buf[sizeof(i2c_sendrecv_t) + 8] = {0}; // Space for header and 64 bit response. + int err = pac195x_block_read(loc, VACCN + (n - 1), 7, buf); // Only as the 7 bytes within the VACCN register + return_err(err); + *val = *(uint32_t *)(&buf[sizeof(i2c_sendrecv_t)]); // TODO: fix byte ordering + return err; +} + +/** + * Calculates the voltage on the SENSE line from the VBUS measurement. + * @param fsr The full scale range to use for the calculation (PAC195X uses a default of 32). + * @param vbus The measured VBUS channel value corresponding to the SENSE line. + * @param bipolar Whether the measurement is bipolar or not (PAC195X uses unipolar by default). + * @return The voltage measurement on the line in millivolts. + */ +uint32_t pac195x_calc_bus_voltage(uint8_t fsr, uint16_t vbus, bool bipolar) { + uint16_t denominator; + if (bipolar) { + denominator = 32768; + } else { + denominator = 65535; // Actual calculation says to use 65536, but this approximation saves 2 bytes + } + return (fsr * vbus * 1000) / denominator; +} + +/** + * Calculate the bus current. + * @param rsense The value of the R_SENSE resistor connected to the SENSE line in milliohms. + * @param vsense The measured VSENSE channel value corresponding to the SENSE line. + * @param bipolar Whether the measurement is bipolar or not (PAC195X uses unipolar by default). + * @return The bus current in milliamps. + */ +uint32_t pac195x_calc_bus_current(uint32_t rsense, uint16_t vsense, bool bipolar) { + uint16_t denominator; + if (bipolar) { + denominator = 32768; + } else { + denominator = 65535; // Actual calculation says to use 65536, but this approximation saves 2 bytes + } + return (100 * vsense * 1000) / (denominator * rsense); +} + +/** + * Calculate the actual power. + * @param rsense The value of the R_SENSE resistor connected to the SENSE line in milliohms. + * @param vsense The measured VSENSE channel value corresponding to the SENSE line. + * @param bipolar Whether the measurement is bipolar or not (PAC195X uses unipolar by default). + * @return The bus current in milliamps. + */ +uint32_t pac195x_calc_power(uint32_t rsense, uint32_t vpower, bool bipolar) { + uint32_t denominator; + if (bipolar) { + denominator = 536870912; // 2^29 + } else { + denominator = 1073741824; // 2^30 + } + // FSR = 32 * (100mV / rsense_ohms) + // Power = FSR * vpower / denominator + return (32 * 100 * 1000 * vpower) / (rsense * denominator); +} diff --git a/src/drivers/pac195x/pac195x.h b/src/drivers/pac195x/pac195x.h new file mode 100644 index 0000000..f239ef0 --- /dev/null +++ b/src/drivers/pac195x/pac195x.h @@ -0,0 +1,73 @@ +#ifndef _PAC195X_H_ +#define _PAC195X_H_ + +#include "../sensor_api.h" +#include +#include + +/** The manufacturer ID for any PAC195X chip. */ +#define MANU_ID 0x54 + +/** The product ID for the PAC1951-1 chip. */ +#define PAC1951_1_PRODID 0x78 +/** The product ID for the PAC1952-1 chip. */ +#define PAC1952_1_PRODID 0x79 +/** The product ID for the PAC1953-1 chip. */ +#define PAC1953_1_PRODID 0x7A +/** The product ID for the PAC1954-1 chip. */ +#define PAC1954_1_PRODID 0x7B +/** The product ID for the PAC1951-2 chip. */ +#define PAC1951_2_PRODID 0x7C +/** The product ID for the PAC1952-2 chip. */ +#define PAC1952_2_PRODID 0x7D + +/** Revision ID of the initial release. */ +#define PAC195X_INIT_REL 0x02 + +/** The different sampling modes for the PAC195X. */ +typedef enum { + SAMPLE_1024_SPS_AD = 0x00, /**< 1024 SPS adaptive accumulation (default). */ + SAMPLE_256_SPS_AD = 0x10, /**< 256 SPS adaptive accumulation. */ + SAMPLE_64_SPS_AD = 0x20, /**< 64 SPS adaptive accumulation. */ + SAMPLE_8_SPS_AD = 0x30, /**< 8 SPS adaptive accumulation. */ + SAMPLE_1024_SPS = 0x40, /**< 1024 SPS. */ + SAMPLE_256_SPS = 0x50, /**< 256 SPS. */ + SAMPLE_64_SPS = 0x60, /**< 64 SPS. */ + SAMPLE_8_SPS = 0x70, /**< 8 SPS. */ + SAMPLE_SINGLE_SHOT = 0x80, /**< Single shot mode. */ + SAMPLE_SINGLE_SHOT8X = 0x90, /**< Single shot 8X. */ + SAMPLE_FAST = 0xA0, /**< Fast mode. */ + SAMPLE_BURST = 0xB0, /**< Burst mode. */ + SAMPLE_SLEEP = 0xF0, /**< Sleep. */ +} pac195x_sm_e; + +/** The different channels that can be enabled/disabled on the PAC195X. */ +typedef enum { + CHANNEL1 = 0x8, /**< Channel 1 */ + CHANNEL2 = 0x4, /**< Channel 2 */ + CHANNEL3 = 0x2, /**< Channel 3 */ + CHANNEL4 = 0x1, /**< Channel 4 */ +} pac195x_channel_e; + +int pac195x_get_manu_id(SensorLocation const *loc, uint8_t *id); +int pac195x_get_prod_id(SensorLocation const *loc, uint8_t *id); +int pac195x_get_rev_id(SensorLocation const *loc, uint8_t *id); +int pac195x_get_vsensen(SensorLocation const *loc, uint8_t n, uint16_t *val); +int pac195x_get_vbusn(SensorLocation const *loc, uint8_t n, uint16_t *val); +int pac195x_get_vbusnavg(SensorLocation const *loc, uint8_t n, uint16_t *val); +int pac195x_get_vsensenavg(SensorLocation const *loc, uint8_t n, uint16_t *val); +int pac195x_get_vpowern(SensorLocation const *loc, uint8_t n, uint32_t *val); +int pac195x_get_vaccn(SensorLocation const *loc, uint8_t n, uintptr64_t *val); + +int pac195x_set_sample_mode(SensorLocation const *loc, pac195x_sm_e mode); +int pac195x_toggle_channel(SensorLocation const *loc, pac195x_channel_e channel, bool enable); + +int pac195x_refresh(SensorLocation const *loc); +int pac195x_refresh_v(SensorLocation const *loc); +int pac195x_refresh_g(SensorLocation const *loc); + +uint32_t pac195x_calc_bus_voltage(uint8_t fsr, uint16_t vbus, bool bipolar); +uint32_t pac195x_calc_bus_current(uint32_t rsense, uint16_t vsense, bool bipolar); +uint32_t pac195x_calc_power(uint32_t rsense, uint32_t vpower, bool bipolar); + +#endif // _PAC195X_H_ diff --git a/src/drivers/sensor_api.c b/src/drivers/sensor_api.c index d123d3a..0183557 100644 --- a/src/drivers/sensor_api.c +++ b/src/drivers/sensor_api.c @@ -5,6 +5,7 @@ * This file contains the implementations for the sensor API interface. */ #include "sensor_api.h" +#include #include #include @@ -13,34 +14,64 @@ /** A list of the possible sensor tags and their metadata. */ const SensorTagData SENSOR_TAG_DATA[] = { - [TAG_PRESSURE] = - {.name = "Pressure", .unit = "kPa", .fmt_str = "%.2f", .dsize = sizeof(float), .dtype = TYPE_FLOAT}, - [TAG_TEMPERATURE] = - {.name = "Temperature", .unit = "C", .fmt_str = "%.2f", .dsize = sizeof(float), .dtype = TYPE_FLOAT}, - [TAG_HUMIDITY] = - {.name = "Humidity", .unit = "%RH", .fmt_str = "%.2f", .dsize = sizeof(float), .dtype = TYPE_FLOAT}, - [TAG_TIME] = {.name = "Time", .unit = "ms", .fmt_str = "%u", .dsize = sizeof(uint32_t), .dtype = TYPE_U32}, - [TAG_ALTITUDE_REL] = - {.name = "Altitude rel", .unit = "m", .fmt_str = "%.2f", .dsize = sizeof(float), .dtype = TYPE_FLOAT}, - [TAG_ALTITUDE_SEA] = - {.name = "Altitude sea level", .unit = "m", .fmt_str = "%.2f", .dsize = sizeof(float), .dtype = TYPE_FLOAT}, + [TAG_PRESSURE] = {.name = "Pressure", + .unit = "kPa", + .fmt_str = "%.2f", + .dsize = sizeof(float), + .dtype = TYPE_FLOAT, + .has_id = 0}, + [TAG_TEMPERATURE] = {.name = "Temperature", + .unit = "C", + .fmt_str = "%.2f", + .dsize = sizeof(float), + .dtype = TYPE_FLOAT, + .has_id = 0}, + [TAG_HUMIDITY] = {.name = "Humidity", + .unit = "%RH", + .fmt_str = "%.2f", + .dsize = sizeof(float), + .dtype = TYPE_FLOAT, + .has_id = 0}, + [TAG_TIME] = + {.name = "Time", .unit = "ms", .fmt_str = "%u", .dsize = sizeof(uint32_t), .dtype = TYPE_U32, .has_id = 0}, + [TAG_ALTITUDE_REL] = {.name = "Altitude rel", + .unit = "m", + .fmt_str = "%.2f", + .dsize = sizeof(float), + .dtype = TYPE_FLOAT, + .has_id = 0}, + [TAG_ALTITUDE_SEA] = {.name = "Altitude sea level", + .unit = "m", + .fmt_str = "%.2f", + .dsize = sizeof(float), + .dtype = TYPE_FLOAT, + .has_id = 0}, [TAG_LINEAR_ACCEL_ABS] = {.name = "Absolute linear acceleration", .unit = "m/s^2", .fmt_str = "%.2fX, %.2fY, %.2fZ", .dsize = sizeof(vec3d_t), - .dtype = TYPE_VEC3D}, + .dtype = TYPE_VEC3D, + .has_id = 0}, [TAG_LINEAR_ACCEL_REL] = {.name = "Relative linear acceleration", .unit = "m/s^2", .fmt_str = "%.2fX, %.2fY, %.2fZ", .dsize = sizeof(vec3d_t), - .dtype = TYPE_VEC3D}, + .dtype = TYPE_VEC3D, + .has_id = 0}, [TAG_ANGULAR_VEL] = {.name = "Angular velocity", .unit = "dps", .fmt_str = "%.2fX, %.2fY, %.2fZ", .dsize = sizeof(vec3d_t), - .dtype = TYPE_VEC3D}, - [TAG_COORDS] = - {.name = "Lat/Long", .unit = "deg", .fmt_str = "%.2fX, %.2fY", .dsize = sizeof(vec2d_t), .dtype = TYPE_VEC2D}, + .dtype = TYPE_VEC3D, + .has_id = 0}, + [TAG_COORDS] = {.name = "Lat/Long", + .unit = "deg", + .fmt_str = "%.2fX, %.2fY", + .dsize = sizeof(vec2d_t), + .dtype = TYPE_VEC2D, + .has_id = 0}, + [TAG_VOLTAGE] = + {.name = "Voltage", .unit = "mV", .fmt_str = "%d", .dsize = sizeof(int16_t), .dtype = TYPE_I16, .has_id = 1}, /* [TAG_LATITUDE] = */ /* {.name = "Latitude", .unit = "0.1udeg", .fmt_str = "%d", .dsize = sizeof(int32_t), .dtype = TYPE_I32}, */ /* [TAG_SPEED] = */ @@ -137,6 +168,12 @@ const char __attribute__((const)) * sensor_strtag(const SensorTag tag) { return * @param data A pointer to the sensor data to be printed. */ void sensor_write_data(FILE *stream, const SensorTag tag, const void *data) { + + if (SENSOR_TAG_DATA[tag].has_id) { + fprintf(stream, "ID: %u ", drefcast(const uint8_t, data)); + data = ((const uint8_t *)(data) + 1); // Skip the ID byte before continuing + } + char format_str[40] = "%s: "; // Format specifier for data name strcat(format_str, SENSOR_TAG_DATA[tag].fmt_str); // Format specifier for data strcat(format_str, " %s\n"); // Format specifier for unit diff --git a/src/drivers/sensor_api.h b/src/drivers/sensor_api.h index 1bbc3e2..4a60f29 100644 --- a/src/drivers/sensor_api.h +++ b/src/drivers/sensor_api.h @@ -34,16 +34,17 @@ typedef struct { /** Describes possible data types that fetcher is capable of producing. */ typedef enum { - TAG_TEMPERATURE = 0, /**< Temperature in degrees Celsius */ - TAG_PRESSURE = 1, /**< Pressure in kilo Pascals */ - TAG_HUMIDITY = 2, /**< Humidity in % relative humidity */ - TAG_TIME = 3, /**< Time in milliseconds */ - TAG_ALTITUDE_SEA = 4, /**< Altitude above sea level in meters */ - TAG_ALTITUDE_REL = 5, /**< Altitude above launch height in meters */ - TAG_ANGULAR_VEL = 6, /**< Angular velocity in degrees per second */ - TAG_LINEAR_ACCEL_REL = 7, /**< Relative linear acceleration in meters per second squared */ - TAG_LINEAR_ACCEL_ABS = 8, /**< Absolute linear acceleration in meters per second squared */ - TAG_COORDS = 9, /**< Latitude and longitude in degrees */ + TAG_TEMPERATURE = 0x0, /**< Temperature in degrees Celsius */ + TAG_PRESSURE = 0x1, /**< Pressure in kilo Pascals */ + TAG_HUMIDITY = 0x2, /**< Humidity in % relative humidity */ + TAG_TIME = 0x3, /**< Time in milliseconds */ + TAG_ALTITUDE_SEA = 0x4, /**< Altitude above sea level in meters */ + TAG_ALTITUDE_REL = 0x5, /**< Altitude above launch height in meters */ + TAG_ANGULAR_VEL = 0x6, /**< Angular velocity in degrees per second */ + TAG_LINEAR_ACCEL_REL = 0x7, /**< Relative linear acceleration in meters per second squared */ + TAG_LINEAR_ACCEL_ABS = 0x8, /**< Absolute linear acceleration in meters per second squared */ + TAG_COORDS = 0x9, /**< Latitude and longitude in degrees */ + TAG_VOLTAGE = 0x10, /**< Voltage in volts with a unique ID. */ } SensorTag; /** Describes the data type of the data associated with a tag. */ @@ -71,6 +72,8 @@ typedef struct { const size_t dsize; /** The C data type this data is associated with. */ const SensorTagDType dtype; + /** Whether or not the data type is preceded by a unique numerical ID. */ + const uint8_t has_id; } SensorTagData; typedef enum { diff --git a/src/main.c b/src/main.c index b17b8a6..c16115b 100644 --- a/src/main.c +++ b/src/main.c @@ -34,6 +34,7 @@ /** The maximum number of sensors that fetcher can support. */ #define MAX_SENSORS 8 +/** The name of the system clock collector. */ #define SYSCLOCK_NAME "sysclock" /** Whether or not to print data to stdout. */ @@ -43,10 +44,10 @@ bool print_output = false; char *select_sensor = NULL; /** Stores the thread IDs of all the collector threads. */ -static pthread_t collector_threads[MAX_SENSORS]; +pthread_t collector_threads[MAX_SENSORS]; /** Stores the collector arguments of all the collector threads. */ -static collector_args_t collector_args[MAX_SENSORS]; +collector_args_t collector_args[MAX_SENSORS]; /** Buffer for reading sensor messages when print option is selected. */ uint8_t buffer[BUFFER_SIZE]; @@ -229,13 +230,21 @@ int main(int argc, char **argv) { } } // Only start the sysclock if we're not debugging a single sensor or if this is the sensor that was selected - if (select_sensor == NULL || !strncasecmp(select_sensor, SYSCLOCK_NAME, sizeof(SYSCLOCK_NAME))) { + if (select_sensor == NULL || !strcasecmp(select_sensor, SYSCLOCK_NAME)) { /* Add sysclock sensor because it won't be specified in board ID. */ collector_t sysclock = collector_search(SYSCLOCK_NAME); err = pthread_create(&collector_threads[num_sensors], NULL, sysclock, NULL); num_sensors++; } + /* Add PAC1952 sensor because it won't be specified in board ID. */ + if (select_sensor == NULL || !strcasecmp(select_sensor, "pac1952-2")) { + collector_t pac1952 = collector_search("pac1952-2"); + collector_args[num_sensors] = (collector_args_t){.bus = bus, .addr = 0x17}; + err = pthread_create(&collector_threads[num_sensors], NULL, pac1952, &collector_args[num_sensors]); + num_sensors++; + } + /* Constantly receive from sensors on message queue and print data. */ while (print_output) { if (mq_receive(sensor_q, (char *)buffer, sensor_q_attr.mq_msgsize, NULL) == -1) {