summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrobert-hh <robert@hammelrath.com>2022-06-05 17:02:46 +0200
committerDamien George <damien@micropython.org>2022-10-06 22:48:44 +1100
commitaa870708ac262824ab5829e0f163f8f1e1332ada (patch)
tree9463d01060eb1578caacc122d0f4d5c13e868061
parentb33f2045290a379d2cefc1798780aa46f56e29a1 (diff)
samd/machine_spi: Add the machine.SPI class.
Suported by both SAMD21 and SAMD51. It follows the generic API, except for the bits=nn option, which is not implemented (yet).
-rw-r--r--ports/samd/Makefile2
-rw-r--r--ports/samd/machine_spi.c330
-rw-r--r--ports/samd/main.c2
-rw-r--r--ports/samd/modmachine.c1
-rw-r--r--ports/samd/modmachine.h1
-rw-r--r--ports/samd/mpconfigport.h1
-rw-r--r--ports/samd/samd_isr.c2
7 files changed, 338 insertions, 1 deletions
diff --git a/ports/samd/Makefile b/ports/samd/Makefile
index 196b7acf1..ca0309fa6 100644
--- a/ports/samd/Makefile
+++ b/ports/samd/Makefile
@@ -93,6 +93,7 @@ SRC_C = \
machine_adc.c \
machine_led.c \
machine_pin.c \
+ machine_spi.c \
machine_uart.c \
main.c \
modutime.c \
@@ -145,6 +146,7 @@ SRC_QSTR += \
machine_led.c \
machine_pin.c \
machine_pwm.c \
+ machine_spi.c \
machine_uart.c \
modutime.c \
modmachine.c \
diff --git a/ports/samd/machine_spi.c b/ports/samd/machine_spi.c
new file mode 100644
index 000000000..b3504aeba
--- /dev/null
+++ b/ports/samd/machine_spi.c
@@ -0,0 +1,330 @@
+/*
+ * This file is part of the MicroPython project, http://micropython.org/
+ *
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2020-2021 Damien P. George
+ * Copyright (c) 2022 Robert Hammelrath
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+#include "py/runtime.h"
+#include "py/mphal.h"
+#include "extmod/machine_spi.h"
+#include "modmachine.h"
+#include "samd_soc.h"
+#include "pin_af.h"
+#include "clock_config.h"
+
+#define DEFAULT_SPI_BAUDRATE (1000000)
+#define DEFAULT_SPI_POLARITY (0)
+#define DEFAULT_SPI_PHASE (0)
+#define DEFAULT_SPI_BITS (8)
+#define DEFAULT_SPI_FIRSTBIT (0)
+
+typedef struct _machine_spi_obj_t {
+ mp_obj_base_t base;
+ uint8_t id;
+ uint8_t polarity;
+ uint8_t phase;
+ uint8_t firstbit;
+ uint8_t sck;
+ uint8_t mosi;
+ uint8_t miso;
+ uint8_t new;
+ uint32_t baudrate;
+ sercom_pad_config_t sck_pad_config;
+ sercom_pad_config_t mosi_pad_config;
+ sercom_pad_config_t miso_pad_config;
+ uint8_t *dest;
+ size_t rxlen;
+} machine_spi_obj_t;
+
+extern Sercom *sercom_instance[];
+void *sercom_table[SERCOM_INST_NUM] = {};
+
+void common_spi_irq_handler(int spi_id) {
+ // handle Sercom IRQ RXC
+ machine_spi_obj_t *self = sercom_table[spi_id];
+ // Handle IRQ
+ if (self != NULL) {
+ Sercom *spi = sercom_instance[self->id];
+ if (spi->SPI.INTFLAG.bit.RXC != 0) {
+ if (self->rxlen > 0) {
+ *(self->dest)++ = spi->SPI.DATA.bit.DATA;
+ self->rxlen--;
+ } else {
+ // Just in the unlikely case there is data but no space in the buffer
+ // discard the data and clear the intflag
+ uint32_t temp;
+ (void)temp;
+ temp = spi->SPI.DATA.bit.DATA;
+ }
+ }
+ }
+}
+
+STATIC void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) {
+ machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in);
+ mp_printf(print, "SPI(%u), baudrate=%u, firstbit=%u, polarity=%u, phase=%u, bits=8",
+ self->id, self->baudrate, self->firstbit, self->polarity, self->phase);
+}
+
+STATIC void machine_spi_init(mp_obj_base_t *self_in, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) {
+ enum { ARG_baudrate, ARG_polarity, ARG_phase, ARG_firstbit,
+ ARG_sck, ARG_mosi, ARG_miso};
+ static const mp_arg_t allowed_args[] = {
+ { MP_QSTR_baudrate, MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_polarity, MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_phase, MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_firstbit, MP_ARG_INT, {.u_int = -1} },
+ { MP_QSTR_sck, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
+ { MP_QSTR_mosi, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
+ { MP_QSTR_miso, MP_ARG_KW_ONLY | MP_ARG_OBJ, {.u_rom_obj = MP_ROM_NONE} },
+ };
+
+ machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in);
+
+ // Parse args
+ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)];
+ mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args);
+
+ // Set baudrate if configured.
+ if (args[ARG_baudrate].u_int >= 0) {
+ self->baudrate = args[ARG_baudrate].u_int;
+ }
+
+ // Set polarity if configured.
+ if (args[ARG_polarity].u_int >= 0) {
+ self->polarity = args[ARG_polarity].u_int;
+ }
+
+ // Set phase if configured.
+ if (args[ARG_phase].u_int >= 0) {
+ self->phase = args[ARG_phase].u_int;
+ }
+
+ // Set firstbit if configured.
+ if (args[ARG_firstbit].u_int >= 0) {
+ self->firstbit = args[ARG_firstbit].u_int;
+ }
+
+ // Set SCK/MOSI/MISO pins if configured.
+ if (args[ARG_sck].u_obj != mp_const_none) {
+ self->sck = mp_hal_get_pin_obj(args[ARG_sck].u_obj);
+ }
+ if (args[ARG_mosi].u_obj != mp_const_none) {
+ self->mosi = mp_hal_get_pin_obj(args[ARG_mosi].u_obj);
+ }
+ if (args[ARG_miso].u_obj != mp_const_none) {
+ self->miso = mp_hal_get_pin_obj(args[ARG_miso].u_obj);
+ }
+
+ // Initialise the SPI peripheral if any arguments given, or it was not initialised previously.
+ if (n_args > 0 || kw_args->used > 0 || self->new) {
+ self->new = false;
+
+ // Get the pad and alt-fct numbers.
+ self->sck_pad_config = get_sercom_config(self->sck, self->id);
+ self->mosi_pad_config = get_sercom_config(self->mosi, self->id);
+
+ uint8_t dopo = 0;
+ #if defined(MCU_SAMD21)
+ if (self->mosi_pad_config.pad_nr == 0 && self->sck_pad_config.pad_nr == 1) {
+ dopo = 0;
+ } else if (self->mosi_pad_config.pad_nr == 2 && self->sck_pad_config.pad_nr == 3) {
+ dopo = 1;
+ } else if (self->mosi_pad_config.pad_nr == 3 && self->sck_pad_config.pad_nr == 1) {
+ dopo = 2;
+ } else if (self->mosi_pad_config.pad_nr == 0 && self->sck_pad_config.pad_nr == 3) {
+ dopo = 3;
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for sck or mosi"));
+ }
+ #elif defined(MCU_SAMD51)
+ if (self->mosi_pad_config.pad_nr == 0 && self->sck_pad_config.pad_nr == 1) {
+ dopo = 0;
+ } else if (self->mosi_pad_config.pad_nr == 3 && self->sck_pad_config.pad_nr == 1) {
+ dopo = 2;
+ } else {
+ mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for sck or mosi"));
+ }
+ #endif
+
+ if (self->miso != 0xff) { // Miso may be undefined
+ self->miso_pad_config = get_sercom_config(self->miso, self->id);
+ mp_hal_set_pin_mux(self->miso, self->miso_pad_config.alt_fct);
+ }
+ // Configure the Pin mux.
+ mp_hal_set_pin_mux(self->sck, self->sck_pad_config.alt_fct);
+ mp_hal_set_pin_mux(self->mosi, self->mosi_pad_config.alt_fct);
+
+ // Set up the clocks
+ enable_sercom_clock(self->id);
+
+ // Configure the SPI
+ Sercom *spi = sercom_instance[self->id];
+ // Reset (clear) the peripheral registers.
+ while (spi->SPI.SYNCBUSY.bit.SWRST) {
+ }
+ spi->SPI.CTRLA.bit.SWRST = 1;
+ while (spi->SPI.SYNCBUSY.bit.SWRST) {
+ }
+
+ // Set the registers
+ spi->SPI.CTRLA.bit.MODE = 0x03; // SPI master mode
+ spi->SPI.CTRLA.bit.CPOL = self->polarity;
+ spi->SPI.CTRLA.bit.CPHA = self->phase;
+ spi->SPI.CTRLA.bit.DIPO = self->miso_pad_config.pad_nr;
+ spi->SPI.CTRLA.bit.DOPO = dopo;
+ spi->SPI.CTRLA.bit.DORD = self->firstbit;
+
+ // Enable receive only if miso is defined
+ if (self->miso != 0xff) {
+ spi->SPI.CTRLB.reg = SERCOM_SPI_CTRLB_RXEN;
+ while (spi->SPI.SYNCBUSY.bit.CTRLB) {
+ }
+ }
+
+ #if defined(MCU_SAMD51)
+ spi->SPI.CTRLC.reg = 1; // 1 clock cycle character spacing
+ #endif
+
+ // SPI is driven by the clock of GCLK Generator 2, freq in bus_freq
+ // baud = bus_freq / (2 * baudrate) - 1
+ uint32_t baud = get_apb_freq() / (2 * self->baudrate) - 1;
+ spi->SPI.BAUD.reg = baud; // Set Baud
+
+ // Enable RXC interrupt only if miso is defined
+ if (self->miso != 0xff) {
+ #if defined(MCU_SAMD21)
+ NVIC_EnableIRQ(SERCOM0_IRQn + self->id);
+ #elif defined(MCU_SAMD51)
+ NVIC_EnableIRQ(SERCOM0_0_IRQn + 4 * self->id + 2);
+ #endif
+ sercom_register_irq(self->id, SERCOM_IRQ_TYPE_SPI);
+ }
+
+ sercom_enable(spi, 1);
+ }
+}
+
+STATIC mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) {
+ mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true);
+
+ // Get SPI bus.
+ int spi_id = mp_obj_get_int(args[0]);
+ if (spi_id < 0 || spi_id > SERCOM_INST_NUM) {
+ mp_raise_msg_varg(&mp_type_ValueError, MP_ERROR_TEXT("SPI(%d) doesn't exist"), spi_id);
+ }
+
+ // Create the SPI object and fill it with defaults.
+ machine_spi_obj_t *self = mp_obj_malloc(machine_spi_obj_t, &machine_spi_type);
+ self->id = spi_id;
+ self->baudrate = DEFAULT_SPI_BAUDRATE;
+ self->polarity = DEFAULT_SPI_POLARITY;
+ self->phase = DEFAULT_SPI_PHASE;
+ self->firstbit = DEFAULT_SPI_FIRSTBIT;
+ self->mosi = 0xff; // 0xff: pin not defined (yet)
+ self->miso = 0xff;
+ self->sck = 0xff;
+
+ self->new = true;
+ sercom_table[spi_id] = self;
+
+ mp_map_t kw_args;
+ mp_map_init_fixed_table(&kw_args, n_kw, args + n_args);
+ machine_spi_init((mp_obj_base_t *)self, n_args - 1, args + 1, &kw_args);
+ return self;
+}
+
+void sercom_deinit_all(void) {
+ for (int i = 0; i < SERCOM_INST_NUM; i++) {
+ if (sercom_table[i] != NULL) {
+ machine_spi_obj_t *self = sercom_table[i];
+ Sercom *spi = sercom_instance[self->id];
+ // Disable interrupts (if any)
+ spi->SPI.INTENCLR.reg = 0xff;
+ // clear table entry of spi
+ sercom_table[i] = NULL;
+ sercom_enable(spi, 0);
+ }
+ }
+}
+
+STATIC void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8_t *src, uint8_t *dest) {
+ machine_spi_obj_t *self = (machine_spi_obj_t *)self_in;
+
+ Sercom *spi = sercom_instance[self->id];
+ size_t txlen = len;
+ // Clear the input queue, if needed
+ while (dest && spi->SPI.INTFLAG.bit.RXC) {
+ uint32_t temp;
+ (void)temp;
+ temp = spi->SPI.DATA.bit.DATA;
+ }
+ // Set up the irq data pointers and enable IRQ
+ if (dest) {
+ if (self->miso == 0xff) {
+ mp_raise_ValueError(MP_ERROR_TEXT("read is not enabled"));
+ }
+ spi->SPI.INTENSET.bit.RXC = 1;
+ self->dest = dest;
+ self->rxlen = len;
+ }
+
+ // Send by polling & receive by IRQ
+ while (txlen) {
+ if (spi->SPI.INTFLAG.bit.DRE) {
+ spi->SPI.DATA.bit.DATA = *src;
+ src += 1;
+ txlen--;
+ }
+ }
+ // Receive the remaining data, if any and clear IRQ
+ // Do no wait forever.
+ if (dest) {
+ int32_t timeout = 1000;
+ while (self->rxlen > 0 && timeout) {
+ timeout--;
+ MICROPY_EVENT_POLL_HOOK
+ }
+ spi->SPI.INTENCLR.bit.RXC = 1;
+ } else {
+ // Wait for the data being shifted out.
+ while (!spi->SPI.INTFLAG.bit.TXC) {
+ }
+ }
+}
+
+
+STATIC const mp_machine_spi_p_t machine_spi_p = {
+ .init = machine_spi_init,
+ .transfer = machine_spi_transfer,
+};
+
+MP_DEFINE_CONST_OBJ_TYPE(
+ machine_spi_type,
+ MP_QSTR_SPI,
+ MP_TYPE_FLAG_NONE,
+ make_new, machine_spi_make_new,
+ print, machine_spi_print,
+ protocol, &machine_spi_p,
+ locals_dict, &mp_machine_spi_locals_dict
+ );
diff --git a/ports/samd/main.c b/ports/samd/main.c
index 60b8e57fa..1f056083a 100644
--- a/ports/samd/main.c
+++ b/ports/samd/main.c
@@ -36,6 +36,7 @@ extern uint8_t _sstack, _estack, _sheap, _eheap;
extern void adc_deinit_all(void);
extern void pin_irq_deinit_all(void);
extern void pwm_deinit_all(void);
+extern void sercom_deinit_all(void);
extern void uart_deinit_all(void);
void samd_main(void) {
@@ -69,6 +70,7 @@ void samd_main(void) {
adc_deinit_all();
pin_irq_deinit_all();
pwm_deinit_all();
+ sercom_deinit_all();
uart_deinit_all();
gc_sweep_all();
mp_deinit();
diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c
index 8c3033f89..712b520f5 100644
--- a/ports/samd/modmachine.c
+++ b/ports/samd/modmachine.c
@@ -155,6 +155,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = {
{ MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) },
{ MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) },
{ MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) },
+ { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) },
{ MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) },
{ MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) },
diff --git a/ports/samd/modmachine.h b/ports/samd/modmachine.h
index 30de339b4..143e3488f 100644
--- a/ports/samd/modmachine.h
+++ b/ports/samd/modmachine.h
@@ -32,6 +32,7 @@ extern const mp_obj_type_t machine_adc_type;
extern const mp_obj_type_t machine_led_type;
extern const mp_obj_type_t machine_pin_type;
extern const mp_obj_type_t machine_pwm_type;
+extern const mp_obj_type_t machine_spi_type;
extern const mp_obj_type_t machine_uart_type;
#endif // MICROPY_INCLUDED_SAMD_MODMACHINE_H
diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h
index b8cb0f3eb..22fed00b2 100644
--- a/ports/samd/mpconfigport.h
+++ b/ports/samd/mpconfigport.h
@@ -99,6 +99,7 @@
#define MICROPY_PY_UZLIB (1)
#define MICROPY_PY_UASYNCIO (1)
#define MICROPY_PY_MACHINE_SOFTI2C (1)
+#define MICROPY_PY_MACHINE_SPI (1)
#define MICROPY_PY_MACHINE_SOFTSPI (1)
#define MICROPY_PY_MACHINE_PWM (1)
#define MICROPY_PY_MACHINE_PWM_INIT (0)
diff --git a/ports/samd/samd_isr.c b/ports/samd/samd_isr.c
index 0e0d84db6..2a5a3e042 100644
--- a/ports/samd/samd_isr.c
+++ b/ports/samd/samd_isr.c
@@ -112,7 +112,7 @@ static uint8_t sercom_irq_type[SERCOM_INST_NUM] = {};
// Temporarily commented until the module is added
void (*sercom_irq_handler_table[])(int num) = {
common_uart_irq_handler,
- NULL, // common_spi_irq_handler,
+ common_spi_irq_handler,
NULL // common_i2c_irq_handler
};