ESP32 and IR Control

Post Reply
DrJFM
Posts: 6
Joined: Wed May 04, 2016 4:20 pm

ESP32 and IR Control

Post by DrJFM »

After a short dalliance with the ESP8266 as an update to Arduino Uno based boards, I have moved on almost completely to the ESP 32. While a bit more costly in single units, the ESP32 development boards are already readily available for under $10 -- not a barrier for a faster, more capable board =incorporating WiFi, USB programing port, Bluetooth, software assignments of almost all ports etc. Here is a chart comparing the two. I was moving to more a more capable platform and would just as soon jump to the superior ESP32 as to invest heavily in the chip it is replacing. Already over a year in the market, the pace of development of the ESP32 in the Maker community and the support from Espressif. The ESP32 Forum
has over 11,000 posts to date. I am here because moving to the ESP32 entailed, for me, porting my IR control efforts to the ESP32. Seeking information on doing this showed me the versatility of the board. I have successfully generated IR control codes with modulated IR carrier frequencies using at least three different approaches and I have one more to try. I have used an interrupt driven approach based on the internal timers of the ESP32, adapted that to use the LED specialty control hardware systems and am planning on using the dedicated IR control hardware baked into the silicone as well. The latter capabilities are not yet as well documented or ported to the Arduino environment I use, so I have left to last. The third approach I have just completed involves application of the UART hack as recently featured here at AnalysIR. The ESP32 has three UART subsystems readily exposed to the programmer. The typical Serial.begin() type access allows the TX0, RX0 use but this is also the conduit for the USB to computer interface used in programming. The TX1, RX1 interface is devoted to onboard flash storage. The TX2/RX2 UART is readily available for the IR Hack.

The Update to the UART Hack of the ESP8266 provided guidance to the port to the ESP32. The UART control registers are fully documented, if barely intelligible to an amateur. The ESP8266 had a key register bit that allows the UART signal to be inverted and, as anticipated, the same register has a bit to invert the signals on the ESP32. Access to this is gained by this include: #include "soc/uart_reg.h"

The register is set in a manner similar to that described in the ESP8266 article:

Code: Select all

  #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV);  
The index of (UART_CONF0_REG(X)...) above is set to 2 for TX2 as output or changed to 0 if using Serial.write on UART 0.
I also include #include <HardwareSerial.h> although this is part of the core, but serves as a reminder of where to reference the code for use of UART #2. While UART #0 is always accessible, you must initialize the serial port for UART #2 as well as then run a begin command to set up the BAUD rate.
The Arduino routines default to the 8N1 protocol the hack expects. The non-Arduino ESPressiff SDK exposes much more of the details of the UART control, but this was not needed to achieve the UART based IR transmitter. Here is the code for initializing the class

Code: Select all

   HardwareSerial IRSerial2 (2);                                               // set ups IRSerial2 as a Hardware serial port on UART26                

   pinMode(TX, OUTPUT);
   digitalWrite(TX, LOW);
 
    IRSerial2.begin(carrierFreq *10);    //carrierFreq is unsigned long and holds the resultant carrier freq ie 38000 in this code
   #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV);  
If you intend to also use regular Serial.print functionality, initialize UART0 with a regular Serial.begin(112500); statement before you do the IR UART2 ie IRSerial2.begin(carrierFreq *10).

I had some issues with this ESP32 port. The ESP32 Arduino core (at least at the moment) seems to have troubles with mixed types and math. While you often get away with mixing integers with longs, small signed longs with unsigned longs in calculations, this might well have been the source of my issues. I strongly recommend being careful with number types and declarations. Since memory is more plentiful and speed is decent with the ESP32, I stuck (finally) with unsigned longs for my IR control program. I also had to re-write the basic sending routines in order to achieve proper timing. I think my resultant code, which sends each MARK with its companion SPACE is easily understood and less complex than the use of separate MARK and SPACE routines. I could not achieve robust timing with the latter. I will be posting on the ESP32 Forum regarding some of the issues with timing I observed, but the demo program here works well for Sony, Samsung and NEC control codes and can be extended, I am sure.
Stick to 41,000 Hz plus or minus a couple thousand for best results. If my sample code will not fit here, I will add as an attachment.
My sendMARKSPACE routine should work well for other MCU boardsand the UART hack approach -- it only requires a UART and the micros() function.).

I have attached a basic demo Arduino ESP 32 sketch outputting Sony, Samsung and NEC codes. I have included Mute or power codes so you can see it work w your device. I am attaching an AnalysIR session showing the output of the program in AnalysIR. I could not have accomplished this without the AnalysIR software and there A.IR Shield RX ($10 only!) to capture not only the output but to check my carrier frequency during early trials.
As always,YMMV.

Code: Select all

[code]
/*
* Author: J F Monthony based on  AnalysIR via https://www.AnalysIR.com/
*UART IR Control w ESP 32
*Title: ESP32UART_IR_Control_Basic
* Date: Sept 19, 2017
*
* License: Creative Commons, Attribution-NonCommercial-ShareAlike 4.0 International
*      http://creativecommons.org/licenses/by-nc-sa/4.0/
*
*
*
* Attribution: Please credit the Author on all media and provide a link to https://www.AnalysIR.com/
*
*
*You must set IRSerial2 as a HardwareSerial type for ESP32
*set using the UART number of the port ie 2
*Works best  at carrierFrequency of 41,000 and 41,600
*carrierFreq is now an unsigned long containing the final target frequency for the IR carrier
*
* AnalysIR software and an A.IR Shield RX receiver with frequency detection were invaluable in developing this applicarion 
* Allowed tuning of timing and confirmed frequency as well as recognizing format as NEC, Sony etc
*The use of complete series of zeros and then a 1010101010 pattern made it easier to track  
*Sending a single NEC code to debug and tune.  Many sketch variationscould be tuned to yield a 
*good NEC output with a to AnalysIR software but then tried other NEC test codes
*Sending 0x0L transmitts the minimal NEC pattern that was  impossible (for me)  to send succesfully without a major re-write of the sending functions
*The serial writes that generate the modulated carrier are cramed into the ether and spit out at the baud rate.
*With a long space ie NEC_ONE_SPACE, works out OK, but with NEC_ZERO_SPACE, timing goes bad.
*Each consecutive zero (in 0x0L ie 32 zeros) sent shows a shrinking following MARK
* The output in original AnalysIR routines (which worked fine on my 8266 device) showed progressively shorter Marks
*until one disappeared, leaving a big space.  In 32 bit NEC code, 4 to 5 bits disappear to shrinking Marks when sent 0x0L
* in final re-written routine, if a Mark exceeds the partner space by much, multiple repeat zeros will yield missing spaces 
* and 2X MARKS.  Testing with 0x0L as a code easily detects this.  Since a code w a missing space doesn't have the 
* required number of bits, always decodes as raw in AnalysIR
*As always, I learned a lot working through to a solution. The re-written sending code allowed me to produce functional IR signals for NEC, SONY and SAMSUNG devices
*Although there was a claim written saying that Serial(2) writes don't return until completed, this doesn't mean they have left
*the building... they accumulate likely in a buffer and are spit out in order but IR signal timing based on when the write routines are done is 
*not for the faint of heart.  Since the total number of writes needed varries depending on how may ones and zeros are to be output
*there is a quite a few combination to accomodate in a 32 bit code.
*Most routines that went back after MARK shoved the correct number of UART write bits at the Serial port  
*and tried to use SPACE duration to send the next space failed particulaely for NEC codes with lots of zeros
*
*In the end, I think my rewritten code is straightforward and simpler. Shove the correct number of UART WRITE bits at the Serial port 
*They come out in the requisite MARK period  and stop when all "printed" to the IR LED.
*Then just wait till the total time for the MARK + its companion SPACE to run out.  This lets the buffered, multicore world the compiler
*creates do its thing getting the MARK right and worked a treat to just wait till the end of the following space before getting the next part of the code
*/

//**************************************************************************************
#include <HardwareSerial.h>                                                // Not finally necessary as an explicit include -- likely in core, but left here to guide users to the appropriate files
#include "soc/uart_reg.h"  

 # define TX 28                                                                            // ESP32 Device pinouts vary.  TX2 is pin 28 on WROOM, Wire your LED output  transistor to TX2 on your board
//  # define TX TX2                                                                      // DoIT ESP32 Tx2 from Wroom pin 28 maps to GPIO 17 and to my board pin labeled TX2  
                                                                                                      // DoIT ESP32 TX pin defined as static constant  TX2  in \variants\Doit32\pins.h for my variant

unsigned long sigTime = 0;                                                    //used in mark & space functions to keep track of time
unsigned long sigStart = 0;                                                     //used to calculate correct length of existing signal, to handle some repeats
unsigned long fudgefactor = 0;

unsigned char DUTY = 0xC3;                                                 // 60% duty cycle  Start bit 1  1100  0011 Stop 1 = 6 on , 4 off per Write (0xC3)

HardwareSerial IRSerial2 (2);                                               // set ups IRSerial2 as a Hardware serial port on UART26                                
unsigned long carrierFreq = 41000;                                              // happier with a higher frequency, give it one set to 42600 Good for SONY, NEC and SAMSUNG

//**********************************************Serial(2) init routine*************************************************************************************************
  void IRSerial2init (unsigned long localHz)
  {
 
     if ((localHz < 30000L)|| (localHz>55000))                  // don't need wild frequencies
      {
        localHz = 41600L;                                                      // just use a 1 or other low number to accept current default carrierFreq
       }

       
    if (localHz == carrierFreq)
     {
      return;
      }
    
HardwareSerial IRSerial2 (2);
   pinMode(TX, OUTPUT);
   digitalWrite(TX, LOW);
  #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV);                                                                           // can be hard on IR LED to run as OFF = HIGH 
 
   unsigned long resetHz = 10 * localHz;
    IRSerial2.begin(resetHz);  
    
   #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV);                                                                            // begin may reset to OFF = HIGH, so can't hurt to set again.
 
   carrierFreq = localHz;                                            // save it         

  }                                                                                                                                                                                                  

//**********************************************SETUP**********************************************
void setup() {
 
  pinMode(TX, OUTPUT);
  digitalWrite(TX, LOW);
  pinMode(BUILTIN_LED, OUTPUT);                           // most boards pre-define BUILTIN_LED or LED_BUILTIN or both 

 Serial.begin(115200);                                                   // uncomment to print don't uncomment if you want to try and use Serial.Write for IR output
 //Serial.begin(carrierFreq*10);                                    // uncomment to run IR on "Serial" without any Hardware Serial declararion. Use TX0 on your board

   IRSerial2.begin((carrierFreq*10));  
   #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV); 
   
   Serial.println (carrierFreq);
                                                                                                               
/*  The timming of the MARKs on the ESP32 is strange.  at a constant 41,000 Hz carrier, one can tune at least for SONY, NEC and SAMSUNG
 *   default duty cycle of 60% works fine.  Too much complexity in the init routines, just change value in sketch if needed.
 *   
 *   This will also be a base point for IR using timers and/or the internal RMT hardware (poorly documented especially in Arduino branch)
 *   I have working timer software, but it uses interrupts and this routine does not so may be complimentary in some applications
 *   I would like to see if restricting portions of the code to run on a single core would have the timing behaving normally
 *   On the ESP8266, you can switch to 36,000 Hz carrier, no problems w original AnalysIR UART Hack approach
 *   Try it here if you want to see it hang!
 */
 }
 
 //********************************Begin LOOP*********************************************************************
void loop() {                                                                              //just send example signals ever 5 seconds at a range of carrier frequencies

     sendHexNEC(0xFFFF0000UL,32,1);                             // good test code for AnalysIR based tuning
//     Serial.println(carrierFreq);                  
      delay(2000);                                                                    //wait 2 seconds between each signal (change to suit)
     sendHexSAMSUNG(0xFFFF0000UL);                       // good test code to look for timing issues
     delay(2000);
     sendHexNEC(0x4BB4807FUL,32,2);                             // NEC Power Code, or use 4BB4807F Mute code
      delay(2000);                                                                   
     sendHexSAMSUNG(0xE0E0F00FUL);                         // Samsung Mute E0E0F00F code + UL for unsigned long type or use E0E040BF power code
//     Serial.println(carrierFreq);                                        
      delay(2000);
     sendHexSAMSUNG(0xE0E0E01FUL);                       // Samsung Vol+ code E0E0E01F
//     Serial.println(carrierFreq); 
      delay(2000);
     sendHexSONY(0XA91, 12, 1);                                   // Sony code Power
     Serial.println(carrierFreq);  
      delay(2000);
     sendHexSONY(0x100,12,2);                                     // Good Test Pattern
//     Serial.println(carrierFreq);  
      delay(2000);
     sendHexSONY(0x7D1, 12, 2 );                                   // Sony code Disc skip
 //    Serial.println(carrierFreq);  
      delay(2000);    
     
      }                                           
 
//********************************************************************************************************************

void sendHexSAMSUNG(unsigned long sigCode)
 {

     #define SAMSUNG_HEADER_MARK        4560      // 4400....4500....4560   Recorded changes during optimization for recognition by AnaysIR
     #define SAMSUNG_HEADER_SPACE      4560      // 4600 ..............4560.....
     #define SAMSUNG_ONE_MARK                  480      //   450.........500...480
     #define SAMSUNG_ZERO_MARK               470       //   480...--> 500...480......470
     #define SAMSUNG_ONE_SPACE             1680      // 1640..............................1680
     #define SAMSUNG_ZERO_SPACE             620      //   620......600.......620
     #define SAMSUNG_TRAILER_MARK         490      //   500............................490
  //***************************do some math here not in critical timing loops of Send routines********************
    unsigned long  SAMSUNG_HEADER_TOTAL            =  SAMSUNG_HEADER_MARK + SAMSUNG_HEADER_SPACE;
    unsigned long SAMSUNG_ONE_TOTAL                     =  SAMSUNG_ONE_MARK + SAMSUNG_ONE_SPACE;   
    unsigned long SAMSUNG_ZERO_TOTAL                  =  SAMSUNG_ZERO_MARK + SAMSUNG_ZERO_SPACE;     

  /*   All my codes are 32 bit Samsung codes
    *   A basic 32 bit Samsunf signal is made up of:
    *  1 x 4600 uSec Header Mark, followed by
    *  1 x 4600 uSec Header Space, followed by
    *  32 x data bits all mark bits are 600 micros followed by  either 500 uS for 0 bit or 1500-1600 us for 1. Space is either 500 (0) or 1500 (1) ie 1x and 3x
    * only have a couple samsung devices so do not know if all follow this standard
    *  1 x 500 uSec Trailer Mark
    *  My Samsung Remote always sends 2 complete codes, with a 108 micros timing.  for 32 bit, 
    *  My frequency is 37500 to 37,800 likely good at 40 and for sure at 38
    *  May vary w the exact frequency you achieve in modulating IR signal.. Again, AnalysIR shows when codes are "Sony" good
*/

   unsigned long  localHz = 41200;                                                                           //optimum for my system and AnalysIR

   if (localHz !=carrierFreq)
   {
    IRSerial2init(localHz);
   }
  
    unsigned long bitMask = (unsigned long) 1 << (32 - 1);                           //  ALL 32 bits
    unsigned long SAMSUNGbitMask = bitMask ;                                            // preserve this for Samsung for use with repeats 
               
  sigTime = micros();                                                                                        //keeps rolling track of signal time to avoid impact of loop & code execution delays
  sigStart = sigTime;                                                                                        //remember for calculating first repeat gap (space), must end 108ms after signal starts
  unsigned long repeatTimeout = 45000;


                            // First send header Mark & Space
  sendMarkSpace(SAMSUNG_HEADER_MARK,SAMSUNG_HEADER_TOTAL);
  sigTime = micros();
                      
  while (bitMask)                                                                                                                     //loop through data bits
  { 
     if (bitMask & sigCode) 
         {                                                                                                                                       // its a One bit
           sendMarkSpace(SAMSUNG_ONE_MARK,SAMSUNG_ONE_TOTAL);
           sigTime = micros();
         }
    else
       {                                                                                                                                            // its a Zero bit
        sendMarkSpace(SAMSUNG_ZERO_MARK,SAMSUNG_ZERO_TOTAL);
        sigTime = micros();
       } 
    
    bitMask = (unsigned long) bitMask >> 1; // shift the mask bit along until it reaches zero & we exit the while loop
  }

 // **************************************Once Done******************************************
  //now repeat the SAMSUNG code completely
  /*  A repeat signal consists of
   *   A space which ends 45 microseconds after the START of the previous signal in this sequence  
   *   the same IR code retransmitted, so reset bitmask and calculate timing space
   *   First calcualte length of space before first repeat
   *  by getting length of signal to date and subtracting from 45 microseconds (repeatTimeout)
  */
//******first repeat must start 108ms after first signal**** to use sendMarkSpace, get a space to go with SAMSUNG_TRAILER_MARK

 unsigned long  bigspace((108000 - (sigTime - sigStart)));   //first repeat Header should start 108ms after first signal
    sendMarkSpace(SAMSUNG_TRAILER_MARK, (SAMSUNG_TRAILER_MARK + bigspace));   
    sigTime = micros();
    
     sendMarkSpace(SAMSUNG_HEADER_MARK,SAMSUNG_HEADER_TOTAL);
   
    bitMask = SAMSUNGbitMask; 
                                   
      while (bitMask)                                                                                                                     // loop through data bits
      { 
         if (bitMask & sigCode) 
           {                                                                                                                                        // its a One bit
             sendMarkSpace(SAMSUNG_ONE_MARK, SAMSUNG_ONE_TOTAL);
             sigTime = micros();
             }
        else
          {                                                                                                                                         // its a Zero bit
            sendMarkSpace(SAMSUNG_ZERO_MARK,SAMSUNG_ZERO_TOTAL);
            sigTime = micros();
           } 
    
       bitMask = (unsigned long) bitMask >> 1;                                                                 // shift the mask bit along until it reaches zero & we exit the while loop
      }
   
  sendMarkSpace(SAMSUNG_TRAILER_MARK,bigspace);   
    Serial.print("Finished one NEC at ");                                                                               // since we most likely will only ever send one repeat code, not really necessary
     Serial.println(carrierFreq);
   digitalWrite(BUILTIN_LED,LOW);                    
   }

 


//***********************************SendHexNEC ********************************************************
void sendHexNEC(unsigned long sigCode, byte numBits, int repeats) 
{
   digitalWrite(BUILTIN_LED,HIGH);                                                   //Power up
   
  /*  A basic 32 bit NEC signal is made up of:
   *  1 x 9000 uSec Header Mark, followed by
   *  1 x 4500 uSec Header Space, followed by
   *  32 x bits of  data  ( 1- bit = 560 uSec Mark followed by 1690 uSec space; 0 - bit= 560 uSec Mark follwed by 560 uSec Space)
   *  1 x 560 uSec Trailer Mark
   *  There can also be a generic repeat signal, which is usually not neccessary & can be replaced by sending multiple signals
   *  A single repeat signal is sent below, no matter how many are requested
   */

 #define  NEC_HEADER_MARK       8940UL       // 8940
  #define NEC_REPEAT_SPACE      2600UL      // 2600.........
  #define NEC_HEADER_SPACE     4500UL      // 4400........4420......4500
  #define NEC_ONE_MARK                 540UL                 //   540-->500........................540
  #define NEC_ZERO_MARK               540UL               //   540.....500.........................540...
  #define NEC_ONE_SPACE            1680UL           // 1640........................1680
  #define NEC_ZERO_SPACE            620UL             //    620.
  #define NEC_TRAILER_MARK        500UL          //    540...... 500......

 unsigned long  NEC_HEADER_TOTAL           = NEC_HEADER_MARK + NEC_HEADER_SPACE;
 unsigned long NEC_ONE_TOTAL                     = NEC_ONE_MARK + NEC_ONE_SPACE;   
 unsigned long NEC_ZERO_TOTAL                  = NEC_ZERO_MARK + NEC_ZERO_SPACE;   
 unsigned long NEC_REPEAT_TOTAL              = NEC_HEADER_MARK + NEC_REPEAT_SPACE;   

  unsigned long localHz = 40500;       // reads best on my system
  
 if (localHz !=carrierFreq)                     // so switch to optimum if needed
   {
    IRSerial2init(localHz);
   }
 
  unsigned long bitMask = (unsigned long) 1 << (numBits - 1);    //allows for signal from 1 bit up to 32 bits

        
  sigTime = micros();                                                                             //keeps rolling track of signal time to avoid impact of loop & code execution delays
  sigStart = sigTime;                                                                              //remember for calculating first repeat gap (space), must end 108ms after signal starts
  
 sendMarkSpace(NEC_HEADER_MARK, NEC_HEADER_TOTAL);    // data starts here
  sigTime = micros();                                                                                       // reset sig time after every MarkSpace pair
 
  while (bitMask) 
  {
    if (bitMask & sigCode)                                                             // its a One bit and stock works
      {                                                                
         sendMarkSpace(NEC_ONE_MARK, NEC_ONE_TOTAL);
         sigTime = micros();                                                         // prepare for next pair
        }
    else
      {                                                                                               // its a Zero bit
         sendMarkSpace(NEC_ZERO_MARK,  NEC_ZERO_TOTAL); 
         sigTime = micros(); 
        }
   bitMask = (unsigned long) bitMask >> 1;                                            // shift the mask bit along until it reaches zero & we exit the while loop
  }
 
 
  //need to add theNEC_ TRAILER_MARK and then send the requested number of NEC repeat signals.
  // Repeats can be useful for certain functions like Vol+, Vol- etc  but will just always do one repeat code
  /*  A repeat signal consists of
   *   A space which ends 108ms after the start of the last signal in this sequence
  *  1 x 9000 uSec Repeat Header Mark, followed by
  *  1 x 2250 uSec Repeat Header Space, followed by
  *  1- bit 560 uSec Mark 
  *  1 x 560 uSec repeat Trailer Mark
  *   Wrap up with a trailer mark and a space long enough for a repeat to follow it
  *   /First calculate length of space for first repeat by getting length of signal to date and subtracting from 108ms
 */

//******first repeat must start 108ms after first signal**** to use sendMarkSpace, get a space to go with NEC_TRAILER_MARK

 unsigned long  bigspace = ((108000 - (sigTime - sigStart)));   //first repeat Header should start 108ms after first signal
    sendMarkSpace(NEC_TRAILER_MARK,(NEC_TRAILER_MARK + bigspace));   
    sigTime = micros();
   sigStart = sigTime; 
    
    sendMarkSpace(NEC_HEADER_MARK,NEC_REPEAT_TOTAL);
    sigTime=micros();
  
   bigspace= (108000 - (sigTime - sigStart)); 
    sendMarkSpace( NEC_TRAILER_MARK,(NEC_TRAILER_MARK + bigspace));                // send Trialer Mark and space to set up for another 
     Serial.print("Finished one NEC at ");                                                                                                                                                             // since we most likely will only ever send one repeat code, not really necessary
     Serial.println(carrierFreq);
   digitalWrite(BUILTIN_LED,LOW);                                                                                                  // End Visual cue.  I also used a wire from Pin2 to drive a transistor powered on/power off "switch" on the modulating transistor but not needed
    sigTime=micros();                                                                                                                          // since we most likely will only ever send one repeat code, not really necessary
}


//**********************************SendHexSONY*************************************************************
void sendHexSONY(unsigned long sigCode, byte numBits, int repeats)
{
#define SONY_HEADER_MARK           2360UL            // 2360
#define SONY_HEADER_SPACE           650UL            //   650
#define SONY_ONE_MARK                  1124UL             // 2 x mark  1200  -->1124 better distribution observed is 100% 1200 to 1250
#define SONY_ZERO_MARK                 470UL             // 490--> 470
#define SONY_ONE_SPACE                 670UL             // 680....670
#define SONY_ZERO_SPACE              670UL              // 680...670

unsigned long  SONY_HEADER_TOTAL           = SONY_HEADER_MARK + SONY_HEADER_SPACE;
unsigned long SONY_ONE_TOTAL                     = SONY_ONE_MARK + SONY_ONE_SPACE;   
unsigned long SONY_ZERO_TOTAL                  = SONY_ZERO_MARK + SONY_ZERO_SPACE;   

  /*   All my codes are 12 bit Sony codes
    *   A basic 12 bit Sony signal is made up of:
    *  1 x 2400 uSec Header Mark, followed by
    *  1 x 600 uSec Header Space, followed by
    *  12 x bits either 1200 uS for 0 bit or 1800 us for 1. Space is always 600us and mark is either 600 (0) or 1200 (1)
    *  Longer codes just add bits as line above
    *  1 x 500 uSec Trailer Mark
    *  My Sony Remote always sends 2 more complete codes, with a 45 msec timing.  for 12 bit, 
    *  Adjusted all spaces to 610 or multiple thereof and marks to 590 and multiples of to get my particular Sony to read
    *  May vary w the exact frequency you achieve in modulating IR signal.. Again, AnalysIR shows when codes are "Sony" good
*/
  unsigned long SonyBestHz = 41600;                  // optimum on my system
  
 if (SonyBestHz !=carrierFreq)
   {
    IRSerial2init(SonyBestHz);
   }
   
   unsigned long bitMask = (unsigned long) 1 << (numBits - 1);                     //allows for signal from 1 bit up to 32 bits
   unsigned long SonybitMask = bitMask ;                                                            // preserve this for Sony since we send 3 times and decrement bitMask
   unsigned long repeatTimeout = 45000;
                                                       
  sigTime = micros();                                                                                              //keeps rolling track of signal time to avoid impact of loop & code execution delays
  sigStart = sigTime;                                                                                              //remember for calculating first repeat gap (space), must end 108ms after signal starts
  
                           
  sendMarkSpace(SONY_HEADER_MARK,SONY_HEADER_TOTAL);     // First send header Mark & Space
  sigTime = micros();
                                  
  while (bitMask)                                                                                                  // loop through data bits
  { 
     if (bitMask & sigCode) 
         {                                                                                                                           //its a One bit
           sendMarkSpace(SONY_ONE_MARK,SONY_ONE_TOTAL);
           sigTime = micros();
         }
    else
       {                                                                                                                       // its a Zero bit
        sendMarkSpace(SONY_ZERO_MARK,SONY_ZERO_TOTAL);
        sigTime = micros();
       } 
    
    bitMask = (unsigned long) bitMask >> 1; // shift the mask bit along until it reaches zero & we exit the while loop
  }

 //  *************************************Once Done******************************************
  //now send the requested number of SONY repeat signals. 
  /* A repeat signal consists of
   *   A space which ends 45 microseconds after the START of the previous signal in this sequence  
   *   the same IR code retransmitted, so reset bitmask and calculate timing space
   *   First calcualte length of space before first repeat
   *  by getting length of signal to date and subtracting from 45 microseconds (repeatTimeout)
  */ 

   while (repeats--  > 0)          // now send repeats at 45 microsecond intervals
     {
         delayMicroseconds(repeatTimeout - (sigTime -sigStart));      // just let the time to the start of any repeats run out, even if no repeat wil be sent
         
         sigTime = micros();                                                      // set up for next repeat  sigTime is updated each send
         sigStart = sigTime;
   
         
         sendMarkSpace(SONY_HEADER_MARK, SONY_HEADER_TOTAL);
         bitMask = SonybitMask;
         sigTime = micros();
         sigStart = sigTime;
                                  //loop through data bits
         while (bitMask)
         { 
            if (bitMask & sigCode) 
              {                                                                                                                           //its a One bit
                sendMarkSpace(SONY_ONE_MARK,SONY_ONE_TOTAL);
                sigTime = micros();
               }
            else
             {                                                                                                                       // its a Zero bit
               sendMarkSpace(SONY_ZERO_MARK,SONY_ZERO_TOTAL);
               sigTime = micros();
                } ana
    
       bitMask = (unsigned long) bitMask >> 1; // shift the mask bit along until it reaches zero & we exit the while loop
        }
   
     }
   
  delay(300);                                                                                                         // don't pile them up
  Serial.println("Completed Sony Send"); 
 }



//****************************************sendMarkSpace******************************************************************************************************************
void sendMarkSpace(unsigned long internalMark,unsigned long internalTotal)
{
             //*******************************Load up the Mark Writes *******************************************
             
  unsigned long cycleCount = (internalMark*carrierFreq)/1000000;          // expects carrierFreq to be unsigned long above 30,000 Hz
 
     while (cycleCount) 
      {
         IRSerial2.write(DUTY);                                                           // while (true)  {  //send continuous carrier, for testing, signal generator or just generic PWM 
//           Serial.write(DUTY);                                                             // uncomment for Serial. or TX0, Comment out preceeding IRSerial2.write line above
         cycleCount = cycleCount -1;                                                 //write a character to emulate carrier, character value determines duty cycle.
       }

 unsigned long endTime = (sigTime +internalTotal);                  // 
 /*
 * /We have stuffed them somewhere, now wait till time is up for both the MARK and the requisite following SPACE  
   * This takes care of the mark, no matter what the lag between finishing the write(Duty) and the actual end of the UART sending
   * So now we just wait for the rest of the total time, finishing mark and adding space. 
   */
                     //***************************Now Wait for the endTime to expire**********************************
                     
   while (micros()<endTime)                                                                            //   just loop like everyone else does
    {
    
   }
                                                              
}
 //*************************************END of CODE***********************************************************
You do not have the required permissions to view the files attached to this post.

User avatar
AnalysIR
Site Admin
Posts: 695
Joined: Sat Aug 31, 2013 3:51 pm
Location: Dublin, Ireland
Contact:

Re: ESP32 and IR Control

Post by AnalysIR »

@DrJFM

Thanks for the post, I will study it in detail during the next week...

...

DrJFM
Posts: 6
Joined: Wed May 04, 2016 4:20 pm

Re: ESP32 and IR Control

Post by DrJFM »

The simplified output routines I developed work fine on the ESP8266. On the 8266, I can vary the frequency widely and get complete code output, usually recognized by AnalysIR software. NEC code recognized from 33000Hz to 42000 Hz.
Same code on ESP32 goes bad at frequencies below 40000 Hz when writing many zeros, a space just disappears giving a double width mark and too few bits of data. Appears as a raw, as it should. Session with examples attached.
Code runs on 8266. May need to change pin for different boards. Output on Pin 0, TX1

Code: Select all

*
 * Adapted for OakESP8266 by James Monthony  Sept 2017
* Author: AnalysIR via https://www.AnalysIR.com/
*
* Date: 1st October 2015 V1.0
*
* License: Creative Commons, Attribution-NonCommercial-ShareAlike 4.0 International
*      http://creativecommons.org/licenses/by-nc-sa/4.0/
*
* For Commercial use or alternative License: Please contact the Author, via the website above
*
* Attribution: Please credit the Author on all media and provide a link to https://www.AnalysIR.com/
*
* Feedback: We would love to hear about how you use this software & suggestions for improvement.
*
* Tested with Arduino IDE 1.6.5. Board: ESP8266 NodeMCU 1.0 (ESP-12E Module), 80 MHz, Serial 115200, 4M(3M SPIFFS) on COMxx, IDE ESP8266 Boards Manager 2.3.0
*
* Questions & Issues via: https://IRforum.AnalysIR.com/  for uPWM & IR related queries only (excludes IDE, WiFi & NodeMCU support which should be directed to the appropriate forum)
*
* Assumes familiarity with ESP8266 NodeMCU & Arduino IDE
*
*/

#include "uart_register.h"
#include <ESP8266WiFi.h>
#define ESP8266PLATFORM  true    //set to true for ESP8266 platform 

#define TX 0                                                                   //Oak2866 pin 0 is TX1 and on the Oak8266 is Pin 0  verify this for your own particular platform

/*
* Author: J F Monthony based on  AnalysIR via https://www.AnalysIR.com/
*UART IR Control w ESP 32
*Title: ESP32UART_IR_Control_Basic
* Date: Sept 19, 2017
*
* License: Creative Commons, Attribution-NonCommercial-ShareAlike 4.0 International
*      http://creativecommons.org/licenses/by-nc-sa/4.0/
*
*
*
* Attribution: Please credit the Author on all media and provide a link to https://www.AnalysIR.com/
*
*
*You must set IRSerial2 as a HardwareSerial type for ESP32
*set using the UART number of the port ie 2
*Works best  at carrierFrequency of 41,000 and 41,600
*carrierFreq is now an unsigned long containing the final target frequency for the IR carrier
*
* AnalysIR software and an A.IR Shield RX receiver with frequency detection were invaluable in developing this applicarion 
* Allowed tuning of timing and confirmed frequency as well as recognizing format as NEC, Sony etc
*The use of complete series of zeros and then a 1010101010 pattern made it easier to track  
*Sending a single NEC code to debug and tune.  Many sketch variationscould be tuned to yield a 
*good NEC output with a to AnalysIR software but then tried other NEC test codes
*Sending 0x0L transmitts the minimal NEC pattern that was  impossible (for me)  to send succesfully without a major re-write of the sending functions
*The serial writes that generate the modulated carrier are cramed into the ether and spit out at the baud rate.
*With a long space ie NEC_ONE_SPACE, works out OK, but with NEC_ZERO_SPACE, timing goes bad.
*Each consecutive zero (in 0x0L ie 32 zeros) sent shows a shrinking following MARK
* The output in original AnalysIR routines (which worked fine on my 8266 device) showed progressively shorter Marks
*until one disappeared, leaving a big space.  In 32 bit NEC code, 4 to 5 bits disappear to shrinking Marks when sent 0x0L
* in final re-written routine, if a Mark exceeds the partner space by much, multiple repeat zeros will yield missing spaces 
* and 2X MARKS.  Testing with 0x0L as a code easily detects this.  Since a code w a missing space doesn't have the 
* required number of bits, always decodes as raw in AnalysIR
*As always, I learned a lot working through to a solution. The re-written sending code allowed me to produce functional IR signals for NEC, SONY and SAMSUNG devices
*Although there was a claim written saying that Serial(2) writes don't return until completed, this doesn't mean they have left
*the building... they accumulate likely in a buffer and are spit out in order but IR signal timing based on when the write routines are done is 
*not for the faint of heart.  Since the total number of writes needed varries depending on how may ones and zeros are to be output
*there is a quite a few combination to accomodate in a 32 bit code.
*Most routines that went back after MARK shoved the correct number of UART write bits at the Serial port  
*and tried to use SPACE duration to send the next space failed particulaely for NEC codes with lots of zeros
*
*In the end, I think my rewritten code is straightforward and simpler. Shove the correct number of UART WRITE bits at the Serial port 
*They come out in the requisite MARK period  and stop when all "printed" to the IR LED.
*Then just wait till the total time for the MARK + its companion SPACE to run out.  This lets the buffered, multicore world the compiler
*creates do its thing getting the MARK right and worked a treat to just wait till the end of the following space before getting the next part of the code
*/

//**************************************************************************************
                                                                            // DoIT ESP32 TX pin defined as static constant  TX2  in \variants\Doit32\pins.h for my variant

unsigned long sigTime = 0;                                                    //used in mark & space functions to keep track of time
unsigned long sigStart = 0;                                                     //used to calculate correct length of existing signal, to handle some repeats
unsigned long fudgefactor = 0;

unsigned char DUTY = 0xC3;                                                 // 60% duty cycle  Start bit 1  1100  0011 Stop 1 = 6 on , 4 off per Write (0xC3)



// HardwareSerial IRSerial2 (2);                                               // set ups IRSerial2 as a Hardware serial port on UART26                                
unsigned long carrierFreq = 41000;                                              // happier with a higher frequency, give it one set to 42600 Good for SONY, NEC and SAMSUNG

//**********************************************Serial(2) init routine*************************************************************************************************
  void Serial1init (unsigned long localHz)
  {
 
     if ((localHz < 30000L)|| (localHz>55000))                  // don't need wild frequencies
      {
        localHz = 41600L;                                                      // just use a 1 or other low number to accept current default carrierFreq
       }

       
    if (localHz == carrierFreq)
     {
      return;
      }
    
// HardwareSerial IRSerial2 (2);
   pinMode(TX, OUTPUT);
   digitalWrite(TX, LOW);
/*   
  #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV);                                                                           // can be hard on IR LED to run as OFF = HIGH 
 */
 #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))
   SET_PERI_REG_MASK(UART_CONF0(UART1) , BIT22);
 
   unsigned long resetHz = 10 * localHz;
    Serial1.begin(resetHz);  
   /* 
   #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))         // similar but not identical to usage for ESP 8266 Inverts signal from off = high to off = low
   SET_PERI_REG_MASK(UART_CONF0_REG(2) , UART_TXD_INV);                                                                            // begin may reset to OFF = HIGH, so can't hurt to set again.
 */
  #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))
  SET_PERI_REG_MASK(UART_CONF0(UART1) , BIT22);
 
   carrierFreq = localHz;                                            // save it         

  }                                                                                                                                                                                                  

//**********************************************SETUP**********************************************
void setup() {
 
  pinMode(TX, OUTPUT);
  digitalWrite(TX, LOW);
  pinMode(BUILTIN_LED, OUTPUT);                           // most boards pre-define BUILTIN_LED or LED_BUILTIN or both 

 Serial.begin(115200);                                                   // uncomment to print don't uncomment if you want to try and use Serial.Write for IR output
 //Serial.begin(carrierFreq*10);                                    // uncomment to run IR on "Serial" without any Hardware Serial declararion. Use TX0 on your board

   Serial1.begin(carrierFreq*10);  
 #define SET_PERI_REG_MASK(reg, mask)   WRITE_PERI_REG((reg), (READ_PERI_REG(reg)|(mask)))
  SET_PERI_REG_MASK(UART_CONF0(UART1) , BIT22);
   
   Serial.println (carrierFreq);
                                                                                                               
/*  The timming of the MARKs on the ESP32 is strange.  at a constant 41,000 Hz carrier, one can tune at least for SONY, NEC and SAMSUNG
 *   default duty cycle of 60% works fine.  Too much complexity in the init routines, just change value in sketch if needed.
 *   
 *   This will also be a base point for IR using timers and/or the internal RMT hardware (poorly documented especially in Arduino branch)
 *   I have working timer software, but it uses interrupts and this routine does not so may be complimentary in some applications
 *   I would like to see if restricting portions of the code to run on a single core would have the timing behaving normally
 *   On the ESP8266, you can switch to 36,000 Hz carrier, no problems w original AnalysIR UART Hack approach
 *   Try it here if you want to see it hang!
 */
 }
 
 //********************************Begin LOOP*********************************************************************
void loop() {                                                                              //just send example signals ever 5 seconds at a range of carrier frequencies

     sendHexNEC(0xFFFF0000UL,32,1);                             // good test code for AnalysIR based tuning
//     Serial.println(carrierFreq);                  
      delay(2000);                                                                    //wait 2 seconds between each signal (change to suit)
     sendHexSAMSUNG(0xFFFF0000UL);                       // good test code to look for timing issues
     delay(2000);
     sendHexNEC(0x4BB4807FUL,32,2);                             // NEC Power Code, or use 4BB4807F Mute code
      delay(2000);                                                                   
     sendHexSAMSUNG(0xE0E0F00FUL);                         // Samsung Mute E0E0F00F code + UL for unsigned long type or use E0E040BF power code
//     Serial.println(carrierFreq);                                        
      delay(2000);
     sendHexSAMSUNG(0xE0E0E01FUL);                       // Samsung Vol+ code E0E0E01F
//     Serial.println(carrierFreq); 
      delay(2000);
     sendHexSONY(0XA91, 12, 1);                                   // Sony code Power
     Serial.println(carrierFreq);  
      delay(2000);
     sendHexSONY(0x100,12,2);                                     // Good Test Pattern
//     Serial.println(carrierFreq);  
      delay(2000);
     sendHexSONY(0x7D1, 12, 2 );                                   // Sony code Disc skip
 //    Serial.println(carrierFreq);  
      delay(2000);    
     
      }                                           
 
//********************************************************************************************************************

void sendHexSAMSUNG(unsigned long sigCode)
 {

     #define SAMSUNG_HEADER_MARK        4560      // 4400....4500....4560   Recorded changes during optimization for recognition by AnaysIR
     #define SAMSUNG_HEADER_SPACE      4560      // 4600 ..............4560.....
     #define SAMSUNG_ONE_MARK                  480      //   450.........500...480
     #define SAMSUNG_ZERO_MARK               470       //   480...--> 500...480......470
     #define SAMSUNG_ONE_SPACE             1680      // 1640..............................1680
     #define SAMSUNG_ZERO_SPACE             620      //   620......600.......620
     #define SAMSUNG_TRAILER_MARK         490      //   500............................490
  //***************************do some math here not in critical timing loops of Send routines********************
    unsigned long  SAMSUNG_HEADER_TOTAL            =  SAMSUNG_HEADER_MARK + SAMSUNG_HEADER_SPACE;
    unsigned long SAMSUNG_ONE_TOTAL                     =  SAMSUNG_ONE_MARK + SAMSUNG_ONE_SPACE;   
    unsigned long SAMSUNG_ZERO_TOTAL                  =  SAMSUNG_ZERO_MARK + SAMSUNG_ZERO_SPACE;     

  /*   All my codes are 32 bit Samsung codes
    *   A basic 32 bit Samsunf signal is made up of:
    *  1 x 4600 uSec Header Mark, followed by
    *  1 x 4600 uSec Header Space, followed by
    *  32 x data bits all mark bits are 600 micros followed by  either 500 uS for 0 bit or 1500-1600 us for 1. Space is either 500 (0) or 1500 (1) ie 1x and 3x
    * only have a couple samsung devices so do not know if all follow this standard
    *  1 x 500 uSec Trailer Mark
    *  My Samsung Remote always sends 2 complete codes, with a 108 micros timing.  for 32 bit, 
    *  My frequency is 37500 to 37,800 likely good at 40 and for sure at 38
    *  May vary w the exact frequency you achieve in modulating IR signal.. Again, AnalysIR shows when codes are "Sony" good
*/

   unsigned long  localHz = 39000;                                                                           //optimum for my system and AnalysIR

   if (localHz !=carrierFreq)
   {
    Serial1init(localHz);
   }
  
    unsigned long bitMask = (unsigned long) 1 << (32 - 1);                           //  ALL 32 bits
    unsigned long SAMSUNGbitMask = bitMask ;                                            // preserve this for Samsung for use with repeats 
               
  sigTime = micros();                                                                                        //keeps rolling track of signal time to avoid impact of loop & code execution delays
  sigStart = sigTime;                                                                                        //remember for calculating first repeat gap (space), must end 108ms after signal starts
  unsigned long repeatTimeout = 45000;


                            // First send header Mark & Space
  sendMarkSpace(SAMSUNG_HEADER_MARK,SAMSUNG_HEADER_TOTAL);
  sigTime = micros();
                      
  while (bitMask)                                                                                                                     //loop through data bits
  { 
     if (bitMask & sigCode) 
         {                                                                                                                                       // its a One bit
           sendMarkSpace(SAMSUNG_ONE_MARK,SAMSUNG_ONE_TOTAL);
           sigTime = micros();
         }
    else
       {                                                                                                                                            // its a Zero bit
        sendMarkSpace(SAMSUNG_ZERO_MARK,SAMSUNG_ZERO_TOTAL);
        sigTime = micros();
       } 
    
    bitMask = (unsigned long) bitMask >> 1; // shift the mask bit along until it reaches zero & we exit the while loop
  }

 // **************************************Once Done******************************************
  //now repeat the SAMSUNG code completely
  /*  A repeat signal consists of
   *   A space which ends 45 microseconds after the START of the previous signal in this sequence  
   *   the same IR code retransmitted, so reset bitmask and calculate timing space
   *   First calcualte length of space before first repeat
   *  by getting length of signal to date and subtracting from 45 microseconds (repeatTimeout)
  */
//******first repeat must start 108ms after first signal**** to use sendMarkSpace, get a space to go with SAMSUNG_TRAILER_MARK

 unsigned long  bigspace((108000 - (sigTime - sigStart)));   //first repeat Header should start 108ms after first signal
    sendMarkSpace(SAMSUNG_TRAILER_MARK, (SAMSUNG_TRAILER_MARK + bigspace));   
    sigTime = micros();
    
     sendMarkSpace(SAMSUNG_HEADER_MARK,SAMSUNG_HEADER_TOTAL);
   
    bitMask = SAMSUNGbitMask; 
                                   
      while (bitMask)                                                                                                                     // loop through data bits
      { 
         if (bitMask & sigCode) 
           {                                                                                                                                        // its a One bit
             sendMarkSpace(SAMSUNG_ONE_MARK, SAMSUNG_ONE_TOTAL);
             sigTime = micros();
             }
        else
          {                                                                                                                                         // its a Zero bit
            sendMarkSpace(SAMSUNG_ZERO_MARK,SAMSUNG_ZERO_TOTAL);
            sigTime = micros();
           } 
    
       bitMask = (unsigned long) bitMask >> 1;                                                                 // shift the mask bit along until it reaches zero & we exit the while loop
      }
   
  sendMarkSpace(SAMSUNG_TRAILER_MARK,bigspace);   
    Serial.print("Finished one NEC at ");                                                                               // since we most likely will only ever send one repeat code, not really necessary
     Serial.println(carrierFreq);
   digitalWrite(BUILTIN_LED,LOW);                    
   }

 


//***********************************SendHexNEC ********************************************************
void sendHexNEC(unsigned long sigCode, byte numBits, int repeats) 
{
   digitalWrite(BUILTIN_LED,HIGH);                                                   //Power up
   
  /*  A basic 32 bit NEC signal is made up of:
   *  1 x 9000 uSec Header Mark, followed by
   *  1 x 4500 uSec Header Space, followed by
   *  32 x bits of  data  ( 1- bit = 560 uSec Mark followed by 1690 uSec space; 0 - bit= 560 uSec Mark follwed by 560 uSec Space)
   *  1 x 560 uSec Trailer Mark
   *  There can also be a generic repeat signal, which is usually not neccessary & can be replaced by sending multiple signals
   *  A single repeat signal is sent below, no matter how many are requested
   */

 #define  NEC_HEADER_MARK       8940UL       // 8940
  #define NEC_REPEAT_SPACE      2600UL      // 2600.........
  #define NEC_HEADER_SPACE     4500UL      // 4400........4420......4500
  #define NEC_ONE_MARK                 540UL                 //   540-->500........................540
  #define NEC_ZERO_MARK               540UL               //   540.....500.........................540...
  #define NEC_ONE_SPACE            1680UL           // 1640........................1680
  #define NEC_ZERO_SPACE            620UL             //    620.
  #define NEC_TRAILER_MARK        500UL          //    540...... 500......

 unsigned long  NEC_HEADER_TOTAL           = NEC_HEADER_MARK + NEC_HEADER_SPACE;
 unsigned long NEC_ONE_TOTAL                     = NEC_ONE_MARK + NEC_ONE_SPACE;   
 unsigned long NEC_ZERO_TOTAL                  = NEC_ZERO_MARK + NEC_ZERO_SPACE;   
 unsigned long NEC_REPEAT_TOTAL              = NEC_HEADER_MARK + NEC_REPEAT_SPACE;   

  unsigned long localHz = 33000;       // reads best on my system
  
 if (localHz !=carrierFreq)                     // so switch to optimum if needed
   {
    Serial1init(localHz);
   }
 
  unsigned long bitMask = (unsigned long) 1 << (numBits - 1);    //allows for signal from 1 bit up to 32 bits

        
  sigTime = micros();                                                                             //keeps rolling track of signal time to avoid impact of loop & code execution delays
  sigStart = sigTime;                                                                              //remember for calculating first repeat gap (space), must end 108ms after signal starts
  
 sendMarkSpace(NEC_HEADER_MARK, NEC_HEADER_TOTAL);    // data starts here
  sigTime = micros();                                                                                       // reset sig time after every MarkSpace pair
 
  while (bitMask) 
  {
    if (bitMask & sigCode)                                                             // its a One bit and stock works
      {                                                                
         sendMarkSpace(NEC_ONE_MARK, NEC_ONE_TOTAL);
         sigTime = micros();                                                         // prepare for next pair
        }
    else
      {                                                                                               // its a Zero bit
         sendMarkSpace(NEC_ZERO_MARK,  NEC_ZERO_TOTAL); 
         sigTime = micros(); 
        }
   bitMask = (unsigned long) bitMask >> 1;                                            // shift the mask bit along until it reaches zero & we exit the while loop
  }
 
 
  //need to add theNEC_ TRAILER_MARK and then send the requested number of NEC repeat signals.
  // Repeats can be useful for certain functions like Vol+, Vol- etc  but will just always do one repeat code
  /*  A repeat signal consists of
   *   A space which ends 108ms after the start of the last signal in this sequence
  *  1 x 9000 uSec Repeat Header Mark, followed by
  *  1 x 2250 uSec Repeat Header Space, followed by
  *  1- bit 560 uSec Mark 
  *  1 x 560 uSec repeat Trailer Mark
  *   Wrap up with a trailer mark and a space long enough for a repeat to follow it
  *   /First calculate length of space for first repeat by getting length of signal to date and subtracting from 108ms
 */

//******first repeat must start 108ms after first signal**** to use sendMarkSpace, get a space to go with NEC_TRAILER_MARK

 unsigned long  bigspace = ((108000 - (sigTime - sigStart)));   //first repeat Header should start 108ms after first signal
    sendMarkSpace(NEC_TRAILER_MARK,(NEC_TRAILER_MARK + bigspace));   
    sigTime = micros();
   sigStart = sigTime; 
    
    sendMarkSpace(NEC_HEADER_MARK,NEC_REPEAT_TOTAL);
    sigTime=micros();
  
   bigspace= (108000 - (sigTime - sigStart)); 
    sendMarkSpace( NEC_TRAILER_MARK,(NEC_TRAILER_MARK + bigspace));                // send Trialer Mark and space to set up for another 
     Serial.print("Finished one NEC at ");                                                                                                                                                             // since we most likely will only ever send one repeat code, not really necessary
     Serial.println(carrierFreq);
   digitalWrite(BUILTIN_LED,LOW);                                                                                                  // End Visual cue.  I also used a wire from Pin2 to drive a transistor powered on/power off "switch" on the modulating transistor but not needed
    sigTime=micros();                                                                                                                          // since we most likely will only ever send one repeat code, not really necessary
}


//**********************************SendHexSONY*************************************************************
void sendHexSONY(unsigned long sigCode, byte numBits, int repeats)
{
#define SONY_HEADER_MARK           2360UL            // 2360
#define SONY_HEADER_SPACE           650UL            //   650
#define SONY_ONE_MARK                  1124UL             // 2 x mark  1200  -->1124 better distribution observed is 100% 1200 to 1250
#define SONY_ZERO_MARK                 470UL             // 490--> 470
#define SONY_ONE_SPACE                 670UL             // 680....670
#define SONY_ZERO_SPACE              670UL              // 680...670

unsigned long  SONY_HEADER_TOTAL           = SONY_HEADER_MARK + SONY_HEADER_SPACE;
unsigned long SONY_ONE_TOTAL                     = SONY_ONE_MARK + SONY_ONE_SPACE;   
unsigned long SONY_ZERO_TOTAL                  = SONY_ZERO_MARK + SONY_ZERO_SPACE;   

  /*   All my codes are 12 bit Sony codes
    *   A basic 12 bit Sony signal is made up of:
    *  1 x 2400 uSec Header Mark, followed by
    *  1 x 600 uSec Header Space, followed by
    *  12 x bits either 1200 uS for 0 bit or 1800 us for 1. Space is always 600us and mark is either 600 (0) or 1200 (1)
    *  Longer codes just add bits as line above
    *  1 x 500 uSec Trailer Mark
    *  My Sony Remote always sends 2 more complete codes, with a 45 msec timing.  for 12 bit, 
    *  Adjusted all spaces to 610 or multiple thereof and marks to 590 and multiples of to get my particular Sony to read
    *  May vary w the exact frequency you achieve in modulating IR signal.. Again, AnalysIR shows when codes are "Sony" good
*/
  unsigned long SonyBestHz = 41600;                  // optimum on my system
  
 if (SonyBestHz !=carrierFreq)
   {
    Serial1init(SonyBestHz);
   }
   
   unsigned long bitMask = (unsigned long) 1 << (numBits - 1);                     //allows for signal from 1 bit up to 32 bits
   unsigned long SonybitMask = bitMask ;                                                            // preserve this for Sony since we send 3 times and decrement bitMask
   unsigned long repeatTimeout = 45000;
                                                       
  sigTime = micros();                                                                                              //keeps rolling track of signal time to avoid impact of loop & code execution delays
  sigStart = sigTime;                                                                                              //remember for calculating first repeat gap (space), must end 108ms after signal starts
  
                           
  sendMarkSpace(SONY_HEADER_MARK,SONY_HEADER_TOTAL);     // First send header Mark & Space
  sigTime = micros();
                                  
  while (bitMask)                                                                                                  // loop through data bits
  { 
     if (bitMask & sigCode) 
         {                                                                                                                           //its a One bit
           sendMarkSpace(SONY_ONE_MARK,SONY_ONE_TOTAL);
           sigTime = micros();
         }
    else
       {                                                                                                                       // its a Zero bit
        sendMarkSpace(SONY_ZERO_MARK,SONY_ZERO_TOTAL);
        sigTime = micros();
       } 
    
    bitMask = (unsigned long) bitMask >> 1; // shift the mask bit along until it reaches zero & we exit the while loop
  }

 //  *************************************Once Done******************************************
  //now send the requested number of SONY repeat signals. 
  /* A repeat signal consists of
   *   A space which ends 45 microseconds after the START of the previous signal in this sequence  
   *   the same IR code retransmitted, so reset bitmask and calculate timing space
   *   First calcualte length of space before first repeat
   *  by getting length of signal to date and subtracting from 45 microseconds (repeatTimeout)
  */ 

   while (repeats--  > 0)          // now send repeats at 45 microsecond intervals
     {
         delayMicroseconds(repeatTimeout - (sigTime -sigStart));      // just let the time to the start of any repeats run out, even if no repeat wil be sent
         
         sigTime = micros();                                                      // set up for next repeat  sigTime is updated each send
         sigStart = sigTime;
   
         
         sendMarkSpace(SONY_HEADER_MARK, SONY_HEADER_TOTAL);
         bitMask = SonybitMask;
         sigTime = micros();
         sigStart = sigTime;
                                  //loop through data bits
         while (bitMask)
         { 
            if (bitMask & sigCode) 
              {                                                                                                                           //its a One bit
                sendMarkSpace(SONY_ONE_MARK,SONY_ONE_TOTAL);
                sigTime = micros();
               }
            else
             {                                                                                                                       // its a Zero bit
               sendMarkSpace(SONY_ZERO_MARK,SONY_ZERO_TOTAL);
               sigTime = micros();
                } 
    
       bitMask = (unsigned long) bitMask >> 1; // shift the mask bit along until it reaches zero & we exit the while loop
        }
   
     }
   
  delay(300);                                                                                                         // don't pile them up
  Serial.println("Completed Sony Send"); 
 }



//****************************************sendMarkSpace******************************************************************************************************************
void sendMarkSpace(unsigned long internalMark,unsigned long internalTotal)
{
             //*******************************Load up the Mark Writes *******************************************
             
  unsigned long cycleCount = (internalMark*carrierFreq)/1000000;          // expects carrierFreq to be unsigned long above 30,000 Hz
 
     while (cycleCount) 
      {
         Serial1.write(DUTY);                                                           // while (true)  {  //send continuous carrier, for testing, signal generator or just generic PWM 
//           Serial.write(DUTY);                                                             // uncomment for Serial. or TX0, Comment out preceeding IRSerial1.write line above
         cycleCount = cycleCount -1;                                                 //write a character to emulate carrier, character value determines duty cycle.
       }

 unsigned long endTime = (sigTime +internalTotal);                  // 
 /*
 * /We have stuffed them somewhere, now wait till time is up for both the MARK and the requisite following SPACE  
   * This takes care of the mark, no matter what the lag between finishing the write(Duty) and the actual end of the UART sending
   * So now we just wait for the rest of the total time, finishing mark and adding space. 
   */
                     //***************************Now Wait for the endTime to expire**********************************
                     
   while (micros()<endTime)                                                                            //   just loop like everyone else does
    {
    
   }
                                                              
}
 //*************************************END of CODE***********************************************************
You do not have the required permissions to view the files attached to this post.

User avatar
AnalysIR
Site Admin
Posts: 695
Joined: Sat Aug 31, 2013 3:51 pm
Location: Dublin, Ireland
Contact:

Re: ESP32 and IR Control

Post by AnalysIR »

Some things to try:

- default carrier frequency to 38 or 40 kHz. NEC/Samsung is 38, SONY is 40.
- when you get it running reliably, you can just reset the carrier to the correct value before sending every signal.
- At the moment you seem to have it set to 41kHz, so change it as above.
- Another thing I noticed is that you have duty cycle set to 0xC3 for 60%
- Duty shouldbe 50% or less. You will get better performance with it set to 50% or 0xF0
- It is better to have the '1's at the start vs in the middle as you have done with 0xC3.
- This may explain why you are having issues with some signals
- Set the carrier in the various functions correctly. (NEC/SAMSUNG=38000, SONY=40000)

Hopefully that will improve things, let me know.

User avatar
AnalysIR
Site Admin
Posts: 695
Joined: Sat Aug 31, 2013 3:51 pm
Location: Dublin, Ireland
Contact:

Re: ESP32 and IR Control

Post by AnalysIR »

one more thing....you seem to be including ESP8266 stuff for the ESP32???

Code: Select all

#include "uart_register.h"
#include <ESP8266WiFi.h>
#define ESP8266PLATFORM  true    //set to true for ESP8266 platform 

DrJFM
Posts: 6
Joined: Wed May 04, 2016 4:20 pm

Re: ESP32 and IR Control

Post by DrJFM »

The only two includes in the ESP32 paste and zip are :

Code: Select all

#include <HardwareSerial.h>                                                // Not finally necessary as an explicit include -- likely in core, but left here to guide users to the appropriate files
#include "soc/uart_reg.h"  
I also pasted in the code modified to run on the ESP8266. This must be the version you were referring to as having ESP8266 includes. The interesting aspect of the similar code on the two processors is that the code runs well at all frequencies on the ESP8266, but fails at frequencies below about 41500 on the ESP32. Something different and unexpected is going on with the ESP32 UART code and at slower baud rates, the writes consume the time alloted for a space in about every 3rd or 4th iteration of a zero value byte transmission. This shows up as several long marks (two marks run together) in a series of zero bit transmissions. I never had trouble with NEC like one bits which utilize a 2x to 3X spaces timing (space = 2 or 3 X mark duration). Without several consecutive 1:1 Mark:Space pairs, things look fine. Then a code with more than 3 or 4 zeros in a row comes along and won't work.

I sent the screenshot and the session data to illustrate the effect of lowering the frequency. It is not a problem of too high a frequency being "missed" by a receiver -- most everything I tried would read NEC codes well at 42,000 Hz -- but that at lower frequency, the code is not properly formed. By cranking down the ratio of Mark to Space to say 400 usec MARKS and 600 usec SPACE eventually a properly formed code can be generated at lower frequencies but at some point using this approach, they no longer look like proper timing to the receiver (that expects about 1:1 timing). But here where this approach falls apart at lower frequencies, the MARKS remain at 500 usec, merging two to give 1000usec MARKS, so the basic 10X baud rate UART approach is working -- something else is consuming upwards of 500 usec of a space periodically -- sort of reeks of a write buffer refresh or some sort of overflow/buffering issue.

No problem with missed spaces on the ESP8266 as frequency is lowered so it is an ESP32 issue with this approach. Since other approaches do exist for the ESP32, (and if you don't find it an interesting puzzle as to what if failing and/or haven't started working with the ESP32) it is mostly an interesting glitch in the ongoing ESP32 evolution. The 32 is very feature rich and (Usually) at least as user friendly as the older ESP8266, more and more effort will be migrating to it (IMHO).

My final reason to post about this is that it was all possible to do and sort and debug due to AnalysIR software combined with the A.IR Shield Rx. The use of a $15 Arduino Uno and your software and the receiver with frequency measurements essentially becomes a really inexpensive oscilloscope. I can see my self using your tools for signal analysis for applications where the only reason to insert an IR LED is to allow me to capture and measure the waveform output. I am curious as to what frequencies can be captured/monitored in this manner if you adjust your firmware to run on a faster and more memory endowed ESP 8266 or ESP 32. The ESP32 is often run at 500,000 baud over its typical USB to serial connection to the PC for programing to help with data bandwidth.

If I get any further insights (by either my efforts or by posting on ESP32 oriented forums) as the the origin of the limitations on the UART Hack glitch on the ESP32, I will update here as well. Glitch not withstanding, works fine at 42,000 Hz.

Cheers

User avatar
AnalysIR
Site Admin
Posts: 695
Joined: Sat Aug 31, 2013 3:51 pm
Location: Dublin, Ireland
Contact:

Re: ESP32 and IR Control

Post by AnalysIR »

OK thanks ....The thing about the ESP32 is that there is no real need for uPWM because its possible to output PWM on almost every pin.

For example, I just completed ESP32 firmware for our new A.IR Shield ESP8266 using the ESP32 LEDC function and it works just fine.
I haven't played with the dedicated IR peripheral yet, as it seems a bit overkill at the moment with a dual core MCU.
I can see my self using your tools for signal analysis for applications where the only reason to insert an IR LED is to allow me to capture and measure the waveform output.
Actually the serial interface is pretty straight forward and you dont really need to use an IR LED, if you just emulate the 'binary' protocol used.

All of our A.IR shields also come with a much simpler text based interface & the ESP ones also come with a web server based interface via HTML POST request which makes things much more flexible.

If you run into any issues, ping me.

DrJFM
Posts: 6
Joined: Wed May 04, 2016 4:20 pm

Re: ESP32 and IR Control

Post by DrJFM »

Glad to hear you are working w the ESP32. Are you using the Espressif SDK or Arduino based? I have been sticking (stuck) with the Arduino implementation. I had previously implemented a timer based, interrupt driven IR transmitter and also used the LEDC function as well. The latter lets you set duty cycle as well as basic frequency. I used 2 pins, one w carrier and one to toggle o/off and no interrupts. So the UART approach works reasonably well with only one pin and no interrupts. Timer uses up a timer channel and used interrupt. I have done OK w just software timing for carrier as well. I am exploring running timing critical code as a task on core 0. Loop runs on core 1. The Arduino implementation allows RTOS tasks to run on the second core. Just a way to learn the chip and improve my skills -- the LEDC approach is very straightforward, as you state.
Will look again at the A.IR Shield ESP8266 since it will support the ESP32. Pinouts vary on the various development boards, but you can re-direct almost everything to any pin, so should not be an issue. Cheers

James

User avatar
AnalysIR
Site Admin
Posts: 695
Joined: Sat Aug 31, 2013 3:51 pm
Location: Dublin, Ireland
Contact:

Re: ESP32 and IR Control

Post by AnalysIR »

We are using the Arduino IDE 1.8.1 with the latest Arduino-ESP32 from github & seems to work just fine for both IR Rx & Tx.

Our A.IR Shield ESP TRx now comes with Firmware that works with AnalysIR for both ESP8266 & ESP32. The footprint matches the Wemos D1 mini and there is an ESP32 variant that has the same footprint. (Search for ESP32 D1 mini). For the ESP shield we have implemented http servers to send/receive the various requests to/from AnalysIR & thus can be easily extended or customised for personal projects to support control from browsers on tablets or phones.
Will look again at the A.IR Shield ESP8266 since it will support the ESP32. Pinouts vary on the various development boards, but you can re-direct almost everything to any pin, so should not be an issue
The main issue here is to get the ESP32 with the compatible footprint for our shield (otherwise wires need to be jumpered etc).

This is the one that is pin compatible. (We will also be stocking these ESP32 modules in a few weeks)
ESP32D1MINI.jpg
You do not have the required permissions to view the files attached to this post.

Post Reply