Microcontroller projects

Teensy tips and tricks

last updated: 2023-10-30

Quick links

Get NTP time and set RTC on Teensy 4.1 with Ethernet

Teensy 4.1 has an Ethernet controller and Ethernet PHY chip. You only need to connect an RJ45 magjack.

Here my code to get NTP UTC time and set the Teensy RTC using the QNEthernet library. I also added daylight saving time for europe (Luxemburg). More info: here.

I use UDP to log the time. This can then be done with the netcat (command: nc -kulw 0 5557 in the terminal of a PC (Linux) or Raspberry Pi.

    /*
      Teensy_4_1_SNTPClient.ino

      weigu.lu

      logging with udp

      we dont care here for the 2036 year problem! more infos:
      https://www.ietf.org/rfc/rfc4330.html and in the QNEthernet SNTPClient example!
    */

    #include <TimeLib.h>
    #include <QNEthernet.h> // for Teensy 4.1
    using namespace qindesign::network; //QNEthernet

    const unsigned long kDHCPTimeout = 15000;  // 15s only if using dhcp (init_eth_dhcp())  
    const unsigned long kEpochDiff = 2208988800; // 01-Jan-1900 -> 01-Jan-1970
    const unsigned long kBreakTime = 2085978496; // Epoch -> 07-Feb-2036 06:28:16

    // store this in config.h
    IPAddress NETWORK_IP      (192,168,1,48); //static IP
    IPAddress NETWORK_MASK    (255,255,255,0);
    IPAddress NETWORK_GATEWAY (192,168,1,20);
    IPAddress NETWORK_DNS     (192,168,1,20);
    IPAddress UDP_LOG_PC_IP   (192,168,1,50);
    const unsigned int UDP_LOG_PORT = 5557;
    const unsigned int UDP_NTP_PORT = 123;

    //EthernetClient eth_Client;
    //PubSubClient MQTT_Client(eth_Client);
    EthernetUDP Eth_Udp;
    EthernetUDP NTP_Udp;

    void setup() {
      //init_eth_dhcp();  
      init_eth_static();
      Eth_Udp.begin(UDP_LOG_PORT);
      NTP_Udp.begin(UDP_NTP_PORT); // Start NTP_Udp listening on the NTP port
      delay(3000);
      udp_log("UDP log started...\n");    
      log_network_info();  
      get_and_log_teensy_time();
      get_ntp_utc_time_and_set_teensy_rtc();
      convert_utc_to_time_europe();
    }

    // Main program loop.
    void loop() {
      udp_display_time();
      delay(1000);        
    }

    // init eth static
    void init_eth_static() {  
      //Ethernet.init(CS_ETH_pin); //needed to avoid conflict with SD card!!
      //delay(250); // needed for propper Reset?
      Ethernet.begin(NETWORK_IP, NETWORK_MASK, NETWORK_GATEWAY, NETWORK_DNS);  
    }

    // init eth dhcp
    void init_eth_dhcp() {  
      if (!Ethernet.begin()) {    
        return;
      }
      if (!Ethernet.waitForLocalIP(kDHCPTimeout)) {    
        return;
      } 
      delay(5000); 
    }

    void log_network_info() { 
      IPAddress ip; 
      byte mac[6];
      ip = Ethernet.localIP();
      udp_log("\nEthernet with DHCP:\nIP add.: " + String(ip[0]) +  '.' + 
              String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]) + '\n');
      ip = Ethernet.subnetMask();
      udp_log("Subnet mask: " + String(ip[0]) + '.' +
              String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]) + '\n');
      ip = Ethernet.gatewayIP();
      udp_log("Gateway: " + String(ip[0]) + '.' +
              String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]) + '\n');
      ip = Ethernet.dnsServerIP();
      udp_log("DNS server: " + String(ip[0]) + '.' +
              String(ip[1]) + '.' + String(ip[2]) + '.' + String(ip[3]) + '\n');
      Ethernet.macAddress(mac);  // retrieves internal mac
      udp_log("Internal Mac address: " + str_hex_02u(mac[0]) + ':' + 
              str_hex_02u(mac[1]) + ':' + str_hex_02u(mac[2]) + ':' +
              str_hex_02u(mac[3]) + ':' + str_hex_02u(mac[4]) + ':' + 
              str_hex_02u(mac[5]) + '\n');
    }  

    // convert to string and add leading zero to int number with only 1 digit
    String str_int_02u(int n) {
      String s = String(n);
      if (n < 10) { 
        s = '0' + s;    
      }
      return s;
    }

    // convert to string and add leading zero to hex number with 1 digit
    String str_hex_02u(byte hex) {
      String s = String(hex,HEX);
      if (hex < 16) {
        s = '0' + s;    
      }
      return s;
    }

    /****** UDP logging and NTP helper functions ********************************/

    void udp_log(String message) {
      Eth_Udp.beginPacket(UDP_LOG_PC_IP,UDP_LOG_PORT);
      Eth_Udp.write(message.c_str());
      Eth_Udp.endPacket();
      delay(1);
    }  

    void udp_ntp_send(byte *buf) {
      NTP_Udp.beginPacket(Ethernet.gatewayIP(), UDP_NTP_PORT); //NTP port is 123
      NTP_Udp.write(buf, 48);
      NTP_Udp.endPacket();
      delay(1);
    } 

    // Send an SNTP request: https://seriot.ch/projects/tiny_ntp_client.html  
    // https://www.ietf.org/rfc/rfc4330.html
    void send_ntp_request() {
      byte buf[48] = {0};  
      buf[0] = 0b00100011;  // LI=0, VN=4, Mode=3 (Client)
      udp_ntp_send(buf);
    }

    time_t get_and_log_teensy_time() {
      time_t t = Teensy3Clock.get();
      udp_log("Teensy time: " + String(year()) + '-' + str_int_02u(month()) + '-' +
              str_int_02u(day()) + 'T' + str_int_02u(hour()) + ':' +
              str_int_02u(minute()) + ':' + str_int_02u(second()) + '\n'); 
      return t;        
    }

    time_t get_ntp_utc_time_and_set_teensy_rtc() {
      time_t t; 
      send_ntp_request(); 
      delay(5000);
      if (NTP_Udp.parsePacket() == 48) {
        const byte *buf = NTP_Udp.data(); 
        int mode = buf[0] & 0x07;
        if (((buf[0] & 0xc0) != 0xc0) &&  // LI == 3 (Alarm condition)
            (buf[1] != 0) &&              // Stratum == 0 (Kiss-o'-Death)
            (mode == 4 || mode == 5)) {   // Must be Server or Broadcast mode      
          t = buf[40]*256*256*256+buf[41]*256*256+buf[42]*256 +buf[43];                        
        }        
        if ((t & 0x80000000U) == 0) {  // See: Section 3, "NTP Timestamp Format"   
          t += kBreakTime;
        } else {
          t -= kEpochDiff;
        }   
        Teensy3Clock.set(t); // Set the RTC and time
        setTime(t);
        udp_log("SNTP reply: " + String(year()) + '-' + str_int_02u(month()) + '-' +
              str_int_02u(day()) + 'T' + str_int_02u(hour()) + ':' +
              str_int_02u(minute()) + ':' + str_int_02u(second()) + '\n');
        return t;      
      }  
      else {
        udp_log("Error while getting NTP time\n");
        return -1;
      }   
    }  

    void udp_display_time() {
      udp_log(String(year()) + '-' + str_int_02u(month()) + '-' +
              str_int_02u(day()) + 'T' + str_int_02u(hour()) + ':' +
              str_int_02u(minute()) + ':' + str_int_02u(second()) + '\n');
    }

    time_t convert_utc_to_time_europe() {
      time_t t = now();
      if (is_daylight_saving_time(day(),month(),year())==1) {
        t = t + 7200;
      }
      else {
        t = t + 3600;
      }
      setTime(t);
      Teensy3Clock.set(t); // Set the RTC and time
      udp_log("Corrected time: " + String(year()) + '-' + str_int_02u(month()) + '-' +
              str_int_02u(day()) + 'T' + str_int_02u(hour()) + ':' +
              str_int_02u(minute()) + ':' + str_int_02u(second()) + '\n');
      return t;      
    }

    bool is_daylight_saving_time(int d, int m, int y) {    
      byte y2 = y % 100;        
      byte x1 = 31 - (y2 + y2 / 4 - 2) % 7; //last Sunday March
      byte x2 = 31 - (y2 + y2 / 4 + 2) % 7; // last Sunday October
      if ((m > 3 && m < 10) || (m == 3 && d >= x1) || (m == 10 && d < x2)) {
        return 1;
      }  
      else {
        return 0;
      }
    }

Problems with Serial on Arduino 1.8.9 (Kubuntu)

This helped, but I don't know why:

    sudo apt purge modemmanager

Fast PWM with Teensy 2.0 (mega32u4) in Arduino

Fast PWM on Teensy 2.0 is cool because it is very simple to initialise and needs no interrupts. Because Timer0 is used by Arduino we will use the 16 Bit Timer1 or Timer3 or the 10 Bit Timer4. We have to set the right bits in TCCRnA and TCCRnB register. That's all. The byte in OCR register (8 bit PWM) defines the pulsewidth from 0 (0%) to 255 (100%).

Timer1 has 3 PWM outputs (14,15 and 4), Timer3 only one output (9) and Timer4 two outputs (10 and 15 (same as Timer1!). Here are the sample codes:

    // Fast PWM for Teensy Timer1 (3 outputs)
    const byte fan1Pin = 14;      // PB5 on Teensy (OC1A of Timer1)
    const byte fan2Pin = 15;      // PB6 on Teensy (OC1B of Timer1)
    const byte fan3Pin = 4;       // PB7 on Teensy (OC1C of Timer1)
    void setup() {
      pinMode(fan1Pin, OUTPUT);
      pinMode(fan2Pin, OUTPUT);
      pinMode(fan3Pin, OUTPUT);
      TCCR1A = 0xA9;              // COM1A1, COM1B1, COM1C1, WGM10
      TCCR1B = 0x0A;              // WGM12 (Fast PWM 8 Bit) CK/8  -> 7,8kHz
      OCR1A = 51;                 // 20%
      OCR1B = 127;                // 50%
      OCR1C = 179;}               // 70%
    void loop() {}
    // Fast PWM for Teensy Timer3 (only one pin available!)
    const byte fan1Pin = 9;       // PC6 on Teensy (OC3A of Timer3)
    void setup() {
      pinMode(fan1Pin, OUTPUT);
      TCCR3A = 0xA1;              // COM3A1,WGM30;
      TCCR3B = 0x0A;              // WGM32 (Fast PWM 8 Bit) CK/8  -> 7,8kHz
      OCR3A = 51;}                // 20%
    void loop() {}
    // Fast PWM for Teensy Timer4 (2 outputs)
    const byte fan1Pin = 10;    // PC7 on Teensy (OC4A of Timer4)
    const byte fan2Pin = 15;    // PB6 on Teensy (OC4B of Timer4)
    void setup() {
      TCCR4A = 0xA3;            // COM4A1, COM4B1, PWM4A, PWM4B
      TCCR4B = 0x04;            // CK/8  -> 3,92kHz (8MHz 10Bit??))
      OCR4A = 51;               // 20%
      OCR4B = 127;}             // 50%
    void loop() {}

To change the PWM frequency, adjust the bits in TCCRnB (see data sheet of mega32u4).

Using Timers in CTC mode on Teensy 2.0 (mega32u4) in Arduino

We will use the Timer1 and Timer3 (both 16 bit) with compare match (CTC mode).

Timer1 will print seconds and minutes once a second in the serial terminal (115200 bits/s) without using an interrupt. For this we watch the interrupt flag. By clearing the flag with "1" (i know it's not logical but hardware dictates the laws) we simulate an interrupt and initiate the timer to run again.

Timer3 fires an interrupt in half a second time to blink the Teensy LED and an LED on PB0 (0) once a second. Setting the PIN register to "1" is an efficient way (2 cycles) to toggle a pin but works only on AVR controller.

    // CTC Mode (with and without interrupt)
    // on Teensy2 (mega32u4)
    // Timer1 prints seconds and minutes in serial terminal without interrupt
    // Timer3 toggles PB0 once a second (with interrupt)
    // weigu.lu
    // This example code is in the public domain.

    const byte teensyLED = 11; // PD6 Teensy LED
    const byte toggleLED = 0;  // PB0 on Teensy
    int seccounter = 0, mincounter = 0;

    void setup() {
    pinMode(teensyLED, OUTPUT);
    pinMode(toggleLED, OUTPUT);
    Serial.begin(115200);
    noInterrupts();
    TCCR1A = 0;
    TCCR1B = 0x0C;         // WGM32 (CTC) , Prescaler = 256
    OCR1A = 62500;         // 16M/256/62500 = 1 second
    TCCR3A = 0;
    TCCR3B = 0x0C;         // WGM32 (CTC) , Prescaler = 256
    OCR3A = 31250;         // 16M/256/31250 = 0,5 seconds
    TIMSK3 = 0x02;         // enable compare interrupt
    interrupts();}

    void loop() {
    if (TIFR1 & 0x02) {    // Flag set?
        seccounter++;
        TIFR1 |= 0x02;       // clear Flag with 1! (data sheet)
        if (seccounter == 60) {
        seccounter = 0;
        mincounter++;}
        Serial.print(mincounter);
        Serial.print(":");
        Serial.println(seccounter);}}

    ISR (TIMER3_COMPA_vect) {
    PINB = PINB | 0x01;    // toggle pin PB0
    PIND = PIND | 0x40; }  // toggle pin PD6 Teensy LED

Using Ethernet shield (WZ5500) with SD-card shield at the same time

Both shields are using SPI. Because the SD-card shield is on top of the Teensy2 it uses pin 0 (PB0) as chip select. The Ethernet shield has to use a different chip select. In the Teensy library (hardware/teensy/avr/libraries/Ethernet) the chip select is on pin 10 (PC7) but only if you use the init() function. Without using this function chip select stays on pin 0 and conflicts with the SD-card. You can pass a parameter to the init() function an so specify another pin for chip select.

So here is the code for pin 10:

    Ethernet.init();
    Ethernet.begin(mac, eth_ip);

And here the code for example pin 13 (PB4):

    Ethernet.init(13);
    Ethernet.begin(mac, eth_ip);

Teensy 3.6 on-board SD-card

This card is not using normal SPI but has its own dedicated SDIO interface, capable of much higher speeds (up to 20 MByte/sec on the T3.6!). To use the card you have to change chipSelect to:

const int chipSelect = BUILTIN_SDCARD;

Look at File-Examples-SD (Teensyduino SD card examples)

I can connect the WizNET only to the first SPI because the ethernet library uses FIFO which only exists on the first SPI.

Teensy 3.6 on-board RTC

The 32.768 kHz crystal is already soldered to the board. You have only to solder the 3V Li battery to 2 pins in the middle (VB and GND).

To use the RTC look at File-Examples-Time-TimeTeensy3 (Teensyduino installed)

The Teensy's RTC is not temperature corrected like the DS3231's. There was discussion about using the internal Teensy temperature sensor (pin 44 on 3.5/3.6) to correct the Teensy's RTC drift (search forum).

Receiving NTP time over serial from ESP8266

For ESP8266 sketch click here and scroll down.

Here a simple sketch to get the time (Teensy 3.6) from ESP8266.

Connect D4 (TxD) from Wemos D1 mini with pin 1 (RXD1) from Teensy 3.6 (connect also the 2 GNDs).

    /*  Get NTP Time from ESP8266 over serial on RXD1 (Teensy 3.6)
     *  weigu.lu
     */

    #define MAX_LINE_LENGTH 10

    uint8_t telegram[MAX_LINE_LENGTH];
    int Tlength;

    void setup() {
      Serial.begin(115200);  // for debugging
      delay(100);
      Serial.println("Serial is working!");
      Serial1.begin(115200);
    }

    void loop() {
      Tlength = readTelegram();
      if (Tlength) {
        int NTP_hour = (telegram[0]-48)*10+(telegram[1]-48);
        int NTP_min = (telegram[2]-48)*10+(telegram[3]-48);
        int NTP_sec = (telegram[4]-48)*10+(telegram[5]-48);
        Serial.print(NTP_hour);
        Serial.print(NTP_min);
        Serial.println(NTP_sec);
      }
      delay(1000);
    }

    int readTelegram() {
      int cnt = 0;
      while ((Serial1.available()) && (cnt!= MAX_LINE_LENGTH)) {
        telegram[cnt] = Serial1.read();
        cnt++; }
      return (cnt);
    }

Downloads