REDAC HybridController
Firmware for LUCIDAC/REDAC Teensy
Loading...
Searching...
No Matches
iblock.cpp
Go to the documentation of this file.
1// Copyright (c) 2024 anabrid GmbH
2// Contact: https://www.anabrid.com/licensing/
3// SPDX-License-Identifier: MIT OR GPL-2.0-or-later
4
5#include "block/iblock.h"
6
7#include "bus/functions.h"
8#include "utils/logging.h"
9
11 4'000'000, MSBFIRST, SPI_MODE2 /* chip expects SPI MODE0, but CLK is inverted on the way */};
12
15
16uint8_t functions::ICommandRegisterFunction::chip_cmd_word(uint8_t chip_input_idx, uint8_t chip_output_idx,
17 bool connect) {
18 return (connect ? 0b1'000'0000 : 0b0'000'0000) | ((chip_output_idx & 0x7) << 4) | (chip_input_idx & 0xF);
19}
20
21blocks::IBlock::IBlock(IBlockHAL *hardware) : FunctionBlock("I"), hardware(hardware), outputs{0} {
22 classifier.class_enum = CLASS_;
23}
24
26
28 if (hardware)
29 return hardware->get_entity_eui();
30 return {};
31}
32
34 return utils::status(hardware->write_upscaling(scaling_factors) and hardware->write_outputs(outputs));
35}
36
37FLASHMEM bool blocks::IBlock::init() {
38 LOG(ANABRID_DEBUG_INIT, __PRETTY_FUNCTION__);
39 if (!FunctionBlock::init()) {
40 return false;
41 };
42 // I-Block matrix is not reset on power-cycle, apparently.
43 if (!write_to_hardware())
44 return false;
45 return true;
46}
47
48FLASHMEM bool blocks::IBlock::_is_connected(uint8_t input, uint8_t output) const {
49 return outputs[output] & INPUT_BITMASK(input);
50}
51
52FLASHMEM bool blocks::IBlock::_is_output_connected(uint8_t output) const { return outputs[output]; }
53
54FLASHMEM bool blocks::IBlock::is_connected(uint8_t input, uint8_t output) const {
55 if (output >= NUM_OUTPUTS or input >= NUM_INPUTS)
56 return false;
57 return _is_connected(input, output);
58}
59
61 for (auto &output : outputs)
62 if (_is_output_connected(output))
63 return true;
64 return false;
65}
66
67FLASHMEM bool blocks::IBlock::connect(uint8_t input, uint8_t output, bool exclusive,
68 bool allow_input_splitting) {
69 if (output >= NUM_OUTPUTS or input >= NUM_INPUTS)
70 return false;
71
72 // Usually, we don't want to connect one input to multiple outputs, so check for other connections
73 if (!allow_input_splitting) {
74 for (size_t other_output_idx = 0; other_output_idx < outputs.size(); other_output_idx++) {
75 if (output == other_output_idx)
76 continue;
77 if (is_connected(input, other_output_idx)) {
78 return false;
79 }
80 }
81 }
82
83 if (exclusive)
84 outputs[output] = INPUT_BITMASK(input);
85 else
86 outputs[output] |= INPUT_BITMASK(input);
87 return true;
88}
89
91 for (auto &output : outputs) {
92 output = 0;
93 }
94}
95
98
100 reset_outputs();
101 reset_upscaling();
102 }
103}
104
106#ifdef ANABRID_DEBUG_ENTITY_CONFIG
107 Serial.println(__PRETTY_FUNCTION__);
108#endif
109 for (auto cfgItr = cfg.begin(); cfgItr != cfg.end(); ++cfgItr) {
110 if (cfgItr->key() == "outputs") {
111 auto res = _config_outputs_from_json(cfgItr->value());
112 if (!res)
113 return res;
114 } else if (cfgItr->key() == "upscaling") {
115 auto res = _config_upscaling_from_json(cfgItr->value());
116 if (!res)
117 return res;
118 } else {
119 return utils::status("IBlock: Unknown configuration key");
120 }
121 }
122 return utils::status::success();
123}
124
125FLASHMEM utils::status blocks::IBlock::_config_outputs_from_json(const JsonVariantConst &cfg) {
126 // Handle a mapping of output to list of inputs
127 // This only overrides outputs that are specifically mentioned
128 if (cfg.is<JsonObjectConst>()) {
129 for (JsonPairConst keyval : cfg.as<JsonObjectConst>()) {
130 // Key defines output
131 // TODO: Check conversion from string to number
132
133 std::string output_str = keyval.key().c_str();
134 if (!utils::is_number(output_str.begin(), output_str.end()))
135 return utils::status("IBlock: Expected number but key is '%s'", output_str.c_str());
136
137 auto output = std::stoul(output_str);
138 // Disconnect also sanity checks output index for us
139 if (!disconnect(output))
140 return utils::status("IBlock: Could not disconnect output '%d', probably out of range", output);
141 // Input may be given as list or as a single number
142 auto res = _connect_from_json(keyval.value(), output);
143 if (!res)
144 return res;
145 }
146 return utils::status::success();
147 }
148 // Handle a list of outputs
149 // This must overwrite all outputs, so we clear all of them
150 else if (cfg.is<JsonArrayConst>()) {
151 auto outputs_json = cfg.as<JsonArrayConst>();
152 if (outputs_json.size() != NUM_OUTPUTS)
153 return utils::status("IBlock: Given array size %d does not meet expected %d", outputs_json.size(),
154 NUM_OUTPUTS);
155 reset_outputs();
156 uint8_t idx = 0;
157 for (JsonVariantConst input_spec : outputs_json) {
158 // Input may be given as list or as a single number
159 auto res = _connect_from_json(input_spec, idx++);
160 if (!res)
161 return res;
162 }
163 return utils::status::success();
164 }
165 return utils::status("IBlock: Expected either array or object as configuration");
166}
167
168FLASHMEM utils::status blocks::IBlock::_config_upscaling_from_json(const JsonVariantConst &cfg) {
169 if (cfg.is<JsonObjectConst>()) {
170 for (JsonPairConst keyval : cfg.as<JsonObjectConst>()) {
171 if (!keyval.value().is<bool>())
172 return utils::status("IBlock upscaling must be boolean");
173
174 auto input = std::stoul(keyval.key().c_str());
175 if (input > NUM_INPUTS)
176 return utils::status("IBlock upscaling too many values");
177
178 set_upscaling(input, keyval.value());
179 return utils::status::success();
180 }
181 } else if (cfg.is<JsonArrayConst>()) {
182 auto array = cfg.as<JsonArrayConst>();
183 if (array.size() != NUM_INPUTS)
184 return utils::status("IBlock upscaling wrong number of inputs");
185
186 for (uint8_t input = 0; input < NUM_INPUTS; input++) {
187 if (!array[input].is<bool>())
188 return utils::status("IBlock upscaling expecting boolean");
189 set_upscaling(input, array[input]);
190 }
191 return utils::status::success();
192 }
193 return utils::status("IBlock upscaling: Either provide list or object");
194}
195
196FLASHMEM utils::status blocks::IBlock::_connect_from_json(const JsonVariantConst &input_spec, uint8_t output) {
197 if (input_spec.isNull()) {
198 // Output is already disconnected from outer code
199 } else if (input_spec.is<JsonArrayConst>()) {
200 for (auto input : input_spec.as<JsonArrayConst>()) {
201 if (!input.is<uint8_t>())
202 return utils::status("IBlock: input is not an integer");
203 if (!connect(input, output))
204 return utils::status("IBlock: Could not connect %d -> %d, probably out of bounds", input, outputs);
205 }
206 } else if (input_spec.is<uint8_t>()) {
207 if (!connect(input_spec, output))
208 return utils::status("IBlock: Could not connect %d -> %d.", input_spec, output);
209 } else {
210 return utils::status("IBlock: Illegal data type in input specification");
211 }
212 return utils::status::success();
213}
214
215FLASHMEM bool blocks::IBlock::disconnect(uint8_t input, uint8_t output) {
216 // Fail if input was not connected
217 if (!is_connected(input, output))
218 return false;
219 outputs[output] &= ~INPUT_BITMASK(input);
220 return true;
221}
222
223FLASHMEM bool blocks::IBlock::disconnect(uint8_t output) {
224 if (output >= NUM_OUTPUTS)
225 return false;
226 outputs[output] = 0;
227 return true;
228}
229
230FLASHMEM bool blocks::IBlock::set_upscaling(uint8_t input, bool upscale) {
231 if (input >= NUM_INPUTS)
232 return false;
233 scaling_factors[input] = upscale;
234 return true;
235}
236
237FLASHMEM void blocks::IBlock::set_upscaling(std::bitset<NUM_INPUTS> scales) { scaling_factors = scales; }
238
239FLASHMEM void blocks::IBlock::reset_upscaling() { scaling_factors.reset(); }
240
241FLASHMEM bool blocks::IBlock::get_upscaling(uint8_t output) const {
242 if (output > 32)
243 return false;
244 else
245 return scaling_factors[output];
246}
247
248FLASHMEM void blocks::IBlock::config_self_to_json(JsonObject &cfg) {
249 Entity::config_self_to_json(cfg);
250 // Save outputs into cfg
251 auto outputs_cfg = cfg.createNestedArray("outputs");
252 for (uint8_t output = 0; output < NUM_OUTPUTS; output++) {
253 if (outputs[output]) {
254 auto inputs_cfg = outputs_cfg.createNestedArray();
255 for (uint8_t input = 0; input < NUM_INPUTS; input++)
256 if (_is_connected(input, output))
257 inputs_cfg.add(input);
258 } else {
259 outputs_cfg.add(nullptr);
260 }
261 }
262
263 auto upscaling_cfg = cfg.createNestedArray("upscaling");
264 for (uint8_t input = 0; input < NUM_INPUTS; input++) {
265 if (scaling_factors[input])
266 upscaling_cfg.add(true);
267 else
268 upscaling_cfg.add(false);
269 }
270}
271
272FLASHMEM blocks::IBlock *blocks::IBlock::from_entity_classifier(entities::EntityClassifier classifier,
273 bus::addr_t block_address) {
274 if (!classifier or classifier.class_enum != CLASS_)
275 return nullptr;
276
277 // Currently, there are no different variants
278 if (classifier.variant != entities::EntityClassifier::DEFAULT_)
279 return nullptr;
280
281 if (classifier.version < entities::Version(1, 2))
282 return nullptr;
283 if (classifier.version < entities::Version(1, 3)) {
284 auto *new_block = new IBlock(new IBlockHAL_V_1_2_X(block_address));
285 new_block->classifier = classifier;
286 return new_block;
287 }
288 return nullptr;
289}
290
291FLASHMEM const std::array<uint32_t, blocks::IBlock::NUM_OUTPUTS> &blocks::IBlock::get_outputs() const {
292 return outputs;
293}
294
295FLASHMEM void blocks::IBlock::set_outputs(const std::array<uint32_t, NUM_OUTPUTS> &outputs_) {
296 outputs = outputs_;
297}
298
300 : f_meta(block_address), f_cmd{bus::replace_function_idx(block_address, 2)},
301 f_imatrix_reset{bus::replace_function_idx(block_address, 4)}, f_imatrix_sync{bus::replace_function_idx(
302 block_address, 3)},
303 scaling_register{bus::replace_function_idx(block_address, 5), true},
304 scaling_register_sync{bus::replace_function_idx(block_address, 6)} {}
305
306FLASHMEM bool blocks::IBlockHAL_V_1_2_X::write_outputs(const std::array<uint32_t, 16> &outputs) {
307 f_imatrix_reset.trigger();
308 delayNanoseconds(420);
309
310 // TODO: This can be further improved by not naively iterating over the output indizes.
311 // For each output, send the corresponding 32bit commands.
312 // For output_idx < 7, the first two MT8816 chips are used, for >8 the later two.
313 // This means, we can set two outputs simultaneously.
314 // When setting later outputs, we need to *not* overwrite previous outputs on the other chips
315 // Thus we remember what we send them and just send them the same thing again
316 uint32_t remembered_command = 0;
317 for (decltype(outputs.size()) output_idx = 0; output_idx < outputs.size() / 2; output_idx++) {
318 uint32_t command = 0;
319 const auto oidx_one_two = output_idx;
320 const auto oidx_three_four = output_idx + outputs.size() / 2;
321 if (!outputs[oidx_one_two] && !outputs[oidx_three_four])
322 continue;
323
324 // We can always set one output in range (0,15) and one in (16,31) in for each output
325 for (uint8_t input_idx = 0; input_idx < NUM_INPUTS / 2; input_idx++) {
326
327 command = 0;
328
329 const auto iidx_one_three = input_idx;
330 const auto iidx_two_four = input_idx + NUM_INPUTS / 2;
331 bool actual_data = false;
332 // First chip combines oidx_one_two and iidx_one_three
333 if (outputs[oidx_one_two] & INPUT_BITMASK(iidx_one_three)) {
334 command |= functions::ICommandRegisterFunction::chip_cmd_word(iidx_one_three, oidx_one_two);
335 actual_data = true;
336 } else {
337 command |= (remembered_command & 0xFF);
338 }
339 // Similar combination for second chip
340 if (outputs[oidx_one_two] & INPUT_BITMASK(iidx_two_four)) {
341 command |= functions::ICommandRegisterFunction::chip_cmd_word(iidx_two_four, oidx_one_two) << 8;
342 actual_data = true;
343 } else {
344 command |= (remembered_command & 0xFF00);
345 }
346 // Third chip
347 if (outputs[oidx_three_four] & INPUT_BITMASK(iidx_one_three)) {
348 command |= functions::ICommandRegisterFunction::chip_cmd_word(iidx_one_three, oidx_three_four) << 16;
349 actual_data = true;
350 } else {
351 command |= (remembered_command & 0xFF0000);
352 }
353 // Fourth chip
354 if (outputs[oidx_three_four] & INPUT_BITMASK(iidx_two_four)) {
355 command |= functions::ICommandRegisterFunction::chip_cmd_word(iidx_two_four, oidx_three_four) << 24;
356 actual_data = true;
357 } else {
358 command |= (remembered_command & 0xFF000000);
359 }
360
361 if (actual_data) {
362 remembered_command = command;
363 // Send out data
364 if (!f_cmd.transfer32(command)) {
365 LOG(ANABRID_PEDANTIC, __PRETTY_FUNCTION__);
366 return false;
367 }
368 // Apply command
369 f_imatrix_sync.trigger();
370 }
371 }
372 }
373 return true;
374}
375
376FLASHMEM bool blocks::IBlockHAL_V_1_2_X::write_upscaling(std::bitset<32> upscaling) {
377 if (!scaling_register.transfer32(upscaling.to_ulong()))
378 return false;
379 scaling_register_sync.trigger();
380 return true;
381}
A function block represents one module in a cluster, such as an M-Block, C-Block, I-Block or U-Block.
Definition base.h:29
bool write_upscaling(std::bitset< 32 > upscaling) override
Definition iblock.cpp:376
IBlockHAL_V_1_2_X(bus::addr_t block_address)
Definition iblock.cpp:299
bool write_outputs(const std::array< uint32_t, 16 > &outputs) override
Definition iblock.cpp:306
The Lucidac I-Block (I for Current; the Implicit Summing Block) is represented by this class.
Definition iblock.h:99
void config_self_to_json(JsonObject &cfg) override
Serialize the configuration of this entity to a JsonObject.
Definition iblock.cpp:248
void reset_upscaling()
Sets all 32 input scales to the default 1.0.
Definition iblock.cpp:239
utils::status config_self_from_json(JsonObjectConst cfg) override
Deserialize a new configuration for this entity from a JsonObject.
Definition iblock.cpp:105
static IBlock * from_entity_classifier(entities::EntityClassifier classifier, bus::addr_t block_address)
Definition iblock.cpp:272
bool _is_connected(uint8_t input, uint8_t output) const
Definition iblock.cpp:48
utils::status write_to_hardware() override
returns true in case of success
Definition iblock.cpp:33
void reset(entities::ResetAction action) override
Definition iblock.cpp:96
void set_outputs(const std::array< uint32_t, NUM_OUTPUTS > &outputs_)
Definition iblock.cpp:295
bool _is_output_connected(uint8_t output) const
Definition iblock.cpp:52
metadata::eui_t get_entity_eui() const override
Definition iblock.cpp:27
const std::array< uint32_t, NUM_OUTPUTS > & get_outputs() const
Definition iblock.cpp:291
utils::status _connect_from_json(const JsonVariantConst &input_spec, uint8_t output)
Definition iblock.cpp:196
bool is_anything_connected() const
Definition iblock.cpp:60
bool is_connected(uint8_t input, uint8_t output) const
Whether an input is connected to an output.
Definition iblock.cpp:54
void reset_outputs()
Definition iblock.cpp:90
bool connect(uint8_t input, uint8_t output, bool exclusive=false, bool allow_input_splitting=false)
Connects an input line [0..31] to an output line [0..15] by setting an appropriate bit/switch in the ...
Definition iblock.cpp:67
utils::status _config_upscaling_from_json(const JsonVariantConst &cfg)
Definition iblock.cpp:168
bool init() override
returns true in case of success
Definition iblock.cpp:37
bool disconnect(uint8_t input, uint8_t output)
Disconnect one input from an output. Fails for invalid arguments or if no input is connected.
Definition iblock.cpp:215
static constexpr auto CLASS_
Definition iblock.h:102
bool set_upscaling(uint8_t input, bool upscale)
Sets the input scale of the corresponding output.
Definition iblock.cpp:230
utils::status _config_outputs_from_json(const JsonVariantConst &cfg)
Definition iblock.cpp:125
bool get_upscaling(uint8_t output) const
Returns the input scale of the corresponding output.
Definition iblock.cpp:241
EntityClassifier classifier
Definition base.h:217
virtual void reset(ResetAction action)
Definition base.h:155
virtual bool init()
returns true in case of success
Definition base.h:153
ICommandRegisterFunction(bus::addr_t address)
Definition iblock.cpp:13
static uint8_t chip_cmd_word(uint8_t chip_input_idx, uint8_t chip_output_idx, bool connect=true)
Definition iblock.cpp:16
static const SPISettings DEFAULT_SPI_SETTINGS
Definition iblock.h:10
The SR74HCT595 is an 8-Bit Shift Register with 3-State Output Registers.
Definition SR74HCT595.h:16
A recoverable error, inspired from https://abseil.io/docs/cpp/guides/status and https://github....
Definition error.h:35
static status success()
Syntactic sugar for success.
Definition error.h:104
uint32_t
Definition flasher.cpp:195
#define LOG(LOG_FLAG, message)
Definition logging.h:36
Definition bus.h:22
uint16_t addr_t
Definition bus.h:27
std::array< uint8_t, 8 > eui_t
Definition base.h:20
bool is_number(const std::string::const_iterator &start, const std::string::const_iterator &end)
Definition is_number.cpp:8
bool has(uint8_t other)
Definition base.h:111
static constexpr uint8_t CIRCUIT_RESET
Definition base.h:102