From 3fa197c56ce1b21d52184495d96554fad8b2389b Mon Sep 17 00:00:00 2001 From: Rolf Laich Date: Sun, 31 May 2026 19:10:40 +0200 Subject: [PATCH] drivers: sensor: Implement i2c adapter layer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adapter layer provides functionality commonly required by Sensirion I²C drivers. It implements the Sensirion protocol on top of I²C (i2c_packet) and provides a consistent set of conversion functions for unmarshalling sensor data into the corresponding host data types. The i2c_general_call reset function is included as a convenience, since many Sensirion I²C sensors support this feature and, in some cases, it is the only available reset mechanism. Signed-off-by: Rolf Laich --- drivers/sensor/sensirion/CMakeLists.txt | 1 + .../sensor/sensirion/common/CMakeLists.txt | 9 + drivers/sensor/sensirion/common/conversions.c | 42 +++ drivers/sensor/sensirion/common/conversions.h | 87 +++++++ .../sensirion/common/i2c_general_call_reset.c | 16 ++ .../sensirion/common/i2c_general_call_reset.h | 35 +++ drivers/sensor/sensirion/common/i2c_packet.c | 162 ++++++++++++ drivers/sensor/sensirion/common/i2c_packet.h | 240 ++++++++++++++++++ 8 files changed, 592 insertions(+) create mode 100644 drivers/sensor/sensirion/common/CMakeLists.txt create mode 100644 drivers/sensor/sensirion/common/conversions.c create mode 100644 drivers/sensor/sensirion/common/conversions.h create mode 100644 drivers/sensor/sensirion/common/i2c_general_call_reset.c create mode 100644 drivers/sensor/sensirion/common/i2c_general_call_reset.h create mode 100644 drivers/sensor/sensirion/common/i2c_packet.c create mode 100644 drivers/sensor/sensirion/common/i2c_packet.h diff --git a/drivers/sensor/sensirion/CMakeLists.txt b/drivers/sensor/sensirion/CMakeLists.txt index 8d131d626046e..577a930382afe 100644 --- a/drivers/sensor/sensirion/CMakeLists.txt +++ b/drivers/sensor/sensirion/CMakeLists.txt @@ -2,6 +2,7 @@ # SPDX-License-Identifier: Apache-2.0 # zephyr-keep-sorted-start +add_subdirectory(common) add_subdirectory_ifdef(CONFIG_SCD4X scd4x) add_subdirectory_ifdef(CONFIG_SGP40 sgp40) add_subdirectory_ifdef(CONFIG_SHT3XD sht3xd) diff --git a/drivers/sensor/sensirion/common/CMakeLists.txt b/drivers/sensor/sensirion/common/CMakeLists.txt new file mode 100644 index 0000000000000..bab020db73de4 --- /dev/null +++ b/drivers/sensor/sensirion/common/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (c) 2026 Sensirion +# +# SPDX-License-Identifier: Apache-2.0 +# +zephyr_library() + +zephyr_library_sources(conversions.c) +zephyr_library_sources(i2c_packet.c) +zephyr_library_sources(i2c_general_call_reset.c) diff --git a/drivers/sensor/sensirion/common/conversions.c b/drivers/sensor/sensirion/common/conversions.c new file mode 100644 index 0000000000000..49113995fa108 --- /dev/null +++ b/drivers/sensor/sensirion/common/conversions.c @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2026 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "conversions.h" + +float sensirion_conversions_bytes_to_float(const uint8_t *const bytes) +{ + union { + uint32_t u32_value; + float float32; + } tmp; + + tmp.u32_value = sys_get_be32(bytes); + return tmp.float32; +} + +void sensirion_conversions_to_integer(const uint8_t *const source, uint8_t *const destination, + size_t destination_size, uint8_t data_length) +{ + if (data_length > destination_size) { + memset(destination, 0xFF, destination_size); + return; + } + /* set all bytes in destination to 0 to make sure that the value is correct even if the + * data_length is smaller than the size of the integer type */ + memset(destination, 0, destination_size); + + /* copy the bytes from source to destination. The source is in big-endian/MSB-first format, + * so the first byte of the source is the most significant byte. The destination should be + * filled in the correct order for the system endianness. + */ + if (IS_ENABLED(CONFIG_LITTLE_ENDIAN)) { + for (uint8_t i = 1; i <= data_length; i++) { + destination[data_length - i] = source[i - 1u]; + } + } else { + memcpy(&destination[destination_size - data_length], source, data_length); + } +} diff --git a/drivers/sensor/sensirion/common/conversions.h b/drivers/sensor/sensirion/common/conversions.h new file mode 100644 index 0000000000000..9ec5bf4e09e32 --- /dev/null +++ b/drivers/sensor/sensirion/common/conversions.h @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2026 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef CONVERSIONS_H +#define CONVERSIONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * NO_ERROR is used as alias for 0 return values of functions to improve + * readability of the code. + */ +#define NO_ERROR 0 + +/** + * @defgroup API to unmarshal basic data type from byte arrays + * + * This API provides functions for converting byte arrays received from the + * sensor. + * The function names in this API use the pattern bytes_to_ to indicate + * conversions from byte arrays to basic data types. + * Since Zephyr does not provide a function for converting big-endian + * (MSB-first) byte arrays to floating-point values, the function + * sensirion_conversions_bytes_to_float() is + * provided for this purpose. + * To maintain a consistent naming scheme for all functions that convert byte + * arrays to basic types, the functions + * sensirion_conversions_bytes_to_() are also provided, + * even in cases where Zephyr already provides equivalent functions for + * specific integer + * types. + * @{ + */ + +/** + * Convert an array of bytes to a float + * + * Convert an array of bytes received from the sensor in big-endian/MSB-first + * format to an float value in the correct system-endianness. + * + * @param bytes An array of at least four bytes (MSB first) + * @return The byte array represented as float + */ +float sensirion_conversions_bytes_to_float(const uint8_t *const bytes); + +#define sensirion_conversions_bytes_to_int16_t(bytes) ((int16_t)sys_get_be16(bytes)) +#define sensirion_conversions_bytes_to_uint16_t(bytes) ((uint16_t)sys_get_be16(bytes)) +#define sensirion_conversions_bytes_to_int32_t(bytes) ((int32_t)sys_get_be32(bytes)) +#define sensirion_conversions_bytes_to_uint32_t(bytes) ((uint32_t)sys_get_be32(bytes)) +#define sensirion_conversions_bytes_to_int64_t(bytes) ((int64_t)sys_get_be64(bytes)) +#define sensirion_conversions_bytes_to_uint64_t(bytes) ((uint64_t)sys_get_be64(bytes)) + +/** @} */ + +/** + * Copy bytes from byte array to an integer of the specified type. + * + * The byte array is expected to be in big-endian/MSB-first format as it is + * received from the sensor. + * The data_length specifies how many bytes are available in the source byte + * array to copy. + * This length may not match the size of the destination integer. + * In this case the function will fill the missing bytes with zeros or + * set the destination to 0 if it is smaller than the source. + * + * @param source Array of bytes to be copied. + * @param destination Pointer to integer that will be filled with the copied + * data. + * @param destination_size Size of the destination integer in bytes. + * @param data_length Number of available bytes in source to copy. + */ +void sensirion_conversions_to_integer(const uint8_t *const source, uint8_t *const destination, + size_t destination_size, uint8_t data_length); + +#ifdef __cplusplus +} +#endif + +#endif /* CONVERSIONS_H */ diff --git a/drivers/sensor/sensirion/common/i2c_general_call_reset.c b/drivers/sensor/sensirion/common/i2c_general_call_reset.c new file mode 100644 index 0000000000000..9cb9c75333c66 --- /dev/null +++ b/drivers/sensor/sensirion/common/i2c_general_call_reset.c @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2026 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "i2c_general_call_reset.h" +#include + +int sensirion_i2c_general_call_reset(const struct i2c_dt_spec *i2c_spec) +{ + const uint8_t data = 0x06; + const uint8_t i2c_address = 0x00; + + return i2c_write(i2c_spec->bus, &data, sizeof(data), i2c_address); +} diff --git a/drivers/sensor/sensirion/common/i2c_general_call_reset.h b/drivers/sensor/sensirion/common/i2c_general_call_reset.h new file mode 100644 index 0000000000000..38d363acc888f --- /dev/null +++ b/drivers/sensor/sensirion/common/i2c_general_call_reset.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2026 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef I2C_GENERAL_CALL_RESET_H +#define I2C_GENERAL_CALL_RESET_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * Send a general call reset on the i2c bus. + * + * This function is used in drivers that implement a call reset. + * + * @warning This will reset all attached I2C devices on the bus which support + * general call reset. + * + * @param i2c_spec Sensor I2C specification + * + * @return 0 on success, an error code otherwise + */ +int sensirion_i2c_general_call_reset(const struct i2c_dt_spec *i2c_spec); + +#ifdef __cplusplus +} +#endif + +#endif /* I2C_GENERAL_CALL_RESET_H */ diff --git a/drivers/sensor/sensirion/common/i2c_packet.c b/drivers/sensor/sensirion/common/i2c_packet.c new file mode 100644 index 0000000000000..4b5e71294d250 --- /dev/null +++ b/drivers/sensor/sensirion/common/i2c_packet.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2026 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include "i2c_packet.h" + +#include +#include +#include +#include + +#define SENSIRION_WORD_SIZE 2u +#define SENSIRION_CRC8_LEN 1u + +uint8_t sensirion_i2c_packet_get_crc(const i2c_packet_t *const i2c_packet, uint16_t index) +{ + return crc8(&i2c_packet->data[index], SENSIRION_WORD_SIZE, i2c_packet->crc8_poly, + i2c_packet->crc8_init, false); +} + +bool sensirion_i2c_packet_check_crc(const i2c_packet_t *const i2c_packet, uint16_t index) +{ + if (sensirion_i2c_packet_get_crc(i2c_packet, index) != + i2c_packet->data[index + SENSIRION_WORD_SIZE]) { + return false; + } + return true; +} + +uint16_t sensirion_i2c_packet_add_command16(i2c_packet_t *const i2c_packet, uint16_t offset, + uint16_t command) +{ + sys_put_be16(command, &i2c_packet->data[offset]); + offset += 2; + return offset; +} + +uint16_t sensirion_i2c_packet_add_command8(i2c_packet_t *const i2c_packet, uint16_t offset, + uint8_t command) +{ + i2c_packet->data[offset++] = command; + return offset; +} + +uint16_t sensirion_i2c_packet_add_uint64_t(i2c_packet_t *const i2c_packet, uint16_t offset, + uint64_t data) +{ + offset = sensirion_i2c_packet_add_uint32_t(i2c_packet, offset, data >> 32u); + offset = sensirion_i2c_packet_add_uint32_t(i2c_packet, offset, data & 0xFFFFFFFFu); + return offset; +} + +uint16_t sensirion_i2c_packet_add_int64_t(i2c_packet_t *const i2c_packet, uint16_t offset, + int64_t data) +{ + return sensirion_i2c_packet_add_uint64_t(i2c_packet, offset, (uint64_t)data); +} + +uint16_t sensirion_i2c_packet_add_uint32_t(i2c_packet_t *const i2c_packet, uint16_t offset, + uint32_t data) +{ + offset = sensirion_i2c_packet_add_uint16_t(i2c_packet, offset, data >> 16u); + offset = sensirion_i2c_packet_add_uint16_t(i2c_packet, offset, data & 0xFFFFu); + + return offset; +} + +uint16_t sensirion_i2c_packet_add_int32_t(i2c_packet_t *const i2c_packet, uint16_t offset, + int32_t data) +{ + return sensirion_i2c_packet_add_uint32_t(i2c_packet, offset, (uint32_t)data); +} + +uint16_t sensirion_i2c_packet_add_uint16_t(i2c_packet_t *const i2c_packet, uint16_t offset, + uint16_t data) +{ + sys_put_be16(data, &i2c_packet->data[offset]); + offset += 2; + if (i2c_packet->crc8_poly != 0) { + i2c_packet->data[offset] = + sensirion_i2c_packet_get_crc(i2c_packet, offset - SENSIRION_WORD_SIZE); + offset++; + } + return offset; +} + +uint16_t sensirion_i2c_packet_add_int16_t(i2c_packet_t *const i2c_packet, uint16_t offset, + int16_t data) +{ + return sensirion_i2c_packet_add_uint16_t(i2c_packet, offset, (uint16_t)data); +} + +uint16_t sensirion_i2c_packet_add_float(i2c_packet_t *const i2c_packet, uint16_t offset, float data) +{ + union { + uint32_t uint32_data; + float float_data; + } convert; + + convert.float_data = data; + return sensirion_i2c_packet_add_uint32_t(i2c_packet, offset, convert.uint32_data); +} + +uint16_t sensirion_i2c_packet_add_bytes(i2c_packet_t *const i2c_packet, uint16_t offset, + const uint8_t *const data, uint16_t data_length) +{ + uint16_t i; + + if (data_length % SENSIRION_WORD_SIZE != 0u) { + return -EINVAL; + } + + for (i = 0; i < data_length; i += 2u) { + memcpy(&i2c_packet->data[offset], &data[i], 2u); + offset += 2u; + if (i2c_packet->crc8_poly != 0u) { + i2c_packet->data[offset] = sensirion_i2c_packet_get_crc( + i2c_packet, offset - SENSIRION_WORD_SIZE); + offset++; + } + } + return offset; +} + +int sensirion_i2c_packet_write(const i2c_packet_t *const i2c_packet, uint16_t data_length, + const struct i2c_dt_spec *const i2c_spec) +{ + return i2c_write_dt(i2c_spec, i2c_packet->data, data_length); +} + +int sensirion_i2c_packet_read(const i2c_packet_t *const i2c_packet, uint16_t expected_data_length, + const struct i2c_dt_spec *const i2c_spec) +{ + int ret; + uint16_t i, j; + uint16_t crc_len = (i2c_packet->crc8_poly != 0) ? SENSIRION_CRC8_LEN : 0; + uint16_t size = + (expected_data_length / SENSIRION_WORD_SIZE) * (SENSIRION_WORD_SIZE + crc_len); + + if (expected_data_length % SENSIRION_WORD_SIZE != 0) { + return -EINVAL; + } + + ret = i2c_read_dt(i2c_spec, i2c_packet->data, size); + if (ret != 0) { + return ret; + } + + for (i = 0, j = 0; i < size; i += SENSIRION_WORD_SIZE + crc_len, j += SENSIRION_WORD_SIZE) { + + if (i2c_packet->crc8_poly != 0) { + if (!sensirion_i2c_packet_check_crc(i2c_packet, i)) { + return -EIO; + } + } + memcpy(&i2c_packet->data[j], &i2c_packet->data[i], SENSIRION_WORD_SIZE); + } + + return 0; +} diff --git a/drivers/sensor/sensirion/common/i2c_packet.h b/drivers/sensor/sensirion/common/i2c_packet.h new file mode 100644 index 0000000000000..fbc1bba2e63de --- /dev/null +++ b/drivers/sensor/sensirion/common/i2c_packet.h @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2026 Sensirion + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef I2C_PACKET_H +#define I2C_PACKET_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include + +/** + * @brief Structure to define a Sensirion i2c packet. + * + * Sensirion sensors use 16-bit words on the I2C layer. Each word is usually + * followed by a CRC8 checksum byte. + * The i2c_packet structure is used to hold the data, as + * well as the CRC8 definition and the maximum length of the data array. + * + * The maximum length of the array is used to prevent buffer overflows + * when accessing the data buffer. + */ +typedef struct { + + /** CRC8 definition data */ + uint8_t crc8_poly; //< CRC8 polynomial for calculating the CRC8. + uint8_t crc8_init; //< Initial value for the CRC8 calculation. + + /** Maximum length of data buffer */ + uint8_t max_length; //< Maximum length of the data array. + + /** Pointer to the data buffer */ + uint8_t *data; //< Pointer to the data array. +} i2c_packet_t; + +/** + * @brief Calculate the CRC8 for a word in the i2c_packet at position . + * + * @param i2c_packet Pointer to the i2c_packet containing the data for which the + * CRC8 will be calculated. + * @param index Index of the word in the data array for which the CRC8 will + * be calculated. + * @return uint8_t Calculated CRC8 value. + */ +uint8_t sensirion_i2c_packet_get_crc(const i2c_packet_t *const i2c_packet, uint16_t index); + +/** + * @brief Check the CRC8 for a word in the i2c_packet at position . + * + * @param i2c_packet Pointer to the i2c_packet containing the data for which the + * CRC8 will be checked. + * @param index Index of the word in the data array for which the CRC8 will + * be checked. + * @return true If the CRC8 is valid. + * @return false If the CRC8 is invalid. + */ +bool sensirion_i2c_packet_check_crc(const i2c_packet_t *const i2c_packet, uint16_t index); + +/** + * Add a command to the i2c_packet at the specified offset. + * + * Most sensirion Sensors use 16 bit commands. This function adds a 2 bytes + * to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param command Command to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_command16(i2c_packet_t *const i2c_packet, uint16_t offset, + uint16_t command); + +/** + * Add a command to the i2c_packet at the specified offset. + * + * Adds one byte command to the i2c_packet. + * This is used for sensors that only take one command byte such as SHT. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param command Command to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_command8(i2c_packet_t *const i2c_packet, uint16_t offset, + uint8_t command); + +/** + * Add a uint64_t to the i2c_packet at the specified offset. + * + * Adds 12 bytes to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data uint64_t to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_uint64_t(i2c_packet_t *const i2c_packet, uint16_t offset, + uint64_t data); + +/** + * Add a uint32_t to the i2c_packet at the specified offset. + * + * Adds 6 bytes to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data uint32_t to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_uint32_t(i2c_packet_t *const i2c_packet, uint16_t offset, + uint32_t data); + +/** + * Add a int32_t to the i2c_packet at the specified offset. + * + * Adds 6 bytes to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data int32_t to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_int32_t(i2c_packet_t *const i2c_packet, uint16_t offset, + int32_t data); + +/** + * Add a uint16_t to the i2c_packet at the specified offset. Adds 3 bytes to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data uint16_t to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_uint16_t(i2c_packet_t *const i2c_packet, uint16_t offset, + uint16_t data); + +/** + * Add a int16_t to the i2c_packet at the specified offset. Adds 3 bytes to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data int16_t to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_int16_t(i2c_packet_t *const i2c_packet, uint16_t offset, + int16_t data); + +/** + * Add a float to the i2c_packet at the specified offset. Adds 6 bytes to the i2c_packet. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be prepared. + * Caller needs to make sure that there is enough space after + * offset left to write the data into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data float to be written into the i2c_packet. + * + * @return Offset of next free byte in the i2c_packet after writing the data. + */ +uint16_t sensirion_i2c_packet_add_float(i2c_packet_t *const i2c_packet, uint16_t offset, + float data); + +/** + * Add a byte array to the i2c_packet at the specified offset. + * + * @param i2c_packet Pointer to i2c_packet in which the write frame will be + * prepared. Caller needs to make sure that there is + * enough space after offset left to write the data + * into the i2c_packet. + * @param offset Offset of the next free byte in the i2c_packet. + * @param data Pointer to data to be written into the i2c_packet. + * @param data_length Number of bytes to be written into the i2c_packet. Needs to + * be a multiple of SENSIRION_WORD_SIZE otherwise the + * function returns BYTE_NUM_ERROR. + * + * @return Offset of next free byte in the i2c_packet after writing the + * data. + */ +uint16_t sensirion_i2c_packet_add_bytes(i2c_packet_t *const i2c_packet, uint16_t offset, + const uint8_t *const data, uint16_t data_length); + +/** + * Writes the i2c_packet to the Sensor. + * + * @param i2c_packet Pointer to the i2c_packet containing the data to write. + * @param data_length Number of bytes to send to the Sensor. + * @param i2c_spec Sensor I2C specification + * + * @return 0 on success, error code otherwise + */ +int sensirion_i2c_packet_write(const i2c_packet_t *const i2c_packet, uint16_t data_length, + const struct i2c_dt_spec *const i2c_spec); + +/** + * Reads data from the Sensor. + * + * @param i2c_packet Pointer to i2c_packet to store data as bytes. Needs + * to be big enough to store the data including + * CRC. Twice the size of data should always + * suffice. + * @param expected_data_length Number of bytes to read (without CRC). Needs + * to be a multiple of SENSIRION_WORD_SIZE, + * otherwise the function returns BYTE_NUM_ERROR. + * @param i2c_spec Sensor I2C specification + * + * @return 0 on success, an error code otherwise + */ +int sensirion_i2c_packet_read(const i2c_packet_t *const i2c_packet, uint16_t expected_data_length, + const struct i2c_dt_spec *const i2c_spec); + +#ifdef __cplusplus +} +#endif + +#endif /* I2C_PACKET_H */