diff --git a/libraries/ms-drivers/inc/generic_gpio.h b/libraries/ms-drivers/inc/generic_gpio.h new file mode 100644 index 000000000..4cb373e5c --- /dev/null +++ b/libraries/ms-drivers/inc/generic_gpio.h @@ -0,0 +1,52 @@ +#pragma once +// Generic GPIO interface used when interfacing with multiple GPIO expanders and/or the onboard GPIO +// pins. Requires the GPIO type to be used to be initialized, as well as any other type-specific +// requirements like I2C. +#include "gpio.h" +#include "mcp23008_gpio_expander.h" +#include "pca9539r_gpio_expander.h" + +// Specific GPIO types to be accessed through generic GPIO. +typedef enum { + GEN_GPIO_TYPE_GPIO = 0, + GEN_GPIO_TYPE_PCA9539R, + GEN_GPIO_TYPE_MCP23008, + NUM_GEN_GPIO_TYPES, +} GenGpioType; + +// Possible generic GPIO states. +typedef enum { + GEN_GPIO_STATE_LOW = 0, + GEN_GPIO_STATE_HIGH, + NUM_GEN_GPIO_STATES, +} GenGpioState; + +// Generic GPIO address. Holds a pointer to the correct type and stores the correct type +// to be used later. +typedef struct { + GenGpioType type; + GpioAddress *gpio_addr; + Pca9539rGpioAddress *pca_addr; + Mcp23008GpioAddress *mcp_addr; +} GenGpioAddress; + +// Initialize the given onboard pin and configure the generic address with it. +StatusCode generic_gpio_init_gpio_pin(const GpioAddress *address, const GpioSettings *settings, + GenGpioAddress *gen_addr); + +// Initialize the given PCA9539R pin and configure the generic address with it. +StatusCode generic_gpio_init_pca9539r(const Pca9539rGpioAddress *address, + const Pca9539rGpioSettings *settings, + GenGpioAddress *gen_addr); + +// Initialize the given MCP23008 pin and configure the generic address with it. +StatusCode generic_gpio_init_mcp23008(const Mcp23008GpioAddress *address, + const Mcp23008GpioSettings *settings, + GenGpioAddress *gen_addr); + +// WARNING: the below functions currently cast between different state types, which assumes that LOW +// = 0 and HIGH = 1. Routes to the set function for address. +StatusCode generic_gpio_set_state(const GenGpioAddress *address, GenGpioState state); + +// Routes to the get function for address. +StatusCode generic_gpio_get_state(const GenGpioAddress *address, GenGpioState *state); diff --git a/libraries/ms-drivers/rules.mk b/libraries/ms-drivers/rules.mk index 5f4d867ff..a29e4855a 100644 --- a/libraries/ms-drivers/rules.mk +++ b/libraries/ms-drivers/rules.mk @@ -20,3 +20,4 @@ endif $(T)_test_bts7200_load_switch_MOCKS := adc_read_converted_pin $(T)_test_bts7040_load_switch_MOCKS := adc_read_converted_pin $(T)_test_voltage_regulator_MOCKS := gpio_get_state +$(T)_test_generic_gpio_MOCKS := gpio_set_state gpio_get_state pca9539r_gpio_set_state pca9539r_gpio_get_state mcp23008_gpio_set_state mcp23008_gpio_get_state diff --git a/libraries/ms-drivers/src/generic_gpio.c b/libraries/ms-drivers/src/generic_gpio.c new file mode 100644 index 000000000..a87b135c3 --- /dev/null +++ b/libraries/ms-drivers/src/generic_gpio.c @@ -0,0 +1,56 @@ +#include "generic_gpio.h" + +#include "log.h" +#include "status.h" + +StatusCode generic_gpio_init_gpio_pin(const GpioAddress *address, const GpioSettings *settings, + GenGpioAddress *gen_addr) { + status_ok_or_return(gpio_init_pin(address, settings)); + gen_addr->type = GEN_GPIO_TYPE_GPIO; + gen_addr->gpio_addr = address; + return STATUS_CODE_OK; +} + +StatusCode generic_gpio_init_pca9539r(const Pca9539rGpioAddress *address, + const Pca9539rGpioSettings *settings, + GenGpioAddress *gen_addr) { + status_ok_or_return(pca9539r_gpio_init_pin(address, settings)); + gen_addr->type = GEN_GPIO_TYPE_PCA9539R; + gen_addr->pca_addr = address; + return STATUS_CODE_OK; +} + +StatusCode generic_gpio_init_mcp23008(const Mcp23008GpioAddress *address, + const Mcp23008GpioSettings *settings, + GenGpioAddress *gen_addr) { + status_ok_or_return(mcp23008_gpio_init_pin(address, settings)); + gen_addr->type = GEN_GPIO_TYPE_MCP23008; + gen_addr->mcp_addr = address; + return STATUS_CODE_OK; +} + +StatusCode generic_gpio_set_state(const GenGpioAddress *address, GenGpioState state) { + switch (address->type) { + case GEN_GPIO_TYPE_GPIO: + return gpio_set_state(address->gpio_addr, state); + case GEN_GPIO_TYPE_PCA9539R: + return pca9539r_gpio_set_state(address->pca_addr, state); + case GEN_GPIO_TYPE_MCP23008: + return mcp23008_gpio_set_state(address->mcp_addr, state); + default: + return STATUS_CODE_UNREACHABLE; + } +} + +StatusCode generic_gpio_get_state(const GenGpioAddress *address, GenGpioState *state) { + switch (address->type) { + case GEN_GPIO_TYPE_GPIO: + return gpio_get_state(address->gpio_addr, (GpioState *)state); + case GEN_GPIO_TYPE_PCA9539R: + return pca9539r_gpio_get_state(address->pca_addr, (Pca9539rGpioState *)state); + case GEN_GPIO_TYPE_MCP23008: + return mcp23008_gpio_get_state(address->mcp_addr, (Mcp23008GpioState *)state); + default: + return STATUS_CODE_UNREACHABLE; + } +} diff --git a/libraries/ms-drivers/test/test_generic_gpio.c b/libraries/ms-drivers/test/test_generic_gpio.c new file mode 100644 index 000000000..9c81b2c24 --- /dev/null +++ b/libraries/ms-drivers/test/test_generic_gpio.c @@ -0,0 +1,231 @@ +#include "controller_board_pins.h" +#include "generic_gpio.h" +#include "log.h" +#include "ms_test_helpers.h" + +#define TEST_I2C_PORT I2C_PORT_2 +#define TEST_I2C_ADDRESS 0x74 + +// For testing gpio_set_state +static GpioAddress s_test_gpio_set_addr; +static GpioState s_test_gpio_set_state; + +StatusCode TEST_MOCK(gpio_set_state)(const GpioAddress *address, GpioState state) { + LOG_DEBUG("gpio_set_state called\n"); + s_test_gpio_set_addr = *address; + s_test_gpio_set_state = state; + return STATUS_CODE_OK; +} + +// For testing gpio_get_state +static GpioState s_test_gpio_get_state; + +StatusCode TEST_MOCK(gpio_get_state)(const GpioAddress *address, GpioState *input_state) { + LOG_DEBUG("gpio_get_state called\n"); + *input_state = s_test_gpio_get_state; + return STATUS_CODE_OK; +} + +// For testing pca9539r_gpio_set_state +static Pca9539rGpioAddress s_test_pca_set_addr; +static Pca9539rGpioState s_test_pca_set_state; + +StatusCode TEST_MOCK(pca9539r_gpio_set_state)(const Pca9539rGpioAddress *address, + Pca9539rGpioState input_state) { + LOG_DEBUG("pca9539r_set_state called\n"); + s_test_pca_set_addr = *address; + s_test_pca_set_state = input_state; + return STATUS_CODE_OK; +} + +// For testing pca9539r_gpio_get_state +static Pca9539rGpioState s_test_pca_get_state; + +StatusCode TEST_MOCK(pca9539r_gpio_get_state)(const Pca9539rGpioAddress *address, + Pca9539rGpioState *input_state) { + LOG_DEBUG("pca9539r_gpio_get_state called\n"); + *input_state = s_test_pca_get_state; + return STATUS_CODE_OK; +} + +// For testing mcp23008_gpio_set_state +static Mcp23008GpioAddress s_test_mcp_set_addr; +static Mcp23008GpioState s_test_mcp_set_state; + +StatusCode TEST_MOCK(mcp23008_gpio_set_state)(const Mcp23008GpioAddress *address, + const Mcp23008GpioState state) { + LOG_DEBUG("mcp23008_gpio_set_state called\n"); + s_test_mcp_set_addr = *address; + s_test_mcp_set_state = state; + return STATUS_CODE_OK; +} + +// For testing mcp23008_gpio_get_state +static Mcp23008GpioState s_test_mcp_get_state; + +StatusCode TEST_MOCK(mcp23008_gpio_get_state)(const Mcp23008GpioAddress *address, + Mcp23008GpioState *input_state) { + LOG_DEBUG("mcp23008_gpio_get_state called\n"); + *input_state = s_test_mcp_get_state; + return STATUS_CODE_OK; +} + +void setup_test(void) { + gpio_init(); + + I2CSettings i2c_settings = { + .speed = I2C_SPEED_FAST, // + .sda = CONTROLLER_BOARD_ADDR_I2C2_SDA, // + .scl = CONTROLLER_BOARD_ADDR_I2C2_SCL, // + }; + i2c_init(TEST_I2C_PORT, &i2c_settings); + + pca9539r_gpio_init(TEST_I2C_PORT, TEST_I2C_ADDRESS); + + mcp23008_gpio_init(TEST_I2C_PORT, TEST_I2C_ADDRESS); +} + +void teardown_test(void) {} + +// Make sure gpio pins initialize OK. +void test_gpio_init(void) { + GpioAddress test_addr = { + .port = GPIO_PORT_A, + .pin = 0, + }; + GpioSettings test_settings = { 0 }; + GenGpioAddress gen_addr = { 0 }; + + TEST_ASSERT_OK(generic_gpio_init_gpio_pin(&test_addr, &test_settings, &gen_addr)); +} + +// Initialize and set pin to high/low. Make sure calls get routed correctly. +void test_gpio_general_operation(void) { + GpioAddress test_addr = { + .port = GPIO_PORT_A, + .pin = 0, + }; + GpioSettings test_settings = { 0 }; + GenGpioAddress gen_addr = { 0 }; + + TEST_ASSERT_OK(generic_gpio_init_gpio_pin(&test_addr, &test_settings, &gen_addr)); + + // Set state + TEST_ASSERT_OK(generic_gpio_set_state(&gen_addr, GEN_GPIO_STATE_HIGH)); + TEST_ASSERT_EQUAL(GPIO_STATE_HIGH, s_test_gpio_set_state); + TEST_ASSERT_EQUAL(test_addr.port, s_test_gpio_set_addr.port); + TEST_ASSERT_EQUAL(test_addr.pin, s_test_gpio_set_addr.pin); + + TEST_ASSERT_OK(generic_gpio_set_state(&gen_addr, GEN_GPIO_STATE_LOW)); + TEST_ASSERT_EQUAL(GPIO_STATE_LOW, s_test_gpio_set_state); + TEST_ASSERT_EQUAL(test_addr.port, s_test_gpio_set_addr.port); + TEST_ASSERT_EQUAL(test_addr.pin, s_test_gpio_set_addr.pin); + + // Get state + s_test_gpio_get_state = GPIO_STATE_HIGH; + GenGpioState test_state = GEN_GPIO_STATE_LOW; + TEST_ASSERT_OK(generic_gpio_get_state(&gen_addr, &test_state)); + TEST_ASSERT_EQUAL(test_state, GEN_GPIO_STATE_HIGH); +} + +// Make sure PCA9539R pins initialize OK. +void test_pca_init(void) { + Pca9539rGpioSettings test_settings = { + .direction = PCA9539R_GPIO_DIR_OUT, + .state = PCA9539R_GPIO_STATE_LOW, + }; + + Pca9539rGpioAddress test_addr = { + .i2c_address = TEST_I2C_ADDRESS, + .pin = PCA9539R_PIN_IO0_0, + }; + + GenGpioAddress gen_addr = { 0 }; + + TEST_ASSERT_OK(generic_gpio_init_pca9539r(&test_addr, &test_settings, &gen_addr)); +} + +// Initialize and set pin to high/low for pca9539r. Make sure calls get routed correctly. +void test_pca_general_operation(void) { + Pca9539rGpioSettings test_settings = { + .direction = PCA9539R_GPIO_DIR_OUT, + .state = PCA9539R_GPIO_STATE_LOW, + }; + + Pca9539rGpioAddress test_addr = { + .i2c_address = TEST_I2C_ADDRESS, + .pin = PCA9539R_PIN_IO0_0, + }; + + GenGpioAddress gen_addr = { 0 }; + + TEST_ASSERT_OK(generic_gpio_init_pca9539r(&test_addr, &test_settings, &gen_addr)); + + // Set state + TEST_ASSERT_OK(generic_gpio_set_state(&gen_addr, GEN_GPIO_STATE_HIGH)); + TEST_ASSERT_EQUAL(PCA9539R_GPIO_STATE_HIGH, s_test_pca_set_state); + TEST_ASSERT_EQUAL(test_addr.i2c_address, s_test_pca_set_addr.i2c_address); + TEST_ASSERT_EQUAL(test_addr.pin, s_test_pca_set_addr.pin); + + TEST_ASSERT_OK(generic_gpio_set_state(&gen_addr, GEN_GPIO_STATE_LOW)); + TEST_ASSERT_EQUAL(PCA9539R_GPIO_STATE_LOW, s_test_pca_set_state); + TEST_ASSERT_EQUAL(test_addr.i2c_address, s_test_pca_set_addr.i2c_address); + TEST_ASSERT_EQUAL(test_addr.pin, s_test_pca_set_addr.pin); + + // Get state + s_test_pca_get_state = PCA9539R_GPIO_STATE_HIGH; + GenGpioState test_state = GEN_GPIO_STATE_LOW; + TEST_ASSERT_OK(generic_gpio_get_state(&gen_addr, &test_state)); + TEST_ASSERT_EQUAL(GEN_GPIO_STATE_HIGH, test_state); +} + +// Make sure MCP23008 pins initialize OK. +void test_mcp_init(void) { + Mcp23008GpioSettings test_settings = { + .direction = MCP23008_GPIO_DIR_OUT, + .state = MCP23008_GPIO_STATE_LOW, + }; + + Mcp23008GpioAddress test_addr = { + .i2c_address = TEST_I2C_ADDRESS, + .pin = 0, + }; + + GenGpioAddress gen_addr = { 0 }; + + TEST_ASSERT_OK(generic_gpio_init_mcp23008(&test_addr, &test_settings, &gen_addr)); +} + +// Initialize and set pin to high/low for MCP23008. Make sure calls get routed correctly. +void test_mcp_general_operation(void) { + Mcp23008GpioSettings test_settings = { + .direction = MCP23008_GPIO_DIR_OUT, + .state = MCP23008_GPIO_STATE_LOW, + }; + + Mcp23008GpioAddress test_addr = { + .i2c_address = TEST_I2C_ADDRESS, + .pin = 0, + }; + + GenGpioAddress gen_addr = { 0 }; + + TEST_ASSERT_OK(generic_gpio_init_mcp23008(&test_addr, &test_settings, &gen_addr)); + + // Set state + TEST_ASSERT_OK(generic_gpio_set_state(&gen_addr, GEN_GPIO_STATE_HIGH)); + TEST_ASSERT_EQUAL(MCP23008_GPIO_STATE_HIGH, s_test_mcp_set_state); + TEST_ASSERT_EQUAL(test_addr.i2c_address, s_test_mcp_set_addr.i2c_address); + TEST_ASSERT_EQUAL(test_addr.pin, s_test_mcp_set_addr.pin); + + TEST_ASSERT_OK(generic_gpio_set_state(&gen_addr, GEN_GPIO_STATE_LOW)); + TEST_ASSERT_EQUAL(MCP23008_GPIO_STATE_LOW, s_test_mcp_set_state); + TEST_ASSERT_EQUAL(test_addr.i2c_address, s_test_mcp_set_addr.i2c_address); + TEST_ASSERT_EQUAL(test_addr.pin, s_test_mcp_set_addr.pin); + + // Get state + s_test_mcp_get_state = MCP23008_GPIO_STATE_HIGH; + GenGpioState test_state = GEN_GPIO_STATE_LOW; + TEST_ASSERT_OK(generic_gpio_get_state(&gen_addr, &test_state)); + TEST_ASSERT_EQUAL(GEN_GPIO_STATE_HIGH, test_state); +}