diff --git a/code/science/implementations/scd40.cpp b/code/science/implementations/scd40.cpp new file mode 100644 index 0000000..2a8cd90 --- /dev/null +++ b/code/science/implementations/scd40.cpp @@ -0,0 +1,19 @@ +#pragma once + +#include +#include +#include "scd40.hpp" + +scd40::create(hal::i2c& p_i2c, hal::steady_clock& clock){ + scd40 scd40 = scd40(p_i2c, clock); + HAL_CHECK(scd40.start()) + +} + +scd40::start(){} + +scd40::get_CO2{} + +scd40::get_RH(){} + +scd40::get_temp(){} \ No newline at end of file diff --git a/drive/implementations/command_lerper.hpp b/drive/implementations/command_lerper.hpp new file mode 100644 index 0000000..653233c --- /dev/null +++ b/drive/implementations/command_lerper.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include "../dto/drive.hpp" + +namespace sjsu::drive { +class command_lerper +{ +public: + int lerp(int speed) + { + previous_speed = + static_cast(std::lerp(static_cast(previous_speed), + static_cast(speed), + speed_lerp)); + return previous_speed; + } + +private: + static constexpr float speed_lerp = 0.1f; + float previous_speed = 0; // don't touch mode or wheel orientation logic +}; +} // namespace Drive \ No newline at end of file diff --git a/drive/implementations/mode_switcher.hpp b/drive/implementations/mode_switcher.hpp new file mode 100644 index 0000000..b2d0376 --- /dev/null +++ b/drive/implementations/mode_switcher.hpp @@ -0,0 +1,66 @@ +#pragma once + +#include "../dto/drive.hpp" +#include "../dto/motor_feedback.hpp" +#include "../platform-implementations/mission_control.hpp" + +namespace sjsu::drive { +class mode_switch +{ +public: + // Should handle all the logic for switching to new steer mode + mission_control::mc_commands switch_steer_mode( + mission_control::mc_commands commands, + tri_wheel_router_arguments previous_arguments, + motor_feedback current_motor_feedback, + hal::serial& terminal) + { + bool hubs_stopped((previous_arguments.left.speed >= -0.01f && + previous_arguments.left.speed <= 0.01f) && + (previous_arguments.right.speed >= -0.01f && + previous_arguments.right.speed <= 0.01f) && + (previous_arguments.back.speed >= -0.01f && + previous_arguments.back.speed <= 0.01f)); + + bool steers_stopped((current_motor_feedback.left_steer_speed >= -0.01f && + current_motor_feedback.left_steer_speed <= 0.01f) && + (current_motor_feedback.right_steer_speed >= -0.01f && + current_motor_feedback.right_steer_speed <= 0.01f) && + (current_motor_feedback.back_steer_speed >= -0.01f && + current_motor_feedback.back_steer_speed <= 0.01f)); + + if (previous_mode_ != commands.mode) { + in_the_middle_of_switching_modes_ = true; + skip_once_ = true; + } + + if (in_the_middle_of_switching_modes_) { + commands.speed = 0; + if (!hubs_stopped) { + commands.mode = previous_mode_; + return commands; + } // hubs must be stopped to pass here + + else if (!skip_once_ && steers_stopped) { + in_the_middle_of_switching_modes_ = false; + skip_once_ = true; + } // only once steer motors have stopped moving after hubs stopped will + // we exit switching modes + previous_mode_ = commands.mode; + skip_once_ = false; + } + + return commands; + } + +private: + + char previous_mode_ = + 'H'; // This is H for homing when the rover turns on, it makes sure that we + // don't switch to drive mode and allow commands to be parsed through + bool in_the_middle_of_switching_modes_ = true; + bool skip_once_ = true; + +}; + +} // namespace Drive diff --git a/drive/implementations/serial.hpp b/drive/implementations/serial.hpp new file mode 100644 index 0000000..f4c49ca --- /dev/null +++ b/drive/implementations/serial.hpp @@ -0,0 +1,22 @@ +#pragma once + +#include + +#include + +namespace Drive { +inline hal::result get_commands(hal::serial& terminal) +{ + std::array buffer{}; + auto received = HAL_CHECK(terminal.read(buffer)).data; + if (received.size() != 0) { + auto response = std::string_view( + reinterpret_cast(received.data()), received.size()); + hal::print<200>( + terminal, "%.*s", static_cast(response.size()), response.data()); + return response; + } else { + return std::string_view(""); + } +} +} // namespace Drive \ No newline at end of file diff --git a/science/CMakeLists.txt b/science/CMakeLists.txt index c06dd26..ea46984 100644 --- a/science/CMakeLists.txt +++ b/science/CMakeLists.txt @@ -1,20 +1,7 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. cmake_minimum_required(VERSION 3.20) -project(starter_project.elf VERSION 0.0.1 LANGUAGES CXX) +project(science VERSION 0.0.1 LANGUAGES CXX) set(platform_library $ENV{LIBHAL_PLATFORM_LIBRARY}) set(platform $ENV{LIBHAL_PLATFORM}) @@ -26,15 +13,21 @@ endif() find_package(libhal-${platform_library} REQUIRED CONFIG) find_package(libhal-util REQUIRED CONFIG) +find_package(libhal-esp8266 REQUIRED CONFIG) add_executable(${PROJECT_NAME} main.cpp - application.cpp + applications/application.cpp platforms/${platform}.cpp) target_include_directories(${PROJECT_NAME} PUBLIC .) target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) +target_link_options(${PROJECT_NAME} PRIVATE +--oslib=semihost --crt0=minimal) target_link_libraries(${PROJECT_NAME} PRIVATE libhal::${platform_library} - libhal::util) + libhal::util + libhal::esp8266) +# target_link_options(${PROJECT_NAME} INTERFACE "LINKER:--no-warn-rwx-segments") libhal_post_build(${PROJECT_NAME}) + diff --git a/science/application.cpp b/science/application.cpp deleted file mode 100644 index c6e43c2..0000000 --- a/science/application.cpp +++ /dev/null @@ -1,26 +0,0 @@ -#include -#include -#include - -#include "application.hpp" - -hal::status application(application_framework& p_framework) -{ - using namespace std::literals; - - auto& led = *p_framework.led; - auto& clock = *p_framework.clock; - auto& console = *p_framework.console; - - while (true) { - // Print message - hal::print(console, "Hello, World\n"); - - // Toggle LED - HAL_CHECK(led.level(true)); - hal::delay(clock, 500ms); - - HAL_CHECK(led.level(false)); - hal::delay(clock, 500ms); - } -} \ No newline at end of file diff --git a/science/application.hpp b/science/application.hpp deleted file mode 100644 index 6185850..0000000 --- a/science/application.hpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#pragma once - -#include -#include -#include -#include - -struct application_framework -{ - hal::output_pin* led; - hal::serial* console; - hal::steady_clock* clock; - hal::callback reset; -}; - -// Application function must be implemented by one of the compilation units -// (.cpp) files. -hal::status initialize_processor(); -hal::result initialize_platform(); -hal::status application(application_framework& p_framework); diff --git a/science/conanfile.py b/science/conanfile.py index 85342b4..f3ebb7d 100644 --- a/science/conanfile.py +++ b/science/conanfile.py @@ -28,11 +28,13 @@ def build_requirements(self): def requirements(self): # Application requirements - self.requires("libhal-util/[^3.0.0]") self.requires("libhal/[^2.0.3]") + self.requires("libhal-util/[^3.0.1]") + self.requires("libhal-esp8266/[^2.0.1]") + # List of supported platforms if str(self.options.platform).startswith("lpc40"): - self.requires("libhal-lpc40/[^2.0.0]") + self.requires("libhal-lpc40/[^2.1.2]") else: raise ConanInvalidConfiguration( f"The platform '{str(self.options.platform)}' is not" diff --git a/science/demos/CMakeLists.txt b/science/demos/CMakeLists.txt index 8fc537c..82b2fc0 100644 --- a/science/demos/CMakeLists.txt +++ b/science/demos/CMakeLists.txt @@ -20,9 +20,16 @@ libhal_build_demos( DEMOS co2_demo pressure_sensor_demo - + soil_sensor_demo + scd40_demo + color_sensor_demo + led_strip_demo + SOURCES + ../platform-implementations/scd40.cpp ../platform-implementations/pressure_sensor_bme680.cpp + ../platform-implementations/soil_sensor_sht21.cpp + ../platform-implementations/color_sensor_opt4048.cpp PACKAGES libhal-lpc40 diff --git a/science/demos/applications/co2_demo.cpp b/science/demos/applications/co2_demo.cpp new file mode 100644 index 0000000..7714f7f --- /dev/null +++ b/science/demos/applications/co2_demo.cpp @@ -0,0 +1,33 @@ +#include +#include +#include +#include +#include +#include + +#include "../../platform-implementations/co2_sensor_sc40.cpp" +#include "../hardware_map.hpp" + +using namespace hal::literals; +using namespace std::chrono_literals; +namespace sjsu::science { + +hal::status application(application_framework& p_framework) +{ + // configure drivers + auto& i2c2 = *p_framework.i2c; + auto& clock = *p_framework.steady_clock; + auto& terminal = *p_framework.terminal; + + auto co2_sensor = HAL_CHECK(science::co2_sensor::create(i2c2, clock)); + + while (true) { + hal::delay(clock, 500ms); + auto co2_level = HAL_CHECK(co2_sensor.read_co2()); + hal::print<64>(terminal, "C02: %d\n", co2_level); + hal::delay(clock, 1ms); + } + + return hal::success(); +} +} // namespace sjsu::science \ No newline at end of file diff --git a/science/demos/applications/color_sensor_demo.cpp b/science/demos/applications/color_sensor_demo.cpp new file mode 100644 index 0000000..84dbe05 --- /dev/null +++ b/science/demos/applications/color_sensor_demo.cpp @@ -0,0 +1,54 @@ +#include +#include +#include +#include +#include +#include + +#include "../../platform-implementations/color_sensor_opt4048.hpp" +#include "../hardware_map.hpp" + +using namespace hal::literals; +using namespace std::chrono_literals; + +hal::status probe_bus(hal::i2c& i2c, hal::serial& console) { + hal::print(console, "\n\nProbing i2c2\n"); + for(hal::byte addr = 0x08; addr < 0x78; addr++) { + if (hal::probe(i2c, addr)) { + hal::print<8>(console, "0x%02X ", addr); + }else{ + hal::print(console, " -- "); + } + if(addr % 8 == 7) { + hal::print(console, "\n"); + } + } + hal::print(console, "\n"); + + return hal::success(); +} + +namespace sjsu::science { + +hal::status application(application_framework& p_framework) +{ + // configure drivers + auto& i2c2 = *p_framework.i2c; + auto& clock = *p_framework.steady_clock; + auto& terminal = *p_framework.terminal; + + hal::print(terminal, "hi\n"); + HAL_CHECK(probe_bus(i2c2, terminal)); + + //create the stuff here + auto color_sensor = HAL_CHECK(science::opt4048::create(i2c2, clock, terminal)); + while(true){ + hal::print(terminal, "\nnew measurement\n"); + auto readings = HAL_CHECK(color_sensor.get_data()); + hal::print<40>(terminal, "R: %f\t G: %f\t B: %f\n", readings.r, readings.g, readings.b); + hal::delay(clock, 50ms); + } + + return hal::success(); +} +} // namespace sjsu::science \ No newline at end of file diff --git a/science/demos/applications/led_strip_demo.cpp b/science/demos/applications/led_strip_demo.cpp new file mode 100644 index 0000000..d374e6e --- /dev/null +++ b/science/demos/applications/led_strip_demo.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include + +#include +#include "../hardware_map.hpp" +#include "../../platform-implementations/sk9822.hpp" + +using namespace hal::literals; +using namespace std::chrono_literals; + +struct effect_hardware { + hal::light_strip_view lights; + hal::sk9822 *driver; + hal::steady_clock *clock; +}; + +/** + * @brief WEE WOO WEE WOO NYPD! + * + * it just flashes the light. + * + * @param hardware + * @param on_value + * @param off_value + * @param period + * @return hal::status + */ +hal::status beedoo_beedoo_beedoo(effect_hardware hardware, hal::rgb_brightness on_value, hal::rgb_brightness off_value, hal::time_duration period) { + hal::time_duration half_period = period / 2; + while(true) { + hal::light_strip_util::set_all(hardware.lights, on_value); + hardware.driver->update(hardware.lights); + hal::delay(*(hardware.clock), half_period); + hal::light_strip_util::set_all(hardware.lights, off_value); + hardware.driver->update(hardware.lights); + hal::delay(*(hardware.clock), half_period); + } +} + +/** + * @brief Each led should each turn on one after each other and then turn off one after each other + * + * @param hardware + * @return hal::status + */ +hal::status rampup_rampdown(effect_hardware hardware) { + while (true) { + for(auto i = hardware.lights.begin(); i != hardware.lights.end(); i ++) { + *i = hal::colors::WHITE; + hardware.driver->update(hardware.lights); + hal::delay(*hardware.clock, 10ms); + } + for(auto i = hardware.lights.rbegin(); i != hardware.lights.rend(); i ++) { + *i = hal::colors::BLACK; + hardware.driver->update(hardware.lights); + hal::delay(*hardware.clock, 10ms); + } + } +} + +namespace sjsu::science { +hal::status application(application_framework& p_resources) +{ + using namespace std::literals; + + auto& clock = *p_resources.steady_clock; +// auto& console = *p_resources.terminal; + + auto clock_pin = HAL_CHECK(hal::lpc40::output_pin::get(1, 15)); + auto data_pin = HAL_CHECK(hal::lpc40::output_pin::get(1, 23)); + + hal::light_strip<35> lights; + hal::sk9822 driver(clock_pin, data_pin, clock); + hal::light_strip_util::set_all(lights, hal::colors::BLACK); + + hal::rgb_brightness ON, OFF; + ON.set(0xff, 0x00, 0x00, 0b11111); + OFF.set(0, 0, 0, 0); + + effect_hardware hardware; + hardware.clock = &clock; + hardware.lights = lights; + hardware.driver = &driver; + + // HAL_CHECK(beedoo_beedoo_beedoo(hardware, hal::color::red, hal::color::black, 100ms)); + HAL_CHECK(rampup_rampdown(hardware)); + + while (true) { + // Print message + // hal::print(console, "Hello, World\n"); + + // for(int i = 0; i < 35; i ++ ) { + // lights[i].set(0xff, 0xff, 0xff, 0b111); + // lights.update(); + // hal::delay(clock, 10ms); + // hal::print(console, "."); + // } + // for(int i = 34; i >= 0; i -- ) { + // lights[i].set(0x00,0x00,0x00, 0b000); + // lights.update(); + // hal::delay(clock, 10ms); + // hal::print(console, "."); + // } + // hal::print(console, "\n"); + + } +} +}; \ No newline at end of file diff --git a/science/demos/applications/scd40_demo.cpp b/science/demos/applications/scd40_demo.cpp new file mode 100644 index 0000000..f1d05bb --- /dev/null +++ b/science/demos/applications/scd40_demo.cpp @@ -0,0 +1,51 @@ +#include +#include +#include +#include +#include +#include + +#include "../../platform-implementations/scd40.hpp" +#include "../hardware_map.hpp" + +using namespace hal::literals; +using namespace std::chrono_literals; +namespace sjsu::science { + +hal::status application(application_framework& p_framework) +{ + // configure drivers + auto& i2c2 = *p_framework.i2c; + auto& clock = *p_framework.steady_clock; + auto& terminal = *p_framework.terminal; + + auto scd40_sensor = HAL_CHECK(scd40::create(i2c2, clock)); + + while(true){ + //get settings test + // scd40_sensor.stop(); + // auto get = HAL_CHECK(scd40_sensor.get_settings()); + // auto temp = get.temp_offset; + // auto alt = get.altitude; + // hal::print<64>(terminal, "%-5.2f\t%-5.2f\n", temp, alt); + + // periodic readings are only updated every 5000ms (temperature) + hal::delay(clock, 5000ms); + auto rd = HAL_CHECK(scd40_sensor.read()); + auto co2_levels = rd.co2; + auto temp = rd.temp; + auto RH_levels = rd.rh; + + + // hal::print<64>(terminal, "%-5.2f\t%-5.2f\t%-5.2f\n", co2_levels, temp, RH_levels); + // hal::delay(clock, 500ms); + + hal::print<64>(terminal, "CO2 Levels: %f\n", co2_levels); + hal::print<64>(terminal, "Temperature %f\n", temp); + hal::print<64>(terminal, "RH Levels: %f\n", RH_levels); + + } + + return hal::success(); +} +} // namespace sjsu::science \ No newline at end of file diff --git a/science/demos/applications/soil_sensor_demo.cpp b/science/demos/applications/soil_sensor_demo.cpp new file mode 100644 index 0000000..0cf7e39 --- /dev/null +++ b/science/demos/applications/soil_sensor_demo.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "../../platform-implementations/pressure_sensor_bme680.hpp" +#include "../../platform-implementations/soil_sensor_sht21.hpp" +#include "../hardware_map.hpp" + +using namespace hal::literals; +using namespace std::chrono_literals; + + + + hal::result probe( + hal::i2c& p_i2c, + hal::byte p_address, hal::steady_clock& p_steady_clock, hal::time_duration p_duration) { + // p_data_in: empty placeholder for transcation's data_in + std::array data_in; + + // p_timeout: no timeout placeholder for transaction's p_timeout + hal::timeout auto timeout = hal::create_timeout(p_steady_clock, p_duration); + + return p_i2c.transaction(p_address, std::span{}, data_in, timeout); +} + +hal::status probe_bus(hal::i2c& i2c, hal::serial& console, hal::steady_clock& p_steady_clock) { + hal::print(console, "\n\nProbing i2c2\n"); + i2c = i2c; + for(hal::byte addr = 0x08; addr < 0x78; addr++) { + if (probe(i2c, addr, p_steady_clock, 10ms)) { + // if(false){ + hal::print<8>(console, "0x%02X ", addr); + }else{ + hal::print(console, " -- "); + } + if(addr % 8 == 7) { + hal::print(console, "\n"); + } + } + hal::print(console, "\n"); + + return hal::success(); +} + + +// static int get_bit_value(hal::byte value, hal::byte bit_position) { +// return (value >> bit_position) & 0x01; +// } + +// static void print_binary(hal::serial& console, hal::byte val) { +// hal::print<11>(console, "0b%c%c%c%c%c%c%c%c", +// get_bit_value(val, 7) + '0', // '1' if the bit is set, '0' if its not +// get_bit_value(val, 6) + '0', +// get_bit_value(val, 5) + '0', +// get_bit_value(val, 4) + '0', +// get_bit_value(val, 3) + '0', +// get_bit_value(val, 2) + '0', +// get_bit_value(val, 1) + '0', +// get_bit_value(val, 0) + '0'); +// } + +namespace sjsu::science { + +hal::status application(application_framework& p_framework) +{ + // configure drivers + // auto& i2c = *p_framework.i2c; + auto& clock = *p_framework.steady_clock; + auto& console = *p_framework.terminal; + + auto i2c = HAL_CHECK(hal::lpc40::i2c::get(2)); + + hal::print(console, "reset?\n"); + + + HAL_CHECK(probe_bus(i2c, console, clock)); + + hal::result maybe_soil = hal::sht::sht21::create(i2c); + while(maybe_soil.has_error()) { + hal::print(console, "sensor create error :(\n"); + maybe_soil = hal::sht::sht21::create(i2c); + hal::delay(clock, 50ms); + } + + hal::sht::sht21 soil = maybe_soil.value(); + +// int i = 0; + while (true) { + hal::result maybe_temp = soil.get_temperature(); + hal::result maybe_humid = soil.get_relative_humidity(); + if(maybe_temp.has_value()) { + hal::print<32>(console, "Temp: %f\n", maybe_temp.value()); + } + if(maybe_humid.has_value()) { + hal::print<32>(console, "humid: %f\n", maybe_humid.value()); + } + hal::delay(clock, 50ms); + } + + return hal::success(); +} +} // namespace sjsu::science \ No newline at end of file diff --git a/science/demos/conanfile.py b/science/demos/conanfile.py index 7053c1e..8a1f639 100644 --- a/science/demos/conanfile.py +++ b/science/demos/conanfile.py @@ -27,6 +27,7 @@ def build_requirements(self): self.tool_requires("libhal-cmake-util/2.2.0") def requirements(self): + self.requires("libhal/[^2.0.3]") if str(self.options.platform).startswith("lpc40"): self.requires("libhal-lpc40/[^2.1.4]") self.requires("libhal-rmd/3.0.0") diff --git a/science/dto/science_dto.hpp b/science/dto/science_dto.hpp new file mode 100644 index 0000000..5e57fb9 --- /dev/null +++ b/science/dto/science_dto.hpp @@ -0,0 +1,63 @@ +#pragma once +#include //include for print and serial + +namespace sjsu::science { + +// commands are parsed from mission control +//this file is specific to last year's science project, so will be deleted or completely rewritten +//for current science plan +struct science_commands +{ + int state_step = 0; + char mode = 'A'; + int is_operational = 0; +}; + +enum status +{ + not_started = 0, + in_progress = 1, + complete = 2 +}; + +struct science_status +{ + int move_revolver_status = status::not_started; + int seal_status = status::not_started; + int depressurize_status = status::not_started; + int inject_status = status::not_started; + int clear_status = status::not_started; + int unseal_status = status::not_started; + void Print(hal::serial& terminal) + { + hal::print<200>( + terminal, + "move_revolver_status: %d | seal_status: %d | depressurize_status: %d | " + "inject_status: %d | clear_status: %d | unseal_status: %d \n", + move_revolver_status, + seal_status, + depressurize_status, + inject_status, + clear_status, + unseal_status); + } +}; + +// science data is what we send mission control +struct science_data +{ + science_status status; + float methane_level = 0.0; + float co2_level = 0.0; + float pressure_level = 0.0; +}; + +const char kResponseBodyFormat[] = + "{\"state_step\":%d,\"mode\":%c,\"is_operational\":%d}\n"; + +const char kGETRequestFormat[] = + "science?rev_status=%d&seal_status=%d&depressure_status=%d&inj_status=%d&" + "clear_status=%d&" + "unseal_status=%d&methane_level=%d&co2_level=%d&pressure_level=%d"; + +} // namespace science \ No newline at end of file diff --git a/science/main.cpp b/science/main.cpp index 2c5c408..f277390 100644 --- a/science/main.cpp +++ b/science/main.cpp @@ -20,24 +20,24 @@ #include #include -#include "application.hpp" +#include "applications/application.hpp" int main() { // Step 1. Call the processor initializer. Setups up processor to run // applications such as turning on a coprocessor, or copying memory from // storage to memory. - if (!initialize_processor()) { + if (!sjsu::science::initialize_processor()) { hal::halt(); } - auto application_resource = initialize_platform(); + auto application_resource = sjsu::science::initialize_platform(); if (!application_resource) { hal::halt(); } - auto is_finished = application(application_resource.value()); + auto is_finished = sjsu::science::application(application_resource.value()); if (!is_finished) { application_resource.value().reset(); diff --git a/science/platform-implementations/adc_sensor.hpp b/science/platform-implementations/adc_sensor.hpp new file mode 100644 index 0000000..3826da3 --- /dev/null +++ b/science/platform-implementations/adc_sensor.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include +#include +#include + +namespace sjsu::science{ + +/// @brief A Generic ADC Driver. This is designed to be a higher level interface for analog devices. +class generic_adc_sensor { +public: + /// @brief Builds driver object + /// @param adc_data libhal adc pin that has been initialized + /// @param digital_detector Optional digital pin for device that is to goes high when a signal is detected + generic_adc_sensor(hal::adc& adc_data) : adc_data_(adc_data) {} + + + /// @brief Reads the raw voltage value from the ADC pin. + /// Does no unit conversion outside of converting the analog signal to a digital signal. + /// @return The voltage value on the analog data pin. + hal::result read_raw_adc() { + float raw_ratio_average = 0; + int read_count = 10; + for (int i = 0; i < read_count; i++) + { + raw_ratio_average += HAL_CHECK(adc_data_.read()).sample; + } + + raw_ratio_average /= read_count; + + return raw_ratio_average; + } + + /// @brief Processes data into usable units. + /// Implementations of this method should include a mathmatical conversion formula that goes from raw voltage + /// to desired units or units specified in device's datasheet. + /// @return Converted value from voltage to desired units. + virtual hal::result get_parsed_data() = 0; + +protected: + hal::adc& adc_data_; +}; +} //namespace science \ No newline at end of file diff --git a/science/platform-implementations/co2_sensor_sc40.cpp b/science/platform-implementations/co2_sensor_sc40.cpp new file mode 100644 index 0000000..b4718a6 --- /dev/null +++ b/science/platform-implementations/co2_sensor_sc40.cpp @@ -0,0 +1,93 @@ +#pragma once + +#include +#include +#include +#include +#include + +// i2cc address is 0x62 +// need a buffer +// 3 things to get from the i2c bus +// use std::span for buffer to get data +// use i2c bus 2 for the dev 2 board while testing + +using namespace hal::literals; +using namespace std::chrono_literals; +namespace sjsu::science { +class co2_sensor +{ +private: + co2_sensor(hal::i2c& p_i2c, hal::steady_clock& c) + : m_i2c(p_i2c) + , steady_clock{ c } + { + + // start periodic measurement //write to hex 0x21b1 + // hal::write(m_i2c, addresses::address, + // std::span{a},hal::never_timeout()); hal::write(m_i2c, + // addresses::address, std::span{a}); hal::transaction(sensor_address, + // start_address, nullptr, ) + } + +public: + enum addresses : hal::byte + { + address = 0x62, + start_first_half = 0x21, + start_second_half = 0xb1, + read_first_half = 0xec, + read_second_half = 0x05, + stop_first_half = 0x3f, + stop_second_half = 0x86 + }; + + static hal::result create(hal::i2c& p_i2c, + hal::steady_clock& clock) + { + co2_sensor co2_sensor(p_i2c, clock); + HAL_CHECK(co2_sensor.start()); + return co2_sensor; + } + + hal::status start() + { + std::array start_address{ { start_first_half, start_second_half } }; + HAL_CHECK(hal::write(m_i2c, addresses::address, start_address, hal::never_timeout())); + return hal::success(); + } + + hal::result read_co2() + { + // std::array buffer; //3 bytes for co2, temperature and + // humidity + std::array read_address{ read_first_half, + read_second_half }; + + std::array buffer; + + // hal::write_then_read(m_i2c, addresses::address, burrito, buffer, + // hal::never_timeout()); + HAL_CHECK(hal::write(m_i2c, addresses::address, read_address)); + hal::delay(steady_clock, 1ms); + + HAL_CHECK( + hal::read(m_i2c, addresses::address, buffer, hal::never_timeout())); + int16_t result = buffer[0] << 8 | buffer[1] << 0; + + return result; + } + + // function that converts data into measurements that we need + hal::status stop() + { + std::array stop_address{ { stop_first_half, stop_second_half } }; + + HAL_CHECK(hal::write( + m_i2c, addresses::address, std::span{ stop_address }, hal::never_timeout())); + } + + hal::i2c& m_i2c; + hal::steady_clock& steady_clock; +}; +} // namespace science \ No newline at end of file diff --git a/science/platform-implementations/color_sensor_opt4048.cpp b/science/platform-implementations/color_sensor_opt4048.cpp new file mode 100644 index 0000000..2b9333d --- /dev/null +++ b/science/platform-implementations/color_sensor_opt4048.cpp @@ -0,0 +1,251 @@ +#include "color_sensor_opt4048.hpp" +#include +#include + + +using namespace hal::literals; +using namespace std::chrono_literals; +namespace sjsu::science { +opt4048::opt4048(hal::i2c& p_i2c, hal::steady_clock& p_clock, hal::serial& p_terminal) + : m_i2c(p_i2c) + , m_clock(p_clock) + , m_terminal(p_terminal) +{ +} +static void print_byte_array(hal::serial& console, std::span arr) { + for(auto& i : arr) { + hal::print<16>(console, "0x%02x ", i); + } + hal::print(console, "\n"); +} + +hal::result opt4048::create(hal::i2c& p_i2c, hal::steady_clock& p_clock, hal::serial& p_terminal){ + opt4048 color_sensor(p_i2c, p_clock, p_terminal); + HAL_CHECK(color_sensor.adjust_settings()); // + //set basic settings + return color_sensor; +} + +hal::status opt4048::adjust_settings(){ + //Datasheet: + //write BYTE 1: [QWAKE, 0, RANGE (0xC), conversion time part 1] + //write BYTE 2: [converstion time part 2, operating mode, 1, 0, 11] + // 1ms conversion time for now: 0010 + // 0xC=1100 + //settings are predetermined + hal::byte write1 = 0x30; + hal::byte write2 = 0xB3; + hal::byte write3 = 0x80; + hal::byte write4 = 0x11; // guessing almost all of these. read about the latch, and the interrupts + std::array configA= {register_addresses::settings,write1,write2}; + HAL_CHECK(hal::write(m_i2c, opt4048::device_address, configA, hal::never_timeout())); + // hal::delay(m_clock, 50ms); + std::array configB= {register_addresses::threshold_channel_select,write3,write4}; + HAL_CHECK(hal::write(m_i2c, opt4048::device_address, configB, hal::never_timeout())); + print_byte_array(m_terminal, configA); //should output: 0x0A, 0x30, 0xB3 + return hal::success(); +} + +hal::status opt4048::read_channel(hal::byte address, std::span output){ + std::array address_out = {address}; + + HAL_CHECK(hal::write_then_read(m_i2c, opt4048::device_address, address_out, output)); + // print<30>(m_terminal, "0x%02x: \t", address); + // print_byte_array(m_terminal, output); + return hal::success(); +} + +hal::result opt4048::get_adc_codes(){ //returns x,y, + //read x channel, y channel and z channel do mantisssa math + //mantissa channel_x = result_msb_chx << 8 + RESULT_LSB_CHx + //ADC_CODES_CHx = (MANTISSA_CHx< 0b110000000000 + uint16_t byte2 = 0b10011001; //153 -> 0b000010011001 + //110010011001 = 3225 + uint16_t exponent = byte1 >> 4; //1100 -> 12 + uint16_t msb = byte1 << 12; //1100000000000000 + msb = msb >> 4; //110000000000 -> 3072 + msb = msb | byte2; //110010011001 -> 3225 + cout << exponent << endl; + cout << msb << endl; + */ + std::array channel_0_msb; // 0 has exponent (4 bits), and 1 has the msb (12bits) + HAL_CHECK(get_msb_exp(register_addresses::channel0_msb, channel_0_msb)); + + hal::delay(m_clock, 50ms); + //do this for channel 1, 2, and 3 + std::array channel_1_msb; + HAL_CHECK(get_msb_exp(register_addresses::channel1_msb, channel_1_msb)); + hal::delay(m_clock, 50ms); + + std::array channel_2_msb; + HAL_CHECK(get_msb_exp(register_addresses::channel2_msb, channel_2_msb)); + hal::delay(m_clock, 50ms); + + std::array channel_3_msb; + HAL_CHECK(get_msb_exp(register_addresses::channel3_msb, channel_3_msb)); + hal::delay(m_clock, 50ms); + + std::array channel_0_lsb; + HAL_CHECK(read_channel(register_addresses::channel0_lsb, channel_0_lsb)); + uint8_t lsb0 = channel_0_lsb[0]; + hal::delay(m_clock, 50ms); + + std::array channel_1_lsb; + HAL_CHECK(read_channel(register_addresses::channel1_lsb, channel_1_lsb)); + uint8_t lsb1 = channel_1_lsb[0]; + hal::delay(m_clock, 50ms); + + std::array channel_2_lsb; + HAL_CHECK(read_channel(register_addresses::channel2_lsb, channel_2_lsb)); + uint8_t lsb2 = channel_2_lsb[0]; + hal::delay(m_clock, 50ms); + + std::array channel_3_lsb; + HAL_CHECK(read_channel(register_addresses::channel3_lsb, channel_3_lsb)); + uint8_t lsb3 = channel_3_lsb[0]; + hal::delay(m_clock, 50ms); + + uint32_t mantissa0 = (channel_0_msb[1] << 8) + lsb0; + uint32_t adc_codes0 = mantissa0 << channel_0_msb[0]; + + uint32_t mantissa1 = (channel_1_msb[1] << 8) + lsb1; + uint32_t adc_codes1 = mantissa1 << channel_1_msb[0]; + + uint32_t mantissa2 = (channel_2_msb[1] << 8) + lsb2; + uint32_t adc_codes2 = mantissa2 << channel_2_msb[0]; + + uint32_t mantissa3 = (channel_3_msb[1] << 8) + lsb3; + uint32_t adc_codes3 = mantissa3 << channel_3_msb[0]; + + + + + + adc_codes codes; + codes.ch0 = adc_codes0; + codes.ch1 = adc_codes1; + codes.ch2 = adc_codes2; + codes.ch3 = adc_codes3; + return codes; + +} + +hal::status opt4048::get_msb_exp(hal::byte register_A, std::span data) { + std::array output; + HAL_CHECK(read_channel(register_A, output )); + // print<30>(m_terminal, "0x%02x: \t", register_A); + // print<30>(m_terminal, "OUTPUT 0: 0x%02x\t", output[0]); + // print<30>(m_terminal, "OUTPUT 1: 0x%02x\n", output[1]); + uint16_t exponent = 0; + uint16_t msb = 0; + exponent = output[0] >> 4; + // print<30>(m_terminal, "OUTPUT 0: 0x%02x\n", output[0]); + + msb = output[0] << 12; + msb = (msb >> 4) | output[1]; + // print<30>(m_terminal, "MSB: %u\n", msb); + // print<30>(m_terminal, "Exponent: %u\n", exponent); + data[0] = exponent; + data[1] = msb; + return hal::success(); +} +hal::result opt4048::adc_codes_to_xyz(adc_codes adc) { + //from datasheet + xyz_values values; + float ADC_to_XYZ[4][4] = { { 0.000234892992, -0.0000189652390, 0.0000120811684, 0 }, + { 0.0000407467441, 0.000198958202, -0.0000158848115, 0.00215 }, + { 0.0000928619404, -0.0000169739553, 0.000674021520, 0 }, + { 0, 0, 0, 0 } + }; + + //matrix multipliations from the datasheet, got the actual calculations from @retrospy's Github and www.brucelindbloom.com + values.x = adc.ch0 * ADC_to_XYZ[0][0] + adc.ch1 * ADC_to_XYZ[1][0] + adc.ch2 * ADC_to_XYZ[2][0] + adc.ch3 * ADC_to_XYZ[3][0]; + values.y = adc.ch0 * ADC_to_XYZ[0][1] + adc.ch1 * ADC_to_XYZ[1][1] + adc.ch2 * ADC_to_XYZ[2][1] + adc.ch3 * ADC_to_XYZ[3][1]; + values.z = adc.ch0 * ADC_to_XYZ[0][2] + adc.ch1 * ADC_to_XYZ[1][2] + adc.ch2 * ADC_to_XYZ[2][2] + adc.ch3 * ADC_to_XYZ[3][2]; + values.lux = adc.ch0 * ADC_to_XYZ[0][3] + adc.ch1 * ADC_to_XYZ[1][3] + adc.ch2 * ADC_to_XYZ[2][3] + adc.ch3 * ADC_to_XYZ[3][3]; + print<30>(m_terminal, "ADC CODES: %d \t", adc.ch0); + print<30>(m_terminal, "%d \t", adc.ch1); + print<30>(m_terminal, "%d \t", adc.ch2); + print<30>(m_terminal, "%d \n", adc.ch3); + print<30>(m_terminal, "XYZ VALUES: %f \t", values.x); + print<30>(m_terminal, "%f \t", values.y); + print<30>(m_terminal, "%f \t", values.z); + print<30>(m_terminal, "%f \n", values.lux); + return values; +} +hal::result opt4048::sRGBCompandingFunction(float val) // helper function retrospy github and www.brucelindbloom.com + { + if (val <= 0.0031308) + return val *= 12.92; + else + return pow(1.055 * val, 1 / 2.4) - 0.055; + } + +//from retrospy github and www.brucelindbloom.com +hal::result opt4048::xyz_to_rgb(xyz_values xyz) { + // XYZ of D65 Illuminant from retrospy github and www.brucelindbloom.com + float x = xyz.x / 100; // X from 0 to 95.047 + float y = xyz.y / 100; // Y from 0 to 100.000 + float z = xyz.z / 100; // Z from 0 to 108.883 + + float r = x * 3.2406 + y * -1.5372 + z * -0.4986; + float g = x * -0.9689 + y * 1.8758 + z * 0.0415; + float b = x * 0.0557 + y * -0.2040 + z * 1.0570; + + if (r > 0.0031308) { + r = 1.055 * (std::pow(r, 0.41666667)) - 0.055; + } else { + r = 12.92 * r; + } + + if (g > 0.0031308) { + g = 1.055 * (std::pow(g, 0.41666667)) - 0.055; + } else { + g = 12.92 * g; + } + + if (b > 0.0031308) { + b = 1.055 * (std::pow(b, 0.41666667)) - 0.055; + } else { + b = 12.92 * b; + } + + r *= 255; + g *= 255; + b *= 255; + rgb_values rgb; + rgb.r = r; + rgb.g = g; + rgb.b = b; + // xyz_values D65_Illuminant = { 95.0500,100.0000,108.9000,NAN}; + // rgb_values rgb; + + // // XYZ of D65 Illuminant + // xyz.x /= D65_Illuminant.x; + // xyz.y /= D65_Illuminant.y; + // xyz.z /= D65_Illuminant.y; + // float rgbR = HAL_CHECK(sRGBCompandingFunction(xyz.x * XYZ_to_RGB[0][0] + xyz.y * XYZ_to_RGB[0][1] + xyz.z * XYZ_to_RGB[0][2])); + // float rgbG = HAL_CHECK(sRGBCompandingFunction(xyz.x * XYZ_to_RGB[1][0] + xyz.y * XYZ_to_RGB[1][1] + xyz.z * XYZ_to_RGB[1][2])); + // float rgbB = HAL_CHECK(sRGBCompandingFunction(xyz.x * XYZ_to_RGB[2][0] + xyz.y * XYZ_to_RGB[2][1] + xyz.z * XYZ_to_RGB[2][2])); + // rgb.r = std::min(1.0f, std::max(0.0f, rgbR)) * 255; + // rgb.g = std::min(1.0f, std::max(0.0f, rgbG)) * 255; + // rgb.b = std::min(1.0f, std::max(0.0f, rgbB)) * 255; + + return rgb; +} + +hal::result opt4048::get_data(){// will do all the conversions and return required data + auto adc = HAL_CHECK(get_adc_codes()); + // hal::print(m_terminal, "got adc codes\n"); + auto xyz = HAL_CHECK(adc_codes_to_xyz(adc)); + // hal::print(m_terminal, "got xyz codes\n"); + + auto rgb = HAL_CHECK(xyz_to_rgb(xyz)); + // hal::print(m_terminal, "got rgb codes\n"); + + return rgb; +} + +} \ No newline at end of file diff --git a/science/platform-implementations/color_sensor_opt4048.hpp b/science/platform-implementations/color_sensor_opt4048.hpp new file mode 100644 index 0000000..c27f44a --- /dev/null +++ b/science/platform-implementations/color_sensor_opt4048.hpp @@ -0,0 +1,122 @@ +#pragma once + +#include +#include +#include + +namespace sjsu::science { +// functions we ened to make +class opt4048 +{ +private: + opt4048(hal::i2c& p_i2c,hal::steady_clock& p_clock,hal::serial& p_terminal); + + // constructor + +public: + enum register_addresses : hal::byte { + channel0_msb = 0x00, + channel0_lsb = 0x01, + channel1_msb = 0x02, + channel1_lsb = 0x03, + channel2_msb = 0x04, + channel2_lsb = 0x05, + channel3_msb = 0x06, + channel3_lsb = 0x07, + threshold_l = 0x08, //optional + threshold_h = 0x09, //optional + settings = 0x0A, + threshold_channel_select = 0x0B, + flags = 0x0C, + // device_id = 0x11 + }; + enum modes: hal::byte{ + power_down =0b00, + forced_auto_range = 0b01, + one_shot =0b10, + continuous = 0b11, + }; + + enum conversion_times: int{ + micro600 = 0b0000, + ms1 = 0b0001, + ms1_8 = 0b0010, + ms3_4 = 0b0011, + ms6_5 = 0b0100, + ms12_7 = 0b0101, + ms25 = 0b0110, + ms50 = 0b0111, + ms100 = 0b1000, + ms200 = 0b1001, + ms400 = 0b1010, + ms800 = 0b1011 + }; + + struct adc_codes + { + uint32_t ch0 = 0; + uint32_t ch1 = 0; + uint32_t ch2 = 0; + uint32_t ch3 = 0; + }; + + struct xyz_values + { + float x = 0; + float y = 0; + float z = 0; + float lux = 0; + }; + + struct rgb_values + { + float r = 0; + float g = 0; + float b = 0; + }; + float XYZ_to_RGB[3][3] = { { 3.2404542, -1.5371385, -0.4985314 }, + { -0.9692660, 1.8760108, 0.0415560 }, + { 0.0556434, -0.2040259, 1.0572252 }}; + int device_address = 0x44; // not sure check this + static hal::result create(hal::i2c& p_i2c, + hal::steady_clock& clock, + hal::serial& p_terminal); + + // select light range + // select conversiokn time + // measure light and color + // choose mode of operation + // set automatic full scale range setting, continuous mode, conversion time + // somewhere in the middle + + // set register range on 0xA to 0xC + hal::status adjust_settings(); // keep it editable to see which performs optimally in the future + + /** + * @brief Takes in channel address and returns 2 bytes that were read + * @param channel_address + * @param buffer + * + */ + hal::status read_channel(hal::byte address, std::span output); + + hal::result get_adc_codes(); + + hal::result adc_codes_to_xyz(adc_codes adc); + + hal::result sRGBCompandingFunction(float val); + + hal::result xyz_to_rgb(xyz_values xyz); + + hal::result get_data(); // will do all the conversions and return required data + + hal::status get_msb_exp(hal::byte register_A, std::span data); + // set to conversion mode + // M^-1 for sRGB @ D65 from www.brucelindbloom.com + + + hal::i2c& m_i2c; + hal::steady_clock& m_clock; + hal::serial& m_terminal; +}; +} // namespace science \ No newline at end of file diff --git a/science/platform-implementations/methane_sensor_mq4.cpp b/science/platform-implementations/methane_sensor_mq4.cpp new file mode 100644 index 0000000..becabed --- /dev/null +++ b/science/platform-implementations/methane_sensor_mq4.cpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +#include "adc_sensor.hpp" + +namespace sjsu::science{ + +/// @brief Implementation driver for MQ4 methane sensor +class mq4_methane_sensor : generic_adc_sensor{ +public: + + /// @brief Builds MQ4 sensor driver object. + /// @param adc_data libhal adc pin that has been initialized + /// @param digital_detector Digital pin that goes HIGH when methane is detected. + mq4_methane_sensor(hal::adc& adc_data) : generic_adc_sensor(adc_data) {}; + + /// @brief Returns read value from sensor in parts per million (PPM). + /// @return The amount of methane near the sensor in PPM. + virtual hal::result get_parsed_data() override{ + float raw_adc_value = HAL_CHECK(read_raw_adc()); + + float ppm_value = static_cast(std::pow(1000 * raw_adc_value, -2.95)); + return ppm_value; + } +}; +} \ No newline at end of file diff --git a/science/platform-implementations/perstaltic-pump.cpp b/science/platform-implementations/perstaltic-pump.cpp new file mode 100644 index 0000000..e69de29 diff --git a/science/platform-implementations/perstaltic-pump.hpp b/science/platform-implementations/perstaltic-pump.hpp new file mode 100644 index 0000000..d12d4a2 --- /dev/null +++ b/science/platform-implementations/perstaltic-pump.hpp @@ -0,0 +1,4 @@ +#pragma once + +#include + diff --git a/science/platform-implementations/scd40.cpp b/science/platform-implementations/scd40.cpp new file mode 100644 index 0000000..9d1628c --- /dev/null +++ b/science/platform-implementations/scd40.cpp @@ -0,0 +1,129 @@ +// #pragma once + +#include "scd40.hpp" + +using namespace std::chrono_literals; +using scd40_nm = sjsu::science::scd40; + + +scd40_nm::scd40(hal::i2c& p_i2c, hal::steady_clock& p_clock) : m_i2c(p_i2c), m_clock(p_clock) {} + +hal::result scd40_nm::create(hal::i2c& p_i2c, hal::steady_clock& clock){ + scd40 scd40(p_i2c, clock); + HAL_CHECK(scd40.start()); + return scd40; + +} + +hal::status scd40_nm::start(){ + std::array start_address = { start_periodic_measurement_first_half, start_periodic_measurement_second_half }; + HAL_CHECK(hal::write(m_i2c, addresses::device_address, start_address, hal::never_timeout())); + return hal::success(); +} + +hal::result scd40_nm::read() { + std::array read_address = {read_measurement_first_half, read_measurement_second_half }; + std::array buffer; + + HAL_CHECK(hal::write(m_i2c,addresses::device_address, read_address)); + hal::delay(m_clock, 1ms); + HAL_CHECK(hal::read(m_i2c, addresses::device_address, buffer, hal::never_timeout())); + + scd40_nm::scd40_read_data rd; + rd.co2 = buffer[0] << 8 | buffer[1]; + rd.temp = (-45 + 175.0*(buffer[3] << 8 | buffer[4])/ (1 << 16)); + rd.rh = 100.0 * (buffer[6] << 8 | buffer[7]) / (1 << 16); + + return rd; +} + +hal::status scd40_nm::stop() { + std::array stop_address = { stop_periodic_measurement_first_half, stop_periodic_measurement_second_half }; + HAL_CHECK(hal::write(m_i2c, addresses::device_address, stop_address, hal::never_timeout())); + return hal::success(); +} + +hal::result scd40_nm::get_settings() { + std::array read_address_temp = {get_temperature_offset_first_half, get_temperature_offset_second_half }; + std::array read_address_alt = { get_sensor_altitude_first_half, get_sensor_altitude_second_half }; + std::array buffer; + + HAL_CHECK(hal::write(m_i2c,addresses::device_address, read_address_temp)); + hal::delay(m_clock, 1ms); + HAL_CHECK(hal::read(m_i2c, addresses::device_address, buffer, hal::never_timeout())); + + scd40_nm::scd40_settings get; + + get.temp_offset = 175 *(buffer[0] << 8 | buffer[1])/(1 << 16); + + HAL_CHECK(hal::write(m_i2c,addresses::device_address, read_address_alt)); + hal::delay(m_clock, 1ms); + HAL_CHECK(hal::read(m_i2c, addresses::device_address, buffer, hal::never_timeout())); + get.altitude = buffer[0]<<8 | buffer[1]; + + return get; +} + +hal::status scd40_nm::set_settings( struct settings setting) { + if(setting.set_temp != 0){ + int temp = int(((setting.set_temp*(1<<16)) / 175)) ; + hal::byte temp_first_half = (temp) >> 8; + hal::byte temp_second_half = temp & 0xff; + std::array set_temp_address = {set_temperature_offset_first_half, set_temperature_offset_second_half, temp_first_half, temp_second_half}; + HAL_CHECK(hal::write(m_i2c,addresses::device_address,set_temp_address)); + hal::delay(m_clock, 1ms); + } + if(setting.set_pressure != -1){ + int pressure = int(setting.set_pressure/100); + hal::byte pressure_first_half = (pressure) >> 8; + hal::byte pressure_second_half = pressure & 0xff; + std::array set_pressure_address = {set_ambient_pressure_first_half, set_ambient_pressure_second_half, pressure_first_half, pressure_second_half}; + HAL_CHECK(hal::write(m_i2c,addresses::device_address,set_pressure_address)); + hal::delay(m_clock, 1ms); + } else if(setting.set_alt != 0){ + int alt = int(setting.set_alt); + hal::byte alt_first_half = (alt) >> 8; + hal::byte alt_second_half = alt & 0xff; + std::array set_alt_address = {set_sensor_altitude_first_half, set_sensor_altitude_second_half, alt_first_half, alt_second_half}; + HAL_CHECK(hal::write(m_i2c,addresses::device_address,set_alt_address)); + hal::delay(m_clock, 1ms); + } + + return hal::success(); +} + +hal::byte scd40_nm::generate_crc(std::array data){ + uint16_t current_byte; + uint8_t crc_bit; + uint8_t crc = 0xFF; + uint8_t CRC8_POLYNOMIAL = 0x31; + + for (current_byte = 0; current_byte < 2 ; ++current_byte) { + crc ^= (data[current_byte]); + for (crc_bit = 8; crc_bit > 0; --crc_bit) { + if (crc & 0x80) + crc = (crc << 1) ^ CRC8_POLYNOMIAL; + else + crc = (crc << 1); + } + } + return crc; +} + +bool scd40_nm::validate_crc(std::array data) { + uint16_t current_byte; + uint8_t crc_bit; + uint8_t crc = 0xFF; + uint8_t CRC8_POLYNOMIAL = 0x31; + + for (current_byte = 0; current_byte < 3 ; ++current_byte) { + crc ^= (data[current_byte]); + for (crc_bit = 8; crc_bit > 0; --crc_bit) { + if (crc & 0x80) + crc = (crc << 1) ^ CRC8_POLYNOMIAL; + else + crc = (crc << 1); + } + } + return crc == 0; +} \ No newline at end of file diff --git a/science/platform-implementations/scd40.hpp b/science/platform-implementations/scd40.hpp new file mode 100644 index 0000000..5bb7360 --- /dev/null +++ b/science/platform-implementations/scd40.hpp @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include +#include + + +//TODO: maybe later add CRC and config for altitude, ambient pressure, temperature offset (or look at setting persistent values) + +namespace sjsu::science { +class scd40 +{ +private: + + scd40(hal::i2c& p_i2c, hal::steady_clock& p_clock); + hal::i2c& m_i2c; + hal::steady_clock& m_clock; + hal::byte generate_crc(std::array data); + bool validate_crc(std::array data); + + + //TODO: revise output type + enum addresses {// deal with hal::byte later + device_address = 0x62, + start_periodic_measurement_first_half = 0x21, + start_periodic_measurement_second_half = 0xb1, + read_measurement_first_half = 0xec, + read_measurement_second_half = 0x05, + stop_periodic_measurement_first_half = 0x3f, + stop_periodic_measurement_second_half = 0x86, + set_temperature_offset_first_half = 0x24, + set_temperature_offset_second_half = 0x1d, + get_temperature_offset_first_half = 0x23, + get_temperature_offset_second_half = 0x18, + set_sensor_altitude_first_half = 0x24, + set_sensor_altitude_second_half = 0x27, + get_sensor_altitude_first_half = 0x23, + get_sensor_altitude_second_half = 0x22, + set_ambient_pressure_first_half = 0xe0, + set_ambient_pressure_second_half = 0x00 + }; + +public: + struct scd40_read_data + { + double co2; + double temp; + double rh; + }; + + struct settings + { + float set_temp = 4; + float set_alt = 0; + float set_pressure = -1; + }; + + struct scd40_settings + { + float temp_offset; + float altitude; + }; + + static hal::result create(hal::i2c& p_i2c,hal::steady_clock& p_clock); + hal::status start(); + hal::result read(); + hal::status stop(); + hal::result get_settings(); + hal::status set_settings( settings setting); +}; + +} // namespace science \ No newline at end of file diff --git a/science/platform-implementations/sk9822.hpp b/science/platform-implementations/sk9822.hpp new file mode 100644 index 0000000..0a57943 --- /dev/null +++ b/science/platform-implementations/sk9822.hpp @@ -0,0 +1,156 @@ +#pragma once +#include +#include +#include +#include +#include + + +using namespace hal::literals; +using namespace std::chrono_literals; + +namespace hal { + +/** + * @brief Stores r, g, b, and brightness values. + * + */ +struct rgb_brightness { + hal::byte r=0, g=0, b=0; + hal::byte brightness=0; + + rgb_brightness() {}; + rgb_brightness(hal::byte p_r, hal::byte p_g, hal::byte p_b, hal::byte p_brightness) { + r = p_r; + g = p_g; + b = p_b; + brightness = p_brightness; + } + + /** + * @brief Set the r, g, b, and brightness values. + * + * @param p_r + * @param p_g + * @param p_b + * @param p_brightness + */ + void set(hal::byte p_r, hal::byte p_g, hal::byte p_b, hal::byte p_brightness) { + r = p_r; + g = p_g; + b = p_b; + brightness = p_brightness; + } +}; + +namespace colors { + const rgb_brightness RED = rgb_brightness(0xff, 0x00, 0x00, 0b11111); + const rgb_brightness GREEN = rgb_brightness(0x00, 0xff, 0x00, 0b11111); + const rgb_brightness BLUE = rgb_brightness(0x00, 0x00, 0xff, 0b11111); + const rgb_brightness WHITE = rgb_brightness(0xff, 0xff, 0xff, 0b11111); + const rgb_brightness BLACK = rgb_brightness(0x00, 0x00, 0x00, 0x00); +} + +template +using light_strip = std::array; +using light_strip_view = std::span; + +namespace light_strip_util { + void set_all(hal::light_strip_view lights, const hal::byte r, const hal::byte g, const hal::byte b, const hal::byte brightness) { + rgb_brightness setting; + setting.r = r; + setting.g = g; + setting.b = b; + setting.brightness = brightness; + for(auto i = lights.begin(); i != lights.end(); i ++) { + *i = setting; + } + } + + void set_all(hal::light_strip_view lights, const rgb_brightness value) { + for(auto i = lights.begin(); i != lights.end(); i ++) { + *i = value; + } + } +}; + +struct sk9822 { + public: + constexpr static auto period = 8ns; + constexpr static auto half_period = period/2; + /** + * @brief Construct a new sk9822 object. + * + * @param clock_pin + * @param data_pin + * @param clock + */ + sk9822(hal::output_pin& clock_pin, hal::output_pin& data_pin, hal::steady_clock& clock); + + /** + * @brief Send the updated rgb_brightness values to the light strips. + * Changes to led brightness are only reflected when this is called. + * + * @return hal::status + */ + hal::status update(hal::light_strip_view lights); + private: + hal::output_pin* clock_pin, *data_pin; + hal::steady_clock* clock; + + /** + * @brief Send a byte through the clock pin and data pins + * + * @param value + * @return hal::status + */ + hal::status send_byte(hal::byte value); +}; +}; + +hal::sk9822::sk9822(hal::output_pin& p_clock_pin, hal::output_pin& p_data_pin, hal::steady_clock& p_clock) { + clock_pin = &p_clock_pin; + data_pin = &p_data_pin; + clock = &p_clock; +} + +hal::status hal::sk9822::update(hal::light_strip_view lights) { + // Start Frame + HAL_CHECK(send_byte(0x00)); + HAL_CHECK(send_byte(0x00)); + HAL_CHECK(send_byte(0x00)); + HAL_CHECK(send_byte(0x00)); + + for(auto i = lights.begin(); i != lights.end(); i ++) { + HAL_CHECK(send_byte((*i).brightness | 0b11100000)); + // HAL_CHECK(send_byte((*i).brightness & 0x00011111)); + HAL_CHECK(send_byte((*i).b)); + HAL_CHECK(send_byte((*i).g)); + HAL_CHECK(send_byte((*i).r)); + } + + // End Frame + HAL_CHECK(send_byte(0xff)); + HAL_CHECK(send_byte(0xff)); + HAL_CHECK(send_byte(0xff)); + HAL_CHECK(send_byte(0xff)); + + return hal::success(); +} + +hal::status hal::sk9822::send_byte(hal::byte data) { + for(int i = 0; i < 8; i ++) { + if(data & (1 << i)) { + HAL_CHECK((*data_pin).level(true)); + }else { + HAL_CHECK((*data_pin).level(false)); + } + hal::delay(*clock, half_period); + HAL_CHECK((*clock_pin).level(true)); + hal::delay(*clock, period); + HAL_CHECK((*clock_pin).level(false)); + hal::delay(*clock, half_period); + } + + return hal::success(); +} diff --git a/science/platform-implementations/soil_sensor_sht21.cpp b/science/platform-implementations/soil_sensor_sht21.cpp new file mode 100644 index 0000000..7521ffd --- /dev/null +++ b/science/platform-implementations/soil_sensor_sht21.cpp @@ -0,0 +1,80 @@ +#include "soil_sensor_sht21.hpp" +#include +#include +#include + +hal::result hal::sht::sht21::create(hal::i2c& p_bus, hal::byte p_address) { + hal::sht::sht21 sensor(p_bus, p_address); + + HAL_CHECK(sensor.soft_reset()); + + return sensor; +} + +hal::status hal::sht::sht21::soft_reset() { + HAL_CHECK(hal::write(*m_i2c, m_address, std::array { command::soft_reset_command })); + return hal::success(); +} + +hal::result hal::sht::sht21::is_low_battery() { + std::array response_buffer; + HAL_CHECK(hal::write_then_read(*m_i2c, m_address, std::array { command::read_user_register_command }, response_buffer)); + return (response_buffer[0] & 0b01000000) == 0b01000000; +} + +hal::status hal::sht::sht21::set_resolution(resolution p_resolution) { + std::array response_buffer; + HAL_CHECK(hal::write_then_read(*m_i2c, m_address, std::array { command::read_user_register_command }, response_buffer)); + response_buffer[0] = (response_buffer[0] & 0b01111110) | p_resolution; + HAL_CHECK(hal::write(*m_i2c, m_address, std::array { command::write_user_register_command, response_buffer[0] } )); + return hal::success(); +} + +hal::status hal::sht::sht21::enable_heater(bool p_enabled) { + std::array response_buffer; + HAL_CHECK(hal::write_then_read(*m_i2c, m_address, std::array { command::read_user_register_command }, response_buffer)); + if(p_enabled) { + response_buffer[0] = (response_buffer[0] & 0b11111011) | 0b00000100; + }else { + response_buffer[0] = (response_buffer[0] & 0b11111011) | 0b00000000; + } + HAL_CHECK(hal::write(*m_i2c, m_address, std::array { command::write_user_register_command, response_buffer[0] } )); + return hal::success(); +} + +hal::result hal::sht::sht21::get_relative_humidity() { + std::array response_buffer; + + HAL_CHECK(hal::write_then_read( + *m_i2c, + m_address, + std::array { command::hold_relative_humidity_measurement_command }, + response_buffer, + hal::never_timeout())); + + uint16_t raw_relative_humidty = (((hal::byte) response_buffer[0]) << 8) | (response_buffer[1] & 0b11111100); + + return -6 + (125.0 / 0x10000) * raw_relative_humidty; +} + + +hal::result hal::sht::sht21::get_temperature() { + std::array response_buffer; + + HAL_CHECK(hal::write_then_read( + *m_i2c, + m_address, + std::array { command::hold_temperature_measurement_command }, + response_buffer, + hal::never_timeout())); + + uint16_t raw_temperature = (((hal::byte) response_buffer[0]) << 8) | (response_buffer[1] & 0b11111100); + + return -46.85 + (175.72 / 0x10000) * raw_temperature; +} + + +hal::sht::sht21::sht21(hal::i2c& p_bus, hal::byte p_address) { + m_i2c = &p_bus; + m_address = p_address; +} diff --git a/science/platform-implementations/soil_sensor_sht21.hpp b/science/platform-implementations/soil_sensor_sht21.hpp new file mode 100644 index 0000000..5448763 --- /dev/null +++ b/science/platform-implementations/soil_sensor_sht21.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include + + +namespace hal::sht { + class sht21 { + public: + const static hal::byte sht21_i2c_address = 0x40; + + /** + * @brief Resolution settings. Used with `set_resolution` + * rh_12bit_temp_14bit is 12 bits of resolution for relative humidity and 14 bit of resolution for temperature. + */ + enum resolution : hal::byte { + rh_12bit_temp_14bit = 0b00000000, + rh_8bit_temp_12bit = 0b00000001, + rh_10bit_temp_13bit = 0b10000000, + rh_11bit_temp_11bit = 0b10000001, + }; + + /** + * @brief Create a sht21 sensor driver + * + * @param p_bus I2C bus the device in on. + * @param p_address I2C address of the device + * @return hal::result + */ + static hal::result create(hal::i2c& p_bus, hal::byte p_address = sht21_i2c_address); + + /** + * @brief Soft reset the sensor. + * + * @return hal::status + */ + hal::status soft_reset(); + + /** + * @brief Set the resolution of the relative humidity and temperature sensor. + * (Untested) + * + * @param p_resolution + * @return hal::status + */ + hal::status set_resolution(resolution p_resolution); + + /** + * @brief Returns true when VDD on the sensor is less than 2.25 V + * (Untested) + * + * @return hal::result + */ + hal::result is_low_battery(); + + /** + * @brief Enable or disable the on-chip heater. + * (Untested) + * + * @param p_enabled Defaults to true (enable) + * @return hal::status + */ + hal::status enable_heater(bool p_enabled = true); + + /** + * @brief Perform a single relative humidity measurement. This will block until the measurement is ready. + * + * @return hal::result + */ + hal::result get_relative_humidity(); + + /** + * @brief Perform a single relative humidity measurement. This will block until the measurement is ready. + * + * @return hal::result + */ + hal::result get_temperature(); + + private: + + enum command { + hold_temperature_measurement_command = 0b11100011, + hold_relative_humidity_measurement_command = 0b11100101, + no_hold_temperature_measurement_command = 0b11110011, + no_hold_relative_humidity_measurement_command = 0b11110101, + + write_user_register_command = 0b11100110, + read_user_register_command = 0b11100111, + + soft_reset_command = 0b11111110, + }; + + /** + * @brief I2C Bus + */ + hal::i2c* m_i2c; + + /** + * @brief Device address + */ + hal::byte m_address; + + sht21(hal::i2c& p_bus, hal::byte p_address); + }; +}; \ No newline at end of file diff --git a/science/platforms/lpc4074.cpp b/science/platforms/lpc4074.cpp deleted file mode 100644 index 90f0f82..0000000 --- a/science/platforms/lpc4074.cpp +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2023 Google LLC -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include -#include -#include - -#include -#include -#include -#include - -#include "application.hpp" - -hal::status initialize_processor() -{ - hal::cortex_m::initialize_data_section(); - - return hal::success(); -} - -hal::result initialize_platform() -{ - using namespace hal::literals; - - // Set the MCU to the maximum clock speed - HAL_CHECK(hal::lpc40::clock::maximum(10.0_MHz)); - - auto& clock = hal::lpc40::clock::get(); - auto cpu_frequency = clock.get_frequency(hal::lpc40::peripheral::cpu); - static hal::cortex_m::dwt_counter counter(cpu_frequency); - - static std::array receive_buffer{}; - static auto uart0 = HAL_CHECK(hal::lpc40::uart::get(0, - receive_buffer, - hal::serial::settings{ - .baud_rate = 115200, - })); - - static auto led = HAL_CHECK(hal::lpc40::output_pin::get(1, 10)); - - return application_framework{ - .led = &led, - .console = &uart0, - .clock = &counter, - .reset = []() { hal::cortex_m::reset(); }, - }; -} diff --git a/science/platforms/lpc4078.cpp b/science/platforms/lpc4078.cpp index 38fdc1d..03b420e 100644 --- a/science/platforms/lpc4078.cpp +++ b/science/platforms/lpc4078.cpp @@ -11,18 +11,27 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. +#include #include #include #include -#include +#include +#include #include +#include +#include #include +#include +// #include //not sure why we need this? +#include #include -#include "application.hpp" +#include +#include "applications/application.hpp" +namespace sjsu::science { hal::status initialize_processor() { hal::cortex_m::initialize_data_section(); @@ -34,27 +43,62 @@ hal::status initialize_processor() hal::result initialize_platform() { using namespace hal::literals; + using namespace std::chrono_literals; // Set the MCU to the maximum clock speed - HAL_CHECK(hal::lpc40::clock::maximum(10.0_MHz)); + HAL_CHECK(hal::lpc40::clock::maximum(12.0_MHz)); auto& clock = hal::lpc40::clock::get(); auto cpu_frequency = clock.get_frequency(hal::lpc40::peripheral::cpu); static hal::cortex_m::dwt_counter counter(cpu_frequency); - static std::array receive_buffer{}; + // Serial + static std::array recieve_buffer0{}; static auto uart0 = HAL_CHECK((hal::lpc40::uart::get(0, - receive_buffer, + recieve_buffer0, hal::serial::settings{ - .baud_rate = 115200, + .baud_rate = 38400, }))); + // Don't think we need can for the science applications thus far + // hal::can::settings can_settings{ .baud_rate = 1.0_MHz }; + // auto& can = HAL_CHECK((hal::lpc40::can::get<2>(can_settings))); + + static auto in_pin0 = + HAL_CHECK(hal::lpc40::input_pin::get(1, 15, hal::input_pin::settings{})); + static auto in_pin1 = + HAL_CHECK(hal::lpc40::input_pin::get(1, 23, hal::input_pin::settings{})); + static auto in_pin2 = + HAL_CHECK(hal::lpc40::input_pin::get(1, 22, hal::input_pin::settings{})); + + static auto pwm_1_6 = HAL_CHECK((hal::lpc40::pwm::get(1, 6))); + static auto pwm_1_5 = HAL_CHECK((hal::lpc40::pwm::get(1, 5))); - static auto led = HAL_CHECK(hal::lpc40::output_pin::get(1, 10)); + static auto adc_4 = HAL_CHECK(hal::lpc40::adc::get(4)); + static auto adc_5 = HAL_CHECK(hal::lpc40::adc::get(5)); + + std::array receive_buffer{}; + static auto uart1 = HAL_CHECK(hal::lpc40::uart::get(0, + receive_buffer, + { + .baud_rate = 115200.0f, + })); + + static auto i2c = HAL_CHECK(hal::lpc40::i2c::get(2)); return application_framework{ - .led = &led, - .console = &uart0, - .clock = &counter, + .terminal = &uart0, + // .can = &can, + .in_pin0 = &in_pin0, + .in_pin1 = &in_pin1, + .in_pin2 = &in_pin2, + .pwm_1_6 = &pwm_1_6, + .pwm_1_5 = &pwm_1_5, + .adc_4 = &adc_4, + .adc_5 = &adc_5, + .esp = &uart1, + .i2c = &i2c, + .steady_clock = &counter, .reset = []() { hal::cortex_m::reset(); }, }; -} +}; +} // namespace sjsu::science