UDP communication between Teensy 4.1 and PC problem

Thread Starter

Vilius_Zalenas

Joined Jul 24, 2022
155
Hi,

I have a task to establish simple UDP communication between teensy 4.1 dev board and PC via twisted pair cable (using a Magjack extension board, teensy if of course connected to PC via standard micro-USB cable for programming too). I am using Arduino IDE for programing and a QNEthernet library (Broadcast chat example.) Since I want my communication to be completely independent from the network (WAN) I dont want to use DHCP (and I basically can not, because I dont have a router at my work, the place I am developing all this. I only have a RJ45 ethernet socket routed to the desk, the network architecture beyond that is unknown for me...)

So after a bit of effort and experiments I have set static IP for both teensy and PC and also made sure that they are on the different subnet than the internet. My idea on testing the communication was to download a UDP client for windows, connect it to the same port with teensy and ideally the text I enter in UDP terminal should be received and seen in the serial monitor of teensy, and of course, the opposite way around. However, when I type anything to the teensy terminal it gives me error, when I type anything to the UDP terminal, it is not received and depicted in the teensy terminal...


My code is basically bare QNEthernet Broadcast chat example, I only omited the DHCP part and assigned the addresses manually, other than that, no mods were made... Do you have any observations, why this could be not working? Did I miss something else?

C:
// SPDX-FileCopyrightText: (c) 2023 Shawn Silverman <shawn@pobox.com>
// SPDX-License-Identifier: MIT

// BroadcastChat is a simple chat application that broadcasts and
// receives text messages over UDP.
//
// This file is part of the QNEthernet library.

#include <QNEthernet.h>

using namespace qindesign::network;

// --------------------------------------------------------------------------
//  Configuration
// --------------------------------------------------------------------------

//constexpr uint32_t kDHCPTimeout = 10'000;  // 10 seconds

constexpr uint16_t kPort = 5190;  // Chat port

// --------------------------------------------------------------------------
//  Program State
// --------------------------------------------------------------------------

// UDP port.
EthernetUDP udp;

// --------------------------------------------------------------------------
//  Main Program
// --------------------------------------------------------------------------

// Forward declarations (not really needed in the Arduino environment)
static void printPrompt();
static void receivePacket();
static void sendLine();

// Program setup.
void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for Serial
  }
  printf("Starting...\r\n");

  uint8_t mac[6];
  Ethernet.macAddress(mac);  // This is informative; it retrieves, not sets
  printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\r\n",
         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

  Ethernet.onLinkState([](bool state) {
    printf("[Ethernet] Link %s\r\n", state ? "ON" : "OFF");
  });

  printf("Starting Ethernet with DHCP...\r\n");
  if (!Ethernet.begin()) {
    printf("Failed to start Ethernet\r\n");
    return;
  }

  /*
  if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
    printf("Failed to get IP address from DHCP\r\n");
    return;
  }
*/
  //IPAddress ip =Ethernet.localIP();

  IPAddress ip = {10, 10, 16, 111};
  printf("    Local IP     = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {255, 255, 255, 0};                     //Ethernet.subnetMask();
  printf("    Subnet mask  = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {255, 255, 255, 255};                   //Ethernet.broadcastIP();
  printf("    Broadcast IP = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {10, 10, 10, 254};                      //Ethernet.gatewayIP();
  printf("    Gateway      = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {10, 10, 10, 244};                      //Ethernet.dnsServerIP();
  printf("    DNS          = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);

  // Start UDP listening on the port
  udp.begin(kPort);

  printPrompt();
}

// Main program loop.
void loop() {
  receivePacket();
  sendLine();
}

// --------------------------------------------------------------------------
//  Internal Functions
// --------------------------------------------------------------------------

// Control character names.
static const String kCtrlNames[]{
  "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
  "BS",  "HT",  "LF",  "VT",  "FF",  "CR",  "SO",  "SI",
  "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
  "CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US",
};

// Receives and prints chat packets.
static void receivePacket() {
  int size = udp.parsePacket();
  if (size < 0) {
    return;
  }

  // Get the packet data and remote address
  const uint8_t *data = udp.data();
  IPAddress ip = udp.remoteIP();

  printf("[%u.%u.%u.%u][%d] ", ip[0], ip[1], ip[2], ip[3], size);

  // Print each character
  for (int i = 0; i < size; i++) {
    uint8_t b = data[i];
    if (b < 0x20) {
      printf("<%s>", kCtrlNames[b].c_str());
    } else if (b < 0x7f) {
      putchar(data[i]);
    } else {
      printf("<%02xh>", data[i]);
    }
  }
  printf("\r\n");
}

// Tries to read a line from the console and returns whether
// a complete line was read. This is CR/CRLF/LF EOL-aware.
static bool readLine(String &line) {
  static bool inCR = false;  // Keeps track of CR state

  while (Serial.available() > 0) {
    int c;
    switch (c = Serial.read()) {
      case '\r':
        inCR = true;
        return true;

      case '\n':
        if (inCR) {
          // Ignore the LF
          inCR = false;
          break;
        }
        return true;

      default:
        if (c < 0) {
          return false;
        }
        inCR = false;
        line.append(static_cast<char>(c));
    }
  }

  return false;
}

// Prints the chat prompt.
static void printPrompt() {
  printf("chat> ");
  fflush(stdout);  // printf may be line-buffered, so ensure there's output
}

// Reads from the console and sends packets.
static void sendLine() {
  static String line;

  // Read from the console and send lines
  if (readLine(line)) {
    if (!udp.send(Ethernet.broadcastIP(), kPort,
                  reinterpret_cast<const uint8_t *>(line.c_str()),
                  line.length())) {
      printf("[Error sending]\r\n");
    }
    line = "";
    printPrompt();
  }
}
 

Attachments

Last edited:

Ya’akov

Joined Jan 27, 2019
8,483
Oops! I inverted the two addresses involved but it doesn't change anything about the logic. I said the Teensy was 10.10.10.x and the gateway 10.10.16.x in the binary part of the post while it is the other way around, but you don't have to worry about that in terms of the explanation, it doesn't matter. Sorry about the error.

So, first an IP address of 10.10.16.x with a netmask of 255.255.255.0 with not be able to communicate with a gateway of 10.10.10.254.

The netmask masks the network portion of the IP address. Wherever the bits are 1 is the network, where they are 0 is the host portion. The dotted decimal notation can be very convenient but it can also be confusing.

An IP address is not a number, though it can be mathematically manipulated which is very useful. The basic truth is that an IP address is a 32 bit string. On its own, it's meaningless. We must either make assumptions about the meaning of the address according to network classes or have an explicit netmask to apply.

Network classes are A, B, and C—indicated by the first two bits in the IP address. But this is very old school and is not actually relevant today where subnets and supernets are the norm. Instead we do rely on the netmask to build the routing table needed to get the packets to the intended target.

Netmasks, like IP addresses are 32 bit strings. In dotted decimal notation, these strings are broken into octets and separated by "dots". This is purely for human convenience. For example, your Teensy's IP address or 10.10.10.111 is actually

00001010000010100000101001101111

and your chosen netmask of 255.255.255.0 is actually

11111111111111111111111100000000

so if we apply the netmask to the IP address with get

00001010000010100000101001101111
11111111111111111111111100000000

000010100000101000001010 24 bit network address
01101111 8 bit host address

doing the same with your gateway address of 10.10.16.254, we get

00001010000010100001000011111110
11111111111111111111111100000000

000010100000101000010000 24 bit network address

now, compare the two networks

000010100000101000001010 10.10.10.0/24
000010100000101000010000 10.10.16.0/24

You can see they don't match. This means your Teensy will need to use a gateway to reach... its gateway!

If something is on the same network, the Teensy will talk directly to the wire and doesn't need any help. It uses ARP (Address Resolution Protocol) to get the MAC (Media Access Control) or hardware or Ethernet address of the device with that IP address and used the Ethernet to talk to it.

But, if it's not, it passes the packet to a gateway that appears in the routing table. In this case, there will only be one and it will be the default route. The trouble is, the default route isn't on the network specified by the netmask. So, the first thing to try is simple enough—change the netmask on both interfaces (Teensy and PC) to use a 255.255.0.0 netmask. This will use only the first 16 bits of the address for network and so the gateway address will match.

Of course the gateway has to exist for it to be useful, and in principle it should be working now, because they are on the same network but start by making this change to be sure it's not a side effect. If it is still not working, we can troubleshoot further.

[EDITED: s/Tiny/Teensy/g (I don't know why I kept saying that, it's not like I don't have Teensys here!]
 
Last edited:

Thread Starter

Vilius_Zalenas

Joined Jul 24, 2022
155
Oops! I inverted the two addresses involved but it doesn't change anything about the logic. I said the Tiny was 10.10.10.x and the gateway 10.10.16.x in the binary part of the post while it is the other way around, but you don't have to worry about that in terms of the explanation, it doesn't matter. Sorry about the error.

So, first an IP address of 10.10.16.x with a netmask of 255.255.255.0 with not be able to communicate with a gateway of 10.10.10.254.

The netmask masks the network portion of the IP address. Wherever the bits are 1 is the network, where they are 0 is the host portion. The dotted decimal notation can be very convenient but it can also be confusing.

An IP address is not a number, though it can be mathematically manipulated which is very useful. The basic truth is that an IP address is a 32 bit string. On its own, it's meaningless. We must either make assumptions about the meaning of the address according to network classes or have an explicit netmask to apply.

Network classes are A, B, and C—indicated by the first two bits in the IP address. But this is very old school and is not actually relevant today where subnets and supernets are the norm. Instead we do rely on the netmask to build the routing table needed to get the packets to the intended target.

Netmasks, like IP addresses are 32 bit strings. In dotted decimal notation, these strings are broken into octets and separated by "dots". This is purely for human convenience. For example, your Tiny's IP address or 10.10.10.111 is actually

00001010000010100000101001101111

and your chosen netmask of 255.255.255.0 is actually

11111111111111111111111100000000

so if we apply the netmask to the IP address with get

00001010000010100000101001101111
11111111111111111111111100000000

000010100000101000001010 24 bit network address
01101111 8 bit host address

doing the same with your gateway address of 10.10.16.254, we get

00001010000010100001000011111110
11111111111111111111111100000000

000010100000101000010000 24 bit network address

now, compare the two networks

000010100000101000001010 10.10.10.0/24
000010100000101000010000 10.10.16.0/24

You can see they don't match. This means your Tiny will need to use a gateway to reach... its gateway!

If something is on the same network, the Tiny will talk directly to the wire and doesn't need any help. It uses ARP (Address Resolution Protocol) to get the MAC (Media Access Control) or hardware or Ethernet address of the device with that IP address and used the Ethernet to talk to it.

But, if it's not, it passes the packet to a gateway that appears in the routing table. In this case, there will only be one and it will be the default route. The trouble is, the default route isn't on the network specified by the netmask. So, the first thing to try is simple enough—change the netmask on both interfaces (Tiny and PC) to use a 255.255.0.0 netmask. This will use only the first 16 bits of the address for network and so the gateway address will match.

Of course the gateway has to exist for it to be useful, and in principle it should be working now, because they are on the same network but start by making this change to be sure it's not a side effect. If it is still not working, we can troubleshoot further.
Thank you for such an extensive reply, I will examine it deeply to gain knowledge about networking when I have a bit more time.

I set my submask to 255.255.0.0 on windows Ethernet 2 port as well as in the Arduino IDE for teensy.

C-like:
// SPDX-FileCopyrightText: (c) 2023 Shawn Silverman <shawn@pobox.com>
// SPDX-License-Identifier: MIT

// BroadcastChat is a simple chat application that broadcasts and
// receives text messages over UDP.
//
// This file is part of the QNEthernet library.

#include <QNEthernet.h>

using namespace qindesign::network;

// --------------------------------------------------------------------------
//  Configuration
// --------------------------------------------------------------------------

//constexpr uint32_t kDHCPTimeout = 10'000;  // 10 seconds

constexpr uint16_t kPort = 5190;  // Chat port

// --------------------------------------------------------------------------
//  Program State
// --------------------------------------------------------------------------

// UDP port.
EthernetUDP udp;

// --------------------------------------------------------------------------
//  Main Program
// --------------------------------------------------------------------------

// Forward declarations (not really needed in the Arduino environment)
static void printPrompt();
static void receivePacket();
static void sendLine();

// Program setup.
void setup() {
  Serial.begin(115200);
  while (!Serial && millis() < 4000) {
    // Wait for Serial
  }
  printf("Starting...\r\n");

  uint8_t mac[6];
  Ethernet.macAddress(mac);  // This is informative; it retrieves, not sets
  printf("MAC = %02x:%02x:%02x:%02x:%02x:%02x\r\n",
         mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);

  Ethernet.onLinkState([](bool state) {
    printf("[Ethernet] Link %s\r\n", state ? "ON" : "OFF");
  });

  printf("Starting Ethernet with DHCP...\r\n");
  if (!Ethernet.begin()) {
    printf("Failed to start Ethernet\r\n");
    return;
  }

  /*
  if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
    printf("Failed to get IP address from DHCP\r\n");
    return;
  }
*/
  //IPAddress ip =Ethernet.localIP();

  IPAddress ip = {10, 10, 16, 111};
  printf("    Local IP     = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {255, 255, 0, 0};                     //Ethernet.subnetMask();
  printf("    Subnet mask  = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {255, 255, 255, 255};                   //Ethernet.broadcastIP();
  printf("    Broadcast IP = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {10, 10, 10, 254};                      //Ethernet.gatewayIP();
  printf("    Gateway      = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = {10, 10, 10, 244};                      //Ethernet.dnsServerIP();
  printf("    DNS          = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);

  // Start UDP listening on the port
  udp.begin(kPort);

  printPrompt();
}

// Main program loop.
void loop() {
  receivePacket();
  sendLine();
}

// --------------------------------------------------------------------------
//  Internal Functions
// --------------------------------------------------------------------------

// Control character names.
static const String kCtrlNames[]{
  "NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
  "BS",  "HT",  "LF",  "VT",  "FF",  "CR",  "SO",  "SI",
  "DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
  "CAN", "EM",  "SUB", "ESC", "FS",  "GS",  "RS",  "US",
};

// Receives and prints chat packets.
static void receivePacket() {
  int size = udp.parsePacket();
  if (size < 0) {
    return;
  }

  // Get the packet data and remote address
  const uint8_t *data = udp.data();
  IPAddress ip = udp.remoteIP();

  printf("[%u.%u.%u.%u][%d] ", ip[0], ip[1], ip[2], ip[3], size);

  // Print each character
  for (int i = 0; i < size; i++) {
    uint8_t b = data[i];
    if (b < 0x20) {
      printf("<%s>", kCtrlNames[b].c_str());
    } else if (b < 0x7f) {
      putchar(data[i]);
    } else {
      printf("<%02xh>", data[i]);
    }
  }
  printf("\r\n");
}

// Tries to read a line from the console and returns whether
// a complete line was read. This is CR/CRLF/LF EOL-aware.
static bool readLine(String &line) {
  static bool inCR = false;  // Keeps track of CR state

  while (Serial.available() > 0) {
    int c;
    switch (c = Serial.read()) {
      case '\r':
        inCR = true;
        return true;

      case '\n':
        if (inCR) {
          // Ignore the LF
          inCR = false;
          break;
        }
        return true;

      default:
        if (c < 0) {
          return false;
        }
        inCR = false;
        line.append(static_cast<char>(c));
    }
  }

  return false;
}

// Prints the chat prompt.
static void printPrompt() {
  printf("chat> ");
  fflush(stdout);  // printf may be line-buffered, so ensure there's output
}

// Reads from the console and sends packets.
static void sendLine() {
  static String line;

  // Read from the console and send lines
  if (readLine(line)) {
    if (!udp.send(Ethernet.broadcastIP(), kPort,
                  reinterpret_cast<const uint8_t *>(line.c_str()),
                  line.length())) {
      printf("[Error sending]\r\n");
    }
    line = "";
    printPrompt();
  }
}
However, my problem still persists... Just a side question. Since I have two ethernet ports on my computer, should I change the default gateway addresses also? Does it impact the communication in any way? In the teensy example code, there also are lines dedicated to a Broadcast IP and DNS IP assignment. As I said, I did it manually, I set teensy Broadcast IP to 255.255.255.255 and the DNS IP same as the Ethernet port 1 DNS IP (found in cmd using ipconfig) Since my goal is to design internet independent network of just 2 devices (PC and teensy) I hope this does not make any impact, but since you have such knowledge about networking, can you provide any observations? Thank you again.
 

Attachments

Last edited:

Ya’akov

Joined Jan 27, 2019
8,483
OK, now it's time to install Wireshark on your PC.

Wireshark is a network sniffer. It will listen to and decide packets of all sorts on the various interfaces of your computer. Once you have it downloaded and installed, you will be able to start it listening to your Ethernet 2 interface.

This will show you what the PC is hearing and saying. The first expectation I have is that the PC is not listening on the port you intend. The exchange captured by Wireshark will show you whatever is happening and probably point to a solution.

While I would use something like nc (netcat) to do that here, it would be much more complicated to set that up in your environment since you'd need to add a computer that can run nc to the 10.10.16.x network. The Teensy can't do it.

It might be worth getting nc running on the PC anyway. You can try to talk to localhost, the is, the machine itself—but this can be misleading with network trouble because it doesn't tell you if the interface is listening to the outside world. It's still a very useful tool to have on hand.

[EDITED: s/Tiny/Teensy/g (I don't know why I kept saying that, it's not like I don't have Teensys here!]
 

Thread Starter

Vilius_Zalenas

Joined Jul 24, 2022
155
OK, now it's time to install Wireshark on your PC.

Wireshark is a network sniffer. It will listen to and decide packets of all sorts on the various interfaces of your computer. Once you have it downloaded and installed, you will be able to start it listening to your Ethernet 2 interface.

This will show you what the PC is hearing and saying. The first expectation I have is that the PC is not listening on the port you intend. The exchange captured by Wireshark will show you whatever is happening and probably point to a solution.

While I would use something like nc (netcat) to do that here, it would be much more complicated to set that up in your environment since you'd need to add a computer that can run nc to the 10.10.16.x network. The Teensy can't do it.

It might be worth getting nc running on the PC anyway. You can try to talk to localhost, the is, the machine itself—but this can be misleading with network trouble because it doesn't tell you if the interface is listening to the outside world. It's still a very useful tool to have on hand.

[EDITED: s/Tiny/Teensy/g (I don't know why I kept saying that, it's not like I don't have Teensys here!]
Alright, this was my first time using wireshark, so I dont really know what to expect but I noticed something strange. When I entered any letter to the teensy terminal, the line would pop up saying that IP starting 169. was the source. I recognized that IP for Ethernet port 2 before I have set a static IP for teensy. So basically, my teensy is still recognized as 169. something while I clearly have set the static IP for teensy to be 10.10.16.111. How can this be?
 

Attachments

Top