Engduino v1.0
|
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 /** @} */