Skip to content

samd: Support WiFi using external esp32 based modules. #11219

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions docs/library/network.NinaW10.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
.. currentmodule:: network
.. _network.NinaW10:

class NinaW10 -- control WiFi modules with NINA firmware
========================================================

This class allows you to control WLAN modules based on the NINA firmware,
like the u.Blox W102, Adafruit Airlift modules or genuine ESP32 boards..

Example usage::

import network
from machine import SPI, Pin
spi=SPI(1, 8000000, sck="SCK", mosi="MOSI", miso="MISO")
wlan = network.NinaW10(network.STA_IF, spi=spi, cs=Pin("D13"), busy=Pin("D11"), reset=Pin("D12"))

# now use socket as usual
...

For this example to work the NINA module must have the following connections:

- the SPI interface with MOSI, MISO and SCK
- cs connected to pin D13
- busy connected to D11
- reset connected to D12

It is possible to use other SPI buses and other pins for cs, busy and reset.

Constructors
------------

.. class:: NinaW10(interface, *, spi=spi, cs=pin_cs, busy=pin_busy, reset=pin_rst)

Create a NinaW10 driver object, initialise the NINA module using the given
SPI bus and pins, and return the Wlan object.

Arguments are:

- *interface* is the interface mode, either network.STA_IF or network.AP_IF. If
it is omitted, network.STA_IF is assumed.
- *spi* is an :ref:`SPI object <machine.SPI>` which is the SPI bus that the NINA module is
connected to (the MOSI, MISO and SCLK pins).
- *pin_cs* is a :ref:`Pin object <machine.Pin>` which is connected to the NINA CS pin.
- *pin_busy* is a :ref:`Pin object <machine.Pin>` which is connected to the NINA busy pin.
- *pin_rst* is a :ref:`Pin object <machine.Pin>` which is connected to the NINA reset pin.

The SPI object must be initialized beforehand. The mode of the Pin objects will be
set by the driver.

The default pins for some boards and extensions are:

======== ========= ======= ======= ========= =========
Airlift ItsyBitsy Feather Airlift METRO M4 Sparkfun
Breakout Add-on Add-on shield Airlift SAMD51_TP
======== ========= ======= ======= ========= =========
MOSI MOSI MOSI D11 MOSI MOSI
MISO MISO MISO D12 MISO MISO
SCK SCK SCK D13 SCK SCK
CS D13 D13 D10 ESP_CS D13
Busy D11 D11 D7 ESP_BUSY D11
Reset D12 D12 D5 ESP_RESET D12
GP0 D10 D10 D6 ESP_GP0 D10
======== ========= ======= ======= ========= =========


A useful function for connecting to your local WiFi network is::

def do_connect():
from machine import SPI, Pin
import time

spi=SPI(1, 8000000, sck="SCK", mosi="MOSI", miso="MISO")
wlan = network.WLAN(network.STA_IF, spi=spi, cs=Pin("D13"), busy=Pin("D11"), reset=Pin("D12"))
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASSWD) # connect to an AP

for _ in range(20):
if wlan.isconnected():
print('\nnetwork config:', wlan.ifconfig())
break
print(".", end="")
time.sleep(0.5)
else:
print(" Connect attempt timed out\n")

Once the network is established the :mod:`socket <socket>` module can be used
to create and use TCP/UDP sockets as usual. WLAN support is at the moment
only supported for SAM51 boards with a matching add-on board.

Note:

- If feasible, pull the RESET line low with an external resistor. The
required value depends on the NINA module. A good value is 2.2 kOhm.
This forces the GPIO lines of the module to high impedance when
not being used.

Methods
-------

The methods of the WLAN class are supported. For further details,
see :ref:`WLAN class <network.WLAN>`
3 changes: 2 additions & 1 deletion docs/library/network.WLAN.rst
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,15 @@ This class provides a driver for WiFi network processors. Example usage::

Constructors
------------
.. class:: WLAN(interface_id)
.. class:: WLAN(interface, *, spi, cs, busy, reset)

Create a WLAN network interface object. Supported interfaces are
``network.STA_IF`` (station aka client, connects to upstream WiFi access
points) and ``network.AP_IF`` (access point, allows other WiFi clients to
connect). Availability of the methods below depends on interface type.
For example, only STA interface may `WLAN.connect()` to an access point.


Methods
-------

Expand Down
1 change: 1 addition & 0 deletions docs/library/network.rst
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ provide a way to control networking interfaces of various kinds.
network.WLAN.rst
network.WLANWiPy.rst
network.WIZNET5K.rst
network.NinaW10.rst
network.LAN.rst

Network functions
Expand Down
41 changes: 41 additions & 0 deletions docs/samd/quickref.rst
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,47 @@ has the same methods as software SPI above::
i2c = I2C(2, scl=Pin("SCL"), sda=Pin("SDA"), freq=400_000)
i2c.writeto(0x76, b"Hello World")


WLAN
----

SAMD51 MCUs support networking using a ESP32 based extension module with NINA firmware.
Some SAMD51 are already equipped with such a module. For some boards
add-on modules are available which can be stacked to that board. But
any generic ESP32 or a ESP32 WROOM module can be used for that purpose.
Since the WLAN module's wiring may vary, it must be specified when
setting up the connection. The example below shows the set-up
for a Adafruit ItsyBitsy M4 module with the matching add-on board,
for which the wiring is defined in the firmware::

wlan = network.WLAN(network.STA_IF)
wlan.active(True) # activate the interface
wlan.scan() # scan for access points
wlan.isconnected() # check if the station is connected to an AP
wlan.connect('ssid', 'key') # connect to an AP
wlan.config('mac') # get the interface's MAC address
wlan.ifconfig() # get the interface's IP/netmask/gw/DNS addresses

If no default wiring exist or the wiring differs from the default, it
can be specified with the alternative NinaW10 WLAN class e.g.::

import network
from machine import SPI, Pin
spi=SPI(1, 8000000, sck="SCK", mosi="MOSI", miso="MISO")
wlan = network.NinaW10(network.STA_IF, spi=spi, cs=Pin("D13"), busy=Pin("D11"), reset=Pin("D12"))

In that case, all wiring parameters must be specified, even if some of them
match the default connection. For further details, see :ref:`NinaW10 class <network.NinaW10>`.

Notes:

- All supported SAMD51 boards provide basic WLAN support. SSL is at the moment only
included for boards with a SAMD51x20 MCU. SSL requires a lot of code space.
- If feasible, pull the RESET line low with an external resistor. A good value
is 2.2 kOhm. This forces the GPIO lines of the module to high impedance when
not being used.


OneWire driver
--------------

Expand Down
136 changes: 107 additions & 29 deletions drivers/ninaw10/nina_wifi_bsp.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#include "py/runtime.h"
#include "modmachine.h"
#include "extmod/machine_spi.h"
#include "extmod/virtpin.h"
#include "mpconfigboard.h"

#include "nina_bsp.h"
Expand All @@ -48,45 +49,122 @@
#define debug_printf(...)
#endif

#define PIN_NOT_SET ((mp_hal_pin_obj_t)0xffffffff)

nina_wiring_t nina_wiring = {
NULL,
PIN_NOT_SET,
PIN_NOT_SET,
PIN_NOT_SET,
PIN_NOT_SET,
};

void nina_bsp_wiring(mp_obj_type_t *spi, mp_obj_type_t *cs, mp_obj_type_t *ack, mp_obj_type_t *reset) {
// Set the default values
nina_wiring.spi = NULL;

#if defined MICROPY_HW_NINA_GPIO1
nina_wiring.cs = MICROPY_HW_NINA_GPIO1;
#elif defined MICROPY_HW_NINA_CS
nina_wiring.cs = MICROPY_HW_NINA_CS;
#else
nina_wiring.cs = PIN_NOT_SET;
#endif

#ifdef MICROPY_HW_NINA_ACK
nina_wiring.ack = MICROPY_HW_NINA_ACK;
#else
nina_wiring.ack = PIN_NOT_SET;
#endif

#ifdef MICROPY_HW_NINA_RESET
nina_wiring.reset = MICROPY_HW_NINA_RESET;
#else
nina_wiring.reset = PIN_NOT_SET;
#endif

// Check for dedicated settings.
if (spi != mp_const_none) {
if (mp_obj_is_type(spi, &machine_spi_type)) {
nina_wiring.spi = spi;
} else {
mp_raise_TypeError(MP_ERROR_TEXT("not an SPI object"));
}
}
if (cs != mp_const_none) {
nina_wiring.cs = mp_hal_get_pin_obj(cs);
}
if (ack != mp_const_none) {
nina_wiring.ack = mp_hal_get_pin_obj(ack);
}
if (reset != mp_const_none) {
nina_wiring.reset = mp_hal_get_pin_obj(reset);
}

if (nina_wiring.cs == PIN_NOT_SET || nina_wiring.ack == PIN_NOT_SET || nina_wiring.reset == PIN_NOT_SET) {
mp_raise_ValueError(MP_ERROR_TEXT("cs, busy or reset not set"));
}
}

int nina_bsp_init(void) {
mp_hal_pin_output(MICROPY_HW_NINA_GPIO1);
mp_hal_pin_input(MICROPY_HW_NINA_ACK);
mp_hal_pin_output(MICROPY_HW_NINA_RESET);
mp_hal_pin_output(MICROPY_HW_NINA_GPIO0);
mp_hal_pin_output(nina_wiring.cs);
mp_hal_pin_input(nina_wiring.ack);
mp_hal_pin_output(nina_wiring.reset);
if (nina_wiring.gpio0 != PIN_NOT_SET) {
mp_hal_pin_input(nina_wiring.gpio0);
}

// Reset module in WiFi mode
mp_hal_pin_write(MICROPY_HW_NINA_GPIO1, 1);
mp_hal_pin_write(MICROPY_HW_NINA_GPIO0, 1);
mp_hal_pin_write(nina_wiring.cs, 1);

mp_hal_pin_write(MICROPY_HW_NINA_RESET, 0);
mp_hal_pin_write(nina_wiring.reset, 0);
mp_hal_delay_ms(100);

mp_hal_pin_write(MICROPY_HW_NINA_RESET, 1);
mp_hal_pin_write(nina_wiring.reset, 1);
mp_hal_delay_ms(750);

mp_hal_pin_write(MICROPY_HW_NINA_GPIO0, 0);
mp_hal_pin_input(MICROPY_HW_NINA_GPIO0);

// Initialize SPI.
mp_obj_t args[] = {
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_ID),
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_BAUDRATE),
};

MP_STATE_PORT(mp_wifi_spi) = MP_OBJ_TYPE_GET_SLOT(&machine_spi_type, make_new)((mp_obj_t)&machine_spi_type, 2, 0, args);
if (nina_wiring.spi == NULL) {
// use the hard-coded setting
#ifdef MICROPY_HW_WIFI_SPI_ID
#ifdef MICROPY_HW_WIFI_SPI_SCK
mp_obj_t sck_obj = MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_SCK);
mp_obj_t miso_obj = MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_MISO);
mp_obj_t mosi_obj = MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_MOSI);
mp_obj_t args[] = {
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_ID),
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_BAUDRATE),
MP_ROM_QSTR(MP_QSTR_sck), mp_pin_make_new(NULL, 1, 0, &sck_obj),
MP_ROM_QSTR(MP_QSTR_miso), mp_pin_make_new(NULL, 1, 0, &miso_obj),
MP_ROM_QSTR(MP_QSTR_mosi), mp_pin_make_new(NULL, 1, 0, &mosi_obj),
};
MP_STATE_PORT(mp_wifi_spi) = MP_OBJ_TYPE_GET_SLOT(&machine_spi_type, make_new)(
(mp_obj_t)&machine_spi_type, 2, 3, args);
#else // MICROPY_HW_WIFI_SPI_SCK
mp_obj_t args[] = {
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_ID),
MP_OBJ_NEW_SMALL_INT(MICROPY_HW_WIFI_SPI_BAUDRATE),
};
MP_STATE_PORT(mp_wifi_spi) = MP_OBJ_TYPE_GET_SLOT(&machine_spi_type, make_new)(
(mp_obj_t)&machine_spi_type, 2, 0, args);
#endif // MICROPY_HW_WIFI_SPI_SCK
#else // MICROPY_HW_WIFI_SPI_ID
mp_raise_ValueError(MP_ERROR_TEXT("no NINA wiring defined"));
#endif // MICROPY_HW_WIFI_SPI_ID
} else {
MP_STATE_PORT(mp_wifi_spi) = (struct _machine_spi_obj_t *)nina_wiring.spi;
}
return 0;
}

int nina_bsp_deinit(void) {
mp_hal_pin_output(MICROPY_HW_NINA_GPIO1);
mp_hal_pin_write(MICROPY_HW_NINA_GPIO1, 1);
mp_hal_pin_output(nina_wiring.cs);
mp_hal_pin_write(nina_wiring.cs, 1);

mp_hal_pin_output(MICROPY_HW_NINA_RESET);
mp_hal_pin_write(MICROPY_HW_NINA_RESET, 0);
mp_hal_pin_output(nina_wiring.reset);
mp_hal_pin_write(nina_wiring.reset, 0);
mp_hal_delay_ms(100);

mp_hal_pin_output(MICROPY_HW_NINA_GPIO0);
mp_hal_pin_write(MICROPY_HW_NINA_GPIO0, 1);
return 0;
}

Expand All @@ -105,24 +183,24 @@ int nina_bsp_atomic_exit(void) {
}

int nina_bsp_read_irq(void) {
return mp_hal_pin_read(MICROPY_HW_NINA_GPIO0);
return nina_wiring.gpio0 != PIN_NOT_SET ? mp_hal_pin_read(nina_wiring.gpio0) : 1;
}

int nina_bsp_spi_slave_select(uint32_t timeout) {
// Wait for ACK to go low.
for (mp_uint_t start = mp_hal_ticks_ms(); mp_hal_pin_read(MICROPY_HW_NINA_ACK) == 1; mp_hal_delay_ms(1)) {
for (mp_uint_t start = mp_hal_ticks_ms(); mp_hal_pin_read(nina_wiring.ack) == 1; mp_hal_delay_ms(1)) {
if (timeout && ((mp_hal_ticks_ms() - start) >= timeout)) {
return -1;
}
}

// Chip select.
mp_hal_pin_write(MICROPY_HW_NINA_GPIO1, 0);
mp_hal_pin_write(nina_wiring.cs, 0);

// Wait for ACK to go high.
for (mp_uint_t start = mp_hal_ticks_ms(); mp_hal_pin_read(MICROPY_HW_NINA_ACK) == 0; mp_hal_delay_ms(1)) {
for (mp_uint_t start = mp_hal_ticks_ms(); mp_hal_pin_read(nina_wiring.ack) == 0; mp_hal_delay_ms(1)) {
if ((mp_hal_ticks_ms() - start) >= 100) {
mp_hal_pin_write(MICROPY_HW_NINA_GPIO1, 1);
mp_hal_pin_write(nina_wiring.cs, 1);
return -1;
}
}
Expand All @@ -131,7 +209,7 @@ int nina_bsp_spi_slave_select(uint32_t timeout) {
}

int nina_bsp_spi_slave_deselect(void) {
mp_hal_pin_write(MICROPY_HW_NINA_GPIO1, 1);
mp_hal_pin_write(nina_wiring.cs, 1);
return 0;
}

Expand Down
9 changes: 9 additions & 0 deletions drivers/ninaw10/nina_wifi_drv.h
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ typedef struct {
uint8_t bssid[NINA_MAC_ADDR_LEN];
} nina_netinfo_t;

typedef struct {
mp_obj_type_t *spi;
mp_hal_pin_obj_t cs;
mp_hal_pin_obj_t ack;
mp_hal_pin_obj_t reset;
mp_hal_pin_obj_t gpio0;
} nina_wiring_t;

typedef int (*nina_scan_callback_t)(nina_scan_result_t *, void *);

int nina_init(void);
Expand Down Expand Up @@ -140,4 +148,5 @@ int nina_socket_poll(int fd, uint8_t *flags);
int nina_socket_setsockopt(int fd, uint32_t level, uint32_t opt, const void *optval, uint16_t optlen);
int nina_socket_getsockopt(int fd, uint32_t level, uint32_t opt, void *optval, uint16_t optlen);
int nina_socket_getpeername(int fd, uint8_t *ip, uint16_t *port);
void nina_bsp_wiring(mp_obj_type_t *spi, mp_obj_type_t *cs, mp_obj_type_t *ack, mp_obj_type_t *reset);
#endif // MICROPY_INCLUDED_DRIVERS_NINAW10_NINA_WIFI_DRV_H
Loading