diff --git a/firmware/mock_hardware/common/i2c.c b/firmware/mock_hardware/common/i2c.c index 191346d..7131671 100644 --- a/firmware/mock_hardware/common/i2c.c +++ b/firmware/mock_hardware/common/i2c.c @@ -18,7 +18,7 @@ int i2c_leader_rx(uint8_t addr, uint8_t* data, uint8_t l) { if (l == 2) { - uint16_t value = hardware_iiGetFollowerData(addr << 8 | last_port); + uint16_t value = hardware_iiGetFollowerData(addr, last_port); data[0] = value >> 8; data[1] = value & 0xFF; } diff --git a/firmware/mock_hardware/mock_hardware_api.c b/firmware/mock_hardware/mock_hardware_api.c index 34d273e..cf00952 100644 --- a/firmware/mock_hardware/mock_hardware_api.c +++ b/firmware/mock_hardware/mock_hardware_api.c @@ -238,22 +238,28 @@ void hardware_setScreenBuffer(uint8_t* buf) screenBuffer = buf; } -// TODO: make this generic for all followers somehow -uint16_t faderbank[16]; +// TODO: make this generic to support more follower types +uint16_t faderbank[64]; -void hardware_iiUpdateFollowerData(uint16_t key, uint16_t data) +void hardware_iiUpdateFollowerData(uint8_t device, uint8_t param, uint16_t data) { - if (key >> 8 == 0x34) + // For now, only support 16 params each on devices 0x34-0x37 + uint8_t offset = device - 0x34; + if (offset < 4 && param < 16) { - faderbank[key & 0xFF] = data; + uint8_t index = offset * 16 + param; + faderbank[index] = data; } } -uint16_t hardware_iiGetFollowerData(uint16_t key) +uint16_t hardware_iiGetFollowerData(uint8_t device, uint8_t param) { - if (key >> 8 == 0x34) + // For now, only support 16 params each on devices 0x34-0x37 + uint8_t offset = device - 0x34; + if (offset < 4 && param < 16) { - return faderbank[key & 0xFF]; + uint8_t index = offset * 16 + param; + return faderbank[index]; } return 0; } diff --git a/firmware/mock_hardware/mock_hardware_api.h b/firmware/mock_hardware/mock_hardware_api.h index c1fb4fe..8babd88 100644 --- a/firmware/mock_hardware/mock_hardware_api.h +++ b/firmware/mock_hardware/mock_hardware_api.h @@ -50,8 +50,8 @@ MOCK_API(void, hidConnect, ()); MOCK_API(void, hidDisconnect, ()); MOCK_API(void, hidMessage, (uint8_t key, uint8_t mod, bool held, bool release)); -MOCK_API(void, iiUpdateFollowerData, (uint16_t key, uint16_t data)); -MOCK_API(uint16_t, iiGetFollowerData, (uint16_t key)); +MOCK_API(void, iiUpdateFollowerData, (uint8_t device, uint8_t param, uint16_t data)); +MOCK_API(uint16_t, iiGetFollowerData, (uint8_t device, uint8_t param)); MOCK_API(bool, iiPushMessage, (uint8_t addr, uint8_t* data, uint8_t length)); MOCK_API(bool, iiPopMessage, (uint8_t* addr, uint8_t* data, uint8_t* length)); diff --git a/src/common/core/FirmwareManager.cpp b/src/common/core/FirmwareManager.cpp index 51729fa..d0fc195 100644 --- a/src/common/core/FirmwareManager.cpp +++ b/src/common/core/FirmwareManager.cpp @@ -459,11 +459,11 @@ void FirmwareManager::hidMessage(uint8_t key, uint8_t mod, bool held, bool relea } } -void FirmwareManager::iiUpdateFollowerData(uint16_t key, uint16_t value) +void FirmwareManager::iiUpdateFollowerData(uint8_t device, uint8_t param, uint16_t value) { if (impl) { - impl->fw_fn_hardware_iiUpdateFollowerData(key, value); + impl->fw_fn_hardware_iiUpdateFollowerData(device, param, value); } } diff --git a/src/common/core/iiBus.cpp b/src/common/core/iiBus.cpp index f1f92d2..44da20c 100644 --- a/src/common/core/iiBus.cpp +++ b/src/common/core/iiBus.cpp @@ -1,55 +1,62 @@ -#include "iiBus.h" +#include "IIBus.h" +#include "LibAVR32Module.hpp" -iiFollowerData_t iiBus::FollowerData; +#define FADERBANK_II_MAX_VALUE 16383 -void iiBus::Initialize() -{ - for (int device = 0x34; device <= 0x37; device++) - { - for (int fader = 0; fader < 16; fader++) - { - iiBus::FollowerData.emplace(std::make_pair((device << 8) | fader, 0)); - } - } -} +extern rack::plugin::Model* modelFaderbank; -iiDevice::iiDevice(rack::Module* module) -: _module(module) +IIBus::IIBus(LibAVR32Module* leader) + : leader(leader) { - if (_module) - { - _module->rightExpander.producerMessage = new iiCommand(); - _module->leftExpander.producerMessage = new iiCommand(); - } } -void iiDevice::setAddress(uint8_t address) +bool IIBus::isFollower(rack::Module* module) { - _address = address; + // Only faderbanks participate in II right now + return module != nullptr && module->model == modelFaderbank; } -void iiDevice::updateFollowerData(uint8_t id, uint16_t data) +void IIBus::step() { - const auto record = iiBus::FollowerData.find((_address << 8) | id); - if (record != iiBus::FollowerData.end()) + if (leader == nullptr) { - record->second.store(data, std::memory_order_relaxed); + return; } -} -void iiDevice::transmit(const iiCommand& msg) -{ - if (_module) + // let's tentatively define a bus as an unbroken chain of II-supporting + // modules directly attached to either the left or right of the leader. + + // scan the "bus" for eligible modules, starting to the leader's left + std::vector followers; + auto module = leader->getLeftExpander().module; + while (isFollower(module)) { - if (_module->rightExpander.producerMessage) - { - *(reinterpret_cast(_module->rightExpander.producerMessage)) = msg; - _module->rightExpander.messageFlipRequested = true; - } - if (_module->leftExpander.producerMessage) + followers.push_back(module); + module = module->getLeftExpander().module; + } + + // flip the order, so we can prioritize left-to-right + if (followers.size() > 1) + { + std::reverse(followers.begin(), followers.end()); + } + + // scan to the leader's right + module = leader->getRightExpander().module; + while (isFollower(module)) + { + followers.push_back(module); + module = module->getRightExpander().module; + } + + // gather params from all followers + for (size_t follower = 0; follower < std::min(static_cast(4), followers.size()); follower++) + { + for (uint8_t fader = 0; fader < std::min(16, followers[follower]->getNumParams()); fader++) { - *(reinterpret_cast(_module->leftExpander.producerMessage)) = msg; - _module->leftExpander.messageFlipRequested = true; + float voltage = followers[follower]->params[fader].getValue(); + uint16_t value = static_cast(voltage / 10.0 * FADERBANK_II_MAX_VALUE); + leader->firmware.iiUpdateFollowerData(follower + 0x34, fader, value); } } -} \ No newline at end of file +} diff --git a/src/common/core/iiBus.h b/src/common/core/iiBus.h index d509573..e951f75 100644 --- a/src/common/core/iiBus.h +++ b/src/common/core/iiBus.h @@ -6,40 +6,24 @@ #include #include -#define MAX_II_DATA_BYTES 8 - -// i2c devices cannot wait on each other. Even if the Rack engine is using multiple threads, -// a leader and follower may be executing on the same thread. -// Since the leader will block waiting on a request for data from a follower, we need to -// have the data ready in advance. Therefore all followers must continuously update a data -// structure accessible to the leader with all data the leader could possible request. - -// cheapo concurrent data structure for time-travelling follower data -typedef std::unordered_map> iiFollowerData_t; - -struct iiBus { - static void Initialize(); - static iiFollowerData_t FollowerData; -}; - -// TODO: send commands from follower over Rack expander bus -struct iiCommand +struct LibAVR32Module; + +// Notes on the work-in-progress implementation of II (monome ecosystem i2c): +// +// An i2c module cannot wait for a response to messages. Even if the Rack engine is +// using multiple threads, a leader and follower may be executing on the same thread. +// +// Since the leader will block waiting on a request for data from a follower, we need to +// have the data ready in advance. Therefore the bus must continuously maintain a data +// structure accessible to the leader with all data the leader could possibly request. + +struct IIBus { - // 7-bit I2C address - uint8_t address; - uint8_t data[MAX_II_DATA_BYTES]; - uint8_t length; -}; - -struct iiDevice -{ - iiDevice(rack::Module* module); + IIBus(LibAVR32Module* leader); + bool isFollower(rack::Module* module); - void setAddress(uint8_t address); - void updateFollowerData(uint8_t id, uint16_t data); - void transmit(const iiCommand& msg); + void step(); protected: - rack::Module* _module; - uint8_t _address; -}; \ No newline at end of file + LibAVR32Module* leader; +}; diff --git a/src/faderbank/FaderbankModule.cpp b/src/faderbank/FaderbankModule.cpp index 9fb33cb..e902941 100644 --- a/src/faderbank/FaderbankModule.cpp +++ b/src/faderbank/FaderbankModule.cpp @@ -1,7 +1,5 @@ #include "FaderbankModule.hpp" -#define FADERBANK_II_MAX_VALUE 16383 - struct FBFaderParam : rack::engine::ParamQuantity { std::string getDisplayValueString() override @@ -37,9 +35,6 @@ void FaderbankModule::process(const ProcessArgs& args) for (unsigned i = 0; i < NUM_FADERS; i++) { outputs[i].setVoltage(params[i].getValue()); - - // float iiValue = params[i].getValue() / 10.0 * FADERBANK_II_MAX_VALUE; - // _iiDevice.updateFollowerData(i, static_cast(iiValue)); } } diff --git a/src/faderbank/FaderbankModule.hpp b/src/faderbank/FaderbankModule.hpp index c0c17b4..9316c62 100644 --- a/src/faderbank/FaderbankModule.hpp +++ b/src/faderbank/FaderbankModule.hpp @@ -1,6 +1,5 @@ #pragma once #include "rack.hpp" -#include "iiBus.h" #include diff --git a/src/plugin.cpp b/src/plugin.cpp index 2fd6f5f..75da872 100644 --- a/src/plugin.cpp +++ b/src/plugin.cpp @@ -17,6 +17,7 @@ using namespace rack; Plugin* pluginInstance; +Model* modelFaderbank; void init(Plugin* p) { @@ -33,7 +34,7 @@ void init(Plugin* p) Model* modelGrid64 = createModel, VirtualGridWidgetTemplate<8, 8>>("grid64"); Model* modelGrid256 = createModel, VirtualGridWidgetTemplate<16, 16>>("grid256"); - Model* modelFaderbank = createModel("faderbank"); + modelFaderbank = createModel("faderbank"); p->addModel(modelWhiteWhale); p->addModel(modelMeadowphysics); diff --git a/src/teletype/TeletypeModule.cpp b/src/teletype/TeletypeModule.cpp index 10c39e1..09f0892 100644 --- a/src/teletype/TeletypeModule.cpp +++ b/src/teletype/TeletypeModule.cpp @@ -33,7 +33,7 @@ struct TTParamQuantity : rack::engine::ParamQuantity TeletypeModule::TeletypeModule() : LibAVR32Module("teletype", "teletype4") -, _iiDevice(this) +, iiBus(this) , screenBuffer{} { // initialize screen with "engine stopped" message @@ -89,15 +89,9 @@ void TeletypeModule::processInputs(const ProcessArgs& args) firmware.setGPIO(A00 + i, inputTriggers[i].isHigh()); } - // for (const auto& [key, value] : iiBus::FollowerData) - // { - // firmware.iiUpdateFollowerData(key, value.load(std::memory_order_relaxed)); - // } - - // iiCommand msg; - // while (firmware.iiPopMessage(&msg.address, msg.data, &msg.length)) { - // _iiDevice.transmit(msg); - // } + // process II follower-to-leader input + // no support for leader-to-follower commands right now + iiBus.step(); } void TeletypeModule::processOutputs(const ProcessArgs& args) diff --git a/src/teletype/TeletypeModule.hpp b/src/teletype/TeletypeModule.hpp index 3063b47..c963fb8 100644 --- a/src/teletype/TeletypeModule.hpp +++ b/src/teletype/TeletypeModule.hpp @@ -1,5 +1,5 @@ #include "LibAVR32Module.hpp" -#include "iiBus.h" +#include "IIBus.h" #include "rack.hpp" @@ -64,7 +64,7 @@ struct TeletypeModule : LibAVR32Module virtual uint8_t* getScreenBuffer() override { return screenBuffer; } protected: - iiDevice _iiDevice; + IIBus iiBus; uint8_t screenBuffer[128 * 64]; };