Call to arms | Teensy + WiFi = true

I had previously mentioned that the aerial should not be shrouded by the SD Card reader. I notice that the aerial is printed past the SD Reader hardware on the T4.1, but not past an SD card actually in the reader. I suggest some tests be carried out to confirm that having an SD Card in the reader slot does not degrade signal strength/data throughput.
Search on this subject:
Code:
SD card radiation levels and effect on nearby WIFI antenna reception

Apparently the SD card radiation is negligible due to the lower operating frequency of the SD card as compared to the 2.4GHz operating frequency of the WIFI device. Of course anybody with the correct equipment and software can test this...
 
actually damage the chip
The 'indicated' dBm was just FYI. Wholly different hardware assigning a value 'programmatically'. The indicated "RSSI" is from Receive so shouldn't affect hardware abuse or FCC stds. Just that the signals are indeed weak for both - but antenna on ESP variety seems more reliably "receptive".
Note: the SFun WiFi is on the PJRC side by side PCB so it is in free air.
 
The 'indicated' dBm was just FYI. Wholly different hardware assigning a value 'programmatically'. The indicated "RSSI" is from Receive so shouldn't affect hardware abuse or FCC stds. Just that the signals are indeed weak for both - but antenna on ESP variety seems more reliably "receptive".
Note: the SFun WiFi is on the PJRC side by side PCB so it is in free air.
Got it :)
 
Note: the SFun WiFi is on the PJRC side by side PCB so it is in free air.
That's not how the SFun board is intended to be used. It should be researched in it's intended mounting position, with and without SD Card inserted.
 
Hi All -

An update on the SparkFun Wireless Board ...

We've found a issue with how the Bluetooth connection was designed on our board, so a revision and retest is required. This will delay our efforts to launch the board 2-3 weeks.

FWIW:
Since a ready to use BLE library didn't exist, I used AL/Claude to create a library based on the Ardunio/ESP32 NIMBLE library - which I've been using recently. Not sure if this implementation actually works, but it did help locate our design error.

If you're interested - the library is here: https://github.com/gigapod/SparkFun_Teensy_Bluetooth

Right now, this is just for hardware testing. The actual Bluetooth library we support/use/port for the final board is still TBD.

-Kirk
 
The culprit in question:

1787853840178.png


That should be RTS, not CTS... Classic problem, but this time with flow control signals!

1787853915102.png


What this means is that no one will be able to use Bluetooth on the prototypes we sent out (unless you have exceptionally good soldering skills to cut and reconnect traces). Even if you disable flow control on the Teensy, the flow control signal on the radio is incorrect, so the radio just never sends anything over UART.

Sorry folks! These things happen, but at least WiFi is working!
 
The culprit in question:

View attachment 39749

That should be RTS, not CTS... Classic problem, but this time with flow control signals!

View attachment 39750

What this means is that no one will be able to use Bluetooth on the prototypes we sent out (unless you have exceptionally good soldering skills to cut and reconnect traces). Even if you disable flow control on the Teensy, the flow control signal on the radio is incorrect, so the radio just never sends anything over UART.

Sorry folks! These things happen, but at least WiFi is working!
Just glad you caught it now. That could really mess with the testing :D
 
I did some very delicate soldering on my board to rewire all the signals, but something simpler that might (heavy emphasis on might) work is to short these 2 pins with a solder blob:

1787855130340.png


Still fiddly, but would't require cutting and resoldering traces. This would hold the CTS pin on the radio low, so the radio is always allowed to transmit. You'd have to disable hardware flow control on the Teensy, since the rest of the wires are still wrong. I've not tested it myself, but I think it could work.
 
Hi All -

An update on the SparkFun Wireless Board ...

We've found a issue with how the Bluetooth connection was designed on our board, so a revision and retest is required. This will delay our efforts to launch the board 2-3 weeks.

FWIW:
Since a ready to use BLE library didn't exist, I used AL/Claude to create a library based on the Ardunio/ESP32 NIMBLE library - which I've been using recently. Not sure if this implementation actually works, but it did help locate our design error.

If you're interested - the library is here: https://github.com/gigapod/SparkFun_Teensy_Bluetooth

Right now, this is just for hardware testing. The actual Bluetooth library we support/use/port for the final board is still TBD.

-Kirk
This library looks like it might be fairly easy to integrate into the current driver using other BT drivers for reference...
 
What this means is that no one will be able to use Bluetooth on the prototypes we sent out (unless you have exceptionally good soldering skills to cut and reconnect traces). Even if you disable flow control on the Teensy, the flow control signal on the radio is incorrect, so the radio just never sends anything over UART.
Correct me if I'm wrong here, but...
Add inverter for CTS, since it's routing through XBAR
if CTS on the Teensy side was always intended to be routed through the XBAR, it should be trivial to swap it with RTS (both pins 33 and 36 are xbar-usable).
 
Ok guys. Going back and forth between projects so decided to give UDP a try on the Arduino Uno Q and see if I can get the T41 talking to the Q. Q is nice but...

Anyways seems like it works going to play more.
1787920589784.png
 
Just a quick update decided to give ArduinoJson lib a try between the T41 and the Q
1787923536657.png


Again it worked
Teensy Sketch

Code:
// SPDX-FileCopyrightText: (c) 2021-2026 Shawn Silverman <shawn@pobox.com>
// SPDX-License-Identifier: AGPL-3.0-or-later

// SNTPClient demonstrates a simple SNTP client.
// See: https://tools.ietf.org/html/rfc4330
//
// This file is part of the QNEthernet library.

// C++ includes
#include <cerrno>
#include <cstring>
#include <ctime>
#include <ArduinoJson.h>
#include <QNEthernet.h>

using namespace qindesign::network;

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

constexpr uint32_t kDHCPTimeout = 15000;  // 15 seconds

constexpr uint16_t kNTPPort = 3333;
constexpr uint16_t remotePort = 8888;
// --------------------------------------------------------------------------
//  Program State
// --------------------------------------------------------------------------

namespace {  // Internal linkage section

// UDP port.
EthernetUDP udp;

// Buffer.
uint8_t packetBuffer[254];

}  // namespace

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

const char* udpAddress = "192.168.1.205";

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

  printf("Starting Ethernet with DHCP...\r\n");
  if (!Ethernet.begin()) {
    printf("Failed to start Ethernet\r\n");
    return;
  }
  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]);
  printf("Waiting for local IP...\r\n");
  if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {
    printf("Failed to get IP address from DHCP\r\n");
    return;
  }

  IPAddress ip = Ethernet.localIP();
  printf("    Local IP    = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.subnetMask();
  printf("    Subnet mask = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.gatewayIP();
  printf("    Gateway     = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);
  ip = Ethernet.dnsServerIP();
  printf("    DNS         = %u.%u.%u.%u\r\n", ip[0], ip[1], ip[2], ip[3]);

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

  // Send an SNTP request

  std::memset(packetBuffer, 0, 254);

}

// Main program loop.
void loop() {
  // 1. RECEIVE & DESERIALIZE GENERIC MSGPACK PACKET
  int packetSize = udp.parsePacket();
  if (packetSize > 0) {
    // Read the binary packet into a local buffer
    uint8_t packetBuffer[packetSize];
    udp.read(packetBuffer, packetSize);

    // Create a dynamic document to parse arbitrary msgpack payloads
    JsonDocument doc;
    DeserializationError error = deserializeMsgPack(doc, packetBuffer, packetSize);

    if (!error) {
      Serial.println("Received MsgPack message successfully!");
      
      // Accessing generic data safely (checks if key exists, otherwise defaults)
      if (doc.containsKey("sensor")) {
        const char* sensorName = doc["sensor"];
        float val = doc["value"];
        Serial.printf("Sensor: %s, Value: %.2f\n", sensorName, val);
      } else {
        // Fallback: Print raw structure converted to JSON format for debugging
        serializeJson(doc, Serial);
        Serial.println();
      }
    } else {
      Serial.print("MsgPack deserialization failed: ");
      Serial.println(error.c_str());
    }
  }

  // 2. SERIALIZE & SEND GENERIC MSGPACK PACKET (Every 5 seconds)
  static unsigned long lastSend = 0;
  if (millis() - lastSend > 5000) {
    lastSend = millis();

    // Construct a generic data map
    JsonDocument doc;
    doc["device"] = "Arduino_Node_1";
    doc["uptime"] = millis() / 1000;
    
    // Nested Array example
    JsonArray data = doc["data"].to<JsonArray>();
    data.add(23.5); // temperature
    data.add(60);   // humidity

    // Measure binary layout footprint size
    size_t len = measureMsgPack(doc);
    uint8_t outputBuffer[len];

    // Export payload object directly to our byte array buffer
    serializeMsgPack(doc, outputBuffer, len);

    // Ship the binary packet out over UDP
    udp.beginPacket(udpAddress, remotePort);
    udp.write(outputBuffer, len);
    udp.endPacket();

    Serial.println("Generic MsgPack UDP packet sent.");
  }
}

Arduino Uno Q Code

Code:
#include <Arduino_RouterBridge.h>

#define ARDUINOJSON_USE_LONG_LONG 0
#include <ArduinoJson.h>

// UDP Configuration
BridgeUDP<4096> udp(Bridge);
const unsigned int localPort = 8888;       // Port Arduino listens on
const char* remoteIPStr      = "192.168.1.12"; // Remote target IP
const unsigned int remotePort = 3333;       // Remote target Port

void setup() {
  Serial.begin(115200);
 
  udp.begin(localPort);
  char msg[128];
  snprintf(msg, sizeof(msg), "Listening on UDP port %d", localPort);
  Serial.println(msg);
}

void loop() {
  // 1. RECEIVE & DESERIALIZE GENERIC MSGPACK PACKET
  int packetSize = udp.parsePacket();
  if (packetSize > 0) {
    // Read the binary packet into a local buffer
    uint8_t packetBuffer[packetSize];
    udp.read(packetBuffer, packetSize);

    // Create a dynamic document to parse arbitrary msgpack payloads
    JsonDocument doc;
    DeserializationError error = deserializeMsgPack(doc, packetBuffer, packetSize);

    if (!error) {
      Serial.println("Received MsgPack message successfully!");
      
      // Accessing generic data safely (checks if key exists, otherwise defaults)
      if (doc.containsKey("sensor")) {
        const char* sensorName = doc["sensor"];
        float val = doc["value"];
        char msg[128];
        snprintf(msg, sizeof(msg), "Sensor: %s, Value: %.2f", sensorName, val);
        Serial.println(msg);
      } else {
        // Fallback: Print raw structure converted to JSON format for debugging
        serializeJson(doc, Serial);
        Serial.println();
      }
    } else {
      Serial.print("MsgPack deserialization failed: ");
      Serial.println(error.c_str());
    }
  }

  // 2. SERIALIZE & SEND GENERIC MSGPACK PACKET (Every 5 seconds)
  static unsigned long lastSend = 0;
  if (millis() - lastSend > 5000) {
    lastSend = millis();

    // Construct a generic data map
    JsonDocument doc;
    doc["device"] = "Arduino_Node_1";
    doc["uptime"] = millis() / 1000;
    
    // Nested Array example
    JsonArray data = doc["data"].to<JsonArray>();
    data.add(23.5); // temperature
    data.add(60);   // humidity

    // Measure binary layout footprint size
    size_t len = measureMsgPack(doc);
    uint8_t outputBuffer[len];

    // Export payload object directly to our byte array buffer
    serializeMsgPack(doc, outputBuffer, len);

    // Ship the binary packet out over UDP
    udp.beginPacket(remoteIPStr, remotePort);
    udp.write(outputBuffer, len);
    udp.endPacket();

    Serial.println("Generic MsgPack UDP packet sent.");
  }
}
 
@mjs513 , I will have to try these out! I have not gotten as far as you have...
Been busy with other non-computer things like redoing the work that a painter we hired did to our exterior wood work...
But Ping and some of the other examples are working!

EDIT: I ran the above sketches... Had make some minor edits, like for some reason his IP addresses did not work 🤔 😉
 
Last edited:
Correct me if I'm wrong here, but...

if CTS on the Teensy side was always intended to be routed through the XBAR, it should be trivial to swap it with RTS (both pins 33 and 36 are xbar-usable).

It's not that I simply swapped the header pins, it's that I put the inverter on the wrong signal (and the wrong direction for that signal). So it's not just a simple pin number change in code, the wiring itself needs to change.
 
As I mentioned, above, was able to get Mike's two board communication working as well.

I may try to see how hard it is to convert a Q app, like weather forecast code to run on Teensy... On the Q it scrolls on their
Charlieplexed LED matrix. I have an external one around here some place, that I believe connects up using QWIIC connector...
With all of the playing lately with other Arduino boards, I have gotten sort of used to having them... Wish the Teensy had one.
I know I can order a shield from Sparkfun:
And add a third board to the mix...

Wish list, add one to your wifi board... Although I know it might confuse things.
But got sort of used to not having to solder anything.
 
I did some very delicate soldering on my board to rewire all the signals, but something simpler that might (heavy emphasis on might) work is to short these 2 pins with a solder blob:

View attachment 39751

Still fiddly, but would't require cutting and resoldering traces. This would hold the CTS pin on the radio low, so the radio is always allowed to transmit. You'd have to disable hardware flow control on the Teensy, since the rest of the wires are still wrong. I've not tested it myself, but I think it could work.
Disabled RTS/CTS flow control in "HCI_Transport.cpp lines 16 and 17 and shorting the two pins on the inverter indicated above:
Code:
    // attachRts()/attachCts() are only available on HardwareSerialIMXRT
    // (Teensy 4.x), not the generic HardwareSerial base class.
//    if (_rtsPin >= 0) serial.attachRts(_rtsPin);                //  changed WW 08-28-26
//    if (_ctsPin >= 0) serial.attachCts(_ctsPin);                //  changed WW 08-28-26
The output went from:
Code:
CYW43439 BLE Scan Example
[SFBLEDevice] init()
[SFBLEDevice]   localName:   SF-Scanner
[SFBLEDevice]   btRegOnPin:  28
[SFBLEDevice]   rtsPin:      36
[SFBLEDevice]   ctsPin:      33
[SFBLEDevice]   starting BLE_Controller...
[BLE_Controller] begin()
[BLE_Controller]   bringing up chip...
[CYW43439_BT] begin()
[CYW43439_BT]   1. holding BT_REG_ON low (reset)
[CYW43439_BT]   2. opening UART at init baud
[CYW43439_BT]   3. releasing BT_REG_ON (boot ROM)
[CYW43439_BT]   4. sending HCI Reset  <-------------------------- Failed here --------------------------------------
[CYW43439_BT]      waitCmdComplete timeout, opcode=0xC03, raw UART bytes received during wait=0
[CYW43439_BT]      HCI Reset failed, status=255
[BLE_Controller]   chip begin() failed
[SFBLEDevice]   BLE_Controller::begin() failed
BLE init failed – halting
To this:
Code:
CYW43439 BLE Scan Example
[SFBLEDevice] init()
[SFBLEDevice]   localName:   SF-Scanner
[SFBLEDevice]   btRegOnPin:  28
[SFBLEDevice]   rtsPin:      36
[SFBLEDevice]   ctsPin:      33
[SFBLEDevice]   starting BLE_Controller...
[BLE_Controller] begin()
[BLE_Controller]   bringing up chip...
[CYW43439_BT] begin()
[CYW43439_BT]   1. holding BT_REG_ON low (reset)
[CYW43439_BT]   2. opening UART at init baud
[CYW43439_BT]   3. releasing BT_REG_ON (boot ROM)
[CYW43439_BT]   4. sending HCI Reset
[CYW43439_BT]   5. downloading firmware patch  <-------------------------- Failed here --------------------------------------
[CYW43439_BT]      downloadFirmware() failed
[BLE_Controller]   chip begin() failed
[SFBLEDevice]   BLE_Controller::begin() failed
BLE init failed – halting
So we got through the HCI reset but not the Firmware Download:( I don't know yet if we can slow the baud rate to 1Yn or if it is fixed at 115200.
I will probably fiddle some more with it. Any more suggestions are very welcome...
 
Ok all been playing with UDP and did something that been wanting to do except this time had AI to help - found that if you feed AI some working examples it helps. Any ways created a msg pack for udp class that simplifies transferring and receiving data. Just finished testing it on the Teensy and Micromod ESP32
IMG_1754.jpeg


Sample output
1788008486585.png


If interested here are the sketches
 

Attachments

  • Teensy_Generic_UDP-260829a.zip
    3.3 KB · Views: 20
  • ESP32_udp__generic_test.zip
    3.8 KB · Views: 14
Last edited:
So we got through the HCI reset but not the Firmware Download:( I don't know yet if we can slow the baud rate to 1Yn or if it is fixed at 115200.
I will probably fiddle some more with it. Any more suggestions are very welcome...
@wwatson decided to give Google Gemini a try and see what it says based on your post of firmware update error see
 
Wondering if there is a sample that I can modify that for example:

You would send:
Code:
https://geocoding-api.open-meteo.com/v1/search?name=DisneyLand

and it returns:
Code:
{"results":[{"id":5343229,"name":"Disneyland","latitude":33.81207,"longitude":-117.91898,"elevation":42.0,"feature_code":"AMUS","country_code":"US","admin1_id":5332921,"admin2_id":5379524,"timezone":"America/Los_Angeles","country_id":6252001,"country":"United States","admin1":"California","admin2":"Orange"},{"id":6698160,"name":"Disneyland Resort Paris","latitude":48.86785,"longitude":2.77863,"elevation":121.0,"feature_code":"AMUS","country_code":"FR","admin1_id":3012874,"admin2_id":2975249,"admin3_id":6457363,"admin4_id":6443606,"timezone":"Europe/Paris","country_id":3017382,"country":"France","admin1":"Île-de-France Region","admin2":"Seine-et-Marne","admin3":"Arrondissement of Torcy","admin4":"Chessy"},{"id":12110875,"name":"Disneyland Paris Parc","latitude":48.87263,"longitude":2.77679,"elevation":122.0,"feature_code":"PRK","country_code":"FR","admin1_id":3012874,"admin2_id":2975249,"admin3_id":6457363,"admin4_id":6443606,"timezone":"Europe/Paris","country_id":3017382,"country":"France","admin1":"Île-de-France Region","admin2":"Seine-et-Marne","admin3":"Arrondissement of Torcy","admin4":"Chessy"}],"generationtime_ms":0.6489754}

Note: in the webbrowser it has a checkbox for pretty print:
Code:
{
  "results": [
    {
      "id": 5343229,
      "name": "Disneyland",
      "latitude": 33.81207,
      "longitude": -117.91898,
      "elevation": 42,
      "feature_code": "AMUS",
      "country_code": "US",
      "admin1_id": 5332921,
      "admin2_id": 5379524,
      "timezone": "America/Los_Angeles",
      "country_id": 6252001,
      "country": "United States",
      "admin1": "California",
      "admin2": "Orange"
    },
    {
      "id": 6698160,
      "name": "Disneyland Resort Paris",
      "latitude": 48.86785,
      "longitude": 2.77863,
      "elevation": 121,
      "feature_code": "AMUS",
      "country_code": "FR",
      "admin1_id": 3012874,
      "admin2_id": 2975249,
      "admin3_id": 6457363,
      "admin4_id": 6443606,
      "timezone": "Europe/Paris",
      "country_id": 3017382,
      "country": "France",
      "admin1": "Île-de-France Region",
      "admin2": "Seine-et-Marne",
      "admin3": "Arrondissement of Torcy",
      "admin4": "Chessy"
    },
    {
      "id": 12110875,
      "name": "Disneyland Paris Parc",
      "latitude": 48.87263,
      "longitude": 2.77679,
      "elevation": 122,
      "feature_code": "PRK",
      "country_code": "FR",
      "admin1_id": 3012874,
      "admin2_id": 2975249,
      "admin3_id": 6457363,
      "admin4_id": 6443606,
      "timezone": "Europe/Paris",
      "country_id": 3017382,
      "country": "France",
      "admin1": "Île-de-France Region",
      "admin2": "Seine-et-Marne",
      "admin3": "Arrondissement of Torcy",
      "admin4": "Chessy"
    }
  ],
  "generationtime_ms": 0.6489754
}

This is first part of Arduino Uno Q brick to get the weather where it then extracts from
this stream. it then extracts the latitued and longitude of the first entry and
passes that to their get the forecast, which will look like a similar query:

like:
Code:
https://api.open-meteo.com/v1/forecast?latitude=33.81207&longitude=-117.91898&daily=temperature_2m_max,temperature_2m_min,precipitation_sum&hourly=temperature_2m&current=temperature_2m,wind_speed_10m,wind_direction_10m,weather_code&timezone=America%2FLos_Angeles&forecast_days=1&wind_speed_unit=mph&temperature_unit=fahrenheit&precipitation_unit=inch
and pretty output:
Code:
{
  "latitude": 33.81355,
  "longitude": -117.93398,
  "generationtime_ms": 0.140905380249023,
  "utc_offset_seconds": -25200,
  "timezone": "America/Los_Angeles",
  "timezone_abbreviation": "GMT-7",
  "elevation": 43,
  "current_units": {
    "time": "iso8601",
    "interval": "seconds",
    "temperature_2m": "°F",
    "wind_speed_10m": "mp/h",
    "wind_direction_10m": "°",
    "weather_code": "wmo code"
  },
  "current": {
    "time": "2026-08-29T14:15",
    "interval": 900,
    "temperature_2m": 95.8,
    "wind_speed_10m": 9.6,
    "wind_direction_10m": 237,
    "weather_code": 0
  },
  "hourly_units": {
    "time": "iso8601",
    "temperature_2m": "°F"
  },
  "hourly": {
    "time": [
      "2026-08-29T00:00",
      "2026-08-29T01:00",
      "2026-08-29T02:00",
      "2026-08-29T03:00",
      "2026-08-29T04:00",
      "2026-08-29T05:00",
      "2026-08-29T06:00",
      "2026-08-29T07:00",
      "2026-08-29T08:00",
      "2026-08-29T09:00",
      "2026-08-29T10:00",
      "2026-08-29T11:00",
      "2026-08-29T12:00",
      "2026-08-29T13:00",
      "2026-08-29T14:00",
      "2026-08-29T15:00",
      "2026-08-29T16:00",
      "2026-08-29T17:00",
      "2026-08-29T18:00",
      "2026-08-29T19:00",
      "2026-08-29T20:00",
      "2026-08-29T21:00",
      "2026-08-29T22:00",
      "2026-08-29T23:00"
    ],
    "temperature_2m": [77.7, 77, 75.8, 76.1, 75.4, 77.1, 75.2, 76.3, 77.7, 81.3, 85.2, 86.9, 91.7, 94.8, 95.6, 94.6, 90, 88, 87.9, 84.4, 81.4, 79.6, 78.1, 76.4]
  },
  "daily_units": {
    "time": "iso8601",
    "temperature_2m_max": "°F",
    "temperature_2m_min": "°F",
    "precipitation_sum": "inch"
  },
  "daily": {
    "time": [
      "2026-08-29"
    ],
    "temperature_2m_max": [95.6],
    "temperature_2m_min": [75.2],
    "precipitation_sum": [0]
  }
}

It probably won't be the query I do, like probably not hourly temp, but maybe...
You can play with it at:
 
Here we go again:
Code:
CYW43439 BLE Scan Example
[SFBLEDevice] init()
[SFBLEDevice]   localName:   SF-Scanner
[SFBLEDevice]   btRegOnPin:  28
[SFBLEDevice]   rtsPin:      36
[SFBLEDevice]   ctsPin:      33
[SFBLEDevice]   starting BLE_Controller...
[BLE_Controller] begin()
[BLE_Controller]   bringing up chip...                                        
[CYW43439_BT] begin()                                                          
[CYW43439_BT]   1. holding BT_REG_ON low (reset)           
[CYW43439_BT]   2. opening UART at init baud                     
[CYW43439_BT]   3. releasing BT_REG_ON (boot ROM)      
[CYW43439_BT]   4. sending HCI Reset
[CYW43439_BT]   5. downloading firmware patch
[CYW43439_BT]   6. re-initialising UART after firmware launch
[CYW43439_BT]   7. escalating baud rate
[CYW43439_BT]   8. setting event mask
[CYW43439_BT]   9. enabling LE host support
[CYW43439_BT]   10. setting LE event mask
[CYW43439_BT]   begin complete, chip ready
[BLE_Controller]   chip up
[BLE_Controller]   requesting BD address...
[BLE_Controller]   begin complete
[SFBLEDevice]   init complete
--- Starting 5-second scan ---
  Addr: 24:DC:C3:ED:62:DE  RSSI: -64  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -64  [NON-CONN]
  Addr: 62:5B:DC:9A:E4:AA  RSSI: -93  [CONN]
  Addr: 62:5B:DC:9A:E4:AA  RSSI: -93  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -57  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -59  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -60  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -60  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -61  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -61  [NON-CONN]
  Addr: 62:5B:DC:9A:E4:AA  RSSI: -92  [CONN]
  Addr: 62:5B:DC:9A:E4:AA  RSSI: -94  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -64  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -64  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -59  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -59  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -57  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -59  [NON-CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -61  Name: Mysa  Svc: 4D790000-7361-5769-4669-436F6E666967  [CONN]
  Addr: 24:DC:C3:ED:62:DE  RSSI: -61  [NON-CONN]
  Addr: 62:5B:DC:9A:E4:AA  RSSI: -95  [CONN]
@mjs513 - Thanks again for the Gemini blurb. That lead me to:
Code:
4. Incorrect Patch File Variant
The CYW43439 requires a specific .hcd firmware matching its silicon revision (typically CYW43439A0.hcd or 43439A0.hcd).

Passing a patch designed for CYW43438, CYW43455, or an incompatible revision will cause the bootloader to reject the download command or return a nonzero status byte in the command complete event.
Which I started checking in the download firmware function in "CYW43439_BT.cpp":
Code:
// ---------------------------------------------------------------------------
// Download the bundled firmware patch to the CYW43439 via HCI UART.
//
// The cyw43-driver firmware blob (cyw43_btfw_43439.h) starts with a
// null-terminated version string:
//   byte[0]         : version string length (including the null terminator)
//   byte[1..vlen-1] : version string (printable ASCII)
//   byte[vlen]      : 0x00  (null terminator, already counted in byte[0])
//
// After that preamble the remainder is a stream of Broadcom .hcd records:
//   [ocf_lo, 0xFC, param_len, param_bytes...]  ...
// ending with HCI_CMD_VSC_LAUNCH_RAM (OCF = 0x4E).
bool CYW43439_BT::downloadFirmware(const uint8_t *fw, size_t len) {
    // Send DOWNLOAD_MINIDRIVER to switch chip into patch-receive mode.
    if (!_hci.sendCommand(HCI_CMD_VSC_DOWNLOAD_MINIDRIVER)) return false;
    uint8_t st = waitCmdComplete(HCI_CMD_VSC_DOWNLOAD_MINIDRIVER);
    if (st != HCI_STATUS_SUCCESS) return false;
/*
    // Skip version-string preamble. *********** THIS DOES NOT MATCH THE ORIGINAL PATCH FILE FORMAT ***************
    // byte[0] = total bytes of the version field (string chars + null terminator).
    size_t pos = 0;
    if (len >= 2 && fw[0] > 0 && fw[0] < 128) {
        size_t vlen = (size_t)fw[0];
        if (1u + vlen <= len) {
            pos = 1u + vlen;
        }
    }
*/
// Different patch file is now being used. See above ^^^^ include files.
    size_t pos = 0;
    // Replay HCI vendor command records: [ocf_lo, 0xFC, param_len, data...]
    while (pos + 3 <= len) {
        uint16_t opcode = (uint16_t)fw[pos] | ((uint16_t)fw[pos + 1] << 8);
        uint8_t  plen   = fw[pos + 2];
        pos += 3;
        if (pos + plen > len) {
        return false;
        }
        // Wait for any prior command to finish before sending next record.
        if (!_hci.sendCommand(opcode, &fw[pos], plen)) {
            uint32_t t = millis();
            while (!_hci.isIdle() && (millis() - t) < 2000) {
                _hci.poll();
                delayMicroseconds(100);
            }
            if (!_hci.sendCommand(opcode, &fw[pos], plen)) return false;
        }

        uint32_t t = millis();
        while (!_hci.isIdle() && (millis() - t) < 2000) {
            _hci.poll();
            delayMicroseconds(100);
        }

        pos += plen;

        // LAUNCH_RAM is the final record; chip resets after receiving it.
        if (opcode == HCI_CMD_VSC_LAUNCH_RAM) break;
    }
    return true;
}
The original patch file did not have the complete opcode for downloading the firmware. It was assuming that the lo byte (0xfc) was being handled in the download function. The download function needed the full 16bit opcode (0xFC4C "HCI_CMD_VSC_WRITE_RAM"). So I found an almost compatible patch file that did not have the header portion of the array but did have the proper opcode (0xFC4C) at the beginning of each record.
I just had to comment out the portion of the download function that processed and skipped over the header.
Here is a dump of both patch files:
Code:
#include "cyw43_btfw_1yn.h" The working patch file.
-------------------------------------------------------------
20000370      4c fc 14 00 e0 21 00 42 52 43 4d 63 66 67 53 00   L....!.BRCMcfgS.
20000380      00 00 00 32 00 00 00 4c fc 14 10 e0 21 00 01 01   ...2...L....!...
20000390      04 18 92 00 00 00 03 06 ac 1f 2a 0a 43 43 4c fc   ..........*.CCL.
200003a0      14 20 e0 21 00 00 01 1c 00 f0 21 00 00 00 00 00   . .!......!.....
200003b0      00 00 00 00 00 4c fc 14 30 e0 21 00 00 00 00 00   .....L..0.!.....
200003c0      00 00 00 00 00 00 00 00 00 00 00 fe 4c fc 06 40   ............L..@
200003d0      e0 21 00 00 00 4c fc 14 00 f0 21 00 42 52 43 4d   .!...L....!.BRCM
200003e0      63 66 67 44 00 00 00 00 38 09 00 00 4c fc 14 10   cfgD....8...L...
200003f0      f0 21 00 03 03 15 55 41 52 54 20 34 33 34 33 41   .!....UART 4343A
20000400      32 20 77 4c fc 14 20 f0 21 00 6c 62 67 61 5f 42   2 wL.. .!.lbga_B
20000410      55 00 16 03 02 00 00 02 01 30 4c fc 14 30 f0 21   U........0L..0.!
20000420      00 08 01 32 00 01 00 00 00 01 00 00 00 00 00 32   ...2...........2
20000430      00 4c fc 14 40 f0 21 00 ff 0f 00 00 62 08 00 00   .L..@.!.....b...
20000440      70 00 64 00 80 00 00 00 4c fc 14 50 f0 21 00 80   p.d.....L..P.!..
20000450      00 00 00 ac 00 32 00 ff ff ff 01 00 00 2f 00 4c   .....2......./.L
20000460      fc 14 60 f0 21 00 04 03 0c 20 01 20 00 0f 14 1a   ..`.!.... . ....
pos = 77

#include "cyw43_btfw_43439.h" The incompatible patch file.
-------------------------------------------------------------
20000370      4a 43 59 57 34 33 34 33 41 32 5f 30 30 31 2e 30   JCYW4343A2_001.0
20000380      30 33 2e 30 31 36 2e 30 30 36 35 2e 30 30 30 30   03.016.0065.0000
20000390      5f 47 65 6e 65 72 69 63 5f 53 44 49 4f 5f 33 37   _Generic_SDIO_37
200003a0      4d 48 7a 5f 77 6c 62 67 61 5f 42 55 5f 52 50 49   MHz_wlbga_BU_RPI
200003b0      5f 64 6c 5f 73 69 67 6e 65 64 00 23 02 00 00 04   _dl_signed.#....
200003c0      00 21 42 e0 00 00 42 52 43 4d 63 66 67 53 00 00   .!B...BRCMcfgS..
200003d0      00 00 32 00 00 00 01 01 04 18 92 00 00 00 03 06   ..2.............
200003e0      ac 1f 12 a2 43 43 00 01 1c 00 f0 21 00 00 00 00   ....CC.....!....
200003f0      00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00   ................
20000400      00 00 00 00 00 fe 00 00 fb f0 00 00 42 52 43 4d   ............BRCM
20000410      63 66 67 44 00 00 00 00 30 0c 00 00 03 03 18 53   cfgD....0......S
20000420      44 49 4f 20 33 37 5f 34 4d 20 77 6c 62 67 61 5f   DIO 37_4M wlbga_
20000430      42 55 20 52 50 49 00 16 03 02 00 00 02 01 90 01   BU RPI..........
20000440      08 01 32 00 01 00 00 00 01 00 00 00 00 00 32 00   ..2...........2.
20000450      ff 0f 00 00 62 08 00 00 70 00 64 00 80 00 00 00   ....b...p.d.....
20000460      80 00 00 00 ac 00 32 00 ff ff ff 01 00 00 2f 00   ......2......./.
pos = 75
You can see that the top patch file dump is showing 0x4CFC LE for each record. The bottom dump was the original patch file. This was using "BLE_Scan.ino". Have not tried the other examples. I will fork the Sparkfun library and update it. It was a lot easier this time after going through the incompatible Firmware, CLM and NVRAM files for CYW43439 WIFI 😌

EDIT: Looks like there is more work to do. Th other three examples are not working. They all fail at step 4 which means the CYW43439 is not getting or responding to the reset command. I hope that is somehow due to the CTS signal issue...
 
Last edited:
It probably won't be the query I do, like probably not hourly temp, but maybe...
You can play with it at:
Actually fairly simple where you don't even need to make use their brick:
Python:
import socket
import threading
import time
import msgpack

# -----------------------------------------------------------------
# Core Infrastructure Setup & Reliable Class
# -----------------------------------------------------------------
udp_connections = {}
connection_counter = 0
connection_lock = threading.Lock()
incoming_payloads = {}

PACKET_MSG = 1
PACKET_ACK = 2

class ReliableMsgPackConnection:
    """Thread-safe Reliable MessagePack UDP wrapper for Linux Side"""
    def __init__(self, connection_id, host, port):
        self.connection_id = connection_id
        self.host = host
        self.port = port
        
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((host, port))
        
        self.sequence_counter = 0
        self.running = True
        
        self.ack_event = threading.Event()
        self.expected_ack_id = None
        
        self.receive_thread = threading.Thread(target=self._receive_loop, daemon=True)
        self.receive_thread.start()
        print(f"Reliable Node initialized. Listening locally on {host}:{port}")

    def _receive_loop(self):
        while self.running:
            try:
                data, addr = self.socket.recvfrom(4096)
                if not data:
                    continue
                try:
                    envelope = msgpack.unpackb(data, strict_map_key=False)
                except Exception as e:
                    print(f"[RECV-ERR] Malformed packet from {addr}: {e}")
                    continue

                packet_type = int(envelope.get("_type", 0))
                msg_id = int(envelope.get("_id", 0))

                if packet_type == PACKET_MSG:
                    self._send_raw_ack(addr, msg_id)
                    payload = envelope.get("payload")
                    with connection_lock:
                        if self.connection_id not in incoming_payloads:
                            incoming_payloads[self.connection_id] = []
                        incoming_payloads[self.connection_id].append((payload, addr))
                        
                elif packet_type == PACKET_ACK:
                    if self.expected_ack_id is not None and msg_id == self.expected_ack_id:
                        self.ack_event.set()
            except Exception as e:
                if self.running:
                    print(f"Socket exception: {e}")
                break

    def _send_raw_ack(self, target_addr, target_id):
        ack_envelope = {"_type": PACKET_ACK, "_id": target_id}
        try:
            self.socket.sendto(msgpack.packb(ack_envelope, use_bin_type=True), target_addr)
        except Exception as e:
            print(f"Failed to transmit ACK: {e}")

    def send_reliable(self, target_host, target_port, payload, timeout=0.15, max_retries=4):
        with connection_lock:
            self.sequence_counter += 1
            current_id = self.sequence_counter

        envelope = {"_type": PACKET_MSG, "_id": current_id, "payload": payload}
        binary_packet = msgpack.packb(envelope, use_bin_type=True)
        
        for attempt in range(max_retries + 1):
            if attempt > 0:
                print(f"[ACK-LOG] Retry {attempt}/{max_retries} for Msg ID {current_id}...")
            self.ack_event.clear()
            self.expected_ack_id = current_id
            
            try:
                self.socket.sendto(binary_packet, (target_host, target_port))
                if self.ack_event.wait(timeout):
                    self.expected_ack_id = None
                    return True
            except Exception as e:
                print(f"Transmission write failure: {e}")
                
        self.expected_ack_id = None
        print(f"[ACK-WARN] Message ID {current_id} dropped permanently. Node failed to respond.")
        return False

# -----------------------------------------------------------------
# RPC Bridge Application Helpers
# -----------------------------------------------------------------
def reliable_connect(hostname: str, port: int) -> int:
    global connection_counter
    with connection_lock:
        connection_counter += 1
        conn_id = connection_counter
        conn = ReliableMsgPackConnection(conn_id, hostname, port)
        udp_connections[conn_id] = conn
        incoming_payloads[conn_id] = []
        return conn_id

def reliable_write(connection_id: int, target_host: str, target_port: int, payload) -> bool:
    conn = udp_connections.get(connection_id)
    if not conn:
        return False
    return conn.send_reliable(target_host, target_port, payload)

def reliable_read(connection_id: int):
    with connection_lock:
        queue = incoming_payloads.get(connection_id)
        if not queue:
            return None
        return queue.pop(0) if queue else None

# -----------------------------------------------------------------
# Main Operational Implementation (Teensy Equivalent)
# -----------------------------------------------------------------

import socket
import threading
import time
import msgpack

# -----------------------------------------------------------------
# Core Infrastructure Setup & Reliable Class
# -----------------------------------------------------------------
udp_connections = {}
connection_counter = 0
connection_lock = threading.Lock()
incoming_payloads = {}

PACKET_MSG = 1
PACKET_ACK = 2

class ReliableMsgPackConnection:
    """Thread-safe Reliable MessagePack UDP wrapper for Linux Side"""
    def __init__(self, connection_id, host, port):
        self.connection_id = connection_id
        self.host = host
        self.port = port
        
        self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind((host, port))
        
        self.sequence_counter = 0
        self.running = True
        
        self.ack_event = threading.Event()
        self.expected_ack_id = None
        
        self.receive_thread = threading.Thread(target=self._receive_loop, daemon=True)
        self.receive_thread.start()
        print(f"Reliable Node initialized. Listening locally on {host}:{port}")

    def _receive_loop(self):
        while self.running:
            try:
                data, addr = self.socket.recvfrom(4096)
                if not data:
                    continue
                try:
                    envelope = msgpack.unpackb(data, strict_map_key=False)
                except Exception as e:
                    print(f"[RECV-ERR] Malformed packet from {addr}: {e}")
                    continue

                packet_type = int(envelope.get("_type", 0))
                msg_id = int(envelope.get("_id", 0))

                if packet_type == PACKET_MSG:
                    self._send_raw_ack(addr, msg_id)
                    payload = envelope.get("payload")
                    with connection_lock:
                        if self.connection_id not in incoming_payloads:
                            incoming_payloads[self.connection_id] = []
                        incoming_payloads[self.connection_id].append((payload, addr))
                        
                elif packet_type == PACKET_ACK:
                    if self.expected_ack_id is not None and msg_id == self.expected_ack_id:
                        self.ack_event.set()
            except Exception as e:
                if self.running:
                    print(f"Socket exception: {e}")
                break

    def _send_raw_ack(self, target_addr, target_id):
        ack_envelope = {"_type": PACKET_ACK, "_id": target_id}
        try:
            self.socket.sendto(msgpack.packb(ack_envelope, use_bin_type=True), target_addr)
        except Exception as e:
            print(f"Failed to transmit ACK: {e}")

    def send_reliable(self, target_host, target_port, payload, timeout=0.15, max_retries=4):
        with connection_lock:
            self.sequence_counter += 1
            current_id = self.sequence_counter

        envelope = {"_type": PACKET_MSG, "_id": current_id, "payload": payload}
        binary_packet = msgpack.packb(envelope, use_bin_type=True)
        
        for attempt in range(max_retries + 1):
            if attempt > 0:
                print(f"[ACK-LOG] Retry {attempt}/{max_retries} for Msg ID {current_id}...")
            self.ack_event.clear()
            self.expected_ack_id = current_id
            
            try:
                self.socket.sendto(binary_packet, (target_host, target_port))
                if self.ack_event.wait(timeout):
                    self.expected_ack_id = None
                    return True
            except Exception as e:
                print(f"Transmission write failure: {e}")
                
        self.expected_ack_id = None
        print(f"[ACK-WARN] Message ID {current_id} dropped permanently. Node failed to respond.")
        return False

# -----------------------------------------------------------------
# RPC Bridge Application Helpers
# -----------------------------------------------------------------
def reliable_connect(hostname: str, port: int) -> int:
    global connection_counter
    with connection_lock:
        connection_counter += 1
        conn_id = connection_counter
        conn = ReliableMsgPackConnection(conn_id, hostname, port)
        udp_connections[conn_id] = conn
        incoming_payloads[conn_id] = []
        return conn_id

def reliable_write(connection_id: int, target_host: str, target_port: int, payload) -> bool:
    conn = udp_connections.get(connection_id)
    if not conn:
        return False
    return conn.send_reliable(target_host, target_port, payload)

def reliable_read(connection_id: int):
    with connection_lock:
        queue = incoming_payloads.get(connection_id)
        if not queue:
            return None
        return queue.pop(0) if queue else None

# -----------------------------------------------------------------
# Main Operational Implementation (Teensy Equivalent)
# -----------------------------------------------------------------

# Network Rules matching the exact logic from your script
LOCAL_PORT = 3333
ESP32_PORT = 8888
ESP32_IP   = "192.168.1.221"

if __name__ == "__main__":
    # Setup / Initialization phase
    print("Starting Linux Node...")
    reliable_node = reliable_connect("0.0.0.0", LOCAL_PORT)
    print(f"Linux Online. Listening on Port: {LOCAL_PORT}")

    last_tx_time = time.time()
    current_step = 0

    # The Loop Execution phase
    while True:
        # ==========================================
        # PHASE 1: RECEIVE DATA FROM ESP32
        # ==========================================
        incoming_data = reliable_read(reliable_node)
        if incoming_data:
            incoming_doc, sender = incoming_data
            print(f"\n[LINUX RECV] Incoming packet from ESP32 ({sender}) recognized!")
            
            # Read variant content safely matching your C++ keys
            msg_type = incoming_doc.get("msg_type", "UNKNOWN")
            payload = incoming_doc.get("payload")

            if msg_type == "STRUCT_DATA":
                print(f"  > Type: Struct\n  > Node ID: {payload.get('id')}, Battery: {payload.get('bat')}V, Active: {'YES' if payload.get('act') else 'NO'}")
            elif msg_type == "ARRAY_DATA":
                print(f"  > Type: Array ({len(payload)} elements)\n  > Values: {' '.join(str(v) for v in payload)}")
            elif msg_type == "GENERIC_DATA":
                print(f"  > Type: Generic Primitive\n  > Value: {payload}")

        # ==========================================
        # PHASE 2: TRANSMIT CORRESPONDING DESIGNS BACK
        # ==========================================
        if time.time() - last_tx_time > 4.0:
            last_tx_time = time.time()
            
            doc = {}
            
            if current_step == 0:
                print("\n[LINUX TX] Distributing state struct to ESP32...")
                doc["msg_type"] = "STRUCT_DATA"
                doc["payload"] = {"id": 999, "bat": 3.82, "act": False}
                
                ok = reliable_write(reliable_node, ESP32_IP, ESP32_PORT, doc)
                print("  >> ESP32 confirmed structural receipt." if ok else "  >> Timeout error.")
                
            elif current_step == 1:
                print("\n[LINUX TX] Distributing a float Array to ESP32...")
                doc["msg_type"] = "ARRAY_DATA"
                doc["payload"] = [12.3, 45.6, 78.9]
                
                ok = reliable_write(reliable_node, ESP32_IP, ESP32_PORT, doc)
                print("  >> ESP32 confirmed array receipt." if ok else "  >> Timeout error.")
                
            elif current_step == 2:
                print("\n[LINUX TX] Distributing basic string diagnostic to ESP32...")
                doc["msg_type"] = "GENERIC_DATA"
                doc["payload"] = "TEENSY_CORE_CRITICAL_OK"
                
                ok = reliable_write(reliable_node, ESP32_IP, ESP32_PORT, doc)
                print("  >> ESP32 confirmed generic receipt." if ok else "  >> Timeout error.")

            current_step = (current_step + 1) % 3

        # Tiny sleep interval to ensure the CPU isn't spinning at 100% load
        time.sleep(0.01)
And looking at the link you posted if click on the python link you can get all the param setup

Probably will play a bit more with this tomorrow.
 
EDIT: Looks like there is more work to do. Th other three examples are not working. They all fail at step 4 which means the CYW43439 is not getting or responding to the reset command. I hope that is somehow due to the CTS signal issue...
Glad that blurb helped and it can very well be the flow control issue.
 
Back
Top