Adding Bluetooth and WiFi to a Teensy 4.1


Well-known member
Thinking of adding WiFi and Bluetooth to my Teensy 4.1 project.

For the WiFi I was thinking of an ESP32C3

Any ideas for bluetooth? A combined Bluetooth and Wifi would be great.... I'll have a Google
A few years ago I built a clock around an ESP32. I wanted WiFi to get the time using NTP and Bluetooth to change clock parameters. I discovered that the ESP32 could only do one or the other, not both simultaneously. Since I only needed WiFi for NTP I decided to primarily use Bluetooth. When it was time to sync the clock I shutdown Bluetooth if there wasn't an active connection and then enabled WiFi. I shutdown WiFi once the time was synchronized and started Bluetooth back up.

I don't know if the hardware/firmware has been changed in the ESP32-C3 to allow simultaneous Bluetooth and WiFi, but it's something to watch for.
Ha... I didn't even notice that had both Bluetooth and WiFi!

Just as a side question for the same project.... I am trying to find an SMD mini piezo. What spec does that need to be for direct drive off the Teensy 3.3v pin?

I found this:
3v, 4Khz, 5mA

This one is 3.6v. 4kHz, 3mA

Does it matter what the voltage range is? I have some through hole ones here that are 5v rated and work fine
Am I missing something with regards to the Seed XIAO ESP32C3 in post 1...

I see Miso, Mosi and SCK. Should there not be a CS/SS as well?

I also see it has RX/TX Maybe the best way to use this is just throw serial data at it and ignore the SPI comms?



  • pin_map-2.png
    127.2 KB · Views: 8
I also see it has RX/TX Maybe the best way to use this is just throw serial data at it and ignore the SPI comms?
Serial Comms, that's the way I would go.
Using Serial comms. gives you buffered communications, mind you that can be problematic if the two systems get out of synch.

Below is some code that I use to get data from an includes code to get back into synch when it goes out.

I have multiple ESP32C3's battery powered that go to sleep and wake up once a minute, gather Temp and Humidity and send it using ESP_Now to a central ESP32C3. This then sends the data to a Teensy over serial link. Code below if it helps...I think the getting back into synch code is particularly useful.
Each data packet from the Central ESP32 to the Teensy has a data identifier, if the first byte(s) received by the Teensy do not match this identifier then the data MUST be out of synch.

Here's the code, probably not very elegant, just something I threw together to collect data from various rooms around the house to investigate heating efficiency.

    T4.1 Take readings from UART (from ESP32C3) in text and save to SD card and show on screen.
    Modified to detect SD Card present, or NOT.

    SoftwareId: S.0004.0004.002
    SoftwareId: S.0004.0004.003 Added MTP support
#define softwareId "S.0004.0004.003 - Added MTP support"

#include <SD.h>
#include <timelib.h>

#include <MTP_Teensy.h>

#define numTargets 4

#define CS_SD BUILTIN_SDCARD  // Works on T_3.6 and T_4.1

#define ledPin 13

const uint32_t identifier = 57767959;

union identifierUnion {
    uint32_t Identifier;
    char     identTxt[sizeof(uint32_t)];

identifierUnion identMatch;

struct msgType {
    uint32_t ident;
    uint8_t  id;
    uint16_t temp;
    uint16_t humidity;

struct uniMsgType {
    union {
        msgType d;
        char    txt[sizeof(msgType)];

uniMsgType recvMsg;

const uint8_t maxArr = numTargets;

typedef struct msgArrType {
    msgType data[maxArr];
    bool    gotData[maxArr];
    uint8_t hour;
    uint8_t min;
} msgArrType;

msgArrType recvMsgArr;
File dataFile;

String GenerateDateStr(char c) {
    String s = "";

    int n = day();
    if (n < 10) s.concat( "0");
    n = month();
    if (n < 10) s.concat("0");
    n = year();
    if (n < 10) s.concat("0");
    if (c != 0x0) s.concat(c);
    return s;

String GenerateTimeStr(bool andSeconds, char c ) {
    String s = "";

    int n = hour();
    if (n < 10) s.concat("0");
    n = minute();
    if (n < 10) s.concat("0");
    if (andSeconds) {
        n = second();
        if (n < 10) s.concat("0");
    if (c != 0x0) s.concat(c);
    return s;

time_t getTeensy3Time(){
    return Teensy3Clock.get();

void send(const msgType* msg)
    Serial1.write((const char*)msg, sizeof(msgType));  // 2 bytes.

bool receive(uniMsgType* msg)
    return (Serial1.readBytes((char*)msg, sizeof(uniMsgType)) == sizeof(uniMsgType));

//String t    = "";
bool   sdSetup = true;

void setup()

    identMatch.Identifier = identifier;


    while (!Serial && millis() < 5000);
    Serial.print("SoftwareId: ");    Serial.println(softwareId);
    Serial.print("Initializing SD card...");

    pinMode(ledPin, OUTPUT);
    // see if the card is present and can be initialized:
    if (SD.begin(CS_SD)) {
        MTP.addFilesystem(SD, "SD Card");
        Serial.println("card initialized.");
    else {
        Serial.println("Card failed, or not present");
        sdSetup = false;
        // No SD card, so don't do anything more - stay stuck here

    if (timeStatus() != timeSet) {
        Serial.println("Unable to sync with the RTC");
    } else Serial.println("RTC has set the system time");

    // mandatory to begin the MTP session.


void PrintDec(uint8_t n) {
    if (n < 10) Serial.print("0");

void PrintN(uint16_t n) {
    uint16_t q;

    Serial.print(n / 100);
    q = n % 100;

void PrintNN(uint16_t n) {
    uint16_t q;

    dataFile.print(n / 100);
    q = n % 100;
    if (q < 10) dataFile.print("0");

void PrintData() {

    String where;
    uint8_t n;
    uint8_t p;

    n = 0;

    Serial.print(" ");

    while (n < maxArr) {
        p =[n].id - 1;
        if (recvMsgArr.gotData[n]) {
            switch (p) {
            case 0:
                where = "GBrm:";
            case 1:
                where = "Lnge:";
            case 2:
                where = "BBrm:";
            case 3:
                where = "CBrm:";
  [n].temp = 0;
            Serial.print(" ");
            PrintN([n].humidity);[n].humidity = 0;
            Serial.print("   ");
        else Serial.print("-----------------  ");
        recvMsgArr.gotData[n] = false;
        n = n + 1;

String tim;
String date;
uint8_t lastMin = 99;
uint8_t currMin = 98;
uint8_t p;
// Add the main program code into the continuous loop() function
void loop()
    MTP.loop();  //This is mandatory to be placed in the loop code.
    while (Serial1.available() >= (int)sizeof(msgType)) {
        digitalWrite(ledPin, HIGH);
        if (receive(&recvMsg)) {
            if (recvMsg.d.ident == identifier) {
                currMin = minute();
                if (currMin != lastMin) {
                    if (lastMin != 99) {
                    lastMin = currMin;

                p = - 1;              //   Serial.print(p); Serial.println(" ");
      [p] = recvMsg.d;
                recvMsgArr.gotData[p] = true;
                recvMsgArr.hour = hour();
                recvMsgArr.min = minute();
//Serial.print("sdSetup:"); Serial.print(sdSetup); Serial.print("  SD.mediaPresent:"); Serial.print(SD.mediaPresent());
                if (sdSetup && SD.mediaPresent()) {
                    dataFile ="datalog.csv", FILE_WRITE);
//Serial.print("   dataFile:"); Serial.println(dataFile);
                    // if the file is available, write to it:
                    if (dataFile) {
                        for (int q = 0; q < ( * 5; q++) {

                        date = GenerateDateStr(' ');
                        tim = GenerateTimeStr(true, ',');

                        dataFile.print(; dataFile.print(",");
                        PrintNN(recvMsg.d.temp);      dataFile.print(",");
                        PrintNN(recvMsg.d.humidity);  dataFile.println();

                    else Serial.println("error opening datalog.csv");

                else Serial.println("No SD Card (Media) present");
            else {

                Serial.println("Bad data, getting back into synch");
                for (uint n = 0; n < sizeof(msgType); n++) {
                bool insynch = false;
                elapsedMillis outOfSynchTime;

                while (!insynch && outOfSynchTime < 5000) {        // after 5 seconds of no serial1 data should be back in synch
                    while (Serial1.available() && (Serial1.peek() != identMatch.identTxt[0])) {
                        outOfSynchTime = 0;
                    if (Serial1.available()) insynch = (Serial1.peek() == identMatch.identTxt[0]);
                Serial.println("Back in synch");
        } else Serial.println("Unable to Receive Data");
        digitalWrite(ledPin, LOW);


Normally serial does not need a CS pin, the receiving device looks for any characters received i.e. if (Serial.available()) ... then do something, however there is nothing to stop you using an IO pin as a CS pin in which case the code becomes if (digitalREad(CSPin)==HIGH) ... do something
I think @SteveSFX was still talking about the SPI interface. The Seeed XIAO ESP32C3 has the SPI CS pin mapped to pin GPIO20, which is also the serial RXD pin. Pinout here. And here.

Last edited:
Sorry.... getting tired. Yes, I meant the SPI interface.

Weird. The data I found said TX was the CS. Doesn't matter, both are brought back to the Teensy anyway

It REALLY annoys me when neither the data sheet (that I can find) or online diagrams miss out key pins E.G CS....


  • 2.jpg
    131.4 KB · Views: 18
Last edited: