mirror of
https://github.com/tinygo-org/drivers.git
synced 2026-07-26 10:38:41 +00:00
w5500: initial version the driver (#788)
Thank you, @nrwiersma for working on this and @soypat for review. Now merging.
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"machine"
|
||||
"net"
|
||||
"net/netip"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
"tinygo.org/x/drivers/w5500"
|
||||
)
|
||||
|
||||
func main() {
|
||||
machine.SPI0.Configure(machine.SPIConfig{
|
||||
Frequency: 33 * machine.MHz,
|
||||
})
|
||||
machine.GPIO17.Configure(machine.PinConfig{Mode: machine.PinOutput})
|
||||
|
||||
eth := w5500.New(machine.SPI0, machine.GPIO17)
|
||||
eth.Configure(w5500.Config{
|
||||
MAC: net.HardwareAddr{0xee, 0xbe, 0xe9, 0xa9, 0xb6, 0x4f},
|
||||
IP: netip.AddrFrom4([4]byte{192, 168, 1, 2}),
|
||||
SubnetMask: netip.AddrFrom4([4]byte{255, 255, 255, 0}),
|
||||
Gateway: netip.AddrFrom4([4]byte{192, 168, 1, 1}),
|
||||
})
|
||||
netdev.UseNetdev(eth)
|
||||
|
||||
for {
|
||||
if eth.LinkStatus() != w5500.LinkStatusUp {
|
||||
println("Waiting for link to be up")
|
||||
|
||||
time.Sleep(1 * time.Second)
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -146,6 +146,7 @@ tinygo build -size short -o ./build/test.uf2 -target=nicenano ./examples/sharpme
|
||||
tinygo build -size short -o ./build/test.hex -target=feather-nrf52840 ./examples/max6675/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/ens160/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/si5351/main.go
|
||||
tinygo build -size short -o ./build/test.hex -target=pico ./examples/w5500/main.go
|
||||
# network examples (espat)
|
||||
tinygo build -size short -o ./build/test.hex -target=challenger-rp2040 ./examples/net/ntpclient/
|
||||
# network examples (wifinina)
|
||||
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
package w5500
|
||||
|
||||
import "time"
|
||||
|
||||
func (d *Device) irqPoll(sockn uint8, state uint8, deadline time.Time) uint8 {
|
||||
waitTime := 500 * time.Microsecond
|
||||
for {
|
||||
if !deadline.IsZero() && time.Now().After(deadline) {
|
||||
// If a deadline is set and it has passed, return 0.
|
||||
return sockIntUnknown
|
||||
}
|
||||
|
||||
irq := d.readByte(sockInt, sockAddr(sockn)) & 0b00011111
|
||||
if got := irq & state; got != 0 {
|
||||
// Acknowledge the interrupt.
|
||||
d.writeByte(sockInt, sockAddr(sockn), got)
|
||||
|
||||
return got
|
||||
}
|
||||
|
||||
d.mu.Unlock()
|
||||
|
||||
time.Sleep(waitTime)
|
||||
|
||||
// Exponential backoff for polling.
|
||||
waitTime *= 2
|
||||
if waitTime > 10*time.Millisecond {
|
||||
waitTime = 10 * time.Millisecond
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) read(addr uint16, bsb uint8, p []byte) {
|
||||
d.cs(false)
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
d.sendReadHeader(addr, bsb)
|
||||
_ = d.bus.Tx(nil, p)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) readUint16(addr uint16, bsb uint8) uint16 {
|
||||
d.cs(false)
|
||||
d.sendReadHeader(addr, bsb)
|
||||
buf := d.cmdBuf
|
||||
_ = d.bus.Tx(nil, buf[:2])
|
||||
d.cs(true)
|
||||
return uint16(buf[1]) | uint16(buf[0])<<8
|
||||
}
|
||||
|
||||
func (d *Device) readByte(addr uint16, bsb uint8) byte {
|
||||
d.cs(false)
|
||||
d.sendReadHeader(addr, bsb)
|
||||
r, _ := d.bus.Transfer(byte(0))
|
||||
d.cs(true)
|
||||
return r
|
||||
}
|
||||
|
||||
func (d *Device) write(addr uint16, bsb uint8, p []byte) {
|
||||
d.cs(false)
|
||||
if len(p) == 0 {
|
||||
return
|
||||
}
|
||||
d.sendWriteHeader(addr, bsb)
|
||||
_ = d.bus.Tx(p, nil)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) writeUint16(addr uint16, bsb uint8, v uint16) {
|
||||
d.cs(false)
|
||||
d.sendWriteHeader(addr, bsb)
|
||||
buf := d.cmdBuf
|
||||
buf[0] = byte(v >> 8)
|
||||
buf[1] = byte(v & 0xff)
|
||||
_ = d.bus.Tx(buf[:2], nil)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) writeByte(addr uint16, bsb uint8, b byte) {
|
||||
d.cs(false)
|
||||
d.sendWriteHeader(addr, bsb)
|
||||
_, _ = d.bus.Transfer(b)
|
||||
d.cs(true)
|
||||
}
|
||||
|
||||
func (d *Device) sendReadHeader(addr uint16, bsb uint8) {
|
||||
buf := d.cmdBuf
|
||||
buf[0] = byte(addr >> 8)
|
||||
buf[1] = byte(addr & 0xff)
|
||||
buf[2] = bsb << 3
|
||||
_ = d.bus.Tx(buf[:], nil)
|
||||
}
|
||||
|
||||
func (d *Device) sendWriteHeader(addr uint16, bsb uint8) {
|
||||
buf := d.cmdBuf
|
||||
buf[0] = byte(addr >> 8)
|
||||
buf[1] = byte(addr & 0xff)
|
||||
buf[2] = bsb<<3 | 0b100
|
||||
_ = d.bus.Tx(buf[:], nil)
|
||||
}
|
||||
+445
@@ -0,0 +1,445 @@
|
||||
package w5500
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"os"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
)
|
||||
|
||||
type socket struct {
|
||||
sockn uint8
|
||||
protocol uint8
|
||||
port uint16
|
||||
inUse bool
|
||||
closed bool
|
||||
}
|
||||
|
||||
func (s *socket) setProtocol(proto byte) *socket {
|
||||
s.protocol = proto
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) setPort(port uint16) *socket {
|
||||
s.port = port
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) setInUse(inUse bool) *socket {
|
||||
s.inUse = inUse
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) setClosed(closed bool) *socket {
|
||||
s.closed = closed
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *socket) reset() {
|
||||
s.protocol = 0
|
||||
s.port = 0
|
||||
s.inUse = false
|
||||
s.closed = false
|
||||
}
|
||||
|
||||
// GetHostByName resolves the given host name to an IP address.
|
||||
func (d *Device) GetHostByName(name string) (netip.Addr, error) {
|
||||
d.mu.Lock()
|
||||
dns := d.dns
|
||||
d.mu.Unlock()
|
||||
|
||||
if dns == nil {
|
||||
return netip.Addr{}, netdev.ErrNotSupported
|
||||
}
|
||||
return dns(name)
|
||||
}
|
||||
|
||||
func (d *Device) Socket(domain int, stype int, protocol int) (int, error) {
|
||||
if domain != netdev.AF_INET {
|
||||
return -1, netdev.ErrFamilyNotSupported
|
||||
}
|
||||
switch {
|
||||
case stype == netdev.SOCK_STREAM && protocol == netdev.IPPROTO_TCP:
|
||||
case stype == netdev.SOCK_DGRAM && protocol == netdev.IPPROTO_UDP:
|
||||
default:
|
||||
return -1, errors.New("unsupported combination of socket type and protocol")
|
||||
}
|
||||
|
||||
var proto byte
|
||||
switch protocol {
|
||||
case netdev.IPPROTO_TCP:
|
||||
proto = 1 // TCP
|
||||
case netdev.IPPROTO_UDP:
|
||||
proto = 2 // UDP
|
||||
default:
|
||||
return -1, netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sockfd, sock, err := d.nextSocket()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
d.openSocket(sock.sockn, proto)
|
||||
|
||||
sock.setProtocol(proto).setInUse(true)
|
||||
return sockfd, nil
|
||||
}
|
||||
|
||||
func (d *Device) openSocket(sockn uint8, proto byte) {
|
||||
d.writeByte(sockMode, sockAddr(sockn), proto&0x0F)
|
||||
}
|
||||
|
||||
func (d *Device) Bind(sockfd int, ip netip.AddrPort) error {
|
||||
// The IP address is irrelevant. The configured ip will always be used.
|
||||
port := ip.Port()
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = d.bindSocket(sock.sockn, port); err != nil {
|
||||
return errors.New("could not set socket port: " + err.Error())
|
||||
}
|
||||
|
||||
sock.setPort(port)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) bindSocket(sockn uint8, port uint16) error {
|
||||
d.writeUint16(sockSrcPort, sockAddr(sockn), port)
|
||||
d.socketSendCmd(sockn, sockCmdOpen)
|
||||
if d.sockStatus(sockn) == sockStatusClosed {
|
||||
return errors.New("socket is closed after binding")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSockOpt sets the socket option for the given socket file descriptor.
|
||||
// It is not supported by the W5500, so it always returns an error.
|
||||
func (d *Device) SetSockOpt(int, int, int, any) error {
|
||||
return netdev.ErrNotSupported
|
||||
}
|
||||
|
||||
// Connect establishes a connection to the specified host and port or ip and port.
|
||||
//
|
||||
// If the host is an empty string, it will use the provided ip address and port,
|
||||
// otherwise it will resolve the host name to an IP address.
|
||||
func (d *Device) Connect(sockfd int, host string, ip netip.AddrPort) error {
|
||||
destIP := ip.Addr()
|
||||
if host != "" {
|
||||
var err error
|
||||
destIP, err = d.GetHostByName(host)
|
||||
if err != nil {
|
||||
return errors.New("could not resolve host " + host + ":" + err.Error())
|
||||
}
|
||||
}
|
||||
if !destIP.IsValid() || !destIP.Is4() {
|
||||
return errors.New("invalid destination IP address: " + destIP.String())
|
||||
}
|
||||
port := ip.Port()
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.write(sockDestIP, sockAddr(sock.sockn), destIP.AsSlice())
|
||||
d.writeUint16(sockDestPort, sockAddr(sock.sockn), port)
|
||||
d.socketSendCmd(sock.sockn, sockCmdOpen)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Listen sets the socket to listen for incoming connections on the specified socket file descriptor.
|
||||
//
|
||||
// The backlog parameter is ignored, as the W5500 does not support it.
|
||||
func (d *Device) Listen(sockfd int, _ int) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if sock.protocol != 1 { // Only TCP sockets can listen
|
||||
return errors.New("not a TCP socket")
|
||||
}
|
||||
|
||||
if err = d.listen(sock.sockn); err != nil {
|
||||
return errors.New("could not send listen command: " + err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) listen(sockn uint8) error {
|
||||
state := d.sockStatus(sockn)
|
||||
if state != sockStatusInit {
|
||||
return errors.New("socket is not in the initial state")
|
||||
}
|
||||
d.socketSendCmd(sockn, sockCmdListen)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Accept waits for an incoming connection on the specified socket file descriptor.
|
||||
func (d *Device) Accept(sockfd int) (int, netip.AddrPort, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
lsock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return -1, netip.AddrPort{}, errors.New("could not get socket: " + err.Error())
|
||||
}
|
||||
|
||||
if err = d.waitForEstablished(lsock.sockn); err != nil {
|
||||
return -1, netip.AddrPort{}, err
|
||||
}
|
||||
|
||||
// Acquire a new socket for the listening connection.
|
||||
csockfd, csock, err := d.nextSocket()
|
||||
if err != nil {
|
||||
return -1, netip.AddrPort{}, err
|
||||
}
|
||||
// Swap the socket numbers of the client and listening sockets.
|
||||
lsock.sockn, csock.sockn = csock.sockn, lsock.sockn
|
||||
|
||||
// Rebind the listening socket to the local address and port and start listening.
|
||||
d.openSocket(lsock.sockn, lsock.protocol)
|
||||
if err = d.bindSocket(lsock.sockn, lsock.port); err != nil {
|
||||
return -1, netip.AddrPort{}, errors.New("could not bind listening socket: " + err.Error())
|
||||
}
|
||||
if err = d.listen(lsock.sockn); err != nil {
|
||||
return -1, netip.AddrPort{}, errors.New("could not set listening socket: " + err.Error())
|
||||
}
|
||||
|
||||
csock.setInUse(true)
|
||||
|
||||
remoteIP := d.remoteIP(csock.sockn)
|
||||
return csockfd, remoteIP, nil
|
||||
}
|
||||
|
||||
func (d *Device) waitForEstablished(sockn uint8) error {
|
||||
for {
|
||||
status := d.sockStatus(sockn)
|
||||
switch status {
|
||||
case sockStatusEstablished:
|
||||
return nil
|
||||
case sockStatusClosed:
|
||||
return net.ErrClosed
|
||||
case sockStatusCloseWait:
|
||||
// The server closed the connection, so we need to reset the socket
|
||||
// and set it to listen again.
|
||||
if err := d.listen(sockn); err != nil {
|
||||
return errors.New("could not set socket to listen: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
d.irqPoll(sockn, sockIntConnect|sockIntDisconnect, time.Time{})
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) remoteIP(sockn uint8) netip.AddrPort {
|
||||
var rip [4]byte
|
||||
d.read(sockDestIP, sockAddr(sockn), rip[:])
|
||||
|
||||
var rport [2]byte
|
||||
d.read(sockDestPort, sockAddr(sockn), rport[:])
|
||||
|
||||
return netip.AddrPortFrom(netip.AddrFrom4(rip), uint16(rport[0])<<8|uint16(rport[1]))
|
||||
}
|
||||
|
||||
// Send sends data to the socket with the given file descriptor.
|
||||
// It blocks until all data is sent or the deadline is reached.
|
||||
func (d *Device) Send(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
|
||||
bufLen := len(buf)
|
||||
if bufLen <= d.maxSockSize {
|
||||
// Fast path for small buffers.
|
||||
return d.sendChunk(sockfd, buf, deadline)
|
||||
}
|
||||
|
||||
var n int
|
||||
for i := 0; i < bufLen; i += d.maxSockSize {
|
||||
end := i + d.maxSockSize
|
||||
if end > bufLen {
|
||||
end = bufLen
|
||||
}
|
||||
|
||||
sent, err := d.sendChunk(sockfd, buf[i:end], deadline)
|
||||
if err != nil {
|
||||
return n, errors.New("could not send chunk: " + err.Error())
|
||||
}
|
||||
n += sent
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
func (d *Device) sendChunk(sockfd int, buf []byte, deadline time.Time) (int, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return 0, errors.New("could not get socket: " + err.Error())
|
||||
}
|
||||
if sock.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
bufLen := uint16(len(buf))
|
||||
if err = d.waitForFreeBuffer(sock.sockn, bufLen, deadline); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
sendPtr := d.readUint16(sockTXWritePtr, sockAddr(sock.sockn))
|
||||
|
||||
d.write(sendPtr, sock.sockn<<2|0b10, buf)
|
||||
d.writeUint16(sockTXWritePtr, sockAddr(sock.sockn), sendPtr+bufLen)
|
||||
d.writeByte(sockCmd, sockAddr(sock.sockn), sockCmdSend)
|
||||
|
||||
irq := d.irqPoll(sock.sockn, sockIntSendOK|sockIntDisconnect|sockIntTimeout, deadline)
|
||||
switch {
|
||||
case irq == sockIntUnknown:
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
case irq&sockIntDisconnect != 0:
|
||||
sock.setClosed(true)
|
||||
return 0, net.ErrClosed
|
||||
case irq&sockIntTimeout != 0:
|
||||
return 0, netdev.ErrTimeout
|
||||
default:
|
||||
return int(bufLen), nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) waitForFreeBuffer(sockn uint8, len uint16, deadline time.Time) error {
|
||||
for {
|
||||
freeSize := d.readUint16(sockTXFreeSize, sockAddr(sockn))
|
||||
if freeSize >= len {
|
||||
return nil
|
||||
}
|
||||
|
||||
if !deadline.IsZero() && time.Now().After(deadline) {
|
||||
return netdev.ErrTimeout
|
||||
}
|
||||
|
||||
status := d.sockStatus(sockn)
|
||||
switch status {
|
||||
case sockStatusEstablished, sockStatusCloseWait:
|
||||
default:
|
||||
return errors.New("socket is not in a valid state for sending data")
|
||||
}
|
||||
|
||||
d.mu.Unlock()
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
d.mu.Lock()
|
||||
}
|
||||
}
|
||||
|
||||
// Recv reads data from the socket with the given file descriptor into the provided buffer.
|
||||
// It blocks until data is available or the deadline is reached.
|
||||
func (d *Device) Recv(sockfd int, buf []byte, _ int, deadline time.Time) (int, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return 0, errors.New("could not get socket: " + err.Error())
|
||||
}
|
||||
if sock.closed {
|
||||
return 0, os.ErrClosed
|
||||
}
|
||||
|
||||
size, err := d.waitForData(sock, deadline)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
recvPtr := d.readUint16(sockRXReadPtr, sockAddr(sock.sockn))
|
||||
|
||||
buf = buf[:min(size, len(buf))]
|
||||
d.read(recvPtr, sock.sockn<<2|0b00011, buf)
|
||||
d.writeUint16(sockRXReadPtr, sockAddr(sock.sockn), recvPtr+uint16(len(buf)))
|
||||
d.socketSendCmd(sock.sockn, sockCmdRecv)
|
||||
|
||||
return len(buf), nil
|
||||
}
|
||||
|
||||
func (d *Device) waitForData(sock *socket, deadline time.Time) (int, error) {
|
||||
for {
|
||||
recvdSize := d.readUint16(sockRXReceivedSize, sockAddr(sock.sockn))
|
||||
if recvdSize > 0 {
|
||||
return int(recvdSize), nil
|
||||
}
|
||||
|
||||
irq := d.irqPoll(sock.sockn, sockIntReceive|sockIntDisconnect, deadline)
|
||||
switch {
|
||||
case irq == sockIntUnknown:
|
||||
return 0, os.ErrDeadlineExceeded
|
||||
case irq&sockIntDisconnect != 0:
|
||||
sock.setClosed(true)
|
||||
return 0, net.ErrClosed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close closes the socket with the given file descriptor.
|
||||
func (d *Device) Close(sockfd int) error {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
sock, err := d.socket(sockfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
d.socketSendCmd(sock.sockn, sockCmdClose)
|
||||
sock.reset()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) nextSocket() (int, *socket, error) {
|
||||
for i, sock := range d.sockets {
|
||||
if sock.inUse {
|
||||
continue
|
||||
}
|
||||
return i, sock, nil
|
||||
}
|
||||
return -1, nil, netdev.ErrNoMoreSockets
|
||||
}
|
||||
|
||||
func (d *Device) socket(sockfd int) (*socket, error) {
|
||||
if sockfd < 0 || sockfd >= len(d.sockets) {
|
||||
return nil, netdev.ErrInvalidSocketFd
|
||||
}
|
||||
return d.sockets[sockfd], nil
|
||||
}
|
||||
|
||||
func (d *Device) socketSendCmd(sockn uint8, cmd byte) {
|
||||
d.writeByte(sockCmd, sockAddr(sockn), cmd)
|
||||
for d.readByte(sockCmd, sockAddr(sockn)) != 0 {
|
||||
runtime.Gosched()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Device) sockStatus(sockn uint8) int {
|
||||
return int(d.readByte(sockStatus, sockAddr(sockn)))
|
||||
}
|
||||
|
||||
func sockAddr(sockn uint8) uint8 {
|
||||
return sockn<<2 | 0b0001
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package w5500
|
||||
|
||||
// Common Registers.
|
||||
const (
|
||||
regMode = 0x0000
|
||||
regGatewayAddr = 0x0001
|
||||
regSubnetMask = 0x0005
|
||||
regMAC = 0x0009
|
||||
regIPAddr = 0x000F
|
||||
regIntLevel = 0x0013
|
||||
regInt = 0x0015
|
||||
regIntMask = 0x0016
|
||||
regSockInt = 0x0017
|
||||
regSockIntMask = 0x0018
|
||||
regRetryTime = 0x0019
|
||||
regRetryN = 0x001B
|
||||
// ... PPP registers, not needed
|
||||
regPHYCfg = 0x002E
|
||||
regChipVer = 0x0039
|
||||
)
|
||||
|
||||
// Socket Registers.
|
||||
const (
|
||||
sockMode = 0x0000
|
||||
sockCmd = 0x0001
|
||||
sockInt = 0x0002
|
||||
sockStatus = 0x0003
|
||||
sockSrcPort = 0x0004
|
||||
sockDestMAC = 0x0006
|
||||
sockDestIP = 0x000C
|
||||
sockDestPort = 0x0010
|
||||
sockMaxSegSize = 0x0012
|
||||
sockIPTOS = 0x0015
|
||||
sockIPTTL = 0x0016
|
||||
sockRXBUFSize = 0x001E
|
||||
sockTXBUFSize = 0x001F
|
||||
sockTXFreeSize = 0x0020
|
||||
sockTXReadPtr = 0x0022
|
||||
sockTXWritePtr = 0x0024
|
||||
sockRXReceivedSize = 0x0026
|
||||
sockRXReadPtr = 0x0028
|
||||
sockRXWritePtr = 0x002A
|
||||
sockIntMask = 0x002C
|
||||
sockKeepInt = 0x002F
|
||||
)
|
||||
|
||||
// Socket Commands.
|
||||
const (
|
||||
sockCmdOpen = 0x01
|
||||
sockCmdClose = 0x10
|
||||
sockCmdListen = 0x02
|
||||
sockCmdConnect = 0x04
|
||||
sockCmdDisconnect = 0x08
|
||||
sockCmdSend = 0x20
|
||||
sockCmdSendMacRaw = 0x21
|
||||
sockCmdSendKeep = 0x22
|
||||
sockCmdRecv = 0x40
|
||||
)
|
||||
|
||||
// Socket Statuses.
|
||||
const (
|
||||
sockStatusClosed = 0x00
|
||||
sockStatusInit = 0x13
|
||||
sockStatusListen = 0x14
|
||||
sockStatusEstablished = 0x17
|
||||
sockStatusCloseWait = 0x1C
|
||||
sockStatusUdp = 0x22
|
||||
sockStatusMacRaw = 0x42
|
||||
// Temporary TCP states
|
||||
sockStatusSynSent = 0x15
|
||||
sockStatusSynRecv = 0x16
|
||||
sockStatusFinWait = 0x18
|
||||
sockStatusClosing = 0x1A
|
||||
sockStatusTimeWait = 0x1B
|
||||
sockStatusLastAck = 0x1D
|
||||
sockStatusUnknown = 0xFF
|
||||
)
|
||||
|
||||
// Socket Interrupts.
|
||||
const (
|
||||
sockIntConnect uint8 = 1 << iota
|
||||
sockIntDisconnect
|
||||
sockIntReceive
|
||||
sockIntTimeout
|
||||
sockIntSendOK
|
||||
|
||||
sockIntUnknown uint8 = 0
|
||||
)
|
||||
+248
@@ -0,0 +1,248 @@
|
||||
// Package w5500 implements a driver for the W5500 Ethernet controller.
|
||||
//
|
||||
// The driver supports basic network functionality including TCP and UDP sockets.
|
||||
// It currently does not use the IRQ or RST pins.
|
||||
//
|
||||
// Datasheet: https://docs.wiznet.io/img/products/w5500/W5500_ds_v110e.pdf
|
||||
// Product Page: https://wiznet.io/products/ethernet-chips/w5500
|
||||
package w5500
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net"
|
||||
"net/netip"
|
||||
"sync"
|
||||
|
||||
"tinygo.org/x/drivers"
|
||||
"tinygo.org/x/drivers/internal/pin"
|
||||
"tinygo.org/x/drivers/netdev"
|
||||
)
|
||||
|
||||
var _ netdev.Netdever = &Device{}
|
||||
|
||||
// Resolver is a function that resolves a hostname to an IP address.
|
||||
type Resolver func(host string) (netip.Addr, error)
|
||||
|
||||
// Device is a driver for the W5500 Ethernet controller.
|
||||
type Device struct {
|
||||
maxSockets int
|
||||
maxSockSize int
|
||||
|
||||
mu sync.Mutex
|
||||
bus drivers.SPI
|
||||
cs pin.OutputFunc
|
||||
dns Resolver
|
||||
|
||||
sockets []*socket
|
||||
laddr netip.Addr
|
||||
|
||||
cmdBuf [3]byte
|
||||
}
|
||||
|
||||
// New returns a new w5500 driver.
|
||||
func New(bus drivers.SPI, csPin pin.Output) *Device {
|
||||
return &Device{
|
||||
bus: bus,
|
||||
cs: csPin.Set,
|
||||
}
|
||||
}
|
||||
|
||||
// Config is the configuration for the device.
|
||||
//
|
||||
// The SPI bus must be fully configured.
|
||||
type Config struct {
|
||||
DNS Resolver
|
||||
|
||||
MAC net.HardwareAddr
|
||||
IP netip.Addr
|
||||
SubnetMask netip.Addr
|
||||
Gateway netip.Addr
|
||||
|
||||
// Optional, default is 8.
|
||||
MaxSockets int
|
||||
}
|
||||
|
||||
// Configure sets up the device.
|
||||
//
|
||||
// MAC address must be provided. The other fields are optional.
|
||||
func (d *Device) Configure(cfg Config) error {
|
||||
d.cs(true)
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.dns = cfg.DNS
|
||||
|
||||
d.reset()
|
||||
|
||||
if err := d.setupSockets(cfg.MaxSockets); err != nil {
|
||||
return errors.New("could not setup sockets: " + err.Error())
|
||||
}
|
||||
|
||||
// Set the MAC address and IP configuration.
|
||||
d.write(regMAC, 0, cfg.MAC)
|
||||
d.write(regIPAddr, 0, cfg.IP.AsSlice())
|
||||
d.write(regSubnetMask, 0, cfg.SubnetMask.AsSlice())
|
||||
d.write(regGatewayAddr, 0, cfg.Gateway.AsSlice())
|
||||
d.laddr = cfg.IP
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Device) setupSockets(maxSockets int) error {
|
||||
if maxSockets == 0 {
|
||||
maxSockets = 8 // Default to 8 sockets if not specified.
|
||||
}
|
||||
switch maxSockets {
|
||||
case 1, 2, 4, 8:
|
||||
// Valid socket counts.
|
||||
default:
|
||||
return errors.New("invalid number of sockets, must be one of 1, 2, 4, or 8")
|
||||
}
|
||||
|
||||
socks := make([]*socket, maxSockets)
|
||||
for i := range socks {
|
||||
socks[i] = &socket{
|
||||
sockn: uint8(i),
|
||||
}
|
||||
}
|
||||
|
||||
d.maxSockets = maxSockets
|
||||
d.maxSockSize = 16 * 1024 / maxSockets
|
||||
d.sockets = socks
|
||||
|
||||
// Set the RX and TX buffer sizes for each socket.
|
||||
for i := 0; i < 8; i++ {
|
||||
size := byte(d.maxSockSize >> 10)
|
||||
if i >= maxSockets {
|
||||
size = 0
|
||||
}
|
||||
|
||||
d.writeByte(sockRXBUFSize, sockAddr(uint8(i)), size)
|
||||
d.writeByte(sockTXBUFSize, sockAddr(uint8(i)), size)
|
||||
}
|
||||
|
||||
mask := byte(0b11111111)
|
||||
switch maxSockets {
|
||||
case 1:
|
||||
mask = 0b00000001
|
||||
case 2:
|
||||
mask = 0b00000011
|
||||
case 4:
|
||||
mask = 0b00001111
|
||||
}
|
||||
d.writeByte(regSockIntMask, 0, mask)
|
||||
d.writeByte(regIntMask, 0, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reset performs a soft reset.
|
||||
func (d *Device) Reset() {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.reset()
|
||||
}
|
||||
|
||||
func (d *Device) reset() {
|
||||
// RST is bit 7 of regMode.
|
||||
d.writeByte(regMode, 0, 0x80)
|
||||
}
|
||||
|
||||
// GetHardwareAddr returns the hardware address of the device.
|
||||
func (d *Device) GetHardwareAddr() (net.HardwareAddr, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
mac := make([]byte, 6)
|
||||
d.read(regMAC, 0, mac)
|
||||
return mac, nil
|
||||
}
|
||||
|
||||
// Addr returns the IP address of the device.
|
||||
func (d *Device) Addr() (netip.Addr, error) {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
var ip [4]byte
|
||||
d.read(regIPAddr, 0, ip[:])
|
||||
return netip.AddrFrom4(ip), nil
|
||||
}
|
||||
|
||||
// SetAddr sets the IP address of the device.
|
||||
//
|
||||
// The IP address must be a valid IPv4 address.
|
||||
func (d *Device) SetAddr(ip netip.Addr) error {
|
||||
if err := d.setAddress(regIPAddr, ip); err != nil {
|
||||
return errors.New("could not set IP address: " + err.Error())
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.laddr = ip
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetSubnetMask sets the subnet mask of the device.
|
||||
//
|
||||
// The subnet mask must be a valid IPv4 address.
|
||||
// It is not checked if the subnet mask is valid for the device's IP address.
|
||||
func (d *Device) SetSubnetMask(mask netip.Addr) error {
|
||||
return d.setAddress(regSubnetMask, mask)
|
||||
}
|
||||
|
||||
// SetGateway sets the gateway address of the device.
|
||||
//
|
||||
// The gateway must be a valid IPv4 address.
|
||||
// It is not checked if the gateway is in the same subnet as the device.
|
||||
func (d *Device) SetGateway(gateway netip.Addr) error {
|
||||
return d.setAddress(regGatewayAddr, gateway)
|
||||
}
|
||||
|
||||
func (d *Device) setAddress(addr uint16, ip netip.Addr) error {
|
||||
if !ip.IsValid() || !ip.Is4() {
|
||||
return errors.New("invalid IP address: " + ip.String())
|
||||
}
|
||||
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
d.write(addr, 0, ip.AsSlice())
|
||||
return nil
|
||||
}
|
||||
|
||||
// LinkStatus is the link status of the device.
|
||||
type LinkStatus = uint8
|
||||
|
||||
// LinkStatus values.
|
||||
const (
|
||||
LinkStatusDown LinkStatus = iota
|
||||
LinkStatusUp
|
||||
)
|
||||
|
||||
// LinkStatus returns the current link status of the device.
|
||||
func (d *Device) LinkStatus() LinkStatus {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
return d.readByte(regPHYCfg, 0) & 0b00000001
|
||||
}
|
||||
|
||||
// LinkInfo returns the current link information of the device.
|
||||
func (d *Device) LinkInfo() string {
|
||||
d.mu.Lock()
|
||||
defer d.mu.Unlock()
|
||||
|
||||
linkInfo := d.readByte(regPHYCfg, 0) & 0b00000110
|
||||
|
||||
speed := "10Mbps"
|
||||
if linkInfo&0b00000010 != 0 {
|
||||
speed = "100Mbps"
|
||||
}
|
||||
duplex := "Half Duplex"
|
||||
if linkInfo&0b00000100 != 0 {
|
||||
duplex = "Full Duplex"
|
||||
}
|
||||
return speed + " " + duplex
|
||||
}
|
||||
Reference in New Issue
Block a user