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