Skip to content

Commit 5490a82

Browse files
add async spi interface
1 parent bd10666 commit 5490a82

4 files changed

Lines changed: 229 additions & 0 deletions

File tree

ports/atmel-samd/common-hal/busio/SPI.c

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -247,6 +247,8 @@ void common_hal_busio_spi_construct(busio_spi_obj_t *self,
247247
claim_pin(ss);
248248
}
249249

250+
self->running_dma.failure = 1; // not started
251+
250252
spi_m_sync_enable(&self->spi_desc);
251253
}
252254

@@ -329,6 +331,9 @@ bool common_hal_busio_spi_write(busio_spi_obj_t *self,
329331
if (len == 0) {
330332
return true;
331333
}
334+
if (self->running_dma.failure != 1) {
335+
mp_raise_RuntimeError(MP_ERROR_TEXT("Async SPI transfer in progress on this bus, keep awaiting."));
336+
}
332337
int32_t status;
333338
if (len >= 16) {
334339
size_t bytes_remaining = len;
@@ -359,6 +364,9 @@ bool common_hal_busio_spi_read(busio_spi_obj_t *self,
359364
if (len == 0) {
360365
return true;
361366
}
367+
if (self->running_dma.failure != 1) {
368+
mp_raise_RuntimeError(MP_ERROR_TEXT("Async SPI transfer in progress on this bus, keep awaiting."));
369+
}
362370
int32_t status;
363371
if (len >= 16) {
364372
status = sercom_dma_read(self->spi_desc.dev.prvt, data, len, write_value);
@@ -377,6 +385,9 @@ bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_ou
377385
if (len == 0) {
378386
return true;
379387
}
388+
if (self->running_dma.failure != 1) {
389+
mp_raise_RuntimeError(MP_ERROR_TEXT("Async SPI transfer in progress on this bus, keep awaiting."));
390+
}
380391
int32_t status;
381392
if (len >= 16) {
382393
status = sercom_dma_transfer(self->spi_desc.dev.prvt, data_out, data_in, len);
@@ -390,6 +401,62 @@ bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_ou
390401
return status >= 0; // Status is number of chars read or an error code < 0.
391402
}
392403

404+
void common_hal_busio_spi_transfer_async_start(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) {
405+
if (len == 0) {
406+
return;
407+
}
408+
if (self->running_dma.failure != 1) {
409+
mp_raise_RuntimeError(MP_ERROR_TEXT("Async SPI transfer in progress on this bus, keep awaiting."));
410+
}
411+
Sercom* sercom = self->spi_desc.dev.prvt;
412+
self->running_dma = shared_dma_transfer_start(sercom, data_out, &sercom->SPI.DATA.reg, &sercom->SPI.DATA.reg, data_in, len, 0);
413+
414+
// There is an issue where if an unexpected SPI transfer is received before the user calls "end" for the in-progress, expected
415+
// transfer, the SERCOM has an error and gets confused. This can be detected from INTFLAG.ERROR. I think the code in
416+
// ports/atmel-samd/peripherals/samd/dma.c at line 277 (as of this commit; it's the part that reads s->SPI.INTFLAG.bit.RXC and
417+
// s->SPI.DATA.reg) is supposed to fix this, but experimentation seems to show that it does not in fact fix anything. Anyways, if
418+
// the ERROR bit is set, let's just reset the peripheral and then setup the transfer again -- that seems to work.
419+
if (hri_sercomspi_get_INTFLAG_ERROR_bit(sercom)) {
420+
shared_dma_transfer_close(self->running_dma);
421+
422+
// disable the sercom
423+
spi_m_sync_disable(&self->spi_desc);
424+
hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK);
425+
426+
// save configurations
427+
hri_sercomspi_ctrla_reg_t ctrla_saved_val = hri_sercomspi_get_CTRLA_reg(sercom, -1); // -1 mask is all ones: save all bits
428+
hri_sercomspi_ctrlb_reg_t ctrlb_saved_val = hri_sercomspi_get_CTRLB_reg(sercom, -1); // -1 mask is all ones: save all bits
429+
hri_sercomspi_baud_reg_t baud_saved_val = hri_sercomspi_get_BAUD_reg(sercom, -1); // -1 mask is all ones: save all bits
430+
// reset
431+
hri_sercomspi_set_CTRLA_SWRST_bit(sercom);
432+
hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK);
433+
// re-write configurations
434+
hri_sercomspi_write_CTRLA_reg(sercom, ctrla_saved_val);
435+
hri_sercomspi_write_CTRLB_reg(sercom, ctrlb_saved_val);
436+
hri_sercomspi_write_BAUD_reg (sercom, baud_saved_val);
437+
hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK);
438+
439+
// re-enable the sercom
440+
spi_m_sync_enable(&self->spi_desc);
441+
hri_sercomspi_wait_for_sync(sercom, SERCOM_SPI_SYNCBUSY_MASK);
442+
443+
self->running_dma = shared_dma_transfer_start(sercom, data_out, &sercom->SPI.DATA.reg, &sercom->SPI.DATA.reg, data_in, len, 0);
444+
}
445+
}
446+
447+
bool common_hal_busio_spi_transfer_async_check(busio_spi_obj_t *self) {
448+
return self->running_dma.failure == 1 || shared_dma_transfer_finished(self->running_dma);
449+
}
450+
451+
int common_hal_busio_spi_transfer_async_end(busio_spi_obj_t *self) {
452+
if (self->running_dma.failure == 1) {
453+
return 0;
454+
}
455+
int res = shared_dma_transfer_close(self->running_dma);
456+
self->running_dma.failure = 1;
457+
return res;
458+
}
459+
393460
uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) {
394461
return samd_peripherals_spi_baud_reg_value_to_baudrate(hri_sercomspi_read_BAUD_reg(self->spi_desc.dev.prvt));
395462
}

ports/atmel-samd/common-hal/busio/SPI.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ typedef struct {
4242
uint8_t MISO_pin;
4343
uint8_t SS_pin;
4444
bool slave_mode;
45+
dma_descr_t running_dma;
4546
} busio_spi_obj_t;
4647

4748
#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_BUSIO_SPI_H

shared-bindings/busio/SPI.c

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,146 @@ MP_DEFINE_CONST_FUN_OBJ_1(busio_spi_get_frequency_obj, busio_spi_obj_get_frequen
481481

482482
MP_PROPERTY_GETTER(busio_spi_frequency_obj,
483483
(mp_obj_t)&busio_spi_get_frequency_obj);
484+
485+
#if CIRCUITPY_SAMD
486+
487+
//| import sys
488+
//| def async_transfer_start(
489+
//| self,
490+
//| out_buffer: ReadableBuffer,
491+
//| in_buffer: WriteableBuffer,
492+
//| *,
493+
//| out_start: int = 0,
494+
//| out_end: int = sys.maxsize,
495+
//| in_start: int = 0,
496+
//| in_end: int = sys.maxsize
497+
//| ) -> None:
498+
//| """Write out the data in ``out_buffer`` while simultaneously reading data into ``in_buffer``.
499+
//| The SPI object must be locked. Note: this method returns immediately, and the data will not
500+
//| actually be transferred until some time has passed. Use `async_transfer_finished` and
501+
//| `async_transfer_end` to check on the status of the transfer and close out its resources.
502+
//|
503+
//| If ``out_start`` or ``out_end`` is provided, then the buffer will be sliced
504+
//| as if ``out_buffer[out_start:out_end]`` were passed, but without copying the data.
505+
//| The number of bytes written will be the length of ``out_buffer[out_start:out_end]``.
506+
//|
507+
//| If ``in_start`` or ``in_end`` is provided, then the input buffer will be sliced
508+
//| as if ``in_buffer[in_start:in_end]`` were passed,
509+
//| The number of bytes read will be the length of ``out_buffer[in_start:in_end]``.
510+
//|
511+
//| The lengths of the slices defined by ``out_buffer[out_start:out_end]``
512+
//| and ``in_buffer[in_start:in_end]`` must be equal.
513+
//| If buffer slice lengths are both 0, nothing happens.
514+
//|
515+
//| Note: This method is currently only available on atmel-samd` ports of CircuitPython.
516+
//|
517+
//| :param ReadableBuffer out_buffer: write out bytes from this buffer
518+
//| :param WriteableBuffer in_buffer: read bytes into this buffer
519+
//| :param int out_start: beginning of ``out_buffer`` slice
520+
//| :param int out_end: end of ``out_buffer`` slice; if not specified, use ``len(out_buffer)``
521+
//| :param int in_start: beginning of ``in_buffer`` slice
522+
//| :param int in_end: end of ``in_buffer slice``; if not specified, use ``len(in_buffer)``
523+
//| """
524+
//| ...
525+
526+
STATIC mp_obj_t busio_spi_start_async_transfer(size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
527+
enum { ARG_out_buffer, ARG_in_buffer, ARG_out_start, ARG_out_end, ARG_in_start, ARG_in_end };
528+
static const mp_arg_t allowed_args[] = {
529+
{ MP_QSTR_out_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
530+
{ MP_QSTR_in_buffer, MP_ARG_REQUIRED | MP_ARG_OBJ, {.u_obj = MP_OBJ_NULL} },
531+
{ MP_QSTR_out_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
532+
{ MP_QSTR_out_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
533+
{ MP_QSTR_in_start, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = 0} },
534+
{ MP_QSTR_in_end, MP_ARG_KW_ONLY | MP_ARG_INT, {.u_int = INT_MAX} },
535+
};
536+
busio_spi_obj_t *self = MP_OBJ_TO_PTR(pos_args[0]);
537+
check_for_deinit(self);
538+
check_lock(self);
539+
mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
540+
mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
541+
542+
mp_buffer_info_t buf_out_info;
543+
mp_get_buffer_raise(args[ARG_out_buffer].u_obj, &buf_out_info, MP_BUFFER_READ);
544+
int out_stride_in_bytes = mp_binary_get_size('@', buf_out_info.typecode, NULL);
545+
int32_t out_start = args[ARG_out_start].u_int;
546+
size_t out_length = buf_out_info.len / out_stride_in_bytes;
547+
normalize_buffer_bounds(&out_start, args[ARG_out_end].u_int, &out_length);
548+
549+
mp_buffer_info_t buf_in_info;
550+
mp_get_buffer_raise(args[ARG_in_buffer].u_obj, &buf_in_info, MP_BUFFER_WRITE);
551+
int in_stride_in_bytes = mp_binary_get_size('@', buf_in_info.typecode, NULL);
552+
int32_t in_start = args[ARG_in_start].u_int;
553+
size_t in_length = buf_in_info.len / in_stride_in_bytes;
554+
normalize_buffer_bounds(&in_start, args[ARG_in_end].u_int, &in_length);
555+
556+
// Treat start and length in terms of bytes from now on.
557+
out_start *= out_stride_in_bytes;
558+
out_length *= out_stride_in_bytes;
559+
in_start *= in_stride_in_bytes;
560+
in_length *= in_stride_in_bytes;
561+
562+
if (out_length != in_length) {
563+
mp_raise_ValueError(MP_ERROR_TEXT("buffer slices must be of equal length"));
564+
}
565+
566+
common_hal_busio_spi_transfer_async_start(self,
567+
((uint8_t *)buf_out_info.buf) + out_start,
568+
((uint8_t *)buf_in_info.buf) + in_start,
569+
out_length);
570+
return mp_const_none;
571+
}
572+
MP_DEFINE_CONST_FUN_OBJ_KW(busio_spi_start_transfer_obj, 1, busio_spi_start_async_transfer);
573+
574+
//| import sys
575+
//| def async_transfer_finished(
576+
//| self
577+
//| ) -> None:
578+
//| """Check whether or not the last async transfer started on this SPI object has finished. If
579+
//| no transfer was started, this method behaves as though the most recent transfer has finished
580+
//| and returns `True`. Otherwise, it returns `False`.
581+
//|
582+
//| Note: This method is currently only available on atmel-samd` ports of CircuitPython.
583+
//| """
584+
//| ...
585+
STATIC mp_obj_t busio_spi_obj_check_async_transfer(mp_obj_t self_in) {
586+
busio_spi_obj_t *self = MP_OBJ_TO_PTR(self_in);
587+
check_for_deinit(self);
588+
return common_hal_busio_spi_transfer_async_check(self) ? mp_const_true : mp_const_false;
589+
}
590+
MP_DEFINE_CONST_FUN_OBJ_1(busio_spi_check_transfer_obj, busio_spi_obj_check_async_transfer);
591+
592+
//| import sys
593+
//| def async_transfer_end(
594+
//| self
595+
//| ) -> None:
596+
//| """Return the status code with which the last async transfer on this SPI object completed. This
597+
//| method MUST be called for all transfers, regardless of user interest in status code. The resources
598+
//| for the transfer will be left open until this method is called. Once this method is called, the
599+
//| peripheral resets and is ready for another transfer. The return code of this method also resets to
600+
//| its pre-transfer state: repeated calls to this method may produce different codes.
601+
//|
602+
//| Return code 0: No transfer has occured, either because `start_async_transfer` was never called, or because
603+
//| it was called with zero-length buffers.
604+
//| Return code -1: The transfer failed because no DMA channels are available.
605+
//| Return code -2: The transfer executed, but the DMA controller indicates that either some data is
606+
//| untransferred, that a software issue prevented the data transfer from completing, or that some other error
607+
//| has occured within the DMA controller.
608+
//| Return code -3: An unaligned buffer was passed to the QSPI peripheral, which prevents the DMA controller from
609+
//| appropriately chunking the transfer.
610+
//| Return code n>0: A transfer of `n` bytes in each direction has succeeded.
611+
//|
612+
//| Note: This method is currently only available on atmel-samd` ports of CircuitPython.
613+
//| """
614+
//| ...
615+
STATIC mp_obj_t busio_spi_obj_end_async_transfer(mp_obj_t self_in) {
616+
busio_spi_obj_t *self = MP_OBJ_TO_PTR(self_in);
617+
check_for_deinit(self);
618+
return MP_OBJ_NEW_SMALL_INT(common_hal_busio_spi_transfer_async_end(self));
619+
}
620+
MP_DEFINE_CONST_FUN_OBJ_1(busio_spi_end_transfer_obj, busio_spi_obj_end_async_transfer);
621+
622+
#endif // CIRCUITPY_SAMD
623+
484624
#endif // CIRCUITPY_BUSIO_SPI
485625

486626

@@ -498,6 +638,14 @@ STATIC const mp_rom_map_elem_t busio_spi_locals_dict_table[] = {
498638
{ MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&busio_spi_write_obj) },
499639
{ MP_ROM_QSTR(MP_QSTR_write_readinto), MP_ROM_PTR(&busio_spi_write_readinto_obj) },
500640
{ MP_ROM_QSTR(MP_QSTR_frequency), MP_ROM_PTR(&busio_spi_frequency_obj) }
641+
642+
#if CIRCUITPY_SAMD
643+
,
644+
{ MP_ROM_QSTR(MP_QSTR_async_transfer_start), MP_ROM_PTR(&busio_spi_start_transfer_obj) },
645+
{ MP_ROM_QSTR(MP_QSTR_async_transfer_finished), MP_ROM_PTR(&busio_spi_check_transfer_obj) },
646+
{ MP_ROM_QSTR(MP_QSTR_async_transfer_end), MP_ROM_PTR(&busio_spi_end_transfer_obj) }
647+
#endif // CIRCUITPY_SAMD
648+
501649
#endif // CIRCUITPY_BUSIO_SPI
502650
};
503651
STATIC MP_DEFINE_CONST_DICT(busio_spi_locals_dict, busio_spi_locals_dict_table);

shared-bindings/busio/SPI.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ extern bool common_hal_busio_spi_read(busio_spi_obj_t *self, uint8_t *data, size
5858
// Reads and write len bytes simultaneously.
5959
extern bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len);
6060

61+
#if CIRCUITPY_SAMD
62+
63+
// Initiates a transfer that reads and write len bytes simultaneously
64+
extern void common_hal_busio_spi_transfer_async_start(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len);
65+
66+
// Reads the state of the in-progress transfer
67+
extern bool common_hal_busio_spi_transfer_async_check(busio_spi_obj_t *self);
68+
69+
// Cleans up the completed transfer and returns any error code produced by the transfer
70+
extern int common_hal_busio_spi_transfer_async_end(busio_spi_obj_t *self);
71+
72+
#endif // CIRCUITPY_SAMD
73+
6174
// Return actual SPI bus frequency.
6275
uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self);
6376

0 commit comments

Comments
 (0)