From f80f1a70777dd84171af02200b77033a08fba28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20S=C3=B6lver?= Date: Thu, 15 Jan 2015 23:16:57 +0100 Subject: [PATCH] stmhal: Add support for CAN rx callbacks. --- docs/library/pyb.CAN.rst | 72 +++++++++++----- stmhal/can.c | 177 +++++++++++++++++++++++++++++++++++---- stmhal/can.h | 4 + stmhal/main.c | 7 ++ stmhal/mpconfigport.h | 3 + stmhal/qstrdefsport.h | 1 + stmhal/stm32f4xx_it.c | 19 +++++ tests/pyb/can.py | 72 ++++++++++++++++ tests/pyb/can.py.exp | 24 ++++++ 9 files changed, 345 insertions(+), 34 deletions(-) diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst index e160f67970..8f417f735b 100644 --- a/docs/library/pyb.CAN.rst +++ b/docs/library/pyb.CAN.rst @@ -26,30 +26,30 @@ Constructors initialised (it has the settings from the last initialisation of the bus, if any). If extra arguments are given, the bus is initialised. See ``init`` for parameters of initialisation. - + The physical pins of the CAN busses are: - + - ``CAN(1)`` is on ``YA``: ``(RX, TX) = (Y3, Y4) = (PB8, PB9)`` - ``CAN(2)`` is on ``YB``: ``(RX, TX) = (Y5, Y6) = (PB12, PB13)`` Class Methods ------------- .. method:: CAN.initfilterbanks(nr) - + Reset and disable all filter banks and assign how many banks should be available for CAN(1). - + STM32F405 has 28 filter banks that are shared between the two available CAN bus controllers. - This function configures how many filter banks should be assigned to each. ``nr`` is the number of banks - that will be assigned to CAN(1), the rest of the 28 are assigned to CAN(2). + This function configures how many filter banks should be assigned to each. ``nr`` is the number of banks + that will be assigned to CAN(1), the rest of the 28 are assigned to CAN(2). At boot, 14 banks are assigned to each controller. - + Methods ------- .. method:: can.init(mode, extframe=False, prescaler=100, \*, sjw=1, bs1=6, bs2=8) Initialise the CAN bus with the given parameters: - + - ``mode`` is one of: NORMAL, LOOPBACK, SILENT, SILENT_LOOPBACK - if ``extframe`` is True then the bus uses extended identifiers in the frames (29 bits); otherwise it uses standard 11 bit identifiers @@ -83,14 +83,14 @@ Methods Turn off the CAN bus. .. method:: can.setfilter(bank, mode, fifo, params) - + Configure a filter bank: - + - ``bank`` is the filter bank that is to be configured. - ``mode`` is the mode the filter should operate in. - - ``fifo`` is which fifo (0 or 1) a message should be stored in, if it is accepted by this filter. + - ``fifo`` is which fifo (0 or 1) a message should be stored in, if it is accepted by this filter. - ``params`` is an array of values the defines the filter. The contents of the array depends on the ``mode`` argument. - + +-----------+---------------------------------------------------------+ |``mode`` |contents of parameter array | +===========+=========================================================+ @@ -106,11 +106,11 @@ Methods +-----------+---------------------------------------------------------+ |CAN.MASK32 |As with CAN.MASK16 but with only one 32 bit id/mask pair.| +-----------+---------------------------------------------------------+ - + .. method:: can.clearfilter(bank) Clear and disables a filter bank: - + - ``bank`` is the filter bank that is to be cleared. .. method:: can.any(fifo) @@ -120,22 +120,56 @@ Methods .. method:: can.recv(fifo, \*, timeout=5000) Receive data on the bus: - + - ``fifo`` is an integer, which is the FIFO to receive on - ``timeout`` is the timeout in milliseconds to wait for the receive. - + Return value: buffer of data bytes. .. method:: can.send(send, addr, \*, timeout=5000) Send a message on the bus: - + - ``send`` is the data to send (an integer to send, or a buffer object). - ``addr`` is the address to send to - ``timeout`` is the timeout in milliseconds to wait for the send. - + Return value: ``None``. +.. method:: can.rxcallback(fifo, fun) + + Register a function to be called when a message is accepted into a empty fifo: + + - ``fifo`` is the receiving fifo. + - ``fun`` is the function to be called when the fifo becomes non empty. + + The callback function takes two arguments the first is the can object it self the second is + a integer that indicates the reason for the callback. + + +--------+------------------------------------------------+ + | Reason | | + +========+================================================+ + | 0 | A message has been accepted into a empty FIFO. | + +--------+------------------------------------------------+ + | 1 | The FIFO is full | + +--------+------------------------------------------------+ + | 2 | A message has been lost due to a full FIFO | + +--------+------------------------------------------------+ + + Example use of rxcallback:: + + def cb0(bus, reason): + print('cb0') + if reason == 0: + print('pending') + if reason == 1: + print('full') + if reason == 2: + print('overflow') + + can = CAN(1, CAN.LOOPBACK) + can.rxcallback(0, cb0) + Constants --------- @@ -151,4 +185,4 @@ Constants .. data:: CAN.LIST32 .. data:: CAN.MASK32 - the operation mode of a filter + the operation mode of a filter diff --git a/stmhal/can.c b/stmhal/can.c index b10ecd13d9..654daddf6f 100644 --- a/stmhal/can.c +++ b/stmhal/can.c @@ -32,6 +32,8 @@ #include "py/nlr.h" #include "py/objtuple.h" #include "py/runtime.h" +#include "py/gc.h" +#include "py/pfenv.h" #include "bufhelper.h" #include "can.h" #include "pybioctl.h" @@ -64,14 +66,27 @@ /// can.send('message!', 123) # send message with id 123 /// can.recv(0) # receive message on FIFO 0 +typedef enum _rx_state_t { + RX_STATE_FIFO_EMPTY = 0, + RX_STATE_MESSAGE_PENDING, + RX_STATE_FIFO_FULL, + RX_STATE_FIFO_OVERFLOW, +} rx_state_t; + typedef struct _pyb_can_obj_t { mp_obj_base_t base; + mp_obj_t rxcallback0; + mp_obj_t rxcallback1; mp_uint_t can_id : 8; bool is_enabled : 1; bool extframe : 1; + byte rx_state0; + byte rx_state1; CAN_HandleTypeDef can; } pyb_can_obj_t; +STATIC mp_obj_t pyb_can_deinit(mp_obj_t self_in); + STATIC uint8_t can2_start_bank = 14; // assumes Init parameters have been set up correctly @@ -124,18 +139,18 @@ STATIC bool can_init(pyb_can_obj_t *can_obj) { return true; } -STATIC void can_deinit(pyb_can_obj_t *can_obj) { - can_obj->is_enabled = false; - CAN_HandleTypeDef *can = &can_obj->can; - HAL_CAN_DeInit(can); - if (can->Instance == CAN1) { - __CAN1_FORCE_RESET(); - __CAN1_RELEASE_RESET(); - __CAN1_CLK_DISABLE(); - } else if (can->Instance == CAN2) { - __CAN2_FORCE_RESET(); - __CAN2_RELEASE_RESET(); - __CAN2_CLK_DISABLE(); +void can_init0(void) { + for (uint i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all)); i++) { + MP_STATE_PORT(pyb_can_obj_all)[i] = NULL; + } +} + +void can_deinit(void) { + for (int i = 0; i < MP_ARRAY_SIZE(MP_STATE_PORT(pyb_can_obj_all)); i++) { + pyb_can_obj_t *can_obj = MP_STATE_PORT(pyb_can_obj_all)[i]; + if (can_obj != NULL) { + pyb_can_deinit(can_obj); + } } } @@ -260,6 +275,11 @@ STATIC mp_obj_t pyb_can_make_new(mp_obj_t type_in, mp_uint_t n_args, mp_uint_t n } else { o->can_id = mp_obj_get_int(args[0]); } + o->rxcallback0 = mp_const_none; + o->rxcallback1 = mp_const_none; + MP_STATE_PORT(pyb_can_obj_all)[o->can_id - 1] = o; + o->rx_state0 = RX_STATE_FIFO_EMPTY; + o->rx_state1 = RX_STATE_FIFO_EMPTY; if (n_args > 1 || n_kw > 0) { // start the peripheral @@ -280,7 +300,21 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_init_obj, 1, pyb_can_init); /// Turn off the CAN bus. STATIC mp_obj_t pyb_can_deinit(mp_obj_t self_in) { pyb_can_obj_t *self = self_in; - can_deinit(self); + self->is_enabled = false; + HAL_CAN_DeInit(&self->can); + if (self->can.Instance == CAN1) { + HAL_NVIC_DisableIRQ(CAN1_RX0_IRQn); + HAL_NVIC_DisableIRQ(CAN1_RX1_IRQn); + __CAN1_FORCE_RESET(); + __CAN1_RELEASE_RESET(); + __CAN1_CLK_DISABLE(); + } else if (self->can.Instance == CAN2) { + HAL_NVIC_DisableIRQ(CAN2_RX0_IRQn); + HAL_NVIC_DisableIRQ(CAN2_RX1_IRQn); + __CAN2_FORCE_RESET(); + __CAN2_RELEASE_RESET(); + __CAN2_CLK_DISABLE(); + } return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(pyb_can_deinit_obj, pyb_can_deinit); @@ -334,7 +368,7 @@ STATIC mp_obj_t pyb_can_send(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_ // send the data CanTxMsgTypeDef tx_msg; - if (self->extframe){ + if (self->extframe) { tx_msg.ExtId = args[1].u_int & 0x1FFFFFFF; tx_msg.IDE = CAN_ID_EXT; } else { @@ -385,6 +419,33 @@ STATIC mp_obj_t pyb_can_recv(mp_uint_t n_args, const mp_obj_t *pos_args, mp_map_ mp_hal_raise(status); } + // Manage the rx state machine + if ((args[0].u_int == CAN_FIFO0 && self->rxcallback0 != mp_const_none) || + (args[0].u_int == CAN_FIFO1 && self->rxcallback1 != mp_const_none)) { + byte *state = (args[0].u_int == CAN_FIFO0) ? &self->rx_state0 : &self->rx_state1; + + switch (*state) { + case RX_STATE_FIFO_EMPTY: + break; + case RX_STATE_MESSAGE_PENDING: + if (__HAL_CAN_MSG_PENDING(&self->can, args[0].u_int) == 0) { + // Fifo is empty + __HAL_CAN_ENABLE_IT(&self->can, (args[0].u_int == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + *state = RX_STATE_FIFO_EMPTY; + } + break; + case RX_STATE_FIFO_FULL: + __HAL_CAN_ENABLE_IT(&self->can, (args[0].u_int == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); + *state = RX_STATE_MESSAGE_PENDING; + break; + case RX_STATE_FIFO_OVERFLOW: + __HAL_CAN_ENABLE_IT(&self->can, (args[0].u_int == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1); + __HAL_CAN_ENABLE_IT(&self->can, (args[0].u_int == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); + *state = RX_STATE_MESSAGE_PENDING; + break; + } + } + // return the received data // TODO use a namedtuple (when namedtuple types can be stored in ROM) mp_obj_tuple_t *tuple = mp_obj_new_tuple(4, NULL); @@ -525,6 +586,39 @@ error: } STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_setfilter_obj, 1, pyb_can_setfilter); +STATIC mp_obj_t pyb_can_rxcallback(mp_obj_t self_in, mp_obj_t fifo_in, mp_obj_t callback_in) { + pyb_can_obj_t *self = self_in; + mp_int_t fifo = mp_obj_get_int(fifo_in); + mp_obj_t *callback; + + callback = (fifo == 0) ? &self->rxcallback0 : &self->rxcallback1; + if (callback_in == mp_const_none) { + __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FF0 : CAN_IT_FF1); + __HAL_CAN_DISABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FOV0 : CAN_IT_FOV1); + *callback = mp_const_none; + } else if (*callback != mp_const_none) { + // Rx call backs has already been initialized + // only the callback function should be changed + *callback = callback_in; + } else if (mp_obj_is_callable(callback_in)) { + *callback = callback_in; + uint32_t irq; + if (self->can_id == PYB_CAN_1) { + irq = (fifo == 0) ? CAN1_RX0_IRQn : CAN1_RX1_IRQn; + } else { + irq = (fifo == 0) ? CAN2_RX0_IRQn : CAN2_RX1_IRQn; + } + HAL_NVIC_SetPriority(irq, 7, 0); + HAL_NVIC_EnableIRQ(irq); + __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FF0 : CAN_IT_FF1); + __HAL_CAN_ENABLE_IT(&self->can, (fifo == 0) ? CAN_IT_FOV0 : CAN_IT_FOV1); + } + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_3(pyb_can_rxcallback_obj, pyb_can_rxcallback); + STATIC const mp_map_elem_t pyb_can_locals_dict_table[] = { // instance methods { MP_OBJ_NEW_QSTR(MP_QSTR_init), (mp_obj_t)&pyb_can_init_obj }, @@ -535,6 +629,7 @@ STATIC const mp_map_elem_t pyb_can_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_initfilterbanks), (mp_obj_t)&pyb_can_initfilterbanks_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_setfilter), (mp_obj_t)&pyb_can_setfilter_obj }, { MP_OBJ_NEW_QSTR(MP_QSTR_clearfilter), (mp_obj_t)&pyb_can_clearfilter_obj }, + { MP_OBJ_NEW_QSTR(MP_QSTR_rxcallback), (mp_obj_t)&pyb_can_rxcallback_obj }, // class constants // Note: we use the ST constants >> 4 so they fit in a small-int. The @@ -543,7 +638,6 @@ STATIC const mp_map_elem_t pyb_can_locals_dict_table[] = { { MP_OBJ_NEW_QSTR(MP_QSTR_LOOPBACK), MP_OBJ_NEW_SMALL_INT(CAN_MODE_LOOPBACK >> 4) }, { MP_OBJ_NEW_QSTR(MP_QSTR_SILENT), MP_OBJ_NEW_SMALL_INT(CAN_MODE_SILENT >> 4) }, { MP_OBJ_NEW_QSTR(MP_QSTR_SILENT_LOOPBACK), MP_OBJ_NEW_SMALL_INT(CAN_MODE_SILENT_LOOPBACK >> 4) }, - { MP_OBJ_NEW_QSTR(MP_QSTR_MASK16), MP_OBJ_NEW_SMALL_INT(MASK16) }, { MP_OBJ_NEW_QSTR(MP_QSTR_LIST16), MP_OBJ_NEW_SMALL_INT(LIST16) }, { MP_OBJ_NEW_QSTR(MP_QSTR_MASK32), MP_OBJ_NEW_SMALL_INT(MASK32) }, @@ -573,6 +667,59 @@ mp_uint_t can_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint_t arg, int *err return ret; } +void can_rx_irq_handler(uint can_id, uint fifo_id) { + mp_obj_t callback; + pyb_can_obj_t *self; + mp_obj_t irq_reason = MP_OBJ_NEW_SMALL_INT(0); + byte *state; + + self = MP_STATE_PORT(pyb_can_obj_all)[can_id - 1]; + + if (fifo_id == CAN_FIFO0) { + callback = self->rxcallback0; + state = &self->rx_state0; + } else { + callback = self->rxcallback1; + state = &self->rx_state1; + } + + switch (*state) { + case RX_STATE_FIFO_EMPTY: + __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FMP0 : CAN_IT_FMP1); + irq_reason = MP_OBJ_NEW_SMALL_INT(0); + *state = RX_STATE_MESSAGE_PENDING; + break; + case RX_STATE_MESSAGE_PENDING: + __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FF0 : CAN_IT_FF1); + irq_reason = MP_OBJ_NEW_SMALL_INT(1); + *state = RX_STATE_FIFO_FULL; + break; + case RX_STATE_FIFO_FULL: + __HAL_CAN_DISABLE_IT(&self->can, (fifo_id == CAN_FIFO0) ? CAN_IT_FOV0 : CAN_IT_FOV1); + irq_reason = MP_OBJ_NEW_SMALL_INT(2); + *state = RX_STATE_FIFO_OVERFLOW; + break; + case RX_STATE_FIFO_OVERFLOW: + // This should never happen + break; + } + + if (callback != mp_const_none) { + gc_lock(); + nlr_buf_t nlr; + if (nlr_push(&nlr) == 0) { + mp_call_function_2(callback, self, irq_reason); + nlr_pop(); + } else { + // Uncaught exception; disable the callback so it doesn't run again. + pyb_can_rxcallback(self, MP_OBJ_NEW_SMALL_INT(fifo_id), mp_const_none); + printf("uncaught exception in CAN(%u) rx interrupt handler\n", self->can_id); + mp_obj_print_exception(printf_wrapper, NULL, (mp_obj_t)nlr.ret_val); + } + gc_unlock(); + } +} + STATIC const mp_stream_p_t can_stream_p = { //.read = can_read, // is read sensible for CAN? //.write = can_write, // is write sensible for CAN? diff --git a/stmhal/can.h b/stmhal/can.h index 07b654d2e8..b9d431e79b 100644 --- a/stmhal/can.h +++ b/stmhal/can.h @@ -34,3 +34,7 @@ #endif extern const mp_obj_type_t pyb_can_type; + +void can_init0(void); +void can_deinit(void); +void can_rx_irq_handler(uint can_id, uint fifo_id); diff --git a/stmhal/main.c b/stmhal/main.c index a53fd05ca2..f91d10d325 100644 --- a/stmhal/main.c +++ b/stmhal/main.c @@ -57,6 +57,7 @@ #include "accel.h" #include "servo.h" #include "dac.h" +#include "can.h" #include "modnetwork.h" #include MICROPY_HAL_H @@ -399,6 +400,9 @@ soft_reset: extint_init0(); timer_init0(); uart_init0(); +#if MICROPY_HW_ENABLE_CAN + can_init0(); +#endif #if MICROPY_HW_ENABLE_RNG rng_init0(); @@ -540,6 +544,9 @@ soft_reset_exit: printf("PYB: soft reboot\n"); timer_deinit(); uart_deinit(); +#if MICROPY_HW_ENABLE_CAN + can_deinit(); +#endif first_soft_reset = false; goto soft_reset; diff --git a/stmhal/mpconfigport.h b/stmhal/mpconfigport.h index 1c4008667d..0224d93f41 100644 --- a/stmhal/mpconfigport.h +++ b/stmhal/mpconfigport.h @@ -153,6 +153,9 @@ extern const struct _mp_obj_module_t mp_module_network; /* pointers to all UART objects (if they have been created) */ \ struct _pyb_uart_obj_t *pyb_uart_obj_all[6]; \ \ + /* pointers to all CAN objects (if they have been created) */ \ + struct _pyb_can_obj_t *pyb_can_obj_all[2]; \ + \ /* list of registered NICs */ \ mp_obj_list_t mod_network_nic_list; \ diff --git a/stmhal/qstrdefsport.h b/stmhal/qstrdefsport.h index 85ab206756..62d9d191e1 100644 --- a/stmhal/qstrdefsport.h +++ b/stmhal/qstrdefsport.h @@ -211,6 +211,7 @@ Q(params) Q(initfilterbanks) Q(clearfilter) Q(setfilter) +Q(rxcallback) Q(NORMAL) Q(LOOPBACK) Q(SILENT) diff --git a/stmhal/stm32f4xx_it.c b/stmhal/stm32f4xx_it.c index e54318e70d..f06c4081c3 100644 --- a/stmhal/stm32f4xx_it.c +++ b/stmhal/stm32f4xx_it.c @@ -75,6 +75,7 @@ #include "timer.h" #include "uart.h" #include "storage.h" +#include "can.h" extern void __fatal_error(const char*); extern PCD_HandleTypeDef pcd_handle; @@ -414,3 +415,21 @@ void UART4_IRQHandler(void) { void USART6_IRQHandler(void) { uart_irq_handler(6); } + +#if MICROPY_HW_ENABLE_CAN +void CAN1_RX0_IRQHandler(void) { + can_rx_irq_handler(PYB_CAN_1, CAN_FIFO0); +} + +void CAN1_RX1_IRQHandler(void) { + can_rx_irq_handler(PYB_CAN_1, CAN_FIFO1); +} + +void CAN2_RX0_IRQHandler(void) { + can_rx_irq_handler(PYB_CAN_2, CAN_FIFO0); +} + +void CAN2_RX1_IRQHandler(void) { + can_rx_irq_handler(PYB_CAN_2, CAN_FIFO1); +} +#endif // MICROPY_HW_ENABLE_CAN diff --git a/tests/pyb/can.py b/tests/pyb/can.py index f1cad860b9..132da23069 100644 --- a/tests/pyb/can.py +++ b/tests/pyb/can.py @@ -48,3 +48,75 @@ else: print('passed') else: print('failed, wrong data received') + +del can + +# Test RxCallbacks +can = CAN(1, CAN.LOOPBACK) +can.setfilter(0, CAN.LIST16, 0, (1, 2, 3, 4)) +can.setfilter(1, CAN.LIST16, 1, (5, 6, 7, 8)) +def cb0(bus, reason): + print('cb0') + if reason == 0: + print('pending') + if reason == 1: + print('full') + if reason == 2: + print('overflow') + +def cb1(bus, reason): + print('cb1') + if reason == 0: + print('pending') + if reason == 1: + print('full') + if reason == 2: + print('overflow') + +def cb0a(bus, reason): + print('cb0a') + if reason == 0: + print('pending') + if reason == 1: + print('full') + if reason == 2: + print('overflow') + +def cb1a(bus, reason): + print('cb1a') + if reason == 0: + print('pending') + if reason == 1: + print('full') + if reason == 2: + print('overflow') + + +can.rxcallback(0, cb0) +can.rxcallback(1, cb1) + +can.send('11111111',1) +can.send('22222222',2) +can.send('33333333',3) +can.rxcallback(0, cb0a) +can.send('44444444',4) + +can.send('55555555',5) +can.send('66666666',6) +can.send('77777777',7) +can.rxcallback(1, cb1a) +can.send('88888888',8) + +print(can.recv(0)) +print(can.recv(0)) +print(can.recv(0)) +print(can.recv(1)) +print(can.recv(1)) +print(can.recv(1)) + +can.send('11111111',1) +can.send('55555555',5) + +print(can.recv(0)) +print(can.recv(1)) + diff --git a/tests/pyb/can.py.exp b/tests/pyb/can.py.exp index 4058202046..b0ede7b9f4 100644 --- a/tests/pyb/can.py.exp +++ b/tests/pyb/can.py.exp @@ -8,3 +8,27 @@ True passed CAN(1, CAN.LOOPBACK, extframe=True) passed +cb0 +pending +cb0 +full +cb0a +overflow +cb1 +pending +cb1 +full +cb1a +overflow +(1, 0, 0, b'11111111') +(2, 0, 1, b'22222222') +(4, 0, 3, b'44444444') +(5, 0, 0, b'55555555') +(6, 0, 1, b'66666666') +(8, 0, 3, b'88888888') +cb0a +pending +cb1a +pending +(1, 0, 0, b'11111111') +(5, 0, 0, b'55555555')