Engduino v1.0
EngduinoIR.cpp
Go to the documentation of this file.
00001 /**
00002 * \addtogroup EngduinoIR
00003 *
00004 * This is the driver code for IR on the Engduino
00005 * On v1.0 this is the Vishay TFBS4711 chip. This driver
00006 * makes use of a timer to measure the length of inter-mark
00007 * gaps, and an edge-triggered ISR to know where the marks
00008 * are.
00009 * 
00010 * Because the ISR for the LEDs occupies considerable time,
00011 * this can run slowly and somewhat erratically, meaning that
00012 * only a rather low data rate is possible. The code
00013 * distinguishes between marks and spaces based on the
00014 * relative timings of them, and the threshold is hardwired
00015 * in the header file.
00016 *
00017 * This code makes use of timer 3, which cannot then be used
00018 * for other purposes.
00019 *
00020 * @{
00021 */
00022 
00023 /**
00024 * \file 
00025 *               Engduino IR driver
00026 * \author
00027 *               Engduino team: support@engduino.org
00028 */
00029 
00030 
00031 #include "EngduinoIR.h"
00032 #include <avr/interrupt.h>      // ISR
00033 #include "pins_arduino.h"
00034 
00035 /*---------------------------------------------------------------------------*/
00036 /**
00037 * \brief Constructor
00038 * 
00039 * C++ constructor for this class. Empty.
00040 */
00041 EngduinoIRClass::EngduinoIRClass()
00042 {
00043 }
00044 
00045 /*---------------------------------------------------------------------------*/
00046 /**
00047 * \brief begin function - must be called before using other functions
00048 *
00049 * This function enables the IR, sets up the receive state machine,
00050 * initialises timer 3 to run at 1MHz, to give high resolution timings, and
00051 * sets the interrupts for the TFBS4711 IR device. The TFBS4711 chip will drive
00052 * the IR_RX line low briefly when receiving a mark, and will not generate any
00053 * event whilst receiving a space. Consequently, we can only tell the difference
00054 * between them by looking at the gaps between two marks; longer gaps mean more
00055 * spaces.
00056 *
00057 */
00058 void EngduinoIRClass::begin()
00059 {
00060         pinMode(IR_SD, OUTPUT);    // Shutdown pin - active high
00061         pinMode(IR_TX, OUTPUT);    // Output to IR
00062         pinMode(IR_RX, INPUT);     // Input from IR
00063 
00064         digitalWrite(IR_TX, LOW);  // Set output to be space initially
00065         digitalWrite(IR_SD, HIGH); // We want it to be shutdown initially
00066     
00067         // Initialise state machine variables
00068         //
00069         rcvstate = STATE_IDLE;
00070         rawlen   = 0;
00071         sending  = false;
00072         
00073         // Set up receive timer
00074         //
00075         cli();                                  // Disable interrupts
00076         
00077         // Use timer 3 to figure identify marks and spaces in the received signal - by counting
00078         // periods between those in which there was a mark. If we know the expected baud rate, we 
00079         // can assess whether a mark or a space was transmitted. We set the clock to sample ten 
00080         // times as fast as the expected baud rate.
00081         //
00082         // We use the timer in CTC (Clear Timer on Compare match) mode in which the timer counts
00083         // up at a given frequency, stopping and being cleared when it reaches a predetermined count
00084     //
00085         // At 8MHz with a /8 prescaler, one count is equal to 1 us
00086 
00087         // Timer setup
00088         // The following are true for the Engduino:
00089         // * The clock runs at 8MHz
00090         // * There is a fixed minimum time to service an ISR of somewhere in the range 10-22us
00091         //   and taking an interrupt more frequently than this is pointless - even with a short ISR.
00092         // * Take care on minimum times with a longer ISR
00093         // * If we set the count to, n, then we will interrupt after n+1 clock cycles (@ 8MHz)
00094         //
00095         // For a 16 bit write we must write the high byte before the low byte.
00096         //
00097     TIMSK3 &= 0xF0;                             // Disable interrupts for all timer 3 comparisons, plus overflow
00098     TCCR3A  = 0x00;                             // Turn off OCnA connections, set up for CTC mode
00099         TCCR3B  = 0x08;                         // CTC mode, clock off, no input capture
00100         OCR3AH  = 0xFF;                         // Set the counter to MAX for now
00101         OCR3AL  = 0xFF;                         // Set the counter to MAX for now
00102         TCNT3H  = 0x00;                         // Reset the timer to zero
00103         TCNT3L  = 0x00;                         // Reset the timer to zero
00104         TIMSK3 |= 0x04;                 // And enable interrupts for a compare B match only
00105         
00106         // Switch on the interrupts on the IR_RX line and set them to function on a falling edge
00107         // We get short (2.2.us) zero-going pulse (i.e. a falling edge) whenever there is a mark
00108         // NOTE: By default, we will get an interrupt when we are transmitting since the
00109         // data is looped back.
00110         //
00111         EICRA &= 0xCF;                  // Make sure interrupt sense control bits for INT2 are cleared
00112         EICRA |= 0x20;                  // ...and set them to sense a falling edge
00113         EIMSK |= 0x04;                  // Enable the INT2 interrupt
00114         
00115         sei();  // Enable interrupts
00116 
00117         // And, finally, wake up the IR
00118         //
00119         digitalWrite(IR_SD, LOW);
00120 }
00121 
00122 /*---------------------------------------------------------------------------*/
00123 /**
00124 * \brief end function - switch off the IR
00125 *
00126 * Switch off both the IR chip and the ISRs.
00127 *
00128 */
00129 void EngduinoIRClass::end()
00130 {
00131         digitalWrite(IR_SD, HIGH);      // Put the IR back to sleep
00132         TIMSK3 &= 0xFB;                         // Disable the timer interrupt
00133         EIMSK  &= 0xFB;                         // Disable the INT2 interrupt
00134 }
00135 
00136 
00137 /*---------------------------------------------------------------------------*/
00138 /**
00139 * \brief Send a single bit.
00140 * \param b The boolean representing the bit to be sent.
00141 *
00142 * This sends a single bit. A mark is sent as a mark signal, and a space is
00143 * sent as a space followed by a mark. This encoding works well because we do
00144 * not have to concern ourselves about the relative timings of runs of spaces.
00145 * Instead a mark - space transition should always be a constant length, and
00146 * this should be considerably longer than a mark - mark transition.
00147 *
00148 */
00149 void EngduinoIRClass::sendBit(bool b)
00150 {   
00151         bool sending_temp = sending;
00152         
00153         while (rcvstate == STATE_READING)               // Wait until we're not receiving
00154                 ;
00155         sending = true;
00156         
00157         if (b & 1)
00158                 mark(BITTIME);
00159         else {
00160                 space(BITTIME);
00161                 mark(BITTIME);
00162         }
00163         
00164         sending = sending_temp;
00165 }
00166 
00167 /*---------------------------------------------------------------------------*/
00168 /**
00169 * \brief Send a byte
00170 * \param b The byte to send
00171 * \param startstop Whether to send mark bits as start/stop markers
00172 *
00173 * If startstop is true, send a mark at the beginning and end of data, but
00174 * between the two, send the byte, LSB first.
00175 *
00176 */
00177 void EngduinoIRClass::send(uint8_t b, bool startstop)
00178 {   
00179         bool sending_temp = sending;
00180         uint8_t mask = 0x01;
00181 
00182         while (rcvstate == STATE_READING)               // Wait until we're not receiving
00183                 ;
00184         sending = true;
00185 
00186         if (startstop)
00187                 sendBit(MARK);
00188         
00189         for (int i = 0; i < 8; i++) {
00190                 Serial.print((b & mask) != 0);
00191                 sendBit((b & mask) != 0);
00192                 mask = mask << 1;
00193         }
00194         Serial.println("");
00195 
00196         if (startstop) {
00197                 sendBit(MARK);
00198                 delayMicroseconds(2*GAP);
00199         }
00200         
00201         sending = sending_temp; 
00202 }
00203 
00204 
00205 /*---------------------------------------------------------------------------*/
00206 /**
00207 * \brief Send a buffer of bytes
00208 * \param buf The buffer of bytes to send as a (uint8_t *)
00209 * \param len Length of the buffer
00210 * \param startstop Whether to send mark bits as start/stop markers
00211 *
00212 * If startstop is true, send a mark at the beginning and end of data, but
00213 * between the two, send the individual bytes, LSB first.
00214 *
00215 */
00216 void EngduinoIRClass::send(uint8_t *buf, unsigned int len, bool startstop)
00217 {   
00218         bool sending_temp = sending;
00219         
00220         while (rcvstate == STATE_READING)               // Wait until we're not receiving
00221                 ;
00222         sending = true;
00223 
00224         if (startstop)
00225                 sendBit(MARK);
00226                 
00227         for (int i = 0; i < len; i++)
00228                 send(buf[i], false);
00229 
00230         if (startstop) {
00231                 sendBit(MARK);
00232                 delayMicroseconds(2*GAP);
00233         }
00234 
00235         sending = sending_temp;
00236 }
00237 
00238 /*---------------------------------------------------------------------------*/
00239 /**
00240 * \brief Send a buffer of bytes
00241 * \param buf The buffer of bytes to send as a (char *) 
00242 * \param len Length of the buffer
00243 * \param startstop Whether to send mark bits as start/stop markers
00244 *
00245 * If startstop is true, send a mark at the beginning and end of data, but
00246 * between the two, send the individual bytes, LSB first.
00247 *
00248 */
00249 void EngduinoIRClass::send(char *buf, unsigned int len, bool startstop)
00250 {       
00251         send((uint8_t *)buf, len, startstop);
00252 }
00253 
00254 /*---------------------------------------------------------------------------*/
00255 /**
00256 * \brief Raw send function. Provide timings for mark and space pairs
00257 * \param buf Buffer containing alternate mark/space timings in microseconds 
00258 * \param len Length of the buffer
00259 *
00260 * The argument to this function is a buffer containing alternate mark/space
00261 * timings, given in milliseconds, allowing a higher-level protocol to be
00262 * written in a sketch. 
00263 *
00264 */
00265 // Buffer contains times for mark/space pairs
00266 //
00267 void EngduinoIRClass::sendRaw(unsigned int *buf, int len)
00268 {
00269         bool sending_temp = sending;
00270         
00271         while (rcvstate == STATE_READING)               // Wait until we're not receiving
00272                 ;
00273         sending = true;
00274         
00275         for (int i = 0; i < len; i++) {
00276                 if ((i & 1) == 0)
00277                         mark(buf[i]);
00278                 else
00279                         space(buf[i]);
00280         }
00281         
00282         space(0); // Just in case the list isn't an even length
00283 
00284         sending = sending_temp; 
00285 }
00286 
00287 /*---------------------------------------------------------------------------*/
00288 /**
00289 * \brief Internal function to send a mark of a given length
00290 * \param time The length of the mark in microseconds
00291 *
00292 * Send a mark, and send it for the given number of microseconds. Note that
00293 * for this chip, a mark is sent on a rising edge of the IR_TX line. It
00294 * persists for the time that the line is high - except that if this is
00295 * longer than 100us, the pulse is trimmed to 100us.
00296 *
00297 */
00298 void EngduinoIRClass::mark(uint16_t time)
00299 {
00300         // We always pull IR_TX low for a short period before the end of the
00301         // bit (note: there's a fixed overhead in the delayMicroseconds call)
00302         // Anyway, if we do this, we have a clean rising edge if there's a
00303         // mark immediately following. It's down to us to ensure that 
00304         //
00305         digitalWrite(IR_TX, HIGH);                                              // Transmit mark
00306         delayMicroseconds(time - DELAYOFFSET - 1);              // Leave high for the given time minus a period of (DELAYOFFSET+1)
00307         digitalWrite(IR_TX, LOW);                                               // Then pull low 
00308         delayMicroseconds(1);                                                   // And delay for around (DELAYOFFSET+1)
00309 }
00310 
00311 /*---------------------------------------------------------------------------*/
00312 /**
00313 * \brief Internal function to send a space of a given length
00314 * \param time The length of the space in microseconds
00315 *
00316 * A space is the absence of a mark, so just delay and send nothing for
00317 * the given time.
00318 *
00319 */
00320 void EngduinoIRClass::space(uint16_t time)
00321 {
00322         // Sends an IR space for the specified number of microseconds.
00323         // We have a space when IR_TX is low, which it is by default, so
00324         // we just delay.
00325         //
00326         delayMicroseconds(time);
00327 }
00328 
00329 
00330 /*---------------------------------------------------------------------------*/
00331 /**
00332 * \brief ISR2 interrupt service routine, called when a falling edge is seen on
00333 *        the RX line
00334 *
00335 * Set for a falling edge on the INT2 pin - i.e. on IR_RX. Such events occur
00336 * in 2.2us periods when there is a mark on the IR. The width of the pulse is
00337 * unrelated to the baud rate of transmission, so we simply time the space
00338 * between marks using timer 3, setting (and resetting) a timeout which, when
00339 * it expires, indicates that the code has ended.
00340 *
00341 */
00342 ISR(INT2_vect)
00343 {       if (EngduinoIR.sending)                         // The IR we send is reflected in the receive.
00344                 return;                                                 // Ignore it
00345 
00346         if (EngduinoIR.rawlen >= RAWBUFSZ) {
00347                 EngduinoIR.rcvstate = STATE_STOP;
00348         }
00349 
00350         switch (EngduinoIR.rcvstate) {
00351                 case STATE_BLOCKED:
00352                 case STATE_IDLE:
00353                         TCCR3B &= 0xF8;                         // Switch off clock.
00354 
00355                         EngduinoIR.rawlen   = 0;
00356                         EngduinoIR.rcvstate = STATE_READING;
00357 
00358                         // Now set a timeout to detect an inter-code space.
00359                         // For a 16 bit write we must write the high byte before the low byte.
00360                         //
00361                         OCR3AH  = (GAP-1) >> 8;         // Set the counter to be the given number of counts
00362                         OCR3AL  = (GAP-1)  % 256;       // Set the counter to be the given number of counts
00363                         TCNT3H  = 0x00;                         // Reset the timer to zero
00364                         TCNT3L  = 0x00;                         // Reset the timer to zero
00365                         TCCR3B |= 0x02;                         // And turn clock back on
00366                         break;
00367                         
00368                 case STATE_READING:
00369                         TCCR3B &= 0xF8;                         // Switch off clock.
00370                         
00371                         { uint16_t l = TCNT3L;          // For a 16 bit read, we must read the low byte before the high byte
00372                           uint16_t h = (TCNT3H << 8);
00373                          EngduinoIR.rawbuf[EngduinoIR.rawlen++] = h + l;
00374                         }
00375                                                                                 // For a 16 bit write we must write the high byte before the low byte.
00376                         TCNT3H  = 0x00;                         // Reset the timer to zero
00377                         TCNT3L  = 0x00;                         // Reset the timer to zero
00378                         TCCR3B |= 0x02;                         // Turn clock back on
00379                         break;
00380                         
00381                 case STATE_STOP:
00382                         break;
00383         }
00384 }
00385 
00386 /*---------------------------------------------------------------------------*/
00387 /**
00388 * \brief ISR3 interrupt service routine, called when the timer has expired.
00389 *
00390 * TIMER3 interrupt code - timer 3 COMPB is used as a timeout to determine
00391 * when a code we are receiving has ended, or when a call to the recv function
00392 * has timed out. 
00393 *
00394 */
00395 ISR(TIMER3_COMPB_vect)
00396 {   
00397         TCCR3B  &= 0xF8;                                        // Switch off clock.
00398 
00399         switch (EngduinoIR.rcvstate) {
00400                 case STATE_READING: 
00401                         EngduinoIR.rcvstate = STATE_STOP;               // We timed out, so the code must have ended
00402                         break;
00403                 case STATE_BLOCKED:
00404                         EngduinoIR.rcvstate = STATE_TIMEOUT;    // We just timed out waiting for an input
00405                         break;
00406         }
00407 }
00408 
00409 /*---------------------------------------------------------------------------*/
00410 /**
00411 * \brief Blocking receive of an IR transmission, with optional timeout
00412 * \param buf The buffer in which to place the received data
00413 * \param timeout The time to wait before returning
00414 * \param startstop Whether to remove start/stop bits from transmission
00415 * \return The length of the buffer in bytes.
00416 *
00417 * Receive a message if it's there to be received. Otherwise wait for a time
00418 * that depends on the timeout value: if this is zero (as it is by default),
00419 * then wait forever, else set a timer. If the timer expires return a negative
00420 * number (-1), else return the length of the buffer received. The timeout is
00421 * considered not to have expired if, by the time the period is passed, a
00422 * message has *started* to arrive.
00423 * 
00424 * The first received bit is placed in the MSB, meaning that if there is not
00425 * a complete byte's worth of data, the lowest bits will be zero.
00426 * 
00427 * Note: Differentiating between a mark and a space is based on a hardwired
00428 * test of length. 
00429 * 
00430 */
00431 int EngduinoIRClass::recv(uint8_t *buf, uint16_t timeout, bool startstop)
00432 {
00433         static uint16_t raw[RAWBUFSZ];
00434         uint8_t b = 0;          // The byte we're building
00435         uint8_t l = 0;          // length of the buffer we've built
00436         
00437         int len = recvRaw(raw, timeout);
00438         
00439         if (len < 0)
00440                 return len;
00441 
00442         if (startstop)
00443                 len = len - 1;  // Remove the stop bit.
00444                                                 // The start bit isn't represented directly - it starts the timer.
00445         
00446         for (int i = 0; i < len; i++) {
00447                 if (raw[i] < MARKSPACESPLIT) {
00448                         // It's a mark.
00449                         // Shift in the appropriate one
00450                         //
00451                         b |= (1 << (i%8));              
00452                 }
00453         
00454                 if ((i+1)%8 == 0) {
00455                         buf[l++] = b;
00456                         b = 0;
00457                 }
00458         }
00459 
00460         // If there's anything left, add it to the
00461         // list
00462         if ((len % 8) != 0) {
00463                 buf[l++] = b;
00464         }
00465 
00466         return l;
00467 }
00468 
00469 /*---------------------------------------------------------------------------*/
00470 /**
00471 * \brief Raw receive function - returns timings for inter-mark gaps
00472 * \param buf The buffer in which to place the inter-mark timings
00473 * \param timeout The time to wait before returning
00474 * \return The length of the buffer in bytes.
00475 *
00476 * Receive a message if it's there to be received. Otherwise wait for a time
00477 * that depends on the timeout value: if this is zero (as it is by default),
00478 * then wait forever, else set a timer. If the timer expires return a negative
00479 * number (-1), else return the length of the buffer received. The timeout is
00480 * considered not to have expired if, by the time the period is passed, a
00481 * message has *started* to arrive.
00482 * 
00483 * On return, the buffer contains the timings, in microseconds, between pairs
00484 * of marks. Again, this allows a higher-level protocol to be written in a
00485 * sketch. 
00486 *
00487 */
00488 int EngduinoIRClass::recvRaw(uint16_t *buf, uint16_t timeout)
00489 {
00490         int retval;
00491         
00492         switch (rcvstate) {
00493                 case STATE_IDLE:
00494                         // We're currently waiting for a code to arrive. Set a timeout
00495                         // and wait either for this to expire or for a code to be received
00496                         //
00497                         rcvstate = STATE_BLOCKED;
00498         
00499                         if (timeout > 0) {
00500                                 // Now set a timeout - the ISR can't be using it to look for an end-of-code gap
00501                                 // because the state is IDLE
00502                                 // For a 16 bit write we must write the high byte before the low byte.
00503                                 //
00504                                 TCCR3B &= 0xF8;                                 // Switch off clock.
00505                                 OCR3AH  = (timeout-1) >> 8;     // Set the counter to be the given number of counts
00506                                 OCR3AL  = (timeout-1)  % 256;   // Set the counter to be the given number of counts
00507                                 TCNT3H  = 0x00;                                 // Reset the timer to zero so we don't miss any events
00508                                 TCNT3L  = 0x00;                                 // Reset the timer to zero so we don't miss any events
00509                                 TCCR3B |= 0x02;                                 // Turn clock back on
00510                         }
00511                         
00512                         // Block until either the timeout expires or there is something to receive
00513                         //
00514                         while (true) {
00515                                 switch (rcvstate) {
00516                                         case STATE_BLOCKED:
00517                                                 continue;
00518                                                 
00519                                         case STATE_TIMEOUT:
00520                                                 rawlen   = 0;
00521                                                 rcvstate = STATE_IDLE;
00522                                                 return E_TIMEOUT;
00523                                                 
00524                                         case STATE_STOP:
00525                                                 // Copy the received code and set up for the next code
00526                                                 //
00527                                                 for (int i = 0; i < rawlen; i++)
00528                                                         buf[i] = rawbuf[i];
00529 
00530                                                 retval   = (int)rawlen;
00531                                                 rawlen   = 0;
00532                                                 rcvstate = STATE_IDLE;
00533                                                 return retval;
00534                                                 
00535                                         default:
00536                                                 // We must have started to receive a code
00537                                                 // The timeout will have been reset so
00538                                                 // just wait until the code is received.
00539                                                 //
00540                                                 continue;
00541                                                 
00542                                 }
00543                         }
00544                         
00545 
00546                 default:
00547                         // We're in the middle of receiving a message - just wait until it has arrived
00548                         //
00549                         while (rcvstate != STATE_STOP) {
00550                                 ;
00551                         }
00552                         
00553                         // Copy the received code and set up for the next code
00554                         //
00555                         for (int i = 0; i < rawlen; i++)
00556                                 buf[i] = rawbuf[i];                     
00557 
00558                         retval   = (int)rawlen;
00559                         rawlen   = 0;
00560                         rcvstate = STATE_IDLE;
00561                         return retval;
00562         }
00563 }
00564 
00565 
00566 
00567 /*---------------------------------------------------------------------------*/
00568 /*
00569  * Preinstantiate Objects
00570  */ 
00571 EngduinoIRClass EngduinoIR = EngduinoIRClass();
00572 
00573 /** @} */