From d9d8f4417a485ba3d059e45e6579e6ccabe4900a Mon Sep 17 00:00:00 2001 From: ZodiusInfuser Date: Fri, 1 Jul 2022 07:05:37 +0100 Subject: [PATCH] Libraries and examples for Automation 2040 W (#418) * Libraries and examples for Automation 2040 W * Fix for initialisation error --- examples/CMakeLists.txt | 1 + examples/automation2040w/CMakeLists.txt | 5 + examples/automation2040w/README.md | 40 ++++ .../automation2040w_read_adcs.cmake | 14 ++ .../automation2040w_read_adcs.cpp | 52 +++++ .../automation2040w_read_inputs.cmake | 14 ++ .../automation2040w_read_inputs.cpp | 52 +++++ .../automation2040w_switches_and_leds.cmake | 14 ++ .../automation2040w_switches_and_leds.cpp | 66 ++++++ .../automation2040w_toggle_outputs.cmake | 14 ++ .../automation2040w_toggle_outputs.cpp | 63 ++++++ .../automation2040w_toggle_relays.cmake | 14 ++ .../automation2040w_toggle_relays.cpp | 63 ++++++ libraries/CMakeLists.txt | 1 + libraries/automation2040w/CMakeLists.txt | 1 + libraries/automation2040w/automation.cmake | 12 ++ libraries/automation2040w/automation.cpp | 178 ++++++++++++++++ libraries/automation2040w/automation.hpp | 113 ++++++++++ .../examples/automation2040w/README.md | 47 +++++ .../examples/automation2040w/read_adcs.py | 33 +++ .../examples/automation2040w/read_inputs.py | 33 +++ .../automation2040w/reset_automation.py | 8 + .../automation2040w/switches_and_leds.py | 43 ++++ .../automation2040w/toggle_outputs.py | 43 ++++ .../examples/automation2040w/toggle_relays.py | 43 ++++ micropython/modules_py/automation.py | 199 ++++++++++++++++++ 26 files changed, 1166 insertions(+) create mode 100644 examples/automation2040w/CMakeLists.txt create mode 100644 examples/automation2040w/README.md create mode 100644 examples/automation2040w/automation2040w_read_adcs.cmake create mode 100644 examples/automation2040w/automation2040w_read_adcs.cpp create mode 100644 examples/automation2040w/automation2040w_read_inputs.cmake create mode 100644 examples/automation2040w/automation2040w_read_inputs.cpp create mode 100644 examples/automation2040w/automation2040w_switches_and_leds.cmake create mode 100644 examples/automation2040w/automation2040w_switches_and_leds.cpp create mode 100644 examples/automation2040w/automation2040w_toggle_outputs.cmake create mode 100644 examples/automation2040w/automation2040w_toggle_outputs.cpp create mode 100644 examples/automation2040w/automation2040w_toggle_relays.cmake create mode 100644 examples/automation2040w/automation2040w_toggle_relays.cpp create mode 100644 libraries/automation2040w/CMakeLists.txt create mode 100644 libraries/automation2040w/automation.cmake create mode 100644 libraries/automation2040w/automation.cpp create mode 100644 libraries/automation2040w/automation.hpp create mode 100644 micropython/examples/automation2040w/README.md create mode 100644 micropython/examples/automation2040w/read_adcs.py create mode 100644 micropython/examples/automation2040w/read_inputs.py create mode 100644 micropython/examples/automation2040w/reset_automation.py create mode 100644 micropython/examples/automation2040w/switches_and_leds.py create mode 100644 micropython/examples/automation2040w/toggle_outputs.py create mode 100644 micropython/examples/automation2040w/toggle_relays.py create mode 100644 micropython/modules_py/automation.py diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index d09eaade..7b0c8027 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -45,6 +45,7 @@ add_subdirectory(pico_wireless) add_subdirectory(inky_pack) +add_subdirectory(automation2040w) add_subdirectory(plasma2040) add_subdirectory(badger2040) add_subdirectory(tufty2040) diff --git a/examples/automation2040w/CMakeLists.txt b/examples/automation2040w/CMakeLists.txt new file mode 100644 index 00000000..f497f5c2 --- /dev/null +++ b/examples/automation2040w/CMakeLists.txt @@ -0,0 +1,5 @@ +include(automation2040w_read_adcs.cmake) +include(automation2040w_read_inputs.cmake) +include(automation2040w_switches_and_leds.cmake) +include(automation2040w_toggle_outputs.cmake) +include(automation2040w_toggle_relays.cmake) diff --git a/examples/automation2040w/README.md b/examples/automation2040w/README.md new file mode 100644 index 00000000..59981f77 --- /dev/null +++ b/examples/automation2040w/README.md @@ -0,0 +1,40 @@ +# Automation 2040 W C++ Examples + +- [Function Examples](#function-examples) + - [Read ADCs](#read-adcs) + - [Read Inputs](#read-inputs) + - [Toggle Relays](#toggle-relays) + - [Toggle Outputs](#toggle-outputs) + - [Switches and LEDs](#switches-and-leds) + + +## Function Examples + +### Read ADCs +[automation2040w_read_adcs.cpp](automation2040w_read_adcs.cpp) + +Shows how to read the 3 ADC terminals of Automation 2040 W. + + +### Read Inputs +[automation2040w_read_inputs.cpp](automation2040w_read_inputs.cpp) + +Shows how to read the 3 Input terminals of Automation 2040 W. + + +### Toggle Relays +[automation2040w_toggle_relays.cpp](automation2040w_toggle_relays.cpp) + +Demonstrates how to toggle the actuation state of each of Automation 2040 W's relays. + + +### Toggle Outputs +[automation2040w_toggle_outputs.cpp](automation2040w_toggle_outputs.cpp) + +Demonstrates how to toggle each of Automation 2040 W's output terminals. + + +### Switches and LEDs +[automation2040w_switches_and_leds.cpp](automation2040w_switches_and_leds.cpp) + +An example of the user switches and LEDs on Automation 2040 W. diff --git a/examples/automation2040w/automation2040w_read_adcs.cmake b/examples/automation2040w/automation2040w_read_adcs.cmake new file mode 100644 index 00000000..3b33bcf1 --- /dev/null +++ b/examples/automation2040w/automation2040w_read_adcs.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME automation2040w_read_adcs) +add_executable(${OUTPUT_NAME} automation2040w_read_adcs.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + automation + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/automation2040w/automation2040w_read_adcs.cpp b/examples/automation2040w/automation2040w_read_adcs.cpp new file mode 100644 index 00000000..1a6513aa --- /dev/null +++ b/examples/automation2040w/automation2040w_read_adcs.cpp @@ -0,0 +1,52 @@ +#include +#include "pico/stdlib.h" + +#include "automation.hpp" + +/* +Shows how to read the 3 ADC terminals of Automation 2040 W. + +Press "A" to exit the program. +*/ + +using namespace automation; + +// How many times to update per second +const uint UPDATES = 10; + +// The friendly names to give each ADC input +const char* ADC_NAMES[] = {"A1", "A2", "A3"}; + + +// Create a new Automation2040W +Automation2040W board; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the board + if(board.init()) { + + // Enable the LED of the switch used to exit the loop + board.switch_led(SWITCH_A, 50.0f); // Half Brightness + + // Read the ADCs until user switch is pressed + while(!board.switch_pressed(SWITCH_A)) { + + // Read each ADC in turn and print its voltage + for(uint i = 0; i < NUM_ADCS; i++) { + float voltage = board.read_adc(i); + printf("%s = %f, ", ADC_NAMES[i], voltage); + } + + // Print a new line + printf("\n"); + + sleep_ms(1000 / UPDATES); + } + + // Put the board back into a safe state + board.reset(); + } +} diff --git a/examples/automation2040w/automation2040w_read_inputs.cmake b/examples/automation2040w/automation2040w_read_inputs.cmake new file mode 100644 index 00000000..7e260267 --- /dev/null +++ b/examples/automation2040w/automation2040w_read_inputs.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME automation2040w_read_inputs) +add_executable(${OUTPUT_NAME} automation2040w_read_inputs.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + automation + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/automation2040w/automation2040w_read_inputs.cpp b/examples/automation2040w/automation2040w_read_inputs.cpp new file mode 100644 index 00000000..e7771584 --- /dev/null +++ b/examples/automation2040w/automation2040w_read_inputs.cpp @@ -0,0 +1,52 @@ +#include +#include "pico/stdlib.h" + +#include "automation.hpp" + +/* +Shows how to read the 3 Input terminals of Automation 2040 W. + +Press "A" to exit the program. +*/ + +using namespace automation; + +// How many times to update per second +const uint UPDATES = 10; + +// The friendly names to give each digital input +const char* INPUT_NAMES[] = {"I1", "I2", "I3", "I4"}; + + +// Create a new Automation2040W +Automation2040W board; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the board + if(board.init()) { + + // Enable the LED of the switch used to exit the loop + board.switch_led(SWITCH_A, 50.0f); // Half Brightness + + // Read the inputs until user switch is pressed + while(!board.switch_pressed(SWITCH_A)) { + + // Read each input in turn and print its voltage + for(uint i = 0; i < NUM_INPUTS; i++) { + bool value = board.read_input(i); + printf("%s = %d, ", INPUT_NAMES[i], (int)value); + } + + // Print a new line + printf("\n"); + + sleep_ms(1000 / UPDATES); + } + + // Put the board back into a safe state + board.reset(); + } +} diff --git a/examples/automation2040w/automation2040w_switches_and_leds.cmake b/examples/automation2040w/automation2040w_switches_and_leds.cmake new file mode 100644 index 00000000..8c03cf06 --- /dev/null +++ b/examples/automation2040w/automation2040w_switches_and_leds.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME automation2040w_switches_and_leds) +add_executable(${OUTPUT_NAME} automation2040w_switches_and_leds.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + automation + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/automation2040w/automation2040w_switches_and_leds.cpp b/examples/automation2040w/automation2040w_switches_and_leds.cpp new file mode 100644 index 00000000..ae4e7e65 --- /dev/null +++ b/examples/automation2040w/automation2040w_switches_and_leds.cpp @@ -0,0 +1,66 @@ +#include +#include "pico/stdlib.h" + +#include "automation.hpp" + +/* +An example of the user switches and LEDs on Automation 2040 W. + +Press "A" and "B" together to exit the program. +*/ + +using namespace automation; + +// How many times to update per second +const uint UPDATES = 10; + +// The friendly names to give each user input +const char* SWITCH_NAMES[] = {"SW_A", "SW_B"}; + +// How much to change a LED's brightess each update +const uint LED_INCREMENT = 2; + + +// Create a new Automation2040W +Automation2040W board; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the board + if(board.init()) { + + // Enable the Conn LED + board.conn_led(true); // Full Brightness + + float led_brightnesses[] = {0.0f, 0.0f}; + + // Interact with the switches and LEDs until both are pressed simultaneously + while(!board.switch_pressed(SWITCH_A) || !board.switch_pressed(SWITCH_B)) { + + for(uint i = 0; i < NUM_SWITCHES; i++) { + // Change the LED brightness based on switch's state + if(board.switch_pressed(i)) { + printf("%s = Pressed, ", SWITCH_NAMES[i]); + led_brightnesses[i] = MIN(led_brightnesses[i] + LED_INCREMENT, 100.0f); + } + else { + printf("%s = Released, ", SWITCH_NAMES[i]); + led_brightnesses[i] = MAX(led_brightnesses[i] - LED_INCREMENT, 0.0f); + } + + // Apply the new brightnesses to the LEDs + board.switch_led(i, led_brightnesses[i]); + } + + // Print a new line + printf("\n"); + + sleep_ms(1000 / UPDATES); + } + + // Put the board back into a safe state + board.reset(); + } +} diff --git a/examples/automation2040w/automation2040w_toggle_outputs.cmake b/examples/automation2040w/automation2040w_toggle_outputs.cmake new file mode 100644 index 00000000..1cc15e66 --- /dev/null +++ b/examples/automation2040w/automation2040w_toggle_outputs.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME automation2040w_toggle_outputs) +add_executable(${OUTPUT_NAME} automation2040w_toggle_outputs.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + automation + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/automation2040w/automation2040w_toggle_outputs.cpp b/examples/automation2040w/automation2040w_toggle_outputs.cpp new file mode 100644 index 00000000..3b6d6921 --- /dev/null +++ b/examples/automation2040w/automation2040w_toggle_outputs.cpp @@ -0,0 +1,63 @@ +#include +#include "pico/stdlib.h" + +#include "automation.hpp" + +/* +Demonstrates how to toggle each of Automation 2040 W's output terminals. + +Press "A" to exit the program. +*/ + +using namespace automation; + +// How much time to wait between each toggle (in milliseconds) +const uint TIME_PER_TOGGLE_MS = 500; + +// The friendly names to give each digital output +const char* OUTPUT_NAMES[] = {"O1", "O2", "O3"}; + + +// Create a new Automation2040W +Automation2040W board; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the board + if(board.init()) { + + // Enable the LED of the switch used to exit the loop + board.switch_led(SWITCH_A, 50.0f); // Half Brightness + + bool toggle = true; + uint index = 0; + + // Toggle the outputs until the user switch is pressed + while(!board.switch_pressed(SWITCH_A)) { + + // Toggle an output + board.output(index, toggle); + + // Print the state of all outputs + for(uint i = 0; i < NUM_OUTPUTS; i++) { + printf("%s = %d, ", OUTPUT_NAMES[i], (int)board.output(i)); + } + + // Print a new line + printf("\n"); + + index++; // Move on to the next output + if(index >= NUM_OUTPUTS) { + index = 0; // Go back to the first output + toggle = !toggle; // Invert the toggle value + } + + sleep_ms(TIME_PER_TOGGLE_MS); + } + + // Put the board back into a safe state + board.reset(); + } +} diff --git a/examples/automation2040w/automation2040w_toggle_relays.cmake b/examples/automation2040w/automation2040w_toggle_relays.cmake new file mode 100644 index 00000000..423ef80d --- /dev/null +++ b/examples/automation2040w/automation2040w_toggle_relays.cmake @@ -0,0 +1,14 @@ +set(OUTPUT_NAME automation2040w_toggle_relays) +add_executable(${OUTPUT_NAME} automation2040w_toggle_relays.cpp) + +# Pull in pico libraries that we need +target_link_libraries(${OUTPUT_NAME} + pico_stdlib + automation + ) + +# enable usb output +pico_enable_stdio_usb(${OUTPUT_NAME} 1) + +# create map/bin/hex file etc. +pico_add_extra_outputs(${OUTPUT_NAME}) diff --git a/examples/automation2040w/automation2040w_toggle_relays.cpp b/examples/automation2040w/automation2040w_toggle_relays.cpp new file mode 100644 index 00000000..5567b35a --- /dev/null +++ b/examples/automation2040w/automation2040w_toggle_relays.cpp @@ -0,0 +1,63 @@ +#include +#include "pico/stdlib.h" + +#include "automation.hpp" + +/* +Demonstrates how to toggle the actuation state of each of Automation 2040 W's relays. + +Press "A" to exit the program. +*/ + +using namespace automation; + +// How much time to wait between each toggle (in milliseconds) +const uint TIME_PER_TOGGLE_MS = 500; + +// The friendly names to give each relay +const char* RELAY_NAMES[] = {"R1", "R2", "R3"}; + + +// Create a new Automation2040W +Automation2040W board; + + +int main() { + stdio_init_all(); + + // Attempt to initialise the board + if(board.init()) { + + // Enable the LED of the switch used to exit the loop + board.switch_led(SWITCH_A, 50.0f); // Half Brightness + + bool toggle = true; + uint index = 0; + + // Toggle the relays until the user switch is pressed + while(!board.switch_pressed(SWITCH_A)) { + + // Toggle a relay + board.relay(index, toggle); + + // Print the state of all relays + for(uint i = 0; i < NUM_RELAYS; i++) { + printf("%s = %d, ", RELAY_NAMES[i], (int)board.relay(i)); + } + + // Print a new line + printf("\n"); + + index++; // Move on to the next relay + if(index >= NUM_RELAYS) { + index = 0; // Go back to the first relay + toggle = !toggle; // Invert the toggle value + } + + sleep_ms(TIME_PER_TOGGLE_MS); + } + + // Put the board back into a safe state + board.reset(); + } +} diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 53f1a3c4..c63b0d6d 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -25,6 +25,7 @@ add_subdirectory(pico_explorer) add_subdirectory(pico_motor_shim) add_subdirectory(pico_rgb_keypad) add_subdirectory(pico_wireless) +add_subdirectory(automation2040w) add_subdirectory(plasma2040) add_subdirectory(badger2040) add_subdirectory(tufty2040) diff --git a/libraries/automation2040w/CMakeLists.txt b/libraries/automation2040w/CMakeLists.txt new file mode 100644 index 00000000..4ef09446 --- /dev/null +++ b/libraries/automation2040w/CMakeLists.txt @@ -0,0 +1 @@ +include(automation.cmake) \ No newline at end of file diff --git a/libraries/automation2040w/automation.cmake b/libraries/automation2040w/automation.cmake new file mode 100644 index 00000000..62c808ac --- /dev/null +++ b/libraries/automation2040w/automation.cmake @@ -0,0 +1,12 @@ +add_library(automation INTERFACE) + +target_sources(automation INTERFACE + ${CMAKE_CURRENT_LIST_DIR}/automation.cpp +) + +target_include_directories(automation INTERFACE ${CMAKE_CURRENT_LIST_DIR}) + +#include(${PIMORONI_PICO_PATH}/drivers/analog/analog.cmake) + +# Pull in pico libraries that we need +target_link_libraries(automation INTERFACE pico_stdlib hardware_pwm hardware_i2c pimoroni_i2c analog) diff --git a/libraries/automation2040w/automation.cpp b/libraries/automation2040w/automation.cpp new file mode 100644 index 00000000..91db0bd6 --- /dev/null +++ b/libraries/automation2040w/automation.cpp @@ -0,0 +1,178 @@ +#include + +#include "automation.hpp" + +namespace automation { + +const uint Automation2040W::ADC_LED_PINS[] = {6, 7, 8}; +const uint Automation2040W::RELAY_PINS[] = {9, 10, 11}; +const uint Automation2040W::USER_SW_PINS[] = {12, 13}; +const uint Automation2040W::USER_LED_PINS[] = {14, 15}; +const uint Automation2040W::OUTPUT_PINS[] = {16, 17, 18}; +const uint Automation2040W::IN_BUFFERED_PINS[] = {19, 20, 21, 22}; +const uint Automation2040W::ADC_PINS[] = {26, 27, 28}; + + +Automation2040W::Automation2040W() +: i2c(I2C_SDA_PIN, I2C_SCL_PIN, 100000) +, analogs{Analog(ADC_PINS[0], VOLTAGE_GAIN, 0.0f, VOLTAGE_OFFSET), + Analog(ADC_PINS[1], VOLTAGE_GAIN, 0.0f, VOLTAGE_OFFSET), + Analog(ADC_PINS[2], VOLTAGE_GAIN, 0.0f, VOLTAGE_OFFSET)} { +} + +bool Automation2040W::init() { + // Set up the relay pins + for(auto i = 0u; i < NUM_RELAYS; i++) { + gpio_set_function(RELAY_PINS[i], GPIO_FUNC_SIO); + gpio_set_dir(RELAY_PINS[i], GPIO_OUT); + release_relay(i); + } + + // Set up the output pins + for(auto i = 0u; i < NUM_OUTPUTS; i++) { + gpio_set_function(OUTPUT_PINS[i], GPIO_FUNC_SIO); + gpio_set_dir(OUTPUT_PINS[i], GPIO_OUT); + output(i, false); + } + + // Set up the input pins + for(auto i = 0u; i < NUM_INPUTS; i++) { + gpio_set_function(IN_BUFFERED_PINS[i], GPIO_FUNC_SIO); + gpio_set_dir(IN_BUFFERED_PINS[i], GPIO_IN); + } + + // Set up the adc leds + for(auto i = 0u; i < NUM_ADCS; i++) { + gpio_put(ADC_LED_PINS[i], false); + pwm_config cfg = pwm_get_default_config(); + pwm_set_wrap(pwm_gpio_to_slice_num(ADC_LED_PINS[i]), 65535); + pwm_init(pwm_gpio_to_slice_num(ADC_LED_PINS[i]), &cfg, true); + gpio_set_function(ADC_LED_PINS[i], GPIO_FUNC_PWM); + pwm_set_gpio_level(ADC_LED_PINS[i], 0); + } + + // Set up the user switches and LEDs + for(auto i = 0u; i < NUM_SWITCHES; i++) { + gpio_set_function(USER_SW_PINS[i], GPIO_FUNC_SIO); + gpio_set_dir(USER_SW_PINS[i], GPIO_IN); + gpio_pull_up(USER_SW_PINS[i]); + + gpio_put(USER_LED_PINS[i], false); + pwm_config cfg = pwm_get_default_config(); + pwm_set_wrap(pwm_gpio_to_slice_num(USER_LED_PINS[i]), 65535); + pwm_init(pwm_gpio_to_slice_num(USER_LED_PINS[i]), &cfg, true); + gpio_set_function(USER_LED_PINS[i], GPIO_FUNC_PWM); + pwm_set_gpio_level(USER_LED_PINS[i], 0); + } + + // Set up the connectivity LED + gpio_put(CONN_LED_PIN, false); + pwm_config cfg = pwm_get_default_config(); + pwm_set_wrap(pwm_gpio_to_slice_num(CONN_LED_PIN), 65535); + pwm_init(pwm_gpio_to_slice_num(CONN_LED_PIN), &cfg, true); + gpio_set_function(CONN_LED_PIN, GPIO_FUNC_PWM); + pwm_set_gpio_level(CONN_LED_PIN, 0); + + return true; // We just return true for now, but could expand in the future +} + +void Automation2040W::conn_led(bool on) { + pwm_set_gpio_level(CONN_LED_PIN, on ? 65535 : 0); +} + +void Automation2040W::conn_led(float brightness) { + brightness = CLAMP(brightness, 0.0f, 100.0f); + const float gamma = 2.8f; + uint16_t value = (uint16_t)(powf(brightness / 100.0f, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(CONN_LED_PIN, value); +} + +bool Automation2040W::switch_pressed(uint sw) { + assert(sw < NUM_SWITCHES); + return !gpio_get(USER_SW_PINS[sw]); +} + +void Automation2040W::switch_led(uint sw, bool on) { + assert(sw < NUM_SWITCHES); + pwm_set_gpio_level(USER_LED_PINS[sw], on ? 65535 : 0); +} + +void Automation2040W::switch_led(uint sw, float brightness) { + assert(sw < NUM_SWITCHES); + + brightness = CLAMP(brightness, 0.0f, 100.0f); + const float gamma = 2.8f; + + uint16_t value = (uint16_t)(powf(brightness / 100.0f, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(USER_LED_PINS[sw], value); +} + +bool Automation2040W::relay(uint relay) { + assert(relay < NUM_RELAYS); + return gpio_get(RELAY_PINS[relay]); +} + +void Automation2040W::relay(uint relay, bool actuate) { + assert(relay < NUM_RELAYS); + gpio_put(RELAY_PINS[relay], actuate); +} + +void Automation2040W::actuate_relay(uint relay) { + assert(relay < NUM_RELAYS); + gpio_put(RELAY_PINS[relay], true); +} + +void Automation2040W::release_relay(uint relay) { + assert(relay < NUM_RELAYS); + gpio_put(RELAY_PINS[relay], false); +} + +bool Automation2040W::output(uint output) { + assert(output < NUM_OUTPUTS); + return gpio_get(OUTPUT_PINS[output]); +} + +void Automation2040W::output(uint output, bool value) { + assert(output < NUM_OUTPUTS); + gpio_put(OUTPUT_PINS[output], value); +} + +bool Automation2040W::read_input(uint input) { + assert(input < NUM_INPUTS); + return gpio_get(IN_BUFFERED_PINS[input]); +} + +float Automation2040W::read_adc(uint adc) { + assert(adc < NUM_ADCS); + float voltage = analogs[adc].read_voltage(); + const float gamma = 2.8f; + uint16_t value = (uint16_t)(powf(voltage / MAX_ADC_LED_VOLTAGE, gamma) * 65535.0f + 0.5f); + pwm_set_gpio_level(ADC_LED_PINS[adc], value); + return voltage; +} + +void Automation2040W::reset() { + // Reset the relays + for(auto i = 0u; i < NUM_RELAYS; i++) { + release_relay(i); + } + + // Reset the outputs + for(auto i = 0u; i < NUM_OUTPUTS; i++) { + output(i, false); + } + + // Reset the adc leds + for(auto i = 0u; i < NUM_ADCS; i++) { + pwm_set_gpio_level(ADC_LED_PINS[i], 0); + } + + // Reset the switch LEDs + for(auto i = 0u; i < NUM_SWITCHES; i++) { + pwm_set_gpio_level(USER_LED_PINS[i], 0); + } + + // Reset the connectivity LED + pwm_set_gpio_level(CONN_LED_PIN, 0); +} +} diff --git a/libraries/automation2040w/automation.hpp b/libraries/automation2040w/automation.hpp new file mode 100644 index 00000000..2b9e75dd --- /dev/null +++ b/libraries/automation2040w/automation.hpp @@ -0,0 +1,113 @@ +#pragma once +#include "pico/stdlib.h" +#include "hardware/pwm.h" + +#include "analog.hpp" +#include "pimoroni_i2c.hpp" + +using namespace pimoroni; + + +namespace automation { + // IO Pin Constants + const uint GP0 = 0; + const uint GP1 = 1; + const uint GP2 = 2; + + + // Index Constants + const uint RELAY_1 = 0; + const uint RELAY_2 = 1; + const uint RELAY_3 = 2; + + const uint OUTPUT_1 = 0; + const uint OUTPUT_2 = 1; + const uint OUTPUT_3 = 2; + + const uint ADC_1 = 0; + const uint ADC_2 = 1; + const uint ADC_3 = 2; + + const uint INPUT_1 = 0; + const uint INPUT_2 = 1; + const uint INPUT_3 = 2; + const uint INPUT_4 = 3; + + const uint SWITCH_A = 0; + const uint SWITCH_B = 1; + + + // Count Constants + const uint NUM_GPIOS = 3; + const uint NUM_RELAYS = 3; + const uint NUM_OUTPUTS = 3; + const uint NUM_ADCS = 3; + const uint NUM_INPUTS = 4; + const uint NUM_SWITCHES = 2; + + + class Automation2040W { + //-------------------------------------------------- + // Constants + //-------------------------------------------------- + public: + static const uint CONN_LED_PIN = 3; + static const uint I2C_SDA_PIN = 4; + static const uint I2C_SCL_PIN = 5; + static const uint ADC_LED_PINS[]; + static const uint RELAY_PINS[]; + static const uint USER_SW_PINS[]; + static const uint USER_LED_PINS[]; + static const uint OUTPUT_PINS[]; + static const uint IN_BUFFERED_PINS[]; + static const uint ADC_PINS[]; + + static constexpr float VOLTAGE_GAIN = 0.06f; // 56 / (56 + 820) + static constexpr float VOLTAGE_OFFSET = -0.06f; + static constexpr float MAX_ADC_LED_VOLTAGE = 45.0f; + + + //-------------------------------------------------- + // Variables + //-------------------------------------------------- + public: + I2C i2c; + + private: + Analog analogs[NUM_ADCS]; + + + //-------------------------------------------------- + // Constructors/Destructor + //-------------------------------------------------- + public: + Automation2040W(); + + + //-------------------------------------------------- + // Methods + //-------------------------------------------------- + public: + bool init(); + + void conn_led(bool on); + void conn_led(float brightness); + bool switch_pressed(uint sw); + void switch_led(uint sw, bool on); + void switch_led(uint sw, float brightness); + + bool relay(uint relay); + void relay(uint relay, bool actuate); + void actuate_relay(uint relay); + void release_relay(uint relay); + + bool output(uint output); + void output(uint output, bool value); + + bool read_input(uint input); + float read_adc(uint adc); + + void reset(); + }; + +} diff --git a/micropython/examples/automation2040w/README.md b/micropython/examples/automation2040w/README.md new file mode 100644 index 00000000..2bce68f5 --- /dev/null +++ b/micropython/examples/automation2040w/README.md @@ -0,0 +1,47 @@ +# Automation 2040 W Micropython Examples + +- [Function Examples](#function-examples) + - [Read ADCs](#read-adcs) + - [Read Inputs](#read-inputs) + - [Toggle Relays](#toggle-relays) + - [Toggle Outputs](#toggle-outputs) + - [Switches and LEDs](#switches-and-leds) + - [Reset Automation](#reset-automation) + + +## Function Examples + +### Read ADCs +[read_adcs.py](read_adcs.py) + +Shows how to read the 3 ADC terminals of Automation 2040 W. + + +### Read Inputs +[read_inputs.py](read_inputs.py) + +Shows how to read the 3 Input terminals of Automation 2040 W. + + +### Toggle Relays +[toggle_relays.py](toggle_relays.py) + +Demonstrates how to toggle the actuation state of each of Automation 2040 W's relays. + + +### Toggle Outputs +[toggle_outputs.py](toggle_outputs.py) + +Demonstrates how to toggle each of Automation 2040 W's output terminals. + + +### Switches and LEDs +[switches_and_leds.py](switches_and_leds.py) + +An example of the user switches and LEDs on Automation 2040 W. + + +### Reset Automation +[reset_automation.py](reset_automation.py) + +A simple program that resets Automation 2040 W, turning off its Relays, Outputs, and LEDs. diff --git a/micropython/examples/automation2040w/read_adcs.py b/micropython/examples/automation2040w/read_adcs.py new file mode 100644 index 00000000..e56b0550 --- /dev/null +++ b/micropython/examples/automation2040w/read_adcs.py @@ -0,0 +1,33 @@ +import time +from automation import Automation2040W, SWITCH_A, NUM_ADCS + +""" +Shows how to read the 3 ADC terminals of Automation 2040 W. + +Press "A" to exit the program. +""" + +UPDATES = 10 # How many times to update per second +ADC_NAMES = ("A1", "A2", "A3") # The friendly names to give each ADC input + +# Create a new Automation2040W +board = Automation2040W() + +# Enable the LED of the switch used to exit the loop +board.switch_led(SWITCH_A, 50) # Half Brightness + +# Read the ADCs until the user switch is pressed +while not board.switch_pressed(SWITCH_A): + + # Read each ADC in turn and print its voltage + for i in range(NUM_ADCS): + voltage = board.read_adc(i) + print(ADC_NAMES[i], " = ", round(voltage, 3), sep="", end=", ") + + # Print a new line + print() + + time.sleep(1.0 / UPDATES) + +# Put the board back into a safe state +board.reset() diff --git a/micropython/examples/automation2040w/read_inputs.py b/micropython/examples/automation2040w/read_inputs.py new file mode 100644 index 00000000..c2379177 --- /dev/null +++ b/micropython/examples/automation2040w/read_inputs.py @@ -0,0 +1,33 @@ +import time +from automation import Automation2040W, SWITCH_A, NUM_INPUTS + +""" +Shows how to read the 3 Input terminals of Automation 2040 W. + +Press "A" to exit the program. +""" + +UPDATES = 10 # How many times to update per second +INPUT_NAMES = ("I1", "I2", "I3", "I4") # The friendly names to give each digital input + +# Create a new Automation2040W +board = Automation2040W() + +# Enable the LED of the switch used to exit the loop +board.switch_led(SWITCH_A, 50) # Half Brightness + +# Read the inputs until the user switch is pressed +while not board.switch_pressed(SWITCH_A): + + # Read each input in turn and print its value + for i in range(NUM_INPUTS): + value = board.read_input(i) + print(INPUT_NAMES[i], " = ", value, sep="", end=", ") + + # Print a new line + print() + + time.sleep(1.0 / UPDATES) + +# Put the board back into a safe state +board.reset() diff --git a/micropython/examples/automation2040w/reset_automation.py b/micropython/examples/automation2040w/reset_automation.py new file mode 100644 index 00000000..5257f0bd --- /dev/null +++ b/micropython/examples/automation2040w/reset_automation.py @@ -0,0 +1,8 @@ +from automation import Automation2040W + +""" +A simple program that resets Automation 2040 W, +turning off its Relays, Outputs, and LEDs. +""" + +board = Automation2040W() diff --git a/micropython/examples/automation2040w/switches_and_leds.py b/micropython/examples/automation2040w/switches_and_leds.py new file mode 100644 index 00000000..c5e05d66 --- /dev/null +++ b/micropython/examples/automation2040w/switches_and_leds.py @@ -0,0 +1,43 @@ +import time +from automation import Automation2040W, SWITCH_A, SWITCH_B, NUM_SWITCHES + +""" +An example of the user switches and LEDs on Automation 2040 W. + +Press "A" and "B" together to exit the program. +""" + +UPDATES = 10 # How many times to update per second +SWITCH_NAMES = ("SW_A", "SW_B") # The friendly names to give each user switch +LED_INCREMENT = 2 # How much to change a LED's brightess each update + +# Create a new Automation2040W +board = Automation2040W() + +# Enable the Conn LED +board.conn_led(True) # Full Brightness + +led_brightnesses = [0.0, 0.0] + +# Interact with the switches and LEDs until both are pressed simultaneously +while not board.switch_pressed(SWITCH_A) or not board.switch_pressed(SWITCH_B): + + for i in range(NUM_SWITCHES): + # Change the LED brightness based on switch's state + if board.switch_pressed(i): + print(SWITCH_NAMES[i], " = Pressed", sep="", end=", ") + led_brightnesses[i] = min(led_brightnesses[i] + LED_INCREMENT, 100) + else: + print(SWITCH_NAMES[i], " = Released", sep="", end=", ") + led_brightnesses[i] = max(led_brightnesses[i] - LED_INCREMENT, 0) + + # Apply the new brightnesses to the LEDs + board.switch_led(i, led_brightnesses[i]) + + # Print a new line + print() + + time.sleep(1.0 / UPDATES) + +# Put the board back into a safe state +board.reset() diff --git a/micropython/examples/automation2040w/toggle_outputs.py b/micropython/examples/automation2040w/toggle_outputs.py new file mode 100644 index 00000000..571b037d --- /dev/null +++ b/micropython/examples/automation2040w/toggle_outputs.py @@ -0,0 +1,43 @@ +import time +from automation import Automation2040W, SWITCH_A, NUM_OUTPUTS + +""" +Demonstrates how to toggle each of Automation 2040 W's output terminals. + +Press "A" to exit the program. +""" + +TIME_PER_TOGGLE = 0.5 # How much time to wait between each toggle (in seconds) +OUTPUT_NAMES = ("O1", "O2", "O3") # The friendly names to give each digital output + +# Create a new Automation2040W +board = Automation2040W() + +# Enable the LED of the switch used to exit the loop +board.switch_led(SWITCH_A, 50) # Half Brightness + +toggle = True +index = 0 + +# Toggle the outputs until the user switch is pressed +while not board.switch_pressed(SWITCH_A): + + # Toggle an output + board.output(index, toggle) + + # Print the state of all outputs + for i in range(NUM_OUTPUTS): + print(OUTPUT_NAMES[i], " = ", board.output(i), sep="", end=", ") + + # Print a new line + print() + + index += 1 # Move on to the next output + if index >= NUM_OUTPUTS: + index = 0 # Go back to the first output + toggle = not toggle # Invert the toggle value + + time.sleep(TIME_PER_TOGGLE) + +# Put the board back into a safe state +board.reset() diff --git a/micropython/examples/automation2040w/toggle_relays.py b/micropython/examples/automation2040w/toggle_relays.py new file mode 100644 index 00000000..56d40198 --- /dev/null +++ b/micropython/examples/automation2040w/toggle_relays.py @@ -0,0 +1,43 @@ +import time +from automation import Automation2040W, SWITCH_A, NUM_RELAYS + +""" +Demonstrates how to toggle the actuation state of each of Automation 2040 W's relays. + +Press "A" to exit the program. +""" + +TIME_PER_TOGGLE = 0.5 # How much time to wait between each toggle (in seconds) +RELAY_NAMES = ("R1", "R2", "R3") # The friendly names to give each relay + +# Create a new Automation2040W +board = Automation2040W() + +# Enable the LED of the switch used to exit the loop +board.switch_led(SWITCH_A, 50) # Half Brightness + +toggle = True +index = 0 + +# Toggle the relays until the user switch is pressed +while not board.switch_pressed(SWITCH_A): + + # Toggle a relay + board.relay(index, toggle) + + # Print the state of all relays + for i in range(NUM_RELAYS): + print(RELAY_NAMES[i], " = ", board.relay(i), sep="", end=", ") + + # Print a new line + print() + + index += 1 # Move on to the next relay + if index >= NUM_RELAYS: + index = 0 # Go back to the first relay + toggle = not toggle # Invert the toggle value + + time.sleep(TIME_PER_TOGGLE) + +# Put the board back into a safe state +board.reset() diff --git a/micropython/modules_py/automation.py b/micropython/modules_py/automation.py new file mode 100644 index 00000000..4ca829de --- /dev/null +++ b/micropython/modules_py/automation.py @@ -0,0 +1,199 @@ +import gc +from machine import Pin, PWM +from pimoroni_i2c import PimoroniI2C +from pimoroni import Analog + +# IO Pin Constants +GP0 = 0 +GP1 = 1 +GP2 = 2 + +# Index Constants +RELAY_1 = 0 +RELAY_2 = 1 +RELAY_3 = 2 + +OUTPUT_1 = 0 +OUTPUT_2 = 1 +OUTPUT_3 = 2 + +ADC_1 = 0 +ADC_2 = 1 +ADC_3 = 2 + +INPUT_1 = 0 +INPUT_2 = 1 +INPUT_3 = 2 +INPUT_4 = 3 + +SWITCH_A = 0 +SWITCH_B = 1 + +# Count Constants +NUM_GPIOS = 3 +NUM_RELAYS = 3 +NUM_OUTPUTS = 3 +NUM_ADCS = 3 +NUM_INPUTS = 4 +NUM_SWITCHES = 2 + + +class Automation2040W(): + CONN_LED_PIN = 3 + I2C_SDA_PIN = 4 + I2C_SCL_PIN = 5 + ADC_LED_PINS = (6, 7, 8) + RELAY_PINS = (9, 10, 11) + USER_SW_PINS = (12, 13) + USER_LED_PINS = (14, 15) + OUTPUT_PINS = (16, 17, 18) + IN_BUFFERED_PINS = (19, 20, 21, 22) + ADC_PINS = (26, 27, 28) + + VOLTAGE_GAIN = 0.06 # 56 / (56 + 820) + VOLTAGE_OFFSET = -0.06 + MAX_ADC_LED_VOLTAGE = 45.0 + + def __init__(self): + # Free up hardware resources + gc.collect() + + # Set up the i2c for Qw/st + self.i2c = PimoroniI2C(self.I2C_SDA_PIN, self.I2C_SCL_PIN, 100000) + + # Set up the relay pins + self.__relays = [] + for i in range(NUM_RELAYS): + self.__relays.append(Pin(self.RELAY_PINS[i], Pin.OUT)) + self.release_relay(i) + + # Set up the output pins + self.__outputs = [] + for i in range(NUM_OUTPUTS): + self.__outputs.append(Pin(self.OUTPUT_PINS[i], Pin.OUT)) + self.output(i, False) + + # Set up the input pins + self.__inputs = [] + for i in range(NUM_INPUTS): + self.__inputs.append(Pin(self.IN_BUFFERED_PINS[i], Pin.IN)) + + # Set up the adc pins and leds + self.__analogs = [] + self.__adc_led_pwms = [] + for i in range(NUM_ADCS): + self.__analogs.append(Analog(self.ADC_PINS[i], self.VOLTAGE_GAIN, offset=self.VOLTAGE_OFFSET)) + led_pwm = PWM(Pin(self.ADC_LED_PINS[i])) + led_pwm.freq(1000) + led_pwm.duty_u16(0) + self.__adc_led_pwms.append(led_pwm) + + # Set up the user switches + self.__switches = [] + self.__switch_led_pwms = [] + for i in range(NUM_SWITCHES): + self.__switches.append(Pin(self.USER_SW_PINS[i], Pin.IN, Pin.PULL_UP)) + led_pwm = PWM(Pin(self.USER_LED_PINS[i])) + led_pwm.freq(1000) + led_pwm.duty_u16(0) + self.__switch_led_pwms.append(led_pwm) + + # Set up the connectivity LED + self.__conn_led_pwm = PWM(Pin(self.CONN_LED_PIN)) + self.__conn_led_pwm.freq(1000) + self.__conn_led_pwm.duty_u16(0) + + def conn_led(self, brightness): + if brightness is True: + self.__conn_led_pwm.duty_u16(65535) + elif brightness is False: + self.__conn_led_pwm.duty_u16(0) + elif brightness < 0.0 or brightness > 100.0: + raise ValueError("brightness out of range. Expected 0 to 100, or True or False") + else: + gamma = 2.8 + value = int(pow(brightness / 100.0, gamma) * 65535 + 0.5) + self.__conn_led_pwm.duty_u16(value) + + def switch_pressed(self, switch): + if switch < 0 or switch > NUM_SWITCHES: + raise ValueError("switch out of range. Expected SWITCH_A (0) or SWITCH_B (1)") + return not self.__switches[switch].value() + + def switch_led(self, switch, brightness): + if switch < 0 or switch > NUM_SWITCHES: + raise ValueError("switch out of range. Expected SWITCH_A (0) or SWITCH_B (1)") + + if brightness is True: + self.__switch_led_pwms[switch].duty_u16(65535) + elif brightness is False: + self.__switch_led_pwms[switch].duty_u16(0) + elif brightness < 0.0 or brightness > 100.0: + raise ValueError("brightness out of range. Expected 0 to 100, or True or False") + else: + gamma = 2.8 + value = int(pow(brightness / 100.0, gamma) * 65535 + 0.5) + self.__switch_led_pwms[switch].duty_u16(value) + + def relay(self, relay, actuate=None): + if relay < 0 or relay > NUM_RELAYS: + raise ValueError("relay out of range. Expected RELAY_1 (0), RELAY_2 (1), or RELAY_3 (2)") + + if actuate is None: + return self.__relays[relay].value() + + self.__relays[relay].value(actuate) + + def actuate_relay(self, relay): + if relay < 0 or relay > NUM_RELAYS: + raise ValueError("relay out of range. Expected RELAY_1 (0), RELAY_2 (1), or RELAY_3 (2)") + self.__relays[relay].on() + + def release_relay(self, relay): + if relay < 0 or relay > NUM_RELAYS: + raise ValueError("relay out of range. Expected RELAY_1 (0), RELAY_2 (1), or RELAY_3 (2)") + self.__relays[relay].off() + + def output(self, output, value=None): + if output < 0 or output > NUM_OUTPUTS: + raise ValueError("output out of range. Expected OUTPUT_1 (0), OUTPUT_2 (1), or OUTPUT_3 (2)") + + if value is None: + return self.__outputs[output].value() + + self.__outputs[output].value(value) + + def read_input(self, input): + if input < 0 or input > NUM_INPUTS: + raise ValueError("input out of range. Expected INPUT_1 (0), INPUT_2 (1), INPUT_3 (2), or INPUT_4 (3)") + return self.__inputs[input].value() + + def read_adc(self, adc): + if adc < 0 or adc > NUM_ADCS: + raise ValueError("adc out of range. Expected ADC_1 (0), ADC_2 (1), or ADC_3 (2)") + + voltage = self.__analogs[adc].read_voltage() + gamma = 2.8 + value = int(pow(voltage / self.MAX_ADC_LED_VOLTAGE, gamma) * 65535 + 0.5) + self.__adc_led_pwms[adc].duty_u16(value) + return voltage + + def reset(self): + # Reset the relays + for i in range(NUM_RELAYS): + self.release_relay(i) + + # Reset the outputs + for i in range(NUM_OUTPUTS): + self.output(i, False) + + # Reset the adc LEDs + for i in range(NUM_ADCS): + self.__adc_led_pwms[i].duty_u16(0) + + # Reset the switch LEDs + for i in range(NUM_SWITCHES): + self.__switch_led_pwms[i].duty_u16(0) + + # Reset the connectivity LED + self.__conn_led_pwm.duty_u16(0)