// Fernbedienung für Velux WLC100 // LCD/Keypad Shield an D4 D5 D6 D7 D8 D9 und A0 // IR-Sendediode über Treibertransistor an D3 // 16MHz Keramikresonator oder Schwingquarz #include // ============================================================ // Globale Variablen // ============================================================ int m = 1; // welcher Motor des Fensters, 1-3 Manual, 4-6 = 1-3 Auto int f = 0; // welches Fenster, 0=All, 1-10 // ============================================================ // LCD 16x2 Keypad Shield // ============================================================ LiquidCrystal lcd(8, 9, 4, 5, 6, 7); // ============================================================ // Tasten // ============================================================ enum Key { KEY_NONE, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_LEFT, KEY_SELECT }; // ============================================================ // Timer 2 // // Arduino Uno: 16 MHz // // Vorteiler 8: // 16 MHz / 8 = 2 MHz // 1 Timer-Tick = 0,5 us // // OCR2A = 63 -> 32 us // OCR2B = 21 -> 11 us // // OC2B = Pin 3 // ============================================================ const uint8_t TIMER2_TOP = 63; const uint8_t TIMER2_HIGH = 21; // ============================================================ // Timer 2 initialisieren // ============================================================ void setupTimer2() { pinMode(3, OUTPUT); digitalWrite(3, LOW); TCCR2A = 0; TCCR2B = 0; TCNT2 = 0; // Fast PWM, TOP = OCR2A TCCR2A |= (1 << WGM20) | (1 << WGM21); TCCR2B |= (1 << WGM22); OCR2A = TIMER2_TOP; OCR2B = TIMER2_HIGH; // OC2B zunächst deaktiviert TCCR2A &= ~(1 << COM2B1); TCCR2A &= ~(1 << COM2B0); // Vorteiler 8 TCCR2B |= (1 << CS21); } // ============================================================ // PWM-Ausgang aktivieren // ============================================================ inline void pwmOutputEnable() { TCCR2A &= ~(1 << COM2B0); TCCR2A |= (1 << COM2B1); } // ============================================================ // PWM-Ausgang deaktivieren // ============================================================ inline void pwmOutputDisable() { TCCR2A &= ~(1 << COM2B1); TCCR2A &= ~(1 << COM2B0); PORTD &= ~(1 << PD3); } // ============================================================ // Auf Timer-2-Zyklen warten // ============================================================ void waitTimer2Cycles(uint16_t cycles) { while (cycles > 0) { while ((TIFR2 & (1 << TOV2)) == 0) ; TIFR2 |= (1 << TOV2); cycles--; } } // ============================================================ // PWM-Zyklen senden // ============================================================ void sendCycles(uint16_t cycles) { pwmOutputEnable(); waitTimer2Cycles(cycles); pwmOutputDisable(); } // ============================================================ // LOW-Zyklen senden // ============================================================ void sendLowCycles(uint16_t cycles) { pwmOutputDisable(); waitTimer2Cycles(cycles); } // ============================================================ // Ein Bit senden // ============================================================ void sendBit(uint8_t bitValue) { uint8_t activeCycles; if (bitValue == 0) { activeCycles = 13; } else { activeCycles = 39; } uint8_t lowCycles = 52 - activeCycles; sendCycles(activeCycles); sendLowCycles(lowCycles); pwmOutputDisable(); } // ============================================================ // Prüfsumme // // Eingabe: Bits 23 bis 14, die Stellung des Security-Codes geht nicht ein // Rückgabewert: für Bits 0..3 // ============================================================ int checksum(int bits) { switch(bits) { case 0b0011110000: return 0b0010; // All Up Aut case 0b1011110000: return 0b1000; // All Stop case 0b0111110000: return 0b0111; // All Down Aut case 0b0010010000: return 0b1000; // All M1 Up Aut case 0b0100010000: return 0b0110; // All M1 Up Man case 0b1010010000: return 0b0010; // All M1 Stop case 0b1000010000: return 0b1001; // All M1 Down Man case 0b0110010000: return 0b1101; // All M1 Down Aut case 0b0010100000: return 0b1101; // All M2 Up Aut case 0b0100100000: return 0b0011; // All M2 Up Man case 0b1010100000: return 0b0111; // All M2 Stop case 0b1000100000: return 0b1100; // All M2 Down Man case 0b0110100000: return 0b1000; // All M2 Down Aut case 0b0011000000: return 0b0111; // All M3 Up Aut case 0b0101000000: return 0b1001; // All M3 Up Man case 0b1011000000: return 0b1101; // All M3 Stop case 0b1001000000: return 0b0110; // All M3 Down Man case 0b0111000000: return 0b0010; // All M3 Down Aut case 0b0010010001: return 0b1001; // 1=1M1 Up Aut case 0b0100010001: return 0b0111; // 1=1M1 Up Man case 0b1010010001: return 0b0011; // 1=1M1 Stop case 0b1000010001: return 0b1000; // 1=1M1 Down Man case 0b0110010001: return 0b1100; // 1=1M1 Down Aut case 0b0010100001: return 0b1100; // 2=1M2 Up Aut case 0b0100100001: return 0b0010; // 2=1M2 Up Man case 0b1010100001: return 0b0110; // 2=1M2 Stop case 0b1000100001: return 0b1101; // 2=1M2 Down Man case 0b0110100001: return 0b1001; // 2=1M2 Down Aut case 0b0011000001: return 0b0110; // 3=1M3 Up Aut case 0b0101000001: return 0b1000; // 3=1M3 Up Man case 0b1011000001: return 0b1100; // 3=1M3 Stop case 0b1001000001: return 0b0111; // 3=1M3 Down Man case 0b0111000001: return 0b0011; // 3=1M3 Down Aut case 0b0010010010: return 0b1010; // 4=2M1 Up Aut case 0b0100010010: return 0b0100; // 4=2M1 Up Man case 0b1010010010: return 0b0000; // 4=2M1 Stop case 0b1000010010: return 0b1011; // 4=2M1 Down Man case 0b0110010010: return 0b1111; // 4=2M1 Down Aut case 0b0010100010: return 0b1111; // 5=2M2 Up Aut case 0b0100100010: return 0b0001; // 5=2M2 Up Man case 0b1010100010: return 0b0101; // 5=2M2 Stop case 0b1000100010: return 0b1110; // 5=2M2 Down Man case 0b0110100010: return 0b1010; // 5=2M2 Down Aut case 0b0011000010: return 0b0101; // 6=2M3 Up Aut case 0b0101000010: return 0b1110; // 6=2M3 Up Man case 0b1011000010: return 0b1111; // 6=2M3 Stop case 0b1001000010: return 0b0100; // 6=2M3 Down Man case 0b0111000010: return 0b0000; // 6=2M3 Down Aut case 0b0010010011: return 0b1011; // 7=3M1 Up Aut case 0b0100010011: return 0b0101; // 7=3M1 Up Man case 0b1010010011: return 0b0001; // 7=3M1 Stop case 0b1000010011: return 0b1010; // 7=3M1 Down Man case 0b0110010011: return 0b1110; // 7=3M1 Down Aut case 0b0010100011: return 0b1110; // 8=3M2 Up Aut case 0b0100100011: return 0b0000; // 8=3M2 Up Man case 0b1010100011: return 0b0100; // 8=3M2 Stop case 0b1000100011: return 0b1111; // 8=3M2 Down Man case 0b0110100011: return 0b1011; // 8=3M2 Down Aut case 0b0011000011: return 0b0100; // 9=3M3 Up Aut case 0b0101000011: return 0b1010; // 9=3M3 Up Man case 0b1011000011: return 0b1110; // 9=3M3 Stop case 0b1001000011: return 0b0101; // 9=3M3 Down Man case 0b0111000011: return 0b0001; // 9=3M3 Down Aut case 0b0010010100: return 0b1100; // 10=4M1 Up Aut case 0b0100010100: return 0b0010; // 10=4M1 Up Man case 0b1010010100: return 0b0110; // 10=4M1 Stop case 0b1000010100: return 0b1101; // 10=4M1 Down Man case 0b0110010100: return 0b1001; // 10=4M1 Down Aut case 0b0010100100: return 0b1001; // 11=4M2 Up Aut case 0b0100100100: return 0b0111; // 11=4M2 Up Man case 0b1010100100: return 0b0011; // 11=4M2 Stop case 0b1000100100: return 0b1000; // 11=4M2 Down Man case 0b0110100100: return 0b1100; // 11=4M2 Down Aut case 0b0011000100: return 0b0011; // 12=4M3 Up Aut case 0b0101000100: return 0b1101; // 12=4M3 Up Man case 0b1011000100: return 0b1001; // 12=4M3 Stop case 0b1001000100: return 0b0010; // 12=4M3 Down Man case 0b0111000100: return 0b0110; // 12=4M3 Down Aut case 0b0010010101: return 0b1101; // 13=5M1 Up Aut case 0b0100010101: return 0b0011; // 13=5M1 Up Man case 0b1010010101: return 0b0111; // 13=5M1 Stop case 0b1000010101: return 0b1100; // 13=5M1 Down Man case 0b0110010101: return 0b1000; // 13=5M1 Down Aut case 0b0010100101: return 0b1000; // 14=5M2 Up Aut case 0b0100100101: return 0b0110; // 14=5M2 Up Man case 0b1010100101: return 0b0010; // 14=5M2 Stop case 0b1000100101: return 0b1001; // 14=5M2 Down Man case 0b0110100101: return 0b1101; // 14=5M2 Down Aut case 0b0011000101: return 0b0010; // 15=5M3 Up Aut case 0b0101000101: return 0b1100; // 15=5M3 Up Man case 0b1011000101: return 0b1000; // 15=5M3 Stop case 0b1001000101: return 0b0011; // 15=5M3 Down Man case 0b0111000101: return 0b0111; // 15=5M3 Down Aut case 0b0010010110: return 0b1110; // 16=6M1 Up Aut case 0b0100010110: return 0b0000; // 16=6M1 Up Man case 0b1010010110: return 0b0100; // 16=6M1 Stop case 0b1000010110: return 0b1111; // 16=6M1 Down Man case 0b0110010110: return 0b1011; // 16=6M1 Down Aut case 0b0010100110: return 0b1011; // 17=6M2 Up Aut case 0b0100100110: return 0b0101; // 17=6M2 Up Man case 0b1010100110: return 0b0001; // 17=6M2 Stop case 0b1000100110: return 0b1010; // 17=6M2 Down Man case 0b0110100110: return 0b1110; // 17=6M2 Down Aut case 0b0011000110: return 0b0001; // 18=6M3 Up Aut case 0b0101000110: return 0b1111; // 18=6M3 Up Man case 0b1011000110: return 0b1011; // 18=6M3 Stop case 0b1001000110: return 0b0000; // 18=6M3 Down Man case 0b0111000110: return 0b0100; // 18=6M3 Down Aut case 0b0010010111: return 0b1111; // 19=7M1 Up Aut case 0b0100010111: return 0b0001; // 19=7M1 Up Man case 0b1010010111: return 0b0101; // 19=7M1 Stop case 0b1000010111: return 0b1110; // 19=7M1 Down Man case 0b0110010111: return 0b1010; // 19=7M1 Down Aut case 0b0010100111: return 0b1010; // 20=7M2 Up Aut case 0b0100100111: return 0b0100; // 20=7M2 Up Man case 0b1010100111: return 0b0000; // 20=7M2 Stop case 0b1000100111: return 0b1011; // 20=7M2 Down Man case 0b0110100111: return 0b1111; // 20=7M2 Down Aut case 0b0011000111: return 0b0000; // 21=7M3 Up Aut case 0b0101000111: return 0b1110; // 21=7M3 Up Man case 0b1011000111: return 0b1010; // 21=7M3 Stop case 0b1001000111: return 0b0001; // 21=7M3 Down Man case 0b0111000111: return 0b0101; // 21=7M3 Down Aut case 0b0010011000: return 0b0000; // 22=8M1 Up Aut case 0b0100011000: return 0b1110; // 22=8M1 Up Man case 0b1010011000: return 0b1010; // 22=8M1 Stop case 0b1000011000: return 0b0001; // 22=8M1 Down Man case 0b0110011000: return 0b0101; // 22=8M1 Down Aut case 0b0010101000: return 0b0101; // 23=8M2 Up Aut case 0b0100101000: return 0b1011; // 23=8M2 Up Man case 0b1010101000: return 0b1111; // 23=8M2 Stop case 0b1000101000: return 0b0100; // 23=8M2 Down Man case 0b0110101000: return 0b0000; // 23=8M2 Down Aut case 0b0011001000: return 0b1111; // 24=8M3 Up Aut case 0b0101001000: return 0b0001; // 24=8M3 Up Man case 0b1011001000: return 0b0101; // 24=8M3 Stop case 0b1001001000: return 0b1110; // 24=8M3 Down Man case 0b0111001000: return 0b1010; // 24=8M3 Down Aut case 0b0010011001: return 0b0001; // 25=9M1 Up Aut case 0b0100011001: return 0b1111; // 25=9M1 Up Man case 0b1010011001: return 0b1011; // 25=9M1 Stop case 0b1000011001: return 0b0000; // 25=9M1 Down Man case 0b0110011001: return 0b0100; // 25=9M1 Down Aut case 0b0010101001: return 0b0100; // 26=9M2 Up Aut case 0b0100101001: return 0b1010; // 26=9M2 Up Man case 0b1010101001: return 0b1110; // 26=9M2 Stop case 0b1000101001: return 0b0101; // 26=9M2 Down Man case 0b0110101001: return 0b0001; // 26=9M2 Down Aut case 0b0011001001: return 0b1110; // 27=9M3 Up Aut case 0b0101001001: return 0b0000; // 27=9M3 Up Man case 0b1011001001: return 0b0100; // 27=9M3 Stop case 0b1001001001: return 0b1111; // 27=9M3 Down Man case 0b0111001001: return 0b1011; // 27=9M3 Down Aut case 0b0010011010: return 0b0010; // 28=10M1 Up Aut case 0b0100011010: return 0b1100; // 28=10M1 Up Man case 0b1010011010: return 0b1000; // 28=10M1 Stop case 0b1000011010: return 0b0011; // 28=10M1 Down Man case 0b0110011010: return 0b0111; // 28=10M1 Down Aut case 0b0010101010: return 0b0111; // 29=10M2 Up Aut case 0b0100101010: return 0b1001; // 29=10M2 Up Man case 0b1010101010: return 0b1101; // 29=10M2 Stop case 0b1000101010: return 0b0110; // 29=10M2 Down Man case 0b0110101010: return 0b0010; // 29=10M2 Down Aut case 0b0011001010: return 0b1101; // 30=10M3 Up Aut case 0b0101001010: return 0b0011; // 30=10M3 Up Man case 0b1011001010: return 0b0111; // 30=10M3 Stop case 0b1001001010: return 0b1100; // 30=10M3 Down Man case 0x0111001010: return 0b1000; // 30=10M3 Down Aut } return 0; } // ============================================================ // Datenvektor erzeugen // ============================================================ uint32_t getData(uint8_t command) { int commandBits = f; // ---------------------------------------------------------- // Bits 23-21 // ---------------------------------------------------------- switch (command) { case 0: // UP if (m >= 4) { commandBits |= 0b0010000000; } else { commandBits |= 0b0100000000; } break; case 1: // DOWN if (m >= 4) { commandBits |= 0b0110000000; } else { commandBits |= 0b1000000000; } break; case 2: // SELECT = STOP commandBits |= 0b1010000000; break; } // ---------------------------------------------------------- // Bits 20-18 abhängig von m // ---------------------------------------------------------- switch (m) { case 0: commandBits |= 0b1110000; break; case 1: commandBits |= 0b0010000; break; case 2: commandBits |= 0b0100000; break; case 3: commandBits |= 0b1000000; break; case 4: commandBits |= 0b0010000; break; case 5: commandBits |= 0b0100000; break; case 6: commandBits |= 0b1000000; break; } uint32_t data = checksum(commandBits); data |= 0b1000000000 << 4; // Security switch set data |= (uint32_t)commandBits << 14; return data; } // ============================================================ // 24 Bit senden // ============================================================ void send24Bit(uint32_t data) { for (int8_t bit = 23; bit >= 0; bit--) { uint8_t value = (data >> bit) & 1; sendBit(value); } pwmOutputDisable(); } // ============================================================ // Display mit Leerzeichen löschen // // Beide Displayzeilen werden vollständig mit Leerzeichen // überschrieben. // ============================================================ void clearDisplay(uint32_t data) { lcd.setCursor(0, 0); lcd.print(" "); } // ============================================================ // UP / DOWN / SELECT übertragen // // command: // 0 = UP // 1 = DOWN // 2 = SELECT // ============================================================ void transmit(uint8_t command) { uint32_t data = getData(command); // ---------------------------------------------------------- // Display vor dem Senden löschen // ---------------------------------------------------------- clearDisplay(data); // ---------------------------------------------------------- // Datenvektor senden // ---------------------------------------------------------- send24Bit(data); // ---------------------------------------------------------- // Erst jetzt 200 ms warten // ---------------------------------------------------------- delay(50); // ---------------------------------------------------------- // Display sofort wiederherstellen // ---------------------------------------------------------- updateDisplay(); } // ============================================================ // LCD aktualisieren // // m: // 0 -> All // 1 -> M1 // 2 -> M2 // 3 -> M3 // 4 -> A1 // 5 -> A2 // 6 -> A3 // // f: // 0 -> All // 1..10 -> Wert // ============================================================ void updateDisplay() { lcd.clear(); lcd.setCursor(0, 0); switch (m) { case 1: lcd.print("M1"); break; case 2: lcd.print("M2"); break; case 3: lcd.print("M3"); break; case 4: lcd.print("A1"); break; case 5: lcd.print("A2"); break; case 6: lcd.print("A3"); break; } lcd.print(", "); if (f == 0) { lcd.print("All"); } else { lcd.print(f); } } // ============================================================ // LCD-Keypad auslesen // ============================================================ Key readKey() { int value = analogRead(A0); if (value < 50) return KEY_RIGHT; if (value < 200) return KEY_UP; if (value < 400) return KEY_DOWN; if (value < 600) return KEY_LEFT; if (value < 800) return KEY_SELECT; return KEY_NONE; } // ============================================================ // SETUP // ============================================================ void setup() { lcd.begin(16, 2); setupTimer2(); updateDisplay(); } // ============================================================ // LOOP // ============================================================ void loop() { Key key = readKey(); switch (key) { // -------------------------------------------------------- // LEFT // // m: 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 1 // -------------------------------------------------------- case KEY_LEFT: if (m == 6) { m = 1; } else { m++; } updateDisplay(); break; // -------------------------------------------------------- // RIGHT // // f: 0 -> 1 -> ... -> 10 -> 0 // -------------------------------------------------------- case KEY_RIGHT: if (f == 10) { f = 0; } else { f++; } updateDisplay(); break; // -------------------------------------------------------- // UP // -------------------------------------------------------- case KEY_UP: transmit(0); return; // -------------------------------------------------------- // DOWN // -------------------------------------------------------- case KEY_DOWN: transmit(1); return; // -------------------------------------------------------- // SELECT // // Bits 23-21 = 101 // -------------------------------------------------------- case KEY_SELECT: transmit(2); return; } // Warten, bis die Taste losgelassen wurde while (readKey() != KEY_NONE) delay(10); }