Work on LED strip functionality

feature/yukon
ZodiusInfuser 2023-08-03 17:22:11 +01:00
rodzic 34f95e4ed7
commit 5afd816197
8 zmienionych plików z 413 dodań i 94 usunięć

Wyświetl plik

@ -42,7 +42,7 @@ int main() {
//y.find_slots_with_module(LEDStripModule::info());
//y.find_slots_with_module(DualSwitchedModule::info());
//y.find_slots_with_module(BenchPowerModule::info());
y.register_with_slot(&strip, 2);
y.register_with_slot(&strip, 3);
y.initialise_modules();
//y.detect_module(Yukon::SLOT1);
@ -53,6 +53,7 @@ int main() {
//y.detect_module(Yukon::SLOT6);
y.enable_main_output();
strip.enable();
while(!y.is_boot_pressed()) {
y.monitored_sleep_ms(100);

Wyświetl plik

@ -0,0 +1,5 @@
#include "logging.hpp"
namespace pimoroni {
logger logging;
}

Wyświetl plik

@ -0,0 +1,38 @@
#pragma once
#include <iostream>
namespace pimoroni {
enum LoggingLevel {
LOG_NONE = 0,
LOG_WARN,
LOG_INFO,
LOG_DEBUG
};
struct logger {
uint level = LOG_INFO;
void warn(std::string message) {
if(level >= LOG_WARN)
std::cout << message;
}
void info(std::string message) {
if(level >= LOG_INFO) {
std::cout << message;
}
}
void debug(std::string message) {
if(level >= LOG_DEBUG) {
std::cout << message;
}
}
};
extern logger logging;
}

Wyświetl plik

@ -3,9 +3,34 @@
#include "pico/stdlib.h"
#include <typeindex>
#include <string>
#include <vector>
#include <math.h>
#include <stdexcept>
namespace pimoroni {
struct TCA {
uint CHIP;
uint GPIO;
};
struct SLOT {
uint ID;
uint FAST1;
uint FAST2;
uint FAST3;
uint FAST4;
TCA SLOW1;
TCA SLOW2;
TCA SLOW3;
uint ADC1_ADDR;
uint ADC2_TEMP_ADDR;
// Needed for use with std::map
bool operator<(const SLOT& o) const {
return (ID < o.ID);
}
};
enum ADC {
ADC_LOW = 0,
ADC_HIGH = 1,
@ -18,6 +43,21 @@ namespace pimoroni {
HIGH = true
};
class SlotAccessor {
public:
virtual bool get_slow_input(TCA gpio) = 0;
virtual bool get_slow_output(TCA gpio) = 0;
virtual bool get_slow_config(TCA gpio) = 0;
virtual bool get_slow_polarity(TCA gpio) = 0;
virtual void set_slow_output(TCA gpio, bool value) = 0;
virtual void set_slow_config(TCA gpio, bool output) = 0;
virtual void set_slow_polarity(TCA gpio, bool polarity) = 0;
virtual float read_slot_adc1(SLOT slot) = 0;
virtual float read_slot_adc2(SLOT slot) = 0;
};
class YukonModule {
public:
static constexpr float ROOM_TEMP = 273.15f + 25.0f;
@ -25,14 +65,106 @@ namespace pimoroni {
static constexpr float BETA = 3435;
virtual std::string name() = 0;
SLOT slot;
SlotAccessor* __accessor;
bool initialised;
YukonModule() :
slot(),
__accessor(nullptr),
initialised(false) {
clear_readings();
// TODO
//self.__monitor_action_callback = None
}
virtual void initialise(const SLOT& slot, SlotAccessor& accessor) {
// Record the slot we are in, and the ADC functions to call
this->slot = slot;
this->__accessor = &accessor;
initialised = true;
}
bool is_initialised() {
return initialised;
}
void deregister() {
initialised = false;
__accessor = nullptr;
}
virtual void configure() {
// Function for (re)configuring pins etc to their default states needed by the module
}
float __read_adc1() {
if(!is_initialised()) {
throw std::runtime_error("Module not initialised\n");
}
return __accessor->read_slot_adc1(slot);
}
float __read_adc2() {
if(!is_initialised()) {
throw std::runtime_error("Module not initialised\n");
}
return __accessor->read_slot_adc2(slot);
}
float __read_adc2_as_temp() {
if(!is_initialised()) {
throw std::runtime_error("Module not initialised\n");
}
float sense = __accessor->read_slot_adc2(slot);
float r_thermistor = sense / ((3.3f - sense) / 5100.0f);
float t_kelvin = (BETA * ROOM_TEMP) / (BETA + (ROOM_TEMP * log(r_thermistor / RESISTOR_AT_ROOM_TEMP)));
float t_celsius = t_kelvin - 273.15f;
// https://www.allaboutcircuits.com/projects/measuring-temperature-with-an-ntc-thermistor/
return t_celsius;
}
void assign_monitor_action() { //TODO callback_function):
//if not None and not callable(callback_function):
// raise TypeError("callback is not callable or None")
//self.__monitor_action_callback = callback_function
}
virtual void monitor() {
}
virtual std::vector<std::pair<std::string, float>> get_readings() {
std::vector<std::pair<std::string, float>> values;
return values;
}
virtual void process_readings() {
// Use this to calculate averages, or do other post-processing on readings after monitor
}
virtual void clear_readings() {
// Clear any readings that may accumulate, such as min, max, or average
}
std::string __message_header() {
if(!is_initialised()) {
throw std::runtime_error("Module not initialised\n");
}
return "[Slot" + std::to_string(slot.ID) + " '" + name() + "'] ";
}
};
typedef bool (&func_is_module)(uint, bool, bool, bool);
struct ModuleType {
const std::type_index TYPE;
const std::string &NAME;
func_is_module is_module;
const std::type_index TYPE; // The typeid of the module class, used for checking if a detected module is a registered module
const std::string& NAME; // The name of the module class to display to the user
func_is_module is_module; // A reference to the function to call to detect a module of this type
};
#define TYPE_FUNCTION(module_class) \

Wyświetl plik

@ -1,6 +1,13 @@
#pragma once
#include "../common.hpp"
#include <limits>
#include "../../logging.hpp"
#include "../../errors.hpp"
#include "apa102.hpp"
#include "ws2812.hpp"
using namespace plasma;
namespace pimoroni {
@ -8,6 +15,13 @@ namespace pimoroni {
public:
static const std::string NAME;
enum strip_type {
NEOPIXEL = 0,
DOTSTAR = 1
};
static constexpr float TEMPERATURE_THRESHOLD = 50.0f;
static bool is_module(uint adc_level, bool slow1, bool slow2, bool slow3);
virtual std::string name() {
@ -15,6 +29,170 @@ namespace pimoroni {
}
TYPE_FUNCTION(LEDStripModule)
bool halt_on_not_pgood;
bool __last_pgood;
bool __power_good_throughout;
float __max_temperature;
float __min_temperature;
float __avg_temperature;
float __count_avg;
WS2812* led_strip;
uint __power_good;
uint __power_en;
LEDStripModule(bool halt_on_not_pgood = false) :
YukonModule(),
halt_on_not_pgood(halt_on_not_pgood),
__last_pgood(false),
led_strip(nullptr) { //TODO strip_type, num_pixels, brightness=1.0, halt_on_not_pgood=False):
//self.__strip_type = strip_type
//if self.__strip_type == self.NEOPIXEL:
// self.NAME += " (NeoPixel)"
//else:
// self.NAME += " (DotStar)"
//self.__num_pixels = num_pixels
//self.__brightness = brightness
}
~LEDStripModule() {
delete(led_strip);
}
virtual void initialise(const SLOT& slot, SlotAccessor& accessor) {
/*
// Create the strip driver object
if self.__strip_type == self.NEOPIXEL:
from neopixel import NeoPixel
self.pixels = NeoPixel(slot.FAST4, self.__num_pixels, brightness=self.__brightness, auto_write=False)
else:
from adafruit_dotstar import DotStar
self.pixels = DotStar(slot.FAST3, slot.FAST4, self.__num_pixels, brightness=self.__brightness, auto_write=False)
*/
led_strip = new WS2812(60, pio0, 0, slot.FAST4);
led_strip->start(60);
for(auto i = 0u; i < led_strip->num_leds; ++i) {
float hue = float(i) / led_strip->num_leds;
led_strip->set_hsv(i, hue, 1.0f, 1.0f);
}
__power_good = slot.FAST1;
__power_en = slot.FAST2;
// Create the power control pin objects
gpio_init(__power_good); // self.__power_good = DigitalInOut(slot.FAST1)
gpio_init(__power_en); // self.__power_en = DigitalInOut(slot.FAST2)
// Configure strip and power pins
configure();
// Pass the slot and adc functions up to the parent now that module specific initialisation has finished
YukonModule::initialise(slot, accessor);
}
virtual void configure() {
//self.__power_en.switch_to_output(False)
gpio_set_function(__power_en, GPIO_FUNC_SIO);
gpio_set_dir(__power_en, GPIO_OUT);
gpio_put(__power_en, false);
//self.__power_good.switch_to_input(Pull.UP)
gpio_set_function(__power_good, GPIO_FUNC_SIO);
gpio_set_dir(__power_good, GPIO_IN);
gpio_set_pulls(__power_good, true, false);
printf(("LED Strip Configured, apparently: " + std::to_string(__power_en) + "\n").c_str());
}
void enable() {
if(!is_initialised()) {
throw std::runtime_error("Module not initialised\n");
}
gpio_put(__power_en, true); //self.__power_en.value = True
printf(("LED Strip Enabled, apparently: " + std::to_string(__power_en) + "\n").c_str());
}
void disable() {
if(!is_initialised()) {
throw std::runtime_error("Module not initialised\n");
}
gpio_put(__power_en, false); //self.__power_en.value = False
}
bool is_enabled() {
// Documentation has note that this is just for debug use!
return gpio_get_out_level(__power_en); //return self.__power_en.value
}
bool read_power_good() {
return gpio_get(__power_good); //return self.__power_en.value
}
float read_temperature() {
return __read_adc2_as_temp();
}
virtual void monitor() {
bool pgood = read_power_good();
if(!pgood) {
if(halt_on_not_pgood) {
throw FaultError(__message_header() + "Power is not good! Turning off output\n");
}
}
float temperature = read_temperature();
if(temperature > TEMPERATURE_THRESHOLD) {
throw OverTemperatureError(__message_header() + "Temperature of " + std::to_string(temperature) + "°C exceeded the user set level of " + std::to_string(TEMPERATURE_THRESHOLD) + "°C! Turning off output\n");
}
if(__last_pgood && !pgood) {
logging.warn(__message_header() + "Power is not good\n");
}
else if(!__last_pgood && pgood) {
logging.warn(__message_header() + "Power is good\n");
}
// Run some user action based on the latest readings
//if self.__monitor_action_callback is not None:
// self.__monitor_action_callback(pgood, temperature)
__last_pgood = pgood;
__power_good_throughout = __power_good_throughout && pgood;
__max_temperature = MAX(temperature, __max_temperature);
__min_temperature = MIN(temperature, __min_temperature);
__avg_temperature += temperature;
__count_avg += 1;
}
virtual std::vector<std::pair<std::string, float>> get_readings() {
std::vector<std::pair<std::string, float>> values;
values.push_back(std::pair("PGood", __power_good_throughout));
values.push_back(std::pair("T_max", __max_temperature));
values.push_back(std::pair("T_min", __min_temperature));
values.push_back(std::pair("T_avg", __avg_temperature));
return values;
}
virtual void process_readings() {
if(__count_avg > 0) {
__avg_temperature /= __count_avg;
}
}
virtual void clear_readings() {
__power_good_throughout = true;
__max_temperature = -std::numeric_limits<float>::infinity();
__min_temperature = std::numeric_limits<float>::infinity();
__avg_temperature = 0;
__count_avg = 0;
}
};
}

Wyświetl plik

@ -2,6 +2,7 @@ add_library(yukon INTERFACE)
target_sources(yukon INTERFACE
${CMAKE_CURRENT_LIST_DIR}/yukon.cpp
${CMAKE_CURRENT_LIST_DIR}/logging.cpp
${CMAKE_CURRENT_LIST_DIR}/modules/led_strip/led_strip.cpp
${CMAKE_CURRENT_LIST_DIR}/modules/quad_servo/quad_servo_direct.cpp
${CMAKE_CURRENT_LIST_DIR}/modules/quad_servo/quad_servo_reg.cpp
@ -16,4 +17,4 @@ target_sources(yukon INTERFACE
target_include_directories(yukon INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(yukon INTERFACE pico_stdlib pico_graphics tca9555 hardware_adc)
target_link_libraries(yukon INTERFACE pico_stdlib pico_graphics tca9555 hardware_adc plasma)

Wyświetl plik

@ -143,6 +143,11 @@ namespace pimoroni {
}
void Yukon::reset() {
// Only disable the output if enabled (avoids duplicate messages)
if(is_main_output()) {
disable_main_output();
}
// Set the first IO expander's initial state
tca0.set_output_port(0x0000);
tca0.set_polarity_port(0x0000);
@ -309,7 +314,7 @@ namespace pimoroni {
YukonModule* module = slot_assignments[slot];
if(module != nullptr) {
//module.deregister() //TODO
module->deregister();
slot_assignments[slot] = nullptr;
}
}
@ -317,10 +322,6 @@ namespace pimoroni {
const ModuleType* Yukon::__match_module(uint adc_level, bool slow1, bool slow2, bool slow3) {
for(uint i = 0; i < count_of(KNOWN_MODULES); i++) {
const ModuleType& m = KNOWN_MODULES[i];
//printf("%s\n", std::get<0>(KNOWN_MODULES[i]).name());
//printf("%s\n", std::get<1>(KNOWN_MODULES[i]).c_str());
//printf("%d\n", std::get<2>(KNOWN_MODULES[i])(adc_level, slow1, slow2, slow3));
//printf("%s\n", KNOWN_MODULES[i]..name());
if(m.is_module(adc_level, slow1, slow2, slow3)) {
return &m;
}
@ -473,7 +474,7 @@ namespace pimoroni {
if(module != nullptr) {
logging.info("[Slot" + std::to_string(slot.ID) + " '" + module->name() + "'] Initialising ... ");
//TODO module.initialise(slot, self.read_slot_adc1, self.read_slot_adc2)
module->initialise(slot, *this);
logging.info("done\n");
}
}
@ -687,13 +688,18 @@ namespace pimoroni {
//if __monitor_action_callback is not None:
// __monitor_action_callback(voltage, current, temperature);
//for module in self.__slot_assignments.values():
// if module is not None:
// try:
// module.monitor()
// except Exception:
// self.disable_main_output()
// raise # Now the output is off, let the exception continue into user code
for(auto it = slot_assignments.begin(); it != slot_assignments.end(); it++) {
YukonModule* module = it->second;
if(module != nullptr) {
try {
module->monitor();
}
catch(const std::exception& e) {
disable_main_output();
throw; // Now the output is off, let the exception continue into user code
}
}
}
readings.max_voltage = MAX(voltage, readings.max_voltage);
readings.min_voltage = MIN(voltage, readings.min_voltage);
@ -760,9 +766,14 @@ namespace pimoroni {
void Yukon::print_readings() {
__print_named_readings("[Yukon]", get_readings());
//for slot, module in self.__slot_assignments.items():
// if module is not None:
// self.__print_dict(f"[Slot{slot.ID}]", module.get_readings(), allowed, excluded)
for(auto it = slot_assignments.begin(); it != slot_assignments.end(); it++) {
SLOT slot = it->first;
YukonModule* module = it->second;
if(module != nullptr) {
__print_named_readings("[Slot" + std::to_string(slot.ID) + "]", module->get_readings());
}
}
std::cout << std::endl;
//printf("\n");
}
@ -789,9 +800,12 @@ namespace pimoroni {
readings.avg_temperature /= count_avg;
}
//for module in self.__slot_assignments.values():
// if module is not None:
// module.process_readings()
for(auto it = slot_assignments.begin(); it != slot_assignments.end(); it++) {
YukonModule* module = it->second;
if(module != nullptr) {
module->process_readings();
}
}
}
void Yukon::__clear_readings() {
@ -811,9 +825,13 @@ namespace pimoroni {
}
void Yukon::clear_readings() {
__clear_readings();
//for module in self.__slot_assignments.values():
// if module is not None:
// module.clear_readings()
for(auto it = slot_assignments.begin(); it != slot_assignments.end(); it++) {
YukonModule* module = it->second;
if(module != nullptr) {
module->clear_readings();
}
}
}
void Yukon::allow_reading(std::string name) {

Wyświetl plik

@ -5,65 +5,11 @@
#include "drivers/tca9555/tca9555.hpp"
#include "errors.hpp"
#include <list>
#include <iostream>
#include "modules.hpp"
#include "logging.hpp"
namespace pimoroni {
struct TCA {
uint CHIP;
uint GPIO;
};
struct SLOT {
uint ID;
uint FAST1;
uint FAST2;
uint FAST3;
uint FAST4;
TCA SLOW1;
TCA SLOW2;
TCA SLOW3;
uint ADC1_ADDR;
uint ADC2_TEMP_ADDR;
// Needed for use with std::map
bool operator<(const SLOT& o) const {
return (ID < o.ID);
}
};
enum LoggingLevel {
LOG_NONE = 0,
LOG_WARN,
LOG_INFO,
LOG_DEBUG
};
struct logger {
uint level = LOG_INFO;
void warn(std::string message) {
if(level >= LOG_WARN)
std::cout << message;
}
void info(std::string message) {
if(level >= LOG_INFO) {
std::cout << message;
}
}
void debug(std::string message) {
if(level >= LOG_DEBUG) {
std::cout << message;
}
}
};
class Yukon {
class Yukon : public SlotAccessor {
public:
static const SLOT SLOT1;
static const SLOT SLOT2;
@ -137,7 +83,7 @@ namespace pimoroni {
float voltage_limit;
float current_limit;
float temperature_limit;
logger logging;
//logger logging;
std::map<SLOT, YukonModule*> slot_assignments;
void* monitor_action_callback;
@ -173,14 +119,14 @@ namespace pimoroni {
TCA9555& get_tca_chip(uint chip);
public:
bool get_slow_input(TCA gpio);
bool get_slow_output(TCA gpio);
bool get_slow_config(TCA gpio);
bool get_slow_polarity(TCA gpio);
virtual bool get_slow_input(TCA gpio);
virtual bool get_slow_output(TCA gpio);
virtual bool get_slow_config(TCA gpio);
virtual bool get_slow_polarity(TCA gpio);
void set_slow_output(TCA gpio, bool value);
void set_slow_config(TCA gpio, bool output);
void set_slow_polarity(TCA gpio, bool polarity);
virtual void set_slow_output(TCA gpio, bool value);
virtual void set_slow_config(TCA gpio, bool output);
virtual void set_slow_polarity(TCA gpio, bool polarity);
void change_output_mask(uint8_t chip, uint16_t mask, uint16_t state);
//--------------------------------------------------
@ -225,8 +171,8 @@ namespace pimoroni {
float read_current();
float read_temperature();
float read_expansion();
float read_slot_adc1(SLOT slot);
float read_slot_adc2(SLOT slot);
virtual float read_slot_adc1(SLOT slot);
virtual float read_slot_adc2(SLOT slot);
float time();
void assign_monitor_action(void* callback_function);