-
Notifications
You must be signed in to change notification settings - Fork 2
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
Changes from all commits
c5d1ac5
fe57daa
323ff6d
14bd9e8
4f0eb36
f1f5777
72833f3
1861969
d3dd1e4
099fb49
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ add_library(bootloader-core | |
util.c | ||
message_handler.c | ||
update_state.c | ||
pipette_node_id.c | ||
${IDS_HPP} | ||
) | ||
|
||
|
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; | ||
} |
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 | ||
} |
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); | ||
} | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
add_subdirectory(core) | ||
if (${CMAKE_CROSSCOMPILING}) | ||
add_subdirectory(firmware) | ||
else() | ||
|
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 | ||
) |
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 = | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🙇