Engduino v2.1
|
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 /** @} */