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