rusEFI
The most advanced open source ECU
Loading...
Searching...
No Matches
can_gpio_msiobox.cpp
Go to the documentation of this file.
1/**
2 * can_gpio_msiobox.cpp
3 *
4 * - discrete output works
5 * - PWM output works
6 *
7 * TODO list:
8 * - implement input reading
9 * - support PWM out mode
10 * - support VSS capture
11 */
12
13#include "pch.h"
14#include "can_gpio_msiobox.h"
15
16#if EFI_CAN_GPIO
17
18#include "gpio/gpio_ext.h"
19#include "can_listener.h"
20#include "can_msg_tx.h"
21#include <rusefi/endian.h>
22
23/*==========================================================================*/
24/* Driver local definitions. */
25/*==========================================================================*/
26#define DRIVER_NAME "IOBox"
27
28/*==========================================================================*/
29/* Driver local definitions. */
30/*==========================================================================*/
31
32/* HW capabilities exported outside */
33/* Actualy only 7 outputs, add one for aligment */
34#define MSIOBOX_OUTPUTS 8
35/* first 4 capable to capture wheel speed of other frequency signal:
36 * then goes 3 switch inputs.
37 * SW2, SW3 are shared with VSS3, VSS4. See tachin_mask */
38#define MSIOBOX_INPUTS 8
39/* total */
40#define MSIOBOX_SIGNALS (MSIOBOX_OUTPUTS + MSIOBOX_INPUTS)
41
42/* Actual HW capabilities */
43/* On/Off or PWM mode */
44#define MSIOBOX_OUT_COUNT 7
45/* ADC input count */
46#define MSIOBOX_ADC_IN_COUNT 7
47/* Speed in sensors */
48#define MSIOBOX_TACH_IN_COUNT 4
49
50/* timeouts for different states */
51#define MSIOBOX_PING_TIMEOUT 100
52#define MSIOBOX_RESTART_TIMEOUT 1000
53
54/*==========================================================================*/
55/* Driver exported variables. */
56/*==========================================================================*/
57
58/*==========================================================================*/
59/* MegaSquirt I/O box protocol definitions. */
60/*==========================================================================*/
61#define CAN_IOBOX_BASE1 0x200
62#define CAN_IOBOX_BASE2 0x220
63#define CAN_IOBOX_BASE3 0x240
64
65/* Packets from ECU to device */
66#define CAN_IOBOX_PING 0x00
67#define CAN_IOBOX_CONFIG 0x01
68#define CAN_IOBOX_SET_PWM(n) (0x02 + ((n) & 0x03))
69#define CAN_IOBOX_LAST_IN 0x05
70
71/* Packets from device to ECU */
72#define CAN_IOBOX_WHOAMI 0x08
73#define CAN_IOBOX_ADC14 0x09
74#define CAN_IOBOX_ADC57 0x0A
75#define CAN_IOBOX_TACH1 0x0B
76/* ... */
77#define CAN_IOBOX_TACH4 0x0E
78
79struct pwm_settings {
80 beuint16_t on;
81 beuint16_t off;
82} __attribute__ ((packed));
83
84/* Base + 0x00 */
85/* "Are you there?" packet with zero payload */
86struct iobox_ping {
87 /* empty */
88} __attribute__((packed));
89
90/* Base + 0x01 */
91struct iobox_cfg {
92 uint8_t pwm_mask; // 0 - On/Off, 1 - PWM
93 uint8_t pad0;
94 uint8_t tachin_mask;
95 uint8_t pad1;
96 uint8_t adc_broadcast_interval; // mS
97 uint8_t tach_broadcast_interval; // mS
98 uint8_t pad2[2];
99} __attribute__((packed));
100
101/* Base + 0x02, 0x03, 0x04 */
102struct iobox_pwm {
103 pwm_settings ch[2];
104} __attribute__ ((packed));
105
106static_assert(sizeof(iobox_pwm) == 8);
107
108/* Base + 0x05 */
109struct iobox_pwm_last {
110 pwm_settings ch[1];
111 uint8_t out_state;
112} __attribute__ ((packed));
113
114static_assert(sizeof(iobox_pwm_last) == 5);
115
116/* Base + 0x08 */
117struct iobox_whoami {
118 uint8_t version;
119 uint8_t pad[3];
120 beuint16_t pwm_period; // PWM clock periods in 0.01 uS
121 beuint16_t tachin_period; // Tach-in clock periods in 0.01 uS
122} __attribute__((packed));
123
124static_assert(sizeof(iobox_whoami) == 8);
125
126/* Base + 0x09 */
127struct iobox_adc14 {
128 beuint16_t adc[4];
129} __attribute__((packed));
130
131static_assert(sizeof(iobox_adc14) == 8);
132
133/* Base + 0x0A */
134struct iobox_adc57 {
135 uint8_t inputs;
136 uint8_t pad;
137 beuint16_t adc[3];
138} __attribute__((packed));
139
140static_assert(sizeof(iobox_adc57) == 8);
141
142/* Base + 0x0B..0x0E */
143struct iobox_tach {
144 beuint32_t period;
145 beuint16_t n_teeth;
146 beuint16_t total_tooth;
147} __attribute__((packed));
148
149static_assert(sizeof(iobox_tach) == 8);
150
151/*==========================================================================*/
152/* Driver local variables and types. */
153/*==========================================================================*/
154
162
163class MsIoBox final : public GpioChip, public CanListener {
164 /* gpio stuff */
165 int writePad(size_t pin, int value) override;
166 int readPad(size_t pin) override;
167 int setPadPWM(size_t pin, float frequency, float duty) override;
168 brain_pin_diag_e getDiag(size_t pin) override;
169
170public:
171 MsIoBox();
172 MsIoBox(uint32_t bus, uint32_t base, uint16_t period);
173
174 void printState() {
175 efiPrintf("IO state: %d", (int)state);
176 efiPrintf("pwmBaseFreq: %d", (int)pwmBaseFreq);
177 }
178
179 CanListener* request() override;
180 bool acceptFrame(const size_t busIndex, const CANRxFrame& frame) const override;
181
182 int init() override;
183 int config(uint32_t bus, uint32_t base, uint16_t period);
184
185#if 0
186 /* pin argument is pin number within gpio chip, not a global number */
187 int writePad(size_t pin, int value) override {
188 state[pin] = value;
189 return 0;
190 }
191#endif
192
193protected:
194 void decodeFrame(const CANRxFrame& frame, efitick_t nowNt) override;
195
196private:
197 int ping();
198 int setup();
199 int update();
200 void checkState();
201
202 /* PWM output helpers */
203 void CalcOnOffPeriod(int ch, pwm_settings &pwm);
204
205 /* Output states */
206 uint8_t OutMode; // on/off (0) vs pwm (1) bitfield
207 uint8_t OutVal; // for on/off outputs
208 struct {
209 float frequency;
210 float duty;
211 } OutPwm[MSIOBOX_OUT_COUNT];
212 /* ADC inputs */
213 uint16_t AdcValue[MSIOBOX_ADC_IN_COUNT];
214 /* Input mode */
215 uint8_t InMode; //Tach-in config bitfield
216 /* Logical inputs */
217 uint8_t InVal;
218 /* Wheel speed */
219 struct {
220 uint32_t period;
221 uint16_t teeths;
222 uint16_t totalTooth;
223 } Tach[MSIOBOX_TACH_IN_COUNT];
224 /* Can settings */
225 uint32_t m_bus;
226 uint32_t m_base;
227 uint32_t m_period;
228 /* IOBox timebase */
229 /* PWM clock period in 0.01 uS units, default is 5000 */
230 uint32_t pwmBaseFreq = 1000 * 1000 * 100 / 5000; /* Hz */
231 /* Tach-in clock period in 0.01 uS units, default is 66 */
232 uint32_t tachinBaseFreq = 1000 * 1000 * 100 / 66; /* Hz */
233 /* Misc */
234 uint8_t version;
235 /* Flags */
236 bool needUpdate;
237 bool needUpdateConfig;
238
239 /* */
241 Timer stateTimer;
242};
243
244MsIoBox::MsIoBox()
245 : CanListener(0), m_bus(0), m_base(0), m_period(20) {
246}
247
248MsIoBox::MsIoBox(uint32_t bus, uint32_t base, uint16_t period)
249 : CanListener(0), m_bus(bus), m_base(base), m_period(period) {
250 /* init state */
252 stateTimer.reset();
253}
254
255int MsIoBox::init()
256{
257 /* TODO: register can listener here */
258 return 0;
259}
260
261int MsIoBox::config(uint32_t bus, uint32_t base, uint16_t period)
262{
263 /* TODO: sanity checks? */
264 m_bus = bus;
265 m_base = base;
266 m_period = period;
267
268 /* Force init */
270 stateTimer.reset();
271
272 /* TODO: */
273 //registerCanListener(this);
274
275 return 0;
276}
277
278bool MsIoBox::acceptFrame(const size_t busIndex, const CANRxFrame& frame) const {
279 /* TODO: check busIndex! */
280 UNUSED(busIndex);
281
282 /* 11 bit only */
283 if (CAN_ISX(frame)) {
284 return false;
285 }
286
287 uint32_t id = CAN_ID(frame);
288
289 /* packets with ID (base + 0) to (base + 5) are received by MSIOBox
290 * (base + 8) to (base + 14) are emited by MSIOBox */
291 if ((id >= m_base + 8) && (id <= m_base + 14)) {
292 return true;
293 }
294
295 return false;
296}
297
298/* Ping iobox */
299int MsIoBox::ping() {
300 CanTxTyped<iobox_ping> frame(CanCategory::CAN_IOBOX, m_base + CAN_IOBOX_PING, false, 0);
301
302 return 0;
303}
304
305/* Send init settings */
306int MsIoBox::setup() {
307 CanTxTyped<iobox_cfg> cfg(CanCategory::CAN_IOBOX, m_base + CAN_IOBOX_CONFIG, false, 0);
308
309 cfg->pwm_mask = OutMode;
310 cfg->tachin_mask = InMode;
311 cfg->adc_broadcast_interval = m_period;
312 cfg->tach_broadcast_interval = m_period;
313
314 return 0;
315}
316
317void MsIoBox::CalcOnOffPeriod(int channel, pwm_settings &pwm)
318{
319 if ((OutMode & BIT(channel)) == 0) {
320 pwm.on = pwm.off = 0;
321 return;
322 }
323
324 int period = (pwmBaseFreq + (OutPwm[channel].frequency / 2)) / OutPwm[channel].frequency;
325
326 pwm.on = period * OutPwm[channel].duty;
327 pwm.off = period - pwm.on;
328}
329
330/* Send current gpio and pwm states */
331int MsIoBox::update() {
332 /* TODO: protect against OutPwm/OutVal change while we are here */
333
334 /* PWM1 .. PWM6 */
335 for (size_t i = 0; i < 3; i++) {
336 /* sent only if PWMs is in use */
337 if ((OutMode & (BIT(i) | BIT(i + 1))) == 0)
338 continue;
339
340 CanTxTyped<iobox_pwm> pwm(CanCategory::CAN_IOBOX, m_base + CAN_IOBOX_SET_PWM(i), false, 0);
341 for (size_t j = 0; j < 2; j++) {
342 CalcOnOffPeriod(i + j, pwm->ch[j]);
343 }
344 }
345
346 /* PWM7 periods and on/off outputs bitfield - sent always */
347 {
348 CanTxTyped<iobox_pwm_last> pwm(CanCategory::CAN_IOBOX, m_base + CAN_IOBOX_SET_PWM(3), false, 0);
349
350 CalcOnOffPeriod(MSIOBOX_OUT_COUNT - 1, pwm->ch[0]);
351
352 pwm->out_state = OutVal;
353 }
354
355 return 0;
356}
357
358void MsIoBox::decodeFrame(const CANRxFrame& frame, efitick_t) {
359 uint32_t id = CAN_ID(frame);
360 uint32_t offset = id - m_base;
361 bool handled = true;
362
363 if (state == MSIOBOX_READY) {
364 if (offset == CAN_IOBOX_ADC14) {
365 auto data = reinterpret_cast<const iobox_adc14*>(&frame.data8[0]);
366
367 for (size_t i = 0; i < 4; i++) {
368 AdcValue[i] = data->adc[i];
369 }
370 } else if (offset == CAN_IOBOX_ADC57) {
371 auto data = reinterpret_cast<const iobox_adc57*>(&frame.data8[0]);
372
373 InVal = data->inputs;
374 for (size_t i = 0; i < 3; i++) {
375 AdcValue[i + 4] = data->adc[i];
376 }
377 } else if ((offset >= CAN_IOBOX_TACH1) && (offset <= CAN_IOBOX_TACH4)) {
378 size_t i = offset - CAN_IOBOX_TACH1;
379 auto data = reinterpret_cast<const iobox_tach*>(&frame.data8[0]);
380
381 /* TODO: should be atomic, add lock here? */
382 /* TODO: accumulate totalTooth? */
383 Tach[i].period = data->period;
384 Tach[i].teeths = data->n_teeth;
385 Tach[i].totalTooth = data->total_tooth;
386 } else {
387 handled = false;
388 }
389 } else if (state == MSIOBOX_WAIT_WHOAMI) {
390 if (offset == CAN_IOBOX_WHOAMI) {
391 auto data = reinterpret_cast<const iobox_whoami*>(&frame.data8[0]);
392
393 version = data->version;
394 /* convert from 0.01 uS units to Hz */
395 pwmBaseFreq = 1000 * 1000 * 100 / data->pwm_period;
396 tachinBaseFreq = 1000 * 1000 * 100 / data->tachin_period;
397
398 /* apply settings and set sync output states */
399 setup();
400 update();
401
402 /* now we are ready */
404 stateTimer.reset();
405 } else {
406 handled = false;
407 }
408 /* ignore everything else */
409 } else {
410 handled = false;
411 }
412
413 if (handled) {
414 /* TODO: check that we receive EVERY expected packed? */
415 stateTimer.reset();
416 }
417}
418
419/* gpio chip stuff */
420int MsIoBox::writePad(unsigned int pin, int value) {
421 if (pin >= MSIOBOX_OUTPUTS)
422 return -1;
423
424 uint8_t OutModeNew = OutMode & (~BIT(pin));
425 uint8_t OutValNew = OutVal;
426 if (value) {
427 OutValNew |= BIT(pin);
428 } else {
429 OutValNew &= ~BIT(pin);
430 }
431
432 if (OutValNew != OutVal) {
433 OutVal = OutValNew;
434 needUpdate = true;
435 }
436 if (OutModeNew != OutMode) {
437 OutMode = OutModeNew;
438 needUpdateConfig = true;
439 }
440
441 return 0;
442}
443
444int MsIoBox::readPad(size_t pin) {
445 if ((pin < MSIOBOX_OUTPUTS) || (pin >= MSIOBOX_SIGNALS))
446 return -1;
447
448 pin -= MSIOBOX_OUTPUTS;
449
450 if (InMode & BIT(pin)) {
451 /* pin is configured for VSS */
452 return -1;
453 }
454
455 return !!(InVal & BIT(pin));
456}
457
458int MsIoBox::setPadPWM(size_t pin, float frequency, float duty)
459{
460 if (pin >= MSIOBOX_OUT_COUNT)
461 return -1;
462
463 /* TODO: validate frequency? Validate duty? */
464
465 /* Just save values.
466 * Do calculation in update() as at this point we may not receive
467 * iobox_whoami packet with pwmPeriodNs */
468 OutPwm[pin].frequency = frequency;
469 OutPwm[pin].duty = duty;
470
471 if ((OutMode & BIT(pin)) == 0) {
472 OutMode |= BIT(pin);
473 needUpdateConfig = true;
474 }
475
476 /* TODO: chech if updated? */
477 needUpdate = true;
478
479 return 0;
480}
481
482brain_pin_diag_e MsIoBox::getDiag(size_t pin)
483{
484 if (pin >= MSIOBOX_SIGNALS)
485 return PIN_UNKNOWN;
486
487 if ((state == MSIOBOX_READY) && (!stateTimer.hasElapsedMs(m_period * 3)))
488 return PIN_OK;
489
490 /* find better state */
491 return PIN_DRIVER_OFF;
492}
493
494void MsIoBox::checkState(void)
495{
496 switch (state) {
497 case MSIOBOX_DISABLED:
498 /* nop */
499 break;
501 ping();
502
504 stateTimer.reset();
505
506 break;
508 if (stateTimer.hasElapsedMs(MSIOBOX_PING_TIMEOUT)) {
510 stateTimer.reset();
511 }
512 break;
513 case MSIOBOX_READY:
514 if (stateTimer.hasElapsedMs(m_period * 3)) {
516 stateTimer.reset();
517 } else {
518 if (needUpdateConfig) {
519 setup();
520 needUpdateConfig = false;
521 /* Force update */
522 needUpdate = true;
523 }
524 if (needUpdate) {
525 update();
526 }
527 }
528 break;
529 case MSIOBOX_FAILED:
530 if (stateTimer.hasElapsedMs(MSIOBOX_RESTART_TIMEOUT)) {
532 stateTimer.reset();
533 }
534 break;
535 }
536}
537
538CanListener* MsIoBox::request(void) {
539 checkState();
540
541 /* return next */
542 return CanListener::request();
543}
544
545static MsIoBox instance[BOARD_CAN_GPIO_COUNT];
546
549 return;
550 }
551
552 // MSIOBOX_0_OUT_1
553 for (size_t i = 0; i < BOARD_CAN_GPIO_COUNT; i++) {
554 uint32_t can_id = CAN_IOBOX_BASE1 + 0x20 * (static_cast<uint32_t>(engineConfiguration->msIoBox0.id) - static_cast<uint32_t>(MsIoBoxId::ID200));
555
556 /* TODO: pick can bus and refresh rate from settings */
557 if (instance[i].config(0, can_id, 20) == 0) {
559 /* register */
560 int ret = gpiochip_register(Gpio::MSIOBOX_0_OUT_1, DRIVER_NAME, instance[i], MSIOBOX_SIGNALS);
561 if (ret < 0) {
562 // no error handling, not returning error code
563 return;
564 }
565 }
566 }
567
568 addConsoleAction("msioinfo", [](){
569 for (size_t i = 0; i < BOARD_CAN_GPIO_COUNT; i++) {
570 instance[i].printState();
571 }
572 });
573}
574#endif // EFI_CAN_GPIO
uint16_t channel
Definition adc_inputs.h:104
uint16_t adc
Definition adc_inputs.h:103
void registerCanListener(CanListener &listener)
Definition can_rx.cpp:84
uint8_t pad0
uint8_t pad2[2]
uint8_t tach_broadcast_interval
uint8_t pad1
typedef __attribute__
Ignition Mode.
uint8_t adc_broadcast_interval
uint8_t tachin_mask
beuint16_t tachin_period
@ MSIOBOX_FAILED
@ MSIOBOX_WAIT_INIT
@ MSIOBOX_DISABLED
@ MSIOBOX_WAIT_WHOAMI
@ MSIOBOX_READY
uint8_t inputs
beuint16_t on
static MsIoBox instance[BOARD_CAN_GPIO_COUNT]
beuint32_t period
uint8_t pad[3]
pwm_settings ch[2]
beuint16_t n_teeth
uint8_t version
uint8_t pwm_mask
beuint16_t pwm_period
uint8_t out_state
beuint16_t total_tooth
void initCanGpioMsiobox()
beuint16_t off
virtual void decodeFrame(const CANRxFrame &frame, efitick_t nowNt)=0
virtual CanListener * request()
virtual bool acceptFrame(const size_t busIndex, const CANRxFrame &frame) const
void addConsoleAction(const char *token, Void callback)
Register console action without parameters.
@ MSIOBOX_0_OUT_1
int gpiochip_register(brain_pin_e base, const char *name, GpioChip &gpioChip, size_t size)
Register gpiochip.
Definition core.cpp:186
static constexpr persistent_config_s * config
static constexpr engine_configuration_s * engineConfiguration
static float frequency
Definition init_flex.cpp:21
UNUSED(samplingTimeSeconds)
brain_pin_diag_e
state("state", SensorCategory.SENSOR_INPUTS, FieldType.INT8, 1871, 1.0, -1.0, -1.0, "")
brain_pin_e pin
Definition stm32_adc.cpp:15
uint8_t data8[8]
Frame data.
Definition can_mocks.h:55
virtual int setPadPWM(size_t, float, float)
Definition gpio_ext.h:30
virtual brain_pin_diag_e getDiag(size_t)
Definition gpio_ext.h:31
virtual int writePad(size_t, int)
Definition gpio_ext.h:28
virtual int readPad(size_t)
Definition gpio_ext.h:29
virtual int init()=0
static float duty
uint16_t offset
Definition tunerstudio.h:0