Results 1 to 10 of 10

Thread: Teensy 4.1 E1.31 receiver/controller

  1. #1
    Join Date
    Aug 2020
    Location
    Enterprise, Utah
    Posts
    9
    Post Thanks / Like

    Default Teensy 4.1 E1.31 receiver/controller

    Just thought I'd throw this onto the interwebs, so when I forget how to get it working, I'll be able to find my own answer...

    After frying my T3.2 I decided to try out the T4.1. Since it's so new, I had a hard time finding ready to use code. I was able to get it working this morning, finally. I haven't had a chance to really push it, as you can see, I'm only using 2 pins. The great thing about the 4.1, besides the number of pinouts, is that you can choose which pins, and the pin order you use for the octows2811 library. I'll probably clean up the code and include some additional features over time, but for now, if anyone is struggling to get it working, here is code that seems to work. I'll keep playing with this. Right now, it's based on mrees MegaPixelController, with additional help from the teensy forums to get the built in ethernet to play nice with FastLED FastLED-with-Teensy-4-1-fast-parallel-DMA-output-on-any-pin. I also increased UDP_TX_PACKET_MAX_SIZE to 1500.

    Code:
    //Running e131 through OctoWS2811 powered by fastled on Teensy 4.1
    //Brian Amos <brian@amoscomputing.com>
    
    #include <OctoWS2811.h>
    #include <FastLED.h>
    #include <NativeEthernet.h>
    #include <Arduino.h>
    
    //debug mode adds some serial feedback during testing. Remove this line for production code
    #define DEBUG_MODE 1
    
    
    //DEFINES for at Compile time.
    //Leave this alone.  At current a full e1.31 frame is 636 bytes..
    #define ETHERNET_BUFFER 636 //540 is artnet leave at 636 for e1.31
    #define NUM_LEDS_PER_STRIP 340 //170 //680 default
    #define NUM_STRIPS 2 //make sure pin list is accurate
    #define DMX_SUBNET 0
    
    int unsigned DMX_UNIVERSE = 1; //**Start** universe 1, 9, 17, 25, 33, 41
    int unsigned UNIVERSE_COUNT = 4; //How Many Universes 8, 8, 8, 4, 8, 8
    int unsigned UNIVERSE_LAST = 4; // List the last universe typically its sequencially from start but does not have to. 8, 16, 24, 28, 32, 40
    int unsigned CHANNEL_COUNT = 510; //max channels per dmx packet
    byte unsigned LEDS_PER_UNIVERSE = 170; // Max RGB pixels //170 default
    int unsigned NUM_LEDS  = UNIVERSE_COUNT * LEDS_PER_UNIVERSE; // with current fastLED and OctoWs2811 libraries buffers... do not go higher than this - Runs out of SRAM
    
    
    //ethernet setup
    unsigned char packetBuffer[ETHERNET_BUFFER];
    int c = 0;
    float fps = 0;
    unsigned long currentMillis = 0;
    unsigned long previousMillis = 0;
    
    EthernetUDP Udp;
    uint8_t mac[6]; //this is determined from the Teensy 4.1 board.  
    IPAddress ip(192,168,1,10); //static ip address of board
    #define UDP_PORT 5568 //E1.31 UDP Port Number
    
    
    //set up OctoWS2811 and FastLED for Teensy 4.1
    const int numPins = NUM_STRIPS;
    byte pinList[numPins] = {19, 18};
    const int ledsPerStrip = NUM_LEDS_PER_STRIP; //this should be NUM_LEDS / numPins => VERIFY THIS MATCHES THE HARDWARE!! //should also be multiples of a single universe of leds
    CRGB rgbarray[numPins * ledsPerStrip];
    
      // These buffers need to be large enough for all the pixels.
      // The total number of pixels is "ledsPerStrip * numPins".
      // Each pixel needs 3 bytes, so multiply by 3.  An "int" is
      // 4 bytes, so divide by 4.  The array is created using "int"
      // so the compiler will align it to 32 bit memory.
    DMAMEM int displayMemory[ledsPerStrip * numPins * 3 / 4];
    int drawingMemory[ledsPerStrip * numPins * 3 / 4];
    OctoWS2811 octo(ledsPerStrip, displayMemory, drawingMemory, WS2811_GRB | WS2811_800kHz, numPins, pinList);
    
    template <EOrder RGB_ORDER = RGB,
              uint8_t CHIP = WS2811_800kHz>
    class CTeensy4Controller : public CPixelLEDController<RGB_ORDER, 8, 0xFF>
    {
        OctoWS2811 *pocto;
    
    public:
        CTeensy4Controller(OctoWS2811 *_pocto)
            : pocto(_pocto){};
    
        virtual void init() {}
        virtual void showPixels(PixelController<RGB_ORDER, 8, 0xFF> &pixels)
        {
    
            uint32_t i = 0;
            while (pixels.has(1))
            {
                uint8_t r = pixels.loadAndScale0();
                uint8_t g = pixels.loadAndScale1();
                uint8_t b = pixels.loadAndScale2();
                pocto->setPixel(i++, r, g, b);
                pixels.stepDithering();
                pixels.advanceData();
            }
    
            pocto->show();
        }
    };
    
    CTeensy4Controller<RGB, WS2811_800kHz> *pcontroller;
    
    
    
    void setup() {
    
    
      Serial.begin(115200);
      delay(10);
    
    
      teensyMAC(mac);
    
      
      static char teensyMac[23];
      sprintf(teensyMac, "MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
      Serial.print("MAC: ");
      Serial.println(teensyMac);
      
      Ethernet.begin(mac, ip);
      
    
      Serial.print("IP Address: ");
      Serial.println(Ethernet.localIP());
      Serial.print("MAC: ");
      Serial.println(teensyMac);
    
    
      
      Udp.begin(UDP_PORT);
      
      octo.begin();
      pcontroller = new CTeensy4Controller<RGB, WS2811_800kHz>(&octo);
    
      FastLED.addLeds(pcontroller, rgbarray, numPins * ledsPerStrip);
      FastLED.setBrightness(50);
      
      initTest();
      Serial.println("Test Sequence Complete");
    } //end setup
    
    static inline void fps2(const int seconds){
      // Create static variables so that the code and variables can
      // all be declared inside a function
      static unsigned long lastMillis;
      static unsigned long frameCount;
      static unsigned int framesPerSecond;
      
      // It is best if we declare millis() only once
      unsigned long now = millis();
      frameCount ++;
      if (now - lastMillis >= seconds * 1000) {
        framesPerSecond = frameCount / seconds;
        
        Serial.print("FPS @ ");
        Serial.println(framesPerSecond);
        frameCount = 0;
        lastMillis = now;
      }
    
    }
    
    static inline void pixelrefresh(const int syncrefresh){
      // Create static variables so that the code and variables can
      // all be declared inside a function 
      static unsigned long frametimestart;
      static unsigned long frametimeend;
      static unsigned long frametimechk;
      static unsigned long frameonce;
      unsigned long now = micros();
     
    
      //start frame time
      frametimestart = now;
      
      //Serial.println(frametimechk);
       //If we have framed no need to frame again update time to most recent
       if  (syncrefresh == 1){
       frametimeend = frametimestart; 
       frameonce = 1;
       }
       
    //If we havent framed this will increment via time and at some point will be true, 
    //if so we need to frame to clear out any buffer and the hold off untill 
    //we receive our next valid dmx packet. We use the pixel protocol to get a general rule of timing to compare to.
    
    frametimechk = frametimestart - frametimeend;
     // num leds time 30us + 300us reset to simulate the time it would take to write out pixels. 
     //this should help us not loop to fast and risk premature framing and jeopordize ethernet buffer
     if  (frametimechk >= (NUM_LEDS * 30) + 300){
      frametimeend = frametimestart;
    
    
        if (frameonce == 1){
    
        octo.show();
    
        Serial.println ("Partial framing detected");
        frameonce = 0;  
      }
      
     }
     
    }
    
    
    
    void sacnDMXReceived(unsigned char* pbuff, int count) {
      Serial.print("sacn DMX Received: Subnet=");
      Serial.print(pbuff[113]);
      Serial.print(", Universe=");
      Serial.print(pbuff[114]);
      Serial.print(", Sequence=");
      Serial.println(pbuff[111]);
      
      static unsigned long uniloopcount;
      if (count > CHANNEL_COUNT) count = CHANNEL_COUNT;
      byte b = pbuff[113]; //DMX Subnet
      if ( b == DMX_SUBNET) {
        b = pbuff[114];  //DMX Universe
        byte s = pbuff[111]; //sequence
        static unsigned long ls; // Last Sequence
        if (s > ls){
        uniloopcount = 0; 
        ls = s;
        }
       //turn framing LED OFF
    //   digitalWrite(4, HIGH);
    //    Serial.print("UNI ");
    //    Serial.println(count );
    //    Serial.println(s);
        if ( b >= DMX_UNIVERSE && b <= UNIVERSE_LAST) {
            //Serial.println(b );
          if ( pbuff[125] == 0 ) {  //start code must be 0   
           int ledNumber = (b - DMX_UNIVERSE) * LEDS_PER_UNIVERSE;
             // sACN packets come in seperate RGB but we have to set each led's RGB value together
             // this 'reads ahead' for all 3 colours before moving to the next led.
             //Serial.println("*");
            for (int i = 126;i < 126+count;i = i + 3){
              byte charValueR = pbuff[i];
              byte charValueG = pbuff[i+1];
              byte charValueB = pbuff[i+2];
              octo.setPixel(ledNumber, charValueR,charValueG,charValueB); //RBG GRB
              //Serial.println(ledNumber);
              ledNumber++;
            }
          }
        }
      }
    
             uniloopcount ++;
             Serial.print("UNILOOP");
             Serial.println(uniloopcount);
    
            //if (b == UNIVERSE_LAST){
            if (uniloopcount >= UNIVERSE_COUNT){ 
            //Turn Framing LED ON
    //        digitalWrite(4, LOW);
    
            octo.show();
            
            pixelrefresh(1);
            uniloopcount = 0;
            //Frames Per Second Function fps(every_seconds)
            fps2(5);
            }
    
    }
    
    
    int checkACNHeaders(unsigned char* messagein, int messagelength) {
      //Do some VERY basic checks to see if it's an E1.31 packet.
      //Bytes 4 to 12 of an E1.31 Packet contain "ACN-E1.17"
      //Only checking for the A and the 7 in the right places as well as 0x10 as the header.
      //Technically this is outside of spec and could cause problems but its enough checks for us
      //to determine if the packet should be tossed or used.
      //This improves the speed of packet processing as well as reducing the memory overhead.
      //On an Isolated network this should never be a problem....
      if ( messagein[1] == 0x10 && messagein[4] == 0x41 && messagein[12] == 0x37) {   
          int addresscount = (byte) messagein[123] * 256 + (byte) messagein[124]; // number of values plus start code
          return addresscount -1; //Return how many values are in the packet.
        }
      return 0;
    }
    
    
    void initTest() //runs at board boot to make sure pixels are working
    {
      LEDS.clear(); //clear led assignments
    //  LEDS.showColor(CRGB(0,0,0)); //turn off all pixels to start
    //  delay(500);
      
      LEDS.showColor(CRGB(255, 0, 0)); //turn all pixels on red
      delay(3000);
    
      LEDS.showColor(CRGB(0, 255, 0)); //turn all pixels on green
      delay(3000);
    
      LEDS.showColor(CRGB(0, 0, 255)); //turn all pixels on blue
      delay(3000);
    
      LEDS.showColor(CRGB(0,0,0)); //turn off all pixels to start
    
    //  LEDS.showColor(CRGB(50, 50, 50)); //turn all pixels off
    /*
      //process dot trace
         for (int n=0; n < NUM_LEDS_PER_STRIP*NUM_STRIPS; n++){
        octo.setPixel(n,100,0,0);
        octo.setPixel(n-2,0,0,0);
        octo.show();
        delay(10);
      
      }
    
        for (int n=0; n < NUM_LEDS_PER_STRIP*NUM_STRIPS; n++){
        octo.setPixel(n,0,100,0);
        octo.setPixel(n-2,0,0,0);
        octo.show();
        delay(10);
      
      }
    
        for (int n=0; n < NUM_LEDS_PER_STRIP*NUM_STRIPS; n++){
        octo.setPixel(n,0,0,100);
        octo.setPixel(n-2,0,0,0);
        octo.show();
        delay(10);
      
      }
    
    */
    
    }
    
    
    
    
    //define mac address of board
    void teensyMAC(uint8_t *mac) {
    
      static char teensyMac[23];
      
      #if defined(HW_OCOTP_MAC1) && defined(HW_OCOTP_MAC0)
        Serial.println("using HW_OCOTP_MAC* - see https://forum.pjrc.com/threads/57595-Serial-amp-MAC-Address-Teensy-4-0");
        for(uint8_t by=0; by<2; by++) mac[by]=(HW_OCOTP_MAC1 >> ((1-by)*8)) & 0xFF;
        for(uint8_t by=0; by<4; by++) mac[by+2]=(HW_OCOTP_MAC0 >> ((3-by)*8)) & 0xFF;
    
        #define MAC_OK
    
      #else
        
        mac[0] = 0x04;
        mac[1] = 0xE9;
        mac[2] = 0xE5;
    
        uint32_t SN=0;
        __disable_irq();
        
        #if defined(HAS_KINETIS_FLASH_FTFA) || defined(HAS_KINETIS_FLASH_FTFL)
          Serial.println("using FTFL_FSTAT_FTFA - vis teensyID.h - see https://github.com/sstaub/TeensyID/blob/master/TeensyID.h");
          
          FTFL_FSTAT = FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL;
          FTFL_FCCOB0 = 0x41;
          FTFL_FCCOB1 = 15;
          FTFL_FSTAT = FTFL_FSTAT_CCIF;
          while (!(FTFL_FSTAT & FTFL_FSTAT_CCIF)) ; // wait
          SN = *(uint32_t *)&FTFL_FCCOB7;
    
          #define MAC_OK
          
        #elif defined(HAS_KINETIS_FLASH_FTFE)
          Serial.println("using FTFL_FSTAT_FTFE - vis teensyID.h - see https://github.com/sstaub/TeensyID/blob/master/TeensyID.h");
          
          kinetis_hsrun_disable();
          FTFL_FSTAT = FTFL_FSTAT_RDCOLERR | FTFL_FSTAT_ACCERR | FTFL_FSTAT_FPVIOL;
          *(uint32_t *)&FTFL_FCCOB3 = 0x41070000;
          FTFL_FSTAT = FTFL_FSTAT_CCIF;
          while (!(FTFL_FSTAT & FTFL_FSTAT_CCIF)) ; // wait
          SN = *(uint32_t *)&FTFL_FCCOBB;
          kinetis_hsrun_enable();
    
          #define MAC_OK
          
        #endif
        
        __enable_irq();
    
        for(uint8_t by=0; by<3; by++) mac[by+3]=(SN >> ((2-by)*8)) & 0xFF;
    
      #endif
    
      #ifdef MAC_OK
        sprintf(teensyMac, "MAC: %02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
        Serial.println(teensyMac);
      #else
        Serial.println("ERROR: could not get MAC");
      #endif
    }
    
    void loop() {
       //Process packets
       int packetSize = Udp.parsePacket(); //Read UDP packet count
        
       if(packetSize){
        Serial.println(packetSize);
        Udp.read(packetBuffer,ETHERNET_BUFFER); //read UDP packet
        
        
        int count = checkACNHeaders(packetBuffer, packetSize);
        if (count) {
          
    //     Serial.print("packet size first ");
    //     Serial.println(packetSize);
    
         
         
    
        
         sacnDMXReceived(packetBuffer, count); //process data function
         
        
         
        
        }  
    
    
    
      }
    
    pixelrefresh(0);
    
    }

  2. #2
    Join Date
    Oct 2014
    Location
    Sauk City, WI USA
    Posts
    1,444
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    What does a fully assembled teensy cost? What does it look like? Do you have video of your install?

  3. #3
    Join Date
    Aug 2020
    Location
    Enterprise, Utah
    Posts
    9
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    I’m still at the breadboard stage, I don’t have it in production yet. Hopefully I’ll be able to get it running in this year's show. You can find the board at https://www.pjrc.com/store/teensy41.html. I got the board and the Ethernet kit, with shipping I think I’m out about $35. I got other things on the same order so I’m not positive on the cost for just the T4.1 and the Ethernet kit. It’s powered by an ARM cortex at 600 MHz, with 8mb memory (expandable) and 35 pwm pins with built in Ethernet. Theoretically it could run 23,800 nodes at 20 hz. I don’t own that many to try it out, and that would probably push the Ethernet past its 100mbps (which is really 90-95 mbps after overhead). It should run my 6000+ nodes just fine. Once I get it working on a prop I’ll post a video.

  4. #4
    Join Date
    Mar 2017
    Location
    Indiana
    Posts
    148
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    I've been using a Teensy 3.2 for my show for a while. This looks like a cool upgrade option! Looking forward to future updates
    Halloween 2020 Lights: https://youtu.be/Wdxp-NLkkaE
    Christmas 2019 Lights: https://youtu.be/e9qOPoaFV6w
    Other shows and behind the scenes: https://www.youtube.com/channel/UCg1...c85-wgbnkNiLOw

  5. #5
    Join Date
    Sep 2017
    Posts
    29
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    I picked up a 4.0 here a couple weeks ago, and using ShadowLight's Sketch it didnt work out of the box so I am picking up another 3.2 for now. But I'll have to follow this on the 4.1 with ethernet. I would love to change over to ethernet vs USB to redo the player for the show. Thanks for the update on this.

  6. #6
    Join Date
    Aug 2020
    Location
    Enterprise, Utah
    Posts
    9
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    Quote Originally Posted by gomer23 View Post
    I picked up a 4.0 here a couple weeks ago, and using ShadowLight's Sketch it didnt work out of the box so I am picking up another 3.2 for now. But I'll have to follow this on the 4.1 with ethernet. I would love to change over to ethernet vs USB to redo the player for the show. Thanks for the update on this.
    This should work on a T4.0 via usb with some minor changes. Just dump the Ethernet code and fill the buffers from usb serial. I don’t use usb since my show computer is 100’ away from the controllers, but it shouldn’t be too hard. With usb you could probably just use fastled, I don’t think serial uses interrupts, so that shouldn’t affect anything. I do know that if the universe and led counts aren’t exactly the same on the controller as they are in the model strange things happen.

  7. #7
    Join Date
    Aug 2020
    Location
    Enterprise, Utah
    Posts
    9
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    Here is a demo of the T4.1 running 1200 pixels at 40hz refresh rate. Control is by xLights on my Mac. The connection is wifi between the host computer and the router, and it's working well. Of course the host computer is within 10 ft of the wifi router, but it's an old router connected at 802.11g. I prefer 20 hz with outside shows, but 40 hz seems to look much better with these strips. Everything is still on breadboards, and the power is only provided at the very start, so you can probably notice low power effects at the far end of the run (which happens to be the closest side to the camera).


  8. #8
    Join Date
    Aug 2020
    Location
    Enterprise, Utah
    Posts
    9
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    I did some stress testing this morning. I reprogrammed it to get 640 pixels per output with 32 outputs. I ran the xlights sequence at about 30 FPS, and it performed quite well. I don’t own 20,000 pixels to try it out in a live show, but the processor was spitting out the data at the required frequency to each pin. As the pins are only writing out data, I assume that it’ll work the same whether or not pixels are connected to each pin or not.

    I’m very pleased with the capabilities of this little chip. I need to cleanup the code a bit then I’ll post it here. I’m currently designing a small interface board with buffer chips and resistors to make connection more secure than the breadboard I’m currently using. If anyone is interested I can have some additional boards printed. My initial thought is to provide connection between the board and the lights via a screw terminal or a thruhole solder connection. I know the octo board uses RJ-45, is that more standard than having to solder connectors in? Does anyone have some experience either way?

  9. #9
    Join Date
    Nov 2019
    Posts
    56
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    Have a look at scooter_seh's github page for his open source controller (Beaglebone based). This is how I understand most people expect the controllers to interface with the pixel ports.

    https://github.com/computergeek1507/PB_16

    Not sure if you are aware, Adafruit recenlty posted on their blog an entry about using a SAMD mcu timer port with DMA to achieve 8 simultaneous pixel ports on one of their feathers. I think they called it NeoPXL8 or something like that. Not sure if this is relevant to you or not. What caught my eye was with the limited RAM they could push 2000 pixels with a SAMD21.

    https://www.adafruit.com/product/3975

  10. #10
    Join Date
    Aug 2020
    Location
    Enterprise, Utah
    Posts
    9
    Post Thanks / Like

    Default Re: Teensy 4.1 E1.31 receiver/controller

    That’s fairly impressive for the feather to push 2000 pixels, the process they use is fairly similar to that used on the T4.1. The cost of the feather is about the same as the cost of the T4.1. The main difference is that the 4.1 can drive 32 pins simultaneously and up to 1000 leds per pin at a frame rate of 33 hz with no performance problems on the chip. Driving 1000 2811s on a single pin is problematic due to the 2811 chip. After somewhere between 680 and 700 leds, you’ll visually notice the degradation of performance, which limits this chip to 21,700 leds directly driven. Though with a faster led chip, the limit would be about 30,000 leds. There are some solutions that multiplex the pinout to split 3 pins into 8 strips worth of data. That may be an option to get the T4.1 to drive 30,000 ws2811s. That’s not my goal, however. I personally prefer to have a single controller run a few props only. My main attraction to the T4.1 is that I can basically be limitless on the amount of leds in each prop. I’ll never get a single prop to the 20,000 led range. My entire show will probably max out at 20,000 leds after my wife has everything she wants in it. Also, the T4.1 is relatively inexpensive, allowing me to have a cost effective way to place a high capacity controller at each prop.

    Thanks for the info on the connection to the board. I do see that many commercial controllers use slip or screw terminals. The current connection to the teensy boards uses RJ-45 to cat 6, which will run 4 strings per RJ45. The footprint of the RJ45 is quite a bit smaller than the screw terminals. And they are quite a bit cheaper. I like the cat6 option for longer runs to props, as there is less passive interference than on other wiring packages. Maybe I’ll design it with both options. I don’t intend to manufacture these boards for mass sales. I’ll make them for myself and if someone else is interested I can have a few more made at a time so we can all get them cheaper. But it’s more of an open source, build it yourself kind of project.

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •