📄 main.c
字号:
// ======================================================================// IR receiver and LCD controller with USB interface.//// Read IR mark/space patterns from a TSOP1738 on the ICP input pin.// Mark and space periods are measured in units of 12Mhz/1024, which is// 85.33 us. Up to 35 periods are stored in a buffer, where they can// be read via a USB request. The buffer layout and USB requests are// compatible with the "IgorPlug-USB" device, which is supported by LIRC// through the "igorplugusb" driver. This implementation should also// work with that driver. If IgorPlug-USB compatibility is not required,// the resolution could be improved by reducing SCALING to 9, and you// could also get rid of the additional endpoint (USBTINY_ENDPOINT).//// There are some IR protocols with very long packets length, such as// the "NEC" protocol (http://www.sbprojects.com/knowledge/ir/nec.htm).// This protocol uses 67 mark/space periods, which is too much for the// limited amount of RAM in the ATtiny2313. Simply truncating the// packet to the amount of available buffer space is not an option,// because the significant bits are at the end of the packet. Instead,// a buffer of 35 bytes (IR_MAX) is used, of which the last 16 bytes// (IR_WRAP) are used as a circular buffer. Because both the address// (16 periods) and the command code (16 periods) are sent twice (normal// and inverted), the wrapping will result in a buffer with both the// device address and the inverted command code. Other IR protocols are// typically shorter than 35 periods.//// 16-bit Timer1 is initialized in normal mode with a clock of 12MHz/8.// Both the positive and negative edges of the IR input signal on the// ICP input pin trigger an input capture interrupt, because the edge// selection is toggled on every interrupt. Output compare register A// is used for a timeout to detect the end of an IR transmission.// The timeout value is updated on every edge. When at least 4 edges// were detected when a timeout occurs, the packet count ir.count is// incremented and the ir.length byte is set to the packed length. This// byte is checked when a read request is received via USB to see if a// packet is available//// The two interrupt handlers in this file run for more than 28 cycles,// so they need to reenable interrupts to allow USB interrupts to be// handled in time. To avoid nested IR interrupts, the input capture and// output compare interrupts are disabled before the global interrupt// flag is set. In the input capture interrupt handler, the interrupts// are disabled for a maximum of 26, 27 and 21 cycles, and in the output// compare interrupt handler for 21, 23 and 21 cycles. Unfortunately,// it was not possible to meet the 28 cycle maximum for the input// capture interrupt handler without replacing the compiler generated// prolog and epilog code by inline assembly, which makes the code a// little fragile.//// The IR receiver is disabled during an IR buffer read, so that a// packet that is being tranmitted to the host, is not overwritten by// the next packet. When a transfer is in progress, 'inpos' has a value// other than 0xff. When the buffer is read completely, the IR receiver// is reenabled again, but when the next IR packet has already started,// this packed is discarded by setting 'edges' to 0xff.//// There are two USB request codes to control the LCD, one to write// instructions, and one to write data to the display. OUT transfers// are used to transfer up to 255 bytes for a single request.//// Stack space calculations for gcc-3.4.3 and gcc-4.1.0://// 3.4.3 4.1.0// ----- -----// 0 0 main// 0 0 lcd_init (inlined)// 2 2 usb_init// 0 0 ir_init (inlined)// 6 5 usb_poll// 0 0 usb_receive (inlined)// 2 2 usb_setup// 5 5 usb_out// 4 4 lcd_write// 2 2 lcd_read4// 2 2 lcd_write4// 0 0 usb_transmit (inlined)// 2 4 usb_int// 2 2 crc//// Maximum stack space is 6+5+4+2=17 for gcc-3.4.3 and 5+5+4+2=16 for// gcc-4.1.0. Total stack usage:// - USB interrupt: 11 bytes// - IR input capture interrupt: 11 bytes// - IR output compare interrupt: 7 bytes// - main(): 17 bytes// The two IR interrupt handler cannot be active simultaneously, so the// maximum stack space is 17+11+11=39 bytes.//// NOTE: The above cycle and stack size figures were obtained by// inspecting the code generated by gcc-3.4.3 and gcc-4.1.0.// Other compiler versions may generate different code, leading// to different figures. Likewise, the replacement prolog and// epilog code may have to be adapted when a different compiler// version allocates different registers.//// Copyright 2006-2008 Dick Streefland//// This is free software, licensed under the terms of the GNU General// Public License as published by the Free Software Foundation.// ======================================================================#include <avr/io.h>#include <avr/interrupt.h>#include "usb.h"// ----------------------------------------------------------------------// IR receiver definitions// ----------------------------------------------------------------------#define TIMEOUT 10500 // IR transmission timeout in us#define IR_MAX 35 // maximum number of IR data bytes#define IR_WRAP 16 // wrap area at end (for 67 byte NEC protocol)#define SCALING 10 // 9: 42.67 us period, 10: 85.33 us period#define LED 5 // PORTD bit number for LED#define PULLUP 3 // PORTD bit number for D- pullupenum{ // Generic requests USBTINY_ECHO, // echo test // IgorPlug-USB requests IGORPLUG_CLEAR, // clear IR data IGORPLUG_READ, // read IR data (wValue: offset) // LCD requests LCD_INSTR = 20, // write instructions to LCD (via OUT) LCD_DATA, // write data to LCD (via OUT)};static byte_t inpos = 0xff; // read position for usb_in(), or 0xffstatic byte_t edges; // incremented for each edgestatic struct // IgorPlug-USB compatible data layout{ byte_t length; // length of data[] byte_t count; // incremented for each IR packet byte_t offset; // not used byte_t data[IR_MAX]; // mark/space periods} ir;// ----------------------------------------------------------------------// LCD definitions// ----------------------------------------------------------------------#define LCD_PRESENT 1 // set to 0 to remove LCD support#define DDR DDRB#define PORT PORTB#define PIN PINB#define D4 0#define RS 4#define RW 5#define E 6#define MASK_D4 (0xf << D4)#define MASK_RS (1 << RS)#define MASK_RW (1 << RW)#define MASK_E (1 << E)#ifndef ICIE1#define ICIE1 TICIE1#endif// ----------------------------------------------------------------------// Handler for timer1 input capture interrupt: edge on IR input// ----------------------------------------------------------------------__attribute__((signal,naked)) // interrupts are DISABLEDextern void SIG_INPUT_CAPTURE1 ( void ){ static uint_t prev; uint_t stamp; byte_t delta; byte_t e; asm volatile( "push r23\n" "in r23,__SREG__\n" "push r18\n" "push r19\n" "push r24\n" "push r25\n" :: ); stamp = ICR1; // get time stamp TIMSK = 0; // disable both IR interrupts TCCR1B ^= _BV(ICES1); // toggle edge detector sei(); // allow USB interrupt PORTD |= _BV(LED); // switch LED on asm volatile( "push r20\n" "push r21\n" "push r30\n" "push r31\n" :: ); delta = (stamp - prev + (1 << (SCALING-3-1))) >> (SCALING-3); asm volatile("" : : "r"(delta) ); // calculate delta (gcc-4.1.0) prev = stamp; OCR1A = stamp + 12L * TIMEOUT / 8; // update timeout value cli(); // enter critical region e = edges; if ( e != 0xff ) // packet should not be ignored? { if ( e > IR_MAX ) // packet too long for buffer? { e -= IR_WRAP; // wrap, don't truncate } if ( inpos == 0xff ) // update ir only when USB idle { ir.length = 0; // discard previous packet if ( e > 0 ) { ir.data[e - 1] = delta; } } edges = e + 1; } sei(); // allow USB interrupt asm volatile("nop"); // reduce latency to 1 cycle asm volatile( "pop r31\n" "pop r30\n" "pop r21\n" "pop r20\n" :: ); cli(); TIMSK = _BV(OCIE1A) | _BV(ICIE1); // reenable IR interrupts asm volatile( "pop r25\n" "pop r24\n" "pop r19\n" "pop r18\n" "out __SREG__,r23\n" "pop r23\n" "reti\n" :: );}// ----------------------------------------------------------------------// Handler for timer1 output compare A interrupt: IR transmission timeout// ----------------------------------------------------------------------__attribute__((signal)) // interrupts are DISABLEDextern void SIG_OUTPUT_COMPARE1A ( void ){ TIMSK = 0; // disable both IR interrupts sei(); // allow USB interrupt PORTD &= ~_BV(LED); // switch LED off cli(); // enter critical region if ( edges >= 4 // at least two pulses (NEC) && edges != 0xff // packet should not be ignored && inpos == 0xff // update ir only when USB idle ) { ir.count++; ir.length = edges - 1; // new packet is complete } edges = 0; sei(); // allow USB interrupt TCCR1B &= ~_BV(ICES1); // reset to negative edge cli(); TIMSK = _BV(OCIE1A) | _BV(ICIE1); // reenable IR interrupts}// ----------------------------------------------------------------------// Initialize the IR receiver.// ----------------------------------------------------------------------static void ir_init ( void ){ DDRD |= _BV(LED); TCCR1B = _BV(ICNC1) // noise canceler, trigger on negative edge | _BV(CS11); // clock source clk/8 TIMSK = _BV(OCIE1A) // output compare 1A match interrupt enable | _BV(ICIE1); // input capture 1 interrupt enable}#if LCD_PRESENT// ----------------------------------------------------------------------// Delay <count> times 100 microseconds.// ----------------------------------------------------------------------static void lcd_delay100u ( byte_t count ){ asm volatile( "0: ldi r25, 240\n" "1: rjmp 2f\n" "2: dec r25\n" " brne 1b\n" // 240 * 5 cycles = 100us " dec r24\n" " brne 0b\n" );}// ----------------------------------------------------------------------// Write 4 bits to the LCD.// ----------------------------------------------------------------------static void lcd_write4 ( byte_t data ){ PORT = (PORT & ~ MASK_D4) | (data << D4); PORT |= MASK_E; // E high period: > 230ns DDR = MASK_RS | MASK_RW | MASK_E | MASK_D4; // Dx setup time: > 80ns PORT &= ~ MASK_E; // hold time: > 10ns DDR = MASK_RS | MASK_RW | MASK_E;}// ----------------------------------------------------------------------// Read 4 bits from the LCD.// ----------------------------------------------------------------------static byte_t lcd_read4 ( void ){ byte_t save; byte_t data; save = PORT; PORT = MASK_RW; // address setup time: > 40ns PORT |= MASK_E; // E high period: > 230ns asm volatile("rjmp 0f\n0:"); data = (PIN & MASK_D4) >> D4; // data access time: > 160ns PORT = save; return data;}// ----------------------------------------------------------------------// Write a byte to the LCD.// ----------------------------------------------------------------------static void lcd_write ( byte_t b ){ byte_t h; do { h = lcd_read4(); (void) lcd_read4(); } while ( h & 0x08 ); lcd_write4( b >> 4 ); lcd_write4( b & 15 );}// ----------------------------------------------------------------------// Initialize the LCD.// ----------------------------------------------------------------------static void lcd_init ( void ){ DDR = MASK_RS | MASK_RW | MASK_E; PORT = 0; //lcd_delay100u( 150 ); -- skipped because of RESET delay lcd_write4( 3 ); lcd_delay100u( 41 ); lcd_write4( 3 ); lcd_delay100u( 1 ); lcd_write( 0x32 ); // switch to 4-bit mode lcd_write( 0x28 ); // function set: 4-bit mode, two lines lcd_write( 0x01 ); // clear display lcd_write( 0x06 ); // entry mode: increment, no shift lcd_write( 0x0c ); // display control: on, no cursor}#endif /* LCD_PRESENT */// ----------------------------------------------------------------------// Handle a non-standard SETUP packet.// ----------------------------------------------------------------------extern byte_t usb_setup ( byte_t data[8] ){ byte_t req; byte_t r; r = 0; // Generic requests req = data[1]; if ( req == USBTINY_ECHO ) { r = 8; } // IgorPlug-USB requests if ( req == IGORPLUG_CLEAR ) { ir.length = 0; inpos = 0xff; } if ( req == IGORPLUG_READ ) { cli(); if ( ir.length > 0 ) { inpos = data[4]; r = 0xff; // call usb_in() to get the data } else { data[0] = 0; r = 1; } sei(); } // LCD requests if ( req == LCD_INSTR ) { PORT &= ~ MASK_RS; // data will be received by usb_out() } if ( req == LCD_DATA ) { PORT |= MASK_RS; // data will be received by usb_out() } return r;}// ----------------------------------------------------------------------// Handle an IN packet.// ----------------------------------------------------------------------extern byte_t usb_in ( byte_t* data, byte_t len ){ byte_t n; byte_t max; max = ir.length + 3; n = 0; while ( n < len ) { if ( inpos >= max ) // end of packet? { cli(); if ( edges ) // next packet already started? { edges = 0xff; // ignore remainder of packet } inpos = 0xff; // reenable receiver sei(); break; } data[n++] = (& ir.length)[inpos++]; } return n;}// ----------------------------------------------------------------------// Handle an OUT packet.// ----------------------------------------------------------------------extern void usb_out ( byte_t* data, byte_t len ){#if LCD_PRESENT while ( len ) { lcd_write( *data++ ); len--; }#endif /* LCD_PRESENT */}// ----------------------------------------------------------------------// Main// ----------------------------------------------------------------------__attribute__((naked)) // suppress redundant SP initializationextern int main ( void ){ PORTD |= _BV(PULLUP); DDRD |= _BV(PULLUP); // enable pullup on D-#if LCD_PRESENT lcd_init();#endif /* LCD_PRESENT */ usb_init(); ir_init(); for ( ;; ) { usb_poll(); } return 0;}
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -