Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(pipette): Detect mount at boot and assign node id #246

Merged
merged 10 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions bootloader/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ add_library(bootloader-core
util.c
message_handler.c
update_state.c
pipette_node_id.c
${IDS_HPP}
)

Expand Down
13 changes: 13 additions & 0 deletions bootloader/core/pipette_node_id.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#include "bootloader/core/node_id.h"

#define A_CARRIER_MIN_VOLTAGE_MV (2350)
#define A_CARRIER_MAX_VOLTAGE_MV (2900)


CANNodeId determine_pipette_node_id(
uint16_t mount_voltage_mv) {
if ((mount_voltage_mv > A_CARRIER_MIN_VOLTAGE_MV) && (mount_voltage_mv < A_CARRIER_MAX_VOLTAGE_MV)) {
return can_nodeid_pipette_right_bootloader;
}
return can_nodeid_pipette_left_bootloader;
}
1 change: 0 additions & 1 deletion bootloader/firmware/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ set(CAN_EXECUTABLE_DIR "${CMAKE_SOURCE_DIR}/can/firmware")
# Add source files that should be checked by clang-tidy here
set(BOOTLOADER_FW_LINTABLE_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/main.c
${CMAKE_CURRENT_SOURCE_DIR}/node_id.c
${CMAKE_CURRENT_SOURCE_DIR}/version.c
${CMAKE_CURRENT_SOURCE_DIR}/updater.c
)
Expand Down
3 changes: 2 additions & 1 deletion bootloader/firmware/stm32G4/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ set(BOOTLOADER_G4FW_NON_LINTABLE_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/system_stm32g4xx.c
${CMAKE_CURRENT_SOURCE_DIR}/stm32g4xx_it.c
${CMAKE_CURRENT_SOURCE_DIR}/clocking.c
${CMAKE_CURRENT_SOURCE_DIR}/can.c)
${CMAKE_CURRENT_SOURCE_DIR}/can.c
${CMAKE_CURRENT_SOURCE_DIR}/node_id_stm32g4xx.c)

macro(target_stm32G4 TARGET)
## todo: The bootloader project should have its own linker files. It cannot share the other subproject's
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@
* @return Node id.
*/
CANNodeId get_node_id(void) {
#if defined node_id_pipette_left
// TODO make this decision based on the mount sense voltage
return can_nodeid_pipette_left_bootloader;
#elif defined node_id_head
#if defined node_id_head
return can_nodeid_head_bootloader;
#elif defined node_id_gantry_x
return can_nodeid_gantry_x_bootloader;
Expand Down
7 changes: 4 additions & 3 deletions bootloader/firmware/stm32L5/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ set(BOOTLOADER_L5_FW_NON_LINTABLE_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/system_stm32l5xx.c
${CMAKE_CURRENT_SOURCE_DIR}/stm32l5xx_it.c
${CMAKE_CURRENT_SOURCE_DIR}/clocking.c
${CMAKE_CURRENT_SOURCE_DIR}/can.c)
${CMAKE_CURRENT_SOURCE_DIR}/can.c
${CMAKE_CURRENT_SOURCE_DIR}/node_id_stm32l5xx.c)


macro(target_stm32L5 TARGET)
Expand Down Expand Up @@ -83,7 +84,7 @@ macro(target_stm32L5 TARGET)

# Runs openocd to flash the board (without using a debugger)
add_custom_target(${TARGET}-flash
COMMAND "${OpenOCD_EXECUTABLE}" "-f" "${COMMON_EXECUTABLE_DIR}/STM32G491RETx/stm32g4discovery.cfg" "-c" "program $<TARGET_FILE:${TARGET}>;reset;exit"
COMMAND "${OpenOCD_EXECUTABLE}" "-f" "${COMMON_EXECUTABLE_DIR}/STM32L562RETx/stm32l5discovery.cfg" "-c" "program $<TARGET_FILE:${TARGET}>;reset;exit"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🙇

VERBATIM
COMMENT "Flashing board"
DEPENDS ${TARGET})
Expand All @@ -95,7 +96,7 @@ add_executable(bootloader-pipettes
${BOOTLOADER_FW_NON_LINTABLE_SRCS}
${BOOTLOADER_L5_FW_NON_LINTABLE_SRCS})

target_compile_definitions(bootloader-pipettes PUBLIC node_id_pipette_left)
target_compile_definitions(bootloader-pipettes PUBLIC node_id_pipette_dynamic)

target_stm32L5(bootloader-pipettes)

Expand Down
116 changes: 116 additions & 0 deletions bootloader/firmware/stm32L5/node_id_stm32l5xx.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include <stdbool.h>
#include <stdint.h>
#include "bootloader/core/node_id.h"
#include "stm32l5xx_hal.h"

#define ADC_MAX_VAL 4095
#define ADC_REF_MV 3300
static CANNodeId dynamic_id_backing = can_nodeid_broadcast;
static ADC_HandleTypeDef hadc;

static bool MX_ADC1_Init(void) {
/** Common config
*/
hadc.Instance = ADC1;
hadc.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc.Init.Resolution = ADC_RESOLUTION_12B;
hadc.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc.Init.ScanConvMode = ADC_SCAN_DISABLE;
hadc.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
hadc.Init.LowPowerAutoWait = DISABLE;
hadc.Init.ContinuousConvMode = DISABLE;
hadc.Init.NbrOfConversion = 1;
hadc.Init.DiscontinuousConvMode = DISABLE;
hadc.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc.Init.DMAContinuousRequests = DISABLE;
hadc.Init.Overrun = ADC_OVR_DATA_PRESERVED;
hadc.Init.OversamplingMode = DISABLE;
return HAL_OK == HAL_ADC_Init(&hadc);
}


/**
* @brief ADC MSP Initialization
* This function configures the hardware resources used in this example
* @param hadc: ADC handle pointer
* @retval None
*/
void HAL_ADC_MspInit(ADC_HandleTypeDef* which_adc) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
if (which_adc->Instance == ADC1) {
/* Peripheral clock enable */
__HAL_RCC_ADC_CLK_ENABLE();

__HAL_RCC_GPIOB_CLK_ENABLE();

GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_ANALOG;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
}

static bool adc_init(void) {
if (!MX_ADC1_Init()) {
return false;
}
if (HAL_ADCEx_Calibration_Start(&hadc, ADC_SINGLE_ENDED) != HAL_OK) {
/* Calibration Error */
return false;
}
ADC_ChannelConfTypeDef sConfig = {0};
// Configure channel 16 (PB1) for single ended long duration read on
// the tool ID pin
sConfig.Channel = ADC_CHANNEL_16;
sConfig.Rank = ADC_REGULAR_RANK_1;
sConfig.SamplingTime = ADC_SAMPLETIME_640CYCLES_5;
sConfig.SingleDiff = ADC_SINGLE_ENDED;
sConfig.OffsetNumber = ADC_OFFSET_NONE;
sConfig.Offset = 0;
return HAL_OK == HAL_ADC_ConfigChannel(&hadc, &sConfig);
}


static uint16_t adc_read(void) {
HAL_ADC_Start(&hadc);
HAL_ADC_PollForConversion(&hadc, HAL_MAX_DELAY);
return HAL_ADC_GetValue(&hadc);
}

static uint16_t adc_convert(uint16_t adc_raw_reading) {
return ((((uint32_t)adc_raw_reading) * ADC_REF_MV) / ADC_MAX_VAL);
}


static CANNodeId update_dynamic_nodeid() {
if (!adc_init()) {
return can_nodeid_pipette_left_bootloader;
}
return determine_pipette_node_id(
adc_convert(adc_read()));
}

static CANNodeId get_dynamic_nodeid() {
if (dynamic_id_backing == can_nodeid_broadcast) {
dynamic_id_backing = update_dynamic_nodeid();
}
return dynamic_id_backing;
}


/**
* Get the node id this bootloader is installed on
* @return Node id.
*/
CANNodeId get_node_id(void) {
#if defined(node_id_pipette_left)
return can_nodeid_pipette_left_bootloader;
#elif defined(node_id_pipette_right)
return can_nodeid_pipette_right_bootloader
#elif defined(node_id_pipette_dynamic)
return get_dynamic_nodeid();
#else
#error "No node id"
#endif
}
71 changes: 71 additions & 0 deletions bootloader/tests/test_tool_detection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "bootloader/core/node_id.h"
#include "common/core/tool_detection.hpp"

SCENARIO("Testing bootloader and application mount ID consistency") {
GIVEN("The boundaries of valid mount voltages for Z") {
auto z_mounts = tool_detection::lookup_table_filtered(
tool_detection::Carrier::Z_CARRIER);
WHEN("testing the values for each mount") {
for (auto tool : z_mounts) {
THEN("the lower bound inclusive value matches") {
auto to_check = tool.bounds.lower;
REQUIRE(determine_pipette_node_id(to_check) ==
can_nodeid_pipette_left_bootloader);
}
THEN("the upper bound under 1 matches") {
auto to_check = tool.bounds.upper - 1;
REQUIRE(determine_pipette_node_id(to_check) ==
can_nodeid_pipette_left_bootloader);
}
THEN("the midpoint value matches") {
auto to_check = (tool.bounds.upper + tool.bounds.lower) / 2;
REQUIRE(determine_pipette_node_id(to_check) ==
can_nodeid_pipette_left_bootloader);
}
}
}
}
GIVEN("The boundaries of valid mount voltages for A") {
auto a_mounts = tool_detection::lookup_table_filtered(
tool_detection::Carrier::A_CARRIER);
WHEN("testing the values for each mount") {
for (auto tool : a_mounts) {
THEN("the lower bound inclusive value matches") {
auto to_check = tool.bounds.lower;
REQUIRE(determine_pipette_node_id(to_check) ==
can_nodeid_pipette_right_bootloader);
}
THEN("the upper bound under 1 matches") {
auto to_check = tool.bounds.upper - 1;
REQUIRE(determine_pipette_node_id(to_check) ==
can_nodeid_pipette_right_bootloader);
}
THEN("the midpoint value matches") {
auto to_check = (tool.bounds.upper + tool.bounds.lower) / 2;
REQUIRE(determine_pipette_node_id(to_check) ==
can_nodeid_pipette_right_bootloader);
}
}
}
}
GIVEN("Arbitrary mount voltages matching neither A nor Z") {
auto values_to_check = GENERATE(3250, 10, 3000, 900);
WHEN("testing the values") {
auto left_check = tool_detection::lookup_table_filtered(
tool_detection::Carrier::Z_CARRIER);
auto right_check = tool_detection::lookup_table_filtered(
tool_detection::Carrier::A_CARRIER);
// Make sure this is still a hole
CHECK(tool_detection::carrier_from_reading(values_to_check,
left_check) ==
tool_detection::Carrier::UNKOWN);
CHECK(tool_detection::carrier_from_reading(values_to_check,
right_check) ==
tool_detection::Carrier::UNKOWN);
THEN("the node id should default to left") {
REQUIRE(determine_pipette_node_id(values_to_check) ==
can_nodeid_pipette_left_bootloader);
}
}
}
}
1 change: 1 addition & 0 deletions common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
add_subdirectory(core)
if (${CMAKE_CROSSCOMPILING})
add_subdirectory(firmware)
else()
Expand Down
19 changes: 19 additions & 0 deletions common/core/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
add_library(common-core STATIC tool_detection.cpp)
target_include_directories(common-core PUBLIC ${CMAKE_SOURCE_DIR}/include)
target_link_libraries(common-core PUBLIC can-core)

set_target_properties(common-core
PROPERTIES CXX_STANDARD 20
CXX_STANDARD_REQUIRED TRUE)

target_compile_options(common-core
PRIVATE
-Wall
-Werror
-Weffc++
-Wreorder
-Wsign-promo
-Wextra-semi
-Wctor-dtor-privacy
-fno-rtti
)
74 changes: 74 additions & 0 deletions common/core/tool_detection.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
#include "common/core/tool_detection.hpp"

#include <array>
#include <span>

using namespace tool_detection;

constexpr static auto pipette_384_chan_z_bounds =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought we were only going to be detecting differences between gripper vs pipette vs no instrument attached

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have the means to figure out the type of pipette -this sheet shows the different expected voltages

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Laura-Danielle we'll probably go that direction eventually but this part was already implemented so I stuck with it

ToolCheckBounds{.upper = 1883, .lower = 1851};

constexpr static auto pipette_96_chan_z_bounds =
ToolCheckBounds{.upper = 1434, .lower = 1400};

constexpr static auto pipette_single_chan_z_bounds =
ToolCheckBounds{.upper = 460, .lower = 444};

constexpr static auto pipette_multiple_chan_z_bounds =
ToolCheckBounds{.upper = 945, .lower = 918};

constexpr static auto pipette_single_chan_a_bounds =
ToolCheckBounds{.upper = 2389, .lower = 2362};

constexpr static auto pipette_multiple_chan_a_bounds =
ToolCheckBounds{.upper = 2860, .lower = 2844};

constexpr static auto nothing_connected_z_bounds =
ToolCheckBounds{.upper = 3, .lower = 1};

constexpr static auto nothing_connected_a_bounds =
ToolCheckBounds{.upper = 54, .lower = 16};

// revisit these, not sure if EE has a calculation for gripper carrier bounds
constexpr static auto nothing_connected_gripper_bounds =
ToolCheckBounds{.upper = 54, .lower = 16};

constexpr static auto gripper_bounds =
ToolCheckBounds{.upper = 999, .lower = 536};

constexpr static auto undefined_bounds =
ToolCheckBounds{.upper = 0, .lower = 0};

static std::array tool_list(std::to_array<Tool>(
{Tool{.tool_type = can_ids::ToolType::pipette_384_chan,
.tool_carrier = Z_CARRIER,
.bounds = pipette_384_chan_z_bounds},
Tool{.tool_type = can_ids::ToolType::pipette_96_chan,
.tool_carrier = Z_CARRIER,
.bounds = pipette_96_chan_z_bounds},
Tool{.tool_type = can_ids::ToolType::pipette_single_chan,
.tool_carrier = Z_CARRIER,
.bounds = pipette_single_chan_z_bounds},
Tool{.tool_type = can_ids::ToolType::pipette_multi_chan,
.tool_carrier = Z_CARRIER,
.bounds = pipette_multiple_chan_z_bounds},
Tool{.tool_type = can_ids::ToolType::nothing_attached,
.tool_carrier = Z_CARRIER,
.bounds = nothing_connected_z_bounds},
Tool{.tool_type = can_ids::ToolType::pipette_single_chan,
.tool_carrier = A_CARRIER,
.bounds = pipette_single_chan_a_bounds},
Tool{.tool_type = can_ids::ToolType::pipette_multi_chan,
.tool_carrier = A_CARRIER,
.bounds = pipette_multiple_chan_a_bounds},
Tool{.tool_type = can_ids::ToolType::nothing_attached,
.tool_carrier = A_CARRIER,
.bounds = nothing_connected_a_bounds},
Tool{.tool_type = can_ids::ToolType::gripper,
.tool_carrier = GRIPPER_CARRIER,
.bounds = gripper_bounds},
Tool{.tool_type = can_ids::ToolType::nothing_attached,
.tool_carrier = GRIPPER_CARRIER,
.bounds = nothing_connected_gripper_bounds}}));

std::span<Tool> tool_detection::lookup_table() { return std::span(tool_list); }
4 changes: 3 additions & 1 deletion common/tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ add_executable(
test_bit_utils.cpp
test_synchronization.cpp
test_spi.cpp
test_tool_detection.cpp
test_voltage_conversion.cpp
)

target_include_directories(common PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/../../include)
Expand All @@ -28,7 +30,7 @@ target_compile_options(common
-Wextra-semi
-Wctor-dtor-privacy
-fno-rtti)
target_link_libraries(common Catch2::Catch2 common-simulation)
target_link_libraries(common Catch2::Catch2 common-simulation common-core)

catch_discover_tests(common)
add_build_and_test_target(common)
Loading