aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George2018-03-19 15:12:24 +1100
committerDamien George2018-03-19 15:12:24 +1100
commit0abbafd42406e4367a80ad996bdd7047ad1e020d (patch)
treec42802ceb0ba0950c5bffa725e76764943095387
parent5e1279d41a4130027c594f087f8539b5b25cc29b (diff)
stm32/can: Add "list" param to CAN.recv() to receive data inplace.
This API matches (as close as possible) how other pyb classes allow inplace operations, such as pyb.SPI.recv(buf).
-rw-r--r--docs/library/pyb.CAN.rst21
-rw-r--r--ports/stm32/can.c62
2 files changed, 66 insertions, 17 deletions
diff --git a/docs/library/pyb.CAN.rst b/docs/library/pyb.CAN.rst
index 5fe4c2ecf..09b97a187 100644
--- a/docs/library/pyb.CAN.rst
+++ b/docs/library/pyb.CAN.rst
@@ -187,11 +187,12 @@ Methods
Return ``True`` if any message waiting on the FIFO, else ``False``.
-.. method:: CAN.recv(fifo, \*, timeout=5000)
+.. method:: CAN.recv(fifo, list=None, \*, timeout=5000)
Receive data on the bus:
- *fifo* is an integer, which is the FIFO to receive on
+ - *list* is an optional list object to be used as the return value
- *timeout* is the timeout in milliseconds to wait for the receive.
Return value: A tuple containing four values.
@@ -201,6 +202,24 @@ Methods
- The FMI (Filter Match Index) value.
- An array containing the data.
+ If *list* is ``None`` then a new tuple will be allocated, as well as a new
+ bytes object to contain the data (as the fourth element in the tuple).
+
+ If *list* is not ``None`` then it should be a list object with a least four
+ elements. The fourth element should be a memoryview object which is created
+ from either a bytearray or an array of type 'B' or 'b', and this array must
+ have enough room for at least 8 bytes. The list object will then be
+ populated with the first three return values above, and the memoryview object
+ will be resized inplace to the size of the data and filled in with that data.
+ The same list and memoryview objects can be reused in subsequent calls to
+ this method, providing a way of receiving data without using the heap.
+ For example::
+
+ buf = bytearray(8)
+ lst = [0, 0, 0, memoryview(buf)]
+ # No heap memory is allocated in the following call
+ can.recv(0, lst)
+
.. method:: CAN.send(data, id, \*, timeout=0, rtr=False)
Send a message on the bus:
diff --git a/ports/stm32/can.c b/ports/stm32/can.c
index 5b5c4ad3f..bc4f1092c 100644
--- a/ports/stm32/can.c
+++ b/ports/stm32/can.c
@@ -29,8 +29,10 @@
#include <stdarg.h>
#include "py/objtuple.h"
+#include "py/objarray.h"
#include "py/runtime.h"
#include "py/gc.h"
+#include "py/binary.h"
#include "py/stream.h"
#include "py/mperrno.h"
#include "py/mphal.h"
@@ -645,18 +647,20 @@ STATIC mp_obj_t pyb_can_send(size_t n_args, const mp_obj_t *pos_args, mp_map_t *
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_send_obj, 1, pyb_can_send);
-/// \method recv(fifo, *, timeout=5000)
+/// \method recv(fifo, list=None, *, timeout=5000)
///
/// Receive data on the bus:
///
/// - `fifo` is an integer, which is the FIFO to receive on
+/// - `list` if not None is a list with at least 4 elements
/// - `timeout` is the timeout in milliseconds to wait for the receive.
///
/// Return value: buffer of data bytes.
STATIC mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
- enum { ARG_fifo, ARG_timeout };
+ enum { ARG_fifo, ARG_list, ARG_timeout };
static const mp_arg_t allowed_args[] = {
{ MP_QSTR_fifo, MP_ARG_REQUIRED | MP_ARG_INT, {.u_int = 0} },
+ { MP_QSTR_list, MP_ARG_OBJ, {.u_obj = mp_const_none} },
{ MP_QSTR_timeout, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 5000} },
};
@@ -700,23 +704,49 @@ STATIC mp_obj_t pyb_can_recv(size_t n_args, const mp_obj_t *pos_args, mp_map_t *
}
}
- // 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);
- if (rx_msg.IDE == CAN_ID_STD) {
- tuple->items[0] = MP_OBJ_NEW_SMALL_INT(rx_msg.StdId);
+ // Create the tuple, or get the list, that will hold the return values
+ // Also populate the fourth element, either a new bytes or reuse existing memoryview
+ mp_obj_t ret_obj = args[ARG_list].u_obj;
+ mp_obj_t *items;
+ if (ret_obj == mp_const_none) {
+ ret_obj = mp_obj_new_tuple(4, NULL);
+ items = ((mp_obj_tuple_t*)MP_OBJ_TO_PTR(ret_obj))->items;
+ items[3] = mp_obj_new_bytes(&rx_msg.Data[0], rx_msg.DLC);
} else {
- tuple->items[0] = MP_OBJ_NEW_SMALL_INT(rx_msg.ExtId);
+ // User should provide a list of length at least 4 to hold the values
+ if (!MP_OBJ_IS_TYPE(ret_obj, &mp_type_list)) {
+ mp_raise_TypeError(NULL);
+ }
+ mp_obj_list_t *list = MP_OBJ_TO_PTR(ret_obj);
+ if (list->len < 4) {
+ mp_raise_ValueError(NULL);
+ }
+ items = list->items;
+ // Fourth element must be a memoryview which we assume points to a
+ // byte-like array which is large enough, and then we resize it inplace
+ if (!MP_OBJ_IS_TYPE(items[3], &mp_type_memoryview)) {
+ mp_raise_TypeError(NULL);
+ }
+ mp_obj_array_t *mv = MP_OBJ_TO_PTR(items[3]);
+ if (!(mv->typecode == (0x80 | BYTEARRAY_TYPECODE)
+ || (mv->typecode | 0x20) == (0x80 | 'b'))) {
+ mp_raise_ValueError(NULL);
+ }
+ mv->len = rx_msg.DLC;
+ memcpy(mv->items, &rx_msg.Data[0], rx_msg.DLC);
}
- tuple->items[1] = rx_msg.RTR == CAN_RTR_REMOTE ? mp_const_true : mp_const_false;
- tuple->items[2] = MP_OBJ_NEW_SMALL_INT(rx_msg.FMI);
- vstr_t vstr;
- vstr_init_len(&vstr, rx_msg.DLC);
- for (mp_uint_t i = 0; i < rx_msg.DLC; i++) {
- vstr.buf[i] = rx_msg.Data[i]; // Data is uint32_t but holds only 1 byte
+
+ // Populate the first 3 values of the tuple/list
+ if (rx_msg.IDE == CAN_ID_STD) {
+ items[0] = MP_OBJ_NEW_SMALL_INT(rx_msg.StdId);
+ } else {
+ items[0] = MP_OBJ_NEW_SMALL_INT(rx_msg.ExtId);
}
- tuple->items[3] = mp_obj_new_str_from_vstr(&mp_type_bytes, &vstr);
- return tuple;
+ items[1] = rx_msg.RTR == CAN_RTR_REMOTE ? mp_const_true : mp_const_false;
+ items[2] = MP_OBJ_NEW_SMALL_INT(rx_msg.FMI);
+
+ // Return the result
+ return ret_obj;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_KW(pyb_can_recv_obj, 1, pyb_can_recv);