; ------------------------------------------------------------------------------ ; Title: Somfy USB remote control program. ; Author: Rob Jansen, Copyright (c) 2021..2021, all rights reserved. ; Compiler: 2.5r5 ; ; Description: Somfy remote control where the commands to be sent are controlled ; via the USB interface. The default settings can be reprogrammed ; by the user. ; ; Sources: https://pushstack.wordpress.com/somfy-rts-protocol/ ; include 16f1455 pragma target clock 48_000_000 ; Settings for external 12 MHz crystal and system clock 48 MHz. ; pragma target OSC HS ; External crystal ; pragma target CLKOUTEN ENABLED ; CLKOUT function is enabled ; pragma target PLLMULT N4X ; PLL Multipler Selection Bit, 4x Output Frequency Selected ; Settings for internal clock and system clock 48 MHz. pragma target OSC INTOSC_NOCLKOUT ; Internal clock pragma target CLKOUTEN DISABLED ; CLKOUT function is disabled. I/O or oscillator function on the CLKOUT pin pragma target PLLMULT N3X ; PLL Multipler Selection Bit, 3x Output Frequency Selected ; Other fuses pragma target CPUDIV P1 ; NO CPU system divide pragma target USBLSCLK F48MHZ ; System clock expects 48 MHz, FS/LS USB CLKENs divide-by is set to 8. pragma target PLLEN ENABLED ; 3x or 4x PLL Enabled pragma target FCMEN DISABLED ; Fail-Safe Clock Monitor is disabled pragma target WRT DISABLED ; Write protection off pragma target STVR ENABLED ; Stack Overflow or Underflow will cause a Reset pragma target LPBOR DISABLED ; Low-Power BOR is disabled pragma target IESO DISABLED ; Internal/External Switchover Mode is disabled pragma target PWRTE DISABLED ; power up timer pragma target BROWNOUT DISABLED ; no brownout detection pragma target WDT DISABLED ; Watchdog disabled pragma target MCLR EXTERNAL ; External reset pragma target LVP ENABLED ; allow low-voltage programming pragma target VOLTAGE MAXIMUM ; brown out voltage pragma target CP DISABLED ; Program memory code protection is disabled OSCCON = 0b1111_1100 ; Select PLL,3x, 16MHz internal oscillator ; Enable weak pull-up for port a and and set port c to output just to ; have no floating input pins. OPTION_REG_WPUEN = FALSE ; Enable weak pull-up for port a. WPUA = 0b0011_1000 ; Weak-pull up for relevant port a bits. TRISC = 0b0000_0000 ; Port c output. enable_digital_io() ; All pins digital io instead of analog. ; Give the hardware some time to stabilize. Especially the HEF needs this time. _usec_delay(500_000) ; Pin aliases. alias configured_led is pin_c3 ; Pin 7. pin_c3_direction = OUTPUT alias rf_test_pin is pin_c2 ; Pin 8. pin_c2_direction = OUTPUT ; Somfy commands. const byte COMMAND_NONE = 0x00 const byte COMMAND_STOP = 0x01 const byte COMMAND_UP = 0x02 const byte COMMAND_DOWN = 0x04 const byte COMMAND_PROGRAM = 0x08 ; HEF addresses. const word HEF_INIT_ADDRESS = 0 ; 1 byte stored const word HEF_REMOTE_ADDRESS = 1 ; 3 bytes stored const word HEF_ROLLING_ADDRESS = 4 ; 2 bytes stored const word HEF_REPEAT_ADDRESS = 6 ; 1 byte stored ; The HEF memory layout is as follows and can be programmed by the ; use in one go to overrule the default settings and/or reset some ; values. ; Address 0: Initialization value. Cannot be overwritten by the user. ; Address 1: 3 bytes remote control address stored as 3 hexadecimal ; bytes, smallest one first (little endian). ; Address 4: 2 bytes rolling counter, stored as little endian. ; Address 6: 1 byte number of repeats. ; Reprogramming has to be done using hexadecimal numbers. ; Example. Given een remote control address of 0x531F71, a rolling ; counter of 0x01F4 and a repeat of 0x0D. The programming ; string then becomes: !531F7101F40D# ; In total the number of characters between ! and # must be exactly 12 ; giving 6 hexadecimal numbers. const PROGRAM_BUFFER_SIZE = 6 ; Include the USB driver. We use the USB interrupt. const USB_INTERRUPT_DRIVEN = TRUE include usb_serial usb_serial_init() include print ; This PIC version does not have EEPROM but High Endurace Flash. const HEF_ADDRESS_OFFSET = 0x1F80 include pic_data_hef ; Somfy RF Transmitter. include somfy_rf rf_transmitter_init() ; Variables. var byte program_buffer[PROGRAM_BUFFER_SIZE] var byte character, command, repeat_counter, index, value var bit programming_mode, programming_ok, waiting_for_data var bit high_nibble, transmitter_activated, test_toggle ; ----------------------------- Procedures and Functions ---------------------- ; Check if the given character is a valid hexadecimal character and return the ; binary value. The function returns TRUE when the character is valid. function character_to_bin(byte in character, byte out value) return bit is var bit correct = TRUE if (character >= "0") & (character <= "9") then value = character - "0" elsif (character >= "A") & (character <= "F") then value = character - "A" + 10 elsif (character >= "a") & (character <= "f") then value = character - "a" + 10 else correct = FALSE end if return correct end function ; Print the settings that are stored in the HEF. procedure print_settings() is print_crlf(usb_serial_data) print_string(usb_serial_data, "Remote address (hexadecimal): ") print_byte_hex(usb_serial_data, data_hef(HEF_REMOTE_ADDRESS + 2)) print_byte_hex(usb_serial_data, data_hef(HEF_REMOTE_ADDRESS + 1)) print_byte_hex(usb_serial_data, data_hef(HEF_REMOTE_ADDRESS)) print_crlf(usb_serial_data) print_string(usb_serial_data, "Rolling counter (hexadecimal): ") print_word_hex(usb_serial_data, data_hef_word(HEF_ROLLING_ADDRESS)) print_crlf(usb_serial_data) print_string(usb_serial_data, "Number of frame repeats (hexadecimal): ") print_byte_hex(usb_serial_data, data_hef(HEF_REPEAT_ADDRESS)) print_crlf(usb_serial_data) end procedure ; ----------------------------- Main loop starts here ------------------------- ; Check if we need to store initial values in the HEF. We do this by checking ; if the first HEF address is 0xC3 (a value that is not in when the HEF is ; erased). If so we load the initial values from the Somfy RF library. if (data_hef(HEF_INIT_ADDRESS) != 0xC3) then ; Store the initialization. data_hef_write(HEF_INIT_ADDRESS, 0xC3) ; Remote control address. data_hef_write(HEF_REMOTE_ADDRESS, REMOTE_ADDRESS[2]) data_hef_write(HEF_REMOTE_ADDRESS + 1, REMOTE_ADDRESS[1]) data_hef_write(HEF_REMOTE_ADDRESS + 2, REMOTE_ADDRESS[0]) ; Rolling code. data_hef_write_word(HEF_ROLLING_ADDRESS, RF_ROLLING_INIT) ; Numer of frame repeats. data_hef_write(HEF_REPEAT_ADDRESS, RF_REPEAT_INIT) end if ; Initialize some of the used variables. command = COMMAND_NONE repeat_counter = 0 programming_mode = FALSE transmitter_activated = FALSE test_toggle = FALSE configured_led = OFF forever loop ; Check if USB device has been configured by the HOST. IF so indicate ; this with the configured led. The user has to enable RTS/CTS control ; to configure the USB. if (usb_cdc_line_status() != 0x00) then configured_led = ON else configured_led = OFF end if ; Check for input characters to be received. if usb_serial_read(character) then ; Check which command is given. command = COMMAND_NONE if (character == "s") | (character == "S") then command = COMMAND_STOP elsif (character == "u") | (character == "U") then command = COMMAND_UP elsif (character == "d") | (character == "D") then command = COMMAND_DOWN elsif (character == "p") | (character == "P") then command = COMMAND_PROGRAM elsif (character == "t") | (character == "T") then test_toggle = !test_toggle if test_toggle then rf_debug_on() print_string(usb_serial_data, "Debug mode on.\r\n") else rf_debug_off() print_string(usb_serial_data, "Debug mode off.\r\n") end if elsif (character == "!") then ; Initialize the programming mode. programming_mode = TRUE waiting_for_data = TRUE programming_ok = TRUE high_nibble = TRUE ; Clear the program buffer. for PROGRAM_BUFFER_SIZE using index loop program_buffer[index] = 0 end loop index = 0 elsif (character == "?") then print_settings() else print_string(usb_serial_data, "Unknown command. Use: S(top), U(p), D(own), P(rogram), ? or !AAAAAACCCCRR# for reprogramming initial settings.\r\n") end if end if if programming_mode then ; Parse the input string and program the HEF if all is OK. while waiting_for_data & programming_ok loop if usb_serial_read(character) then if (character == "#") then ; We are done, program the data if the correct number of ; bytes is received. if (index == PROGRAM_BUFFER_SIZE) then ; Data is stored in HEF in little endian format so we ; cannot just copy the buffer. Put it in the right order. ; Remote control address. data_hef_write(HEF_REMOTE_ADDRESS, program_buffer[2]) data_hef_write(HEF_REMOTE_ADDRESS + 1, program_buffer[1]) data_hef_write(HEF_REMOTE_ADDRESS + 2, program_buffer[0]) ; Rolling code. data_hef_write(HEF_ROLLING_ADDRESS, program_buffer[4]) data_hef_write(HEF_ROLLING_ADDRESS + 1, program_buffer[3]) ; Numer of frame repeats. data_hef_write(HEF_REPEAT_ADDRESS, program_buffer[5]) waiting_for_data = FALSE else ; Incorrect number of bytes received. programming_ok = FALSE end if elsif character_to_bin(character, value) & (index != PROGRAM_BUFFER_SIZE) then if high_nibble then program_buffer[index] = value * 16 else program_buffer[index] = program_buffer[index] + value index = index + 1 end if high_nibble = !high_nibble else ; Incorrect character or too many bytes received. programming_ok = FALSE end if end if end loop programming_mode = FALSE if programming_ok then ; Show the user what he programmed. print_settings() else print_string(usb_serial_data, "Incorrect programming string.\r\n") end if ; Remove remaining characters. while usb_serial_read(character) loop ; Empty. end loop end if ; programming_mode ; See if we need to transmit a message. if (command != COMMAND_NONE) then ; Create a new frame and transmit it. build_frame(command) transmit_frame(FIRST_FRAME) ; Send repeat frames, if any. for data_hef(HEF_REPEAT_ADDRESS) loop transmit_frame(REPEAT_FRAME) end loop command = COMMAND_NONE print_string(usb_serial_data, "Done!\r\n") end if end loop