/*
單片機: ATMega16A
晶振: 外部11.0592MHz (熔絲位: 低E1 高99)
紅外接收頭端口: PD6(ICP1)
數碼管段選: PA
數碼管位選: 從高到低PC0~7
LED燈: PB7~0
*/
#include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <avr/sfr_defs.h>
const uint8_t seg8[] PROGMEM = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e}; // 共陽數碼管段碼錶
uint8_t repeat_cnt = 0; // 重複按鍵次數
uint16_t user_code = 0, op_code = 0; // 用戶碼和操作碼
int main(void)
{
// 數碼管管腳配置
DDRA = 0xff;
PORTA = 0xff; // 先熄滅數碼管
DDRC = 0xff;
// 數碼管動態掃描定時器配置 (CTC自動重裝模式)
OCR0 = 85; // 定時時間: 2ms
TCNT0 = OCR0; // 開啟定時器後立即溢出, 點亮數碼管
TIMSK = _BV(OCIE0); // 打開定時器自動重裝中斷
sei(); // 打開總中斷
TCCR0 |= _BV(WGM01) | _BV(CS02); // 開定時器0, 時鐘為256分頻 (基準: 23.148us)
// PB口LED燈配置
PORTB = ~repeat_cnt;
DDRB = 0xff;
// 輸入捕獲定時器配置
TIMSK |= _BV(TICIE1); // 打開輸入捕獲中斷
TCCR1B = _BV(CS11); // 開定時器1, 時鐘為8分頻 (基準: 0.723us)
TCCR1B |= _BV(ICNC1); // 打開噪聲過濾功能 (可選操作)
while (1); // 在主循環中可對按鍵操作進行處理
}
// 數碼管動態掃描
ISR(TIMER0_COMP_vect)
{
static uint8_t mask = _BV(PORTC7); // 從最低位開始掃描
static uint32_t numbuf;
sei(); // 允許輸入捕獲中斷搶佔本中斷
if (mask == _BV(PORTC7))
numbuf = op_code | ((uint32_t)user_code << 16); // 裝入要顯示的數字
// 左邊顯示用戶碼, 右邊顯示操作碼
PORTC = 0xff; // 熄滅數碼管
PORTA = pgm_read_byte(&seg8[numbuf & 0x0f]); // 設置顯示字符 (十六進制格式)
PORTC = ~mask; // 設置數碼管位選
numbuf >>= 4;
if (mask == _BV(PORTC0))
mask = _BV(PORTC7);
else
mask >>= 1; // 掃描下一位
}
// 只有定時器1才有輸入捕獲功能
ISR(TIMER1_CAPT_vect)
{
static uint8_t IR_step = 0; // 步驟號
static uint16_t high_time = 0, low_time = 0; // 保存高低電平的持續時間
static uint32_t data; // 從高位到低位依次是: 操作碼反碼, 操作碼, 用戶識別碼反碼, 用戶識別碼
TCNT1 = 0; // 定時器清零
TCCR1B ^= _BV(ICES1); // 設置(翻轉)下次捕獲的電平
TIFR = _BV(ICF1); // 清除可能產生的ICF1標誌位, 特別注意: 由於是寫1清零, 所以這裡的運算符絕對不能寫成|=
// 否則可能會同時把已經置位但還未處理的OCF0也清除掉, 從而錯過一個定時器溢出中斷導致數碼管閃爍
if (TCCR1B & _BV(ICES1)) // 若本次捕獲的是下降沿
{
high_time = ICR1; // 記錄高電平持續時間
if (IR_step == 0)
{
if (low_time >= 11757 && low_time <= 13140)
{
// 低電平在8.5~9.5ms之間才合法
if (high_time >= 5533 && high_time <= 6916)
{
// 若高電平在4~5ms之間, 則認為是引導碼
data = 0;
IR_step++; // 開始讀取用戶碼
}
else if (high_time >= 2075 && high_time <= 3458)
{
// 若高電平在1.5~2.5ms之間, 則認為是重複碼
repeat_cnt++; // 重複次數加1
PORTB = ~repeat_cnt; // 在LED上顯示出來
}
}
}
else
{
// 低電平長度必須在0.4~0.7ms之間
if (low_time >= 553 && low_time <= 968)
{
data >>= 1;
if (high_time > 1162)
data |= 0x80000000; // 如果高電平長度大於0.84ms, 則認為本位為1
if (IR_step == 32)
{
// 若所有位都已讀取完畢, 則將解碼結果保存到全局變量中
user_code = data & 0xffff;
op_code = data >> 16;
IR_step = 0;
repeat_cnt = 0; // 重複次數清零
PORTB = ~repeat_cnt;
}
else
IR_step++; // 讀下一位
}
else
IR_step = 0; // 低電平長度不合法, 退出
}
}
else
low_time = ICR1; // 若本次捕獲的是上升沿, 則記錄低電平持續時間
}