Site Tools


en:tech:gw60

GW60 Rollo Controller Upgrade

Motivation

If you want to include the shutters in home automation, you need a corresponding control. If you don't want to retrofit the roller shutters yourself, you can switch to electronic belt winders. These are fully developed but require an external power supply. There are several manufacturers on the market who also have a wireless remote control in their range. Almost all of them have in common that they rely on their own standards and need a headquarters from the respective manufacturer. If you want to avoid this, you have to find your own, open solution.

GW60 ZB40

An inexpensive electronic belt winder is the GW60 from SUPERROLLO. It can be expanded with a RF Remote Control (433.92MHz) with Keeloq encryption. With this remote control, individual blinds or groups can be operated remotely.
There are also a few remodeling solutions on the Internet that simulate the keystrokes, e.g. with the help of a ESP8266. It works quite reliably and was also used by me for 2 years. However, the installation is already complex and the current position is not transmitted because the pulse evaluation is very complex.

The dream would be a module that fits into the space provided in the GW60 housing and can also transmit the current position. A return channel is actually already available at the module interface, since RxD and TxD of the GW60 controller go to the receiving module. The applied voltage of 5V can only be loaded with significantly less than 100mA. An ESP8266 is out of the question.

Until the dream came true, I had to go on the new roller blinds FYRTUR from IKEA TRÅDFRI Wait for integration. After a little reverse engineering, it is clear that the TRÅDFRI modules communicate serially with the roller blind control and also exchange position, voltage and current. The power consumption in the peak area is max. 33mA. The internal regulator of the GW60 can handle that.

Hardware

The first task is to connect the TRÅDFRI module to the GW60 so that it looks mechanically and electrically like the original ZB40 receiver. The TRÅDFRI module works with 3.3V while the GW60 controller is supplied with 5V. The circuit diagram must therefore contain two level shifters and a voltage regulator. My solution looks like this:

Schematic

On the left, the connection to the GW60 is originally designed as a spring contact. Unfortunately I couldn't find a supplier for these contacts and therefore switched to POGO Pins. They are available at Ebay] ] in almost all variants. A 6mm long version is used here. {{ :tech:pogo_pin.png?direct&100|POGO Pin}} On the right, the connections for module programming are designed as pads. This allows the TRÅDFRI module to be flashed. After all, you don't want to buy an expensive roller blind control from IKEA every time to get hold of a module for your own solution. But more about that in another [[de:tech:tradfri_mod| article. The button and the LED are provided for connection to the TRÅDFRI control unit. Once trained, we no longer need either. The available space is sufficient to accommodate all components.

In order for the cover to fit on the housing, the PCB should be only 1mm thick like the original.

Housing

With a caliper, the housing is quickly created in Fusion360 and then looks like this:

As shown, it can be printed in the 3D printer without support structures. The small block on the right serves to protect the POGO pins and can be pushed onto them.

TRÅDFRI Module GW60 mit TRÅDFRI Module

The PCB antenna looks out of the housing for better reception. Although the module disappears in the belt case when the GW60 is installed, I did not have any connection problems with the TRÅDFRI buttons or the gateway.

Software

The TRÅDFRI module is now electrically and mechanically connected to the GW60, but the GW60 controller cannot yet do anything with the data. To do this, the existing firmware must be expanded. The GW60 is controlled by a PIC16F690 with an on-board programming interface. The PICkit3, which is also available as a China replica, is an inexpensive programmer.

PICkit3

To be able to read out the original firmware first, we have to connect the GW60 board to the PICkit3. The test pads on the back of the GW60 PCB are very helpful. There you can easily solder the 5 cables.

GW60 Programing

MICROCHIP provides a free IDE with the name MPLAB X LAB. The MPLAP IPE programming software is part of this package. This enables the firmware including EEPROM etc. to be read out. Fortunately, the FLASH of the PIC16F690 was not protected on my GW60.

Extension

The real part of the work now again consisted of reverse engineering. There is enough space in the FLASH of the PIC16F690 for expansion. The area 0600h - 07FFh is unused. In order to keep the changes to the original software as low as possible, the software extension is only embedded in 4 places in the original code:

  1. INIT → is called once during the initialization.
  2. ISR → extension of the interrupt service routine
  3. LOOP → Main loop
  4. PANEL → Simulation of the keystrokes

To do this, the code is exchanged in the following places (a 16-bit word each):

 ADDR  CODE
------------
            ; ------------------------------------------------------
            if	1	; 1 = include extension
 0304  2609    call	isr_ext	; ### include isr extension
            else
 0304  00AC    movwf	44	; save received byte in 0x2C
            endif
            ; ------------------------------------------------------

            ; ------------------------------------------------------
            if	1	; 1 = include extension
 03D7  260C    call	init_ext	; ### include init extension
            else
 03D7  17B5    bsf	53,7	; ### original code
            endif
            ; ------------------------------------------------------

            ; ------------------------------------------------------
            if	1	; 1 = include extension
 03FD  261D    call	loop_ext	; ### include extension loop
            else
 03FD  0064    clrwdt	; ### original code  
            endif
            ; ------------------------------------------------------

            ; ------------------------------------------------------
            if	1	; 1 = include extension=1
 04C9  2E00    goto	panel_ext	; ### include panel extension
            else
 04C9  0008    return
            endif
            ; -------------------------------------------------------                           

Attention! The addresses are 16-bit word addresses. The lower line shows the original code and the line above the code to be patched. Of course, this only applies to the software version I have. However, in contrast to the PCB, the software was identical in all of the GW60s I bought at different times. With the exchange, the controller jumps into our extension software and comes back as if nothing had happened. This means that the entire functionality of the GW60 is retained.

The size of the extension code is 374 words and is simply listed here:

ADDR 0600h - 0775h
==================
7C18 3014 7C10 FC18 B014 FC10 7C1B 2526 0800 AC00 FC17 0800 FC01 C401 8316 E401 9B30 E500 6530 9800
0830 9B00 4030 9900 0330 9A00 8312 B517 0800 8312 0313 FC1F 232E FC13 9226 6400 0800 5626 8316 3608 
E402 031D 2F2E 8312 7C13 FC14 0800 8312 FC1A 362E 0318 2B2E 8312 0800 0318 342E 2B2E 2030 2C06 031D 
0800 8316 3108 8312 E021 8316 3208 8312 E021 8316 3308 8312 E021 8316 3408 8312 E021 8316 3808 8312 
E021 8316 3908 8312 E021 0800 8316 3108 EA00 3208 EB00 3808 EA02 031C EB03 3908 EB02 FF30 6B06 031D 
672E EA01 EB01 E701 2B27 3108 EC00 3208 ED00 3308 EC02 031C ED03 3408 ED02 5C27 6A08 E400 8312 0800 
0030 E021 FF30 E021 D830 E021 6430 E021 8316 FF30 8312 E021 8316 5308 8312 E021 8316 6408 8312 E021 
8316 6508 8312 E021 8312 0800 4408 031D 9A2E 2C08 031D C72E C40A C72E 4403 031D A32E 2C0A 031D C62E 
982E C401 C72E 0230 4406 031D AC2E 9A30 2C06 031D C62E 982E 0630 4402 0318 B72E 0330 4406 031D B72E 
2C08 C000 982E 0430 4406 031D BE2E 2C08 C100 982E 0530 4406 031D C62E 2C08 C200 C826 C72E C401 0800 
C401 0A30 4006 031D EB2E DD30 4106 031D D72E D730 4206 031D D72E 0327 0800 EE30 4106 031D E12E E430 
4206 031D E12E 0627 0800 CC30 4106 031D EB2E C630 4206 031D EB2E 0927 0800 CC30 4006 031D F82E CC30 
4106 031D F82E 4208 031D F82E 0C27 0800 DD30 4006 031D 022F DD30 4106 4206 031D 022F 0F27 0800 7C14 
7C13 0800 FC14 7C13 0800 FC14 7C13 0800 5626 7826 0800 6430 4106 0319 062F 4108 0319 032F 4108 8316 
B600 8312 5626 8316 3608 E402 0319 0800 7C17 031C 272F 8312 FC16 7C14 0800 8312 FC12 FC14 0800 0310 
EA0D EB0D E701 E70D EA0D EB0D E70D 6708 E800 6B08 ED00 6A08 EC00 0310 EC0D ED0D E80D EC0D ED0D E80D 
EC0D ED0D E80D 6C08 EA07 6D08 0318 6D0F EB07 6808 0318 680F E707 0310 EC0D ED0D E80D 6C08 EA07 6D08 
0318 6D0F EB07 6808 0318 680F E707 0800 E601 E901 1830 CA00 6A0D EB0D E70D E90D E60D EA0D 6C08 E902 
6D08 031C 6D0F E602 0318 6A14 6A18 732F E607 6C08 E907 CA0B 602F 0800

The extensions in the four areas mentioned above have the following function:

INIT

The own variables are initialized here and the baud rate is set to 2400. The execution takes place at the end of the INIT sequence of the original software.

ISR

The original software is already receiving bytes from the UART. Here we only have to signal to the extension with a flag that a new byte has been received. Here, too, the expansion at the end of the ISR can be heard.

PANEL

The keystrokes are simulated here when a corresponding command came from the TRÅDFRI module. At the same time, the STOP button is simulated during the run when the preselected intermediate position is reached.

LOOP

Here the evaluation of a received frame takes place and on request the status frame with the current position is sent.

The ASM file of the extension contains more explanations:

GW60_ext.asm
; File:	    GW60_ext.asm
; Target:   PIC16F690
; Author:   DIRB
; Date:     2021-01-15
; Compiler: pic-as (v2.31)
; IDE:      MPLABX v5.45
; ===========================================================================   
; ### Extension code for GW60 firmware to support serial TRÅDFRI protocol ###
; ===========================================================================  
; Free code area in original F/W: 
;   BLOCK0  0x0600 to 0x07FF
; ---------------------------------------------------------------------------
; Free RAM area in original F/W: 
;   BANK0   0x40-42, 0x44, 0x7C, 0x7D
;   BANK1   0xB6, 0xCA, 0xE2-E8, (E9), 0xEA-ED, 0x7C, 0xFD
; ---------------------------------------------------------------------------
; Used RAM in extention:
;
;  BANK0:
;   RAM_ext_stat (0x7C)
;    bit 0:	1 = UP command
;    bit 1:	1 = DOWN command
;    ...
;    bit 5:     0 = DOWN, 1 = UP  direction of new position
;    bit 6:     1 = new position to go to
;    bit 7:	1 = new byte received
;   RAM_rxd_byte (0x7D)	    stores received byte
;   RAM_frm_cnt	(0x44)	    counts received byte of frame
;   RAM_cmd1 	(0x40)	    individual command byte 1
;   RAM_cmd2	(0x41)	    individual command byte 2
;   RAM_cmd3	(0x42)	    individual command byte 3 (check sum)
;
;  BANK1:
;   RAM_newpos (0xB6)		    new position in percent
;   RAM_div16_lp (0xCA)		    DIV16 loop count
;   RAM_p_stat (0xE4)		    actual position in percent
;   RAM_chksum (0xE5)		    checksum of status frame
;   RAM_div16_rem0 (0xE6)	    DIV16 REMB0
;   RAM_div16_tmp1_mm (0xE7)	    DIV16 TEMP1 (MMSB)
;   RAM_div16_tmp2_mm (0xE8)	    DIV16 TEMP2 (MMSB)
;   RAM_div16_rem1 (0xE9)	    DIV16 REMB1
;   RAM_div16_tmp1_l (0xEA)	    DIV16 TEMP1 (LSB)
;   RAM_div16_tmp1_m (0xEB)	    DIV16 TEMP1 (MSB)
;   RAM_div16_tmp2_l (0xEC)	    DIV16 TEMP2 (LSB)
;   RAM_div16_tmp2_m (0xED)	    DIV16 TEMP2 (MSB)    
;    
; RAM used by original F/W:
;   byte_DATA_2C	 RxD from UART
;   byte_DATA_30	 button counter flags (short touch)
;			    0x01 = DOWN
;			    0x02 = UP
;			    0x04 = SUN
;			    0x08 = CLOCK
;			    0x10 = SET
;			    0x20 = REED
;   byte_DATA_53	 actual current
;   byte_DATA_B1	 upper limit (LSB)	
;   byte_DATA_B2	 upper limit (MSB)	
;   byte_DATA_B3	 lower limit (LSB)	
;   byte_DATA_B4	 lower limit (MSB)
;   byte_DATA_B8	 actual position (LSB)	
;   byte_DATA_B9	 actual position (MSB)
; --------------------------------------------------------------------------- 
    PROCESSOR 16F690
    #include	<xc.inc>
    #include	"RAM_def.INC"    
; ---------------------------------------------------------------------------  
; Globals    
    GLOBAL isr_ext, init_ext, loop_ext, panel_ext    
    EXTRN sub_send_byte
; ---------------------------------------------------------------------------   
    PSECT ext,class=CODE,abs,reloc=2,delta=2
    ORG	0x0600
 
    IF EXTEND=1
 
; ===========================================================================
; called by F/W at each button read out from panel 
; ---------------------------------------------------------------------------    
panel_ext:
	btfsc	RAM_ext_stat, 0	; UP command received ?	
	bsf	byte_DATA_30, 0	; then simulate UP button pressed 
	bcf	RAM_ext_stat, 0
	btfsc	RAM_ext_stat, 1	; DOWN command received ?
	bsf	byte_DATA_30, 1 ; then simiulate DOWN button pressed   
	bcf	RAM_ext_stat, 1
	btfsc	RAM_ext_stat, 6	; new position to reach  ?
	call	on_pos		; check if position reached
; ---------------------------------------------------------------------------
	return			; (replaced instruction)
; ---------------------------------------------------------------------------
 
; ===========================================================================	
; called from interrupt service routine 
; --------------------------------------------------------------------------- 
isr_ext:
; ---------------------------------------------------------------------------
	movwf	byte_DATA_2C	; save received byte in 0x2C (replaced instr)
; ---------------------------------------------------------------------------
	bsf	RAM_ext_stat, 7	; set flag for new byte received
	return
 
; ===========================================================================
; called by firmware once at the end of init phase
; --------------------------------------------------------------------------- 
init_ext:
 
	clrf	RAM_ext_stat	; clear extension STATUS
	clrf	RAM_frm_cnt	; clear frame counter
	bsf	RP0
	clrf	RAM_p_stat	; status pos = 0
	movlw	0x9B		; status check sum 
	movwf	RAM_chksum
 
	;movlw	0x0f		; ====== just for devopment version ======
	;movwf	OSCTUNE		; ====== just for devopment version ======
	movlw	0x65		; setup serial I/F
	movwf	TXSTA		; TX9+TXEN+BRGH+TX9D = 1
	movlw	0x08
	movwf	BAUDCTL		; BRG16 = 1
	movlw	0x40		; Baudrate 0x340 = 832 -> 2401 Baud
	movwf	SPBRG		
	movlw	0x03		; Baudrate High
	movwf	SPBRGH		
 
	;movlw	0xA5		; send test byte
	;call	sub_send_byte	; transmit byte
 
	bcf	RP0	
init_exit:
; ---------------------------------------------------------------------------    
	bsf	byte_DATA_35, 7 ; replaced instruction from original code
; ---------------------------------------------------------------------------
	return
 
; ===========================================================================
; called from main loop
; ---------------------------------------------------------------------------
loop_ext:   
	bcf	RP0
	bcf	RP1
 
	; new byte received ?
	btfss	RAM_ext_stat, 7	; new byte received ?
	goto	loop_exit	; NO!
	bcf	RAM_ext_stat, 7
	call	receive_frame	; decode frame byte by byte 
 
	;##### 6-byte RAM monitor
	;call	monitor
loop_exit:
; ---------------------------------------------------------------------------
	clrwdt			; replaced instruction from original code
; ---------------------------------------------------------------------------
	return	
 
 
; ===========================================================================
; Subroutines	
; ===========================================================================
 
; --------------------------------------------------------------------	
; check if on position
on_pos:
	call	calcpos			
	bsf	RP0
	movf	RAM_newpos,w
	subwf	RAM_p_stat
	btfss	ZERO		; already on position ?	
	goto	notpos
reached:
	bcf	RP0
	bcf	RAM_ext_stat, 6	; clear positioning bit	
	bsf	RAM_ext_stat, 1 ; press button to stop	
	return
 
notpos: bcf	RP0	
	btfsc	RAM_ext_stat, 5	; UP direction ?
	goto	its_up
	btfsc	CARRY
	goto	reached	
 
posdone:	
	bcf	RP0
	return
 
its_up:	btfsc	CARRY
	goto	posdone
	goto	reached
 
; --------------------------------------------------------------------	
; debug output only
;
monitor:
	movlw	0x20		; wait for SPACE key
	xorwf	byte_DATA_2C,w
	btfss	STATUS,2    
	return
 
    	bsf	RP0
	movf	byte_DATA_B1,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	bsf	RP0
    	movf	byte_DATA_B2,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
    	bsf	RP0
	movf	byte_DATA_B3,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	bsf	RP0
    	movf	byte_DATA_B4,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
    	bsf	RP0
	movf	byte_DATA_B8,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	bsf	RP0
    	movf	byte_DATA_B9,w
	bcf	RP0
	call	sub_send_byte	; transmit byte	
 
	return
 
; --------------------------------------------------------------------			
; calc position in %
;
calcpos:
	; upper limit - actual position
    	bsf	RP0
	movf	byte_DATA_B1,w	; copy upper limit to temp1
	movwf	RAM_div16_tmp1_l
	movf	byte_DATA_B2,w
	movwf	RAM_div16_tmp1_m
	MOVF	byte_DATA_B8,W	; substraction
	SUBWF	RAM_div16_tmp1_l,F
	BTFSS	CARRY  
	DECF	RAM_div16_tmp1_m,F
	MOVF	byte_DATA_B9,W
	SUBWF	RAM_div16_tmp1_m,F
	; check if actual position was higher then the upper limit 
	movlw	0xFF
	xorwf	RAM_div16_tmp1_m,w
	btfss	ZERO	
	goto	noff
	clrf	RAM_div16_tmp1_l
	clrf	RAM_div16_tmp1_m
	; upper limit - lower limit = range
noff:
	clrf	RAM_div16_tmp1_mm	; temp 1 MMSB
 
	; multiply with 100
	call	mul100
 
	movf	byte_DATA_B1,w	; copy upper limit to temp2
	movwf	RAM_div16_tmp2_l
	movf	byte_DATA_B2,w
	movwf	RAM_div16_tmp2_m
	MOVF	byte_DATA_B3,W	; substraction
	SUBWF	RAM_div16_tmp2_l,F
	BTFSS	CARRY  
	DECF	RAM_div16_tmp2_m,F
	MOVF	byte_DATA_B4,W
	SUBWF	RAM_div16_tmp2_m,F
 
	;devide by range 
	call	div16bit	
 
	movf	RAM_div16_tmp1_l,w
	movwf	RAM_p_stat
 
    	bcf	RP0
	return
 
; --------------------------------------------------------------------
; send STATUS:
; 00 FF D8 64 FF 00 00 9B
;              +-------------->	Voltage		
;	          +----------->	Current		
;		     +-------->	Position 0-100%	 (0x00 - 0x64)
;                       +----->	Checksum		
send_status:
    	movlw	0x00		; send test byte
	call	sub_send_byte	; transmit byte
	movlw	0xFF		; send test byte
	call	sub_send_byte	; transmit byte	
	movlw	0xD8		; send test byte
	call	sub_send_byte	; transmit byte
	movlw	0x64		; send test byte
	call	sub_send_byte	; transmit byte
	bsf	RP0
	movlw	0xFF		; max voltage level
	bcf	RP0
	call	sub_send_byte	; transmit byte	
	bsf	RP0
	movf	byte_DATA_53,w	; get actual current
	bcf	RP0
	call	sub_send_byte	; transmit byte	
	bsf	RP0
	movf	RAM_p_stat,w
	bcf	RP0
	call	sub_send_byte	; transmit byte
	bsf	RP0
	movf	RAM_chksum,w
	bcf	RP0
	call	sub_send_byte	; transmit byte
	bcf	RP0
	return
 
; --------------------------------------------------------------------	
; IKEA Tradfri commands:
; UP		0x00 0xFF 0x9A 0x0A 0xDD 0xD7	00FF9A0ADDD7
; DOWN		0x00 0xFF 0x9A 0x0A 0xEE 0xE4   00FF9A0AEEE4
; STOP		0x00 0xFF 0x9A 0x0A 0xCC 0xC6	00FF9A0ACCC6
; GOTO		0x00 0xFF 0x9A 0XDD (POS) (CS)	00FF9ADD32EF
; Status reqest	0x00 0xFF 0x9A 0xCC 0xCC 0x00	00FF9ACCCC00
receive_frame:	
; if (RAM_frm_cnt == 0)
	movf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1650	
; if (RAM_rxd_byte == 0x00)
	movf	byte_DATA_2C,w
	btfss	STATUS,2
	goto	loc_142
loc_1648:	
    ; RAM_frm_cnt++
	incf	RAM_frm_cnt
	goto	loc_142
; else if (RAM_frm_cnt == 1)
loc_1650:
	decf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1658
; if (RAM_rxd_byte == 0xFF)
	incf	byte_DATA_2C,w
	btfss	STATUS,2
	goto	loc_1682
	goto	loc_1648
loc_1656:	
; else;main.c: 43: RAM_frm_cnt = 0
	clrf	RAM_frm_cnt
	goto	loc_142
loc_1658:	
; else if (RAM_frm_cnt == 2)
	movlw	2
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1666
; if (RAM_rxd_byte == 0x9A)
	movlw	0x9A
	xorwf	byte_DATA_2C,w
	btfss	STATUS,2
	goto	loc_1682
	goto	loc_1648
loc_1666:	
; else if (RAM_frm_cnt < 6)	
	movlw	6
	subwf	RAM_frm_cnt,w
	BTFSC	CARRY
	goto	loc_1672
loc_1668:	
; if (RAM_frm_cnt == 3)
	movlw	3
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1672	
; RAM_cmd1 = RAM_rxd_byte
	movf	byte_DATA_2C,w
	movwf	RAM_cmd1
	goto	loc_1648
loc_1672:	
; if (RAM_frm_cnt == 4)
	movlw	4
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1676
; RAM_cmd2 = RAM_rxd_byte;
	movf	byte_DATA_2C,w
	movwf	RAM_cmd2
	goto	loc_1648
loc_1676:	
; if (RAM_frm_cnt == 5) 
	movlw	5
	xorwf	RAM_frm_cnt,w
	btfss	STATUS,2
	goto	loc_1682	
; RAM_cmd3 = RAM_rxd_byte;
	movf	byte_DATA_2C,w
	movwf	RAM_cmd3
; decode_frame();
	call	decode_frame
	goto	loc_142
loc_1682:	
; else RAM_frm_cnt = 0;
	clrf	RAM_frm_cnt
loc_142:	
	return
 
; --------------------------------------------------------------------	
decode_frame:
; RAM_frm_cnt = 0;
	clrf	RAM_frm_cnt
; if (RAM_cmd1 == 0x0A) {
	movlw	0x0A
	xorwf	RAM_cmd1,w
	btfss	STATUS,2
	goto	loc_1767	
; if (RAM_cmd2 == 0xDD && RAM_cmd3 == 0xD7) UP();
	movlw	0xDD
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1755
	movlw	0xD7
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1755
	call	cmd_up
	return
loc_1755:	
; if (RAM_cmd2 == 0xEE && RAM_cmd3 == 0xE4) DOWN();
	movlw	0xEE
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1761
	movlw	0xE4
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1761
	call	cmd_down
	return
loc_1761:	
; if (RAM_cmd2 == 0xCC && RAM_cmd3 == 0xC6) STOP();
	movlw	0xCC
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1767
	movlw	0xC6
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1767
	call	cmd_stop
	return
loc_1767:	
; } if (RAM_cmd1 == 0xCC) {
	movlw	0xCC
	xorwf	RAM_cmd1,w
	btfss	STATUS,2
	goto	loc_1775	
; if (RAM_cmd2 == 0xCC && RAM_cmd3 == 0x00) STAT();
	movlw	0xCC
	xorwf	RAM_cmd2,w
	btfss	STATUS,2
	goto	loc_1775	
	movf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1775
	call	cmd_stat
	return
loc_1775:	
; } if (RAM_cmd1 == 0xDD) {
	movlw	0xDD
	xorwf	RAM_cmd1,w
	btfss	STATUS,2
	goto	loc_1781
; if ((RAM_cmd2 ^ 221) == RAM_cmd3) GOTO();
	movlw	0xDD
	xorwf	RAM_cmd2,w
	xorwf	RAM_cmd3,w
	btfss	STATUS,2
	goto	loc_1781
	call	cmd_goto
loc_1781:	
	return    
 
; --------------------------------------------------------------------	
; Command: UP
cmd_up:
	bsf	RAM_ext_stat, 0  
	bcf	RAM_ext_stat, 6	; clear new position move
	;movlw	'U'		; send test byte
	;call	sub_send_byte	; transmit byte
	return    
; --------------------------------------------------------------------	
; Command: DOWN
cmd_down:
	bsf	RAM_ext_stat, 1 
	bcf	RAM_ext_stat, 6	; clear new position move
	;movlw	'D'		; send test byte
	;call	sub_send_byte	; transmit byte
	return    
; --------------------------------------------------------------------	
; Command: STOP
cmd_stop:
	bsf	RAM_ext_stat, 1
	bcf	RAM_ext_stat, 6	; clear new position move
	;movlw	'S'		; send test byte
	;call	sub_send_byte	; transmit byte 
	return
; --------------------------------------------------------------------	
; Command: STATUS
cmd_stat:
	call	calcpos
	call	send_status
	return
; --------------------------------------------------------------------	
; Command: GOTO position
; new position in RAM_cmd2
cmd_goto:
	;movlw	'P'		; send test byte
	;call	sub_send_byte	; transmit byte  
	;movf	RAM_cmd2,w	; send test byte
	;call	sub_send_byte	; transmit byte  	
	movlw	0x64
	xorwf	RAM_cmd2,w
	btfsc	STATUS,2
	goto	cmd_down	; new pos = 100% -~ DOWN	
	movf	RAM_cmd2,w
	btfsc	STATUS,2
	goto	cmd_up		; new pos = 0% -~ UP
 
	movf	RAM_cmd2,w	; copy new position
    	bsf	RP0
	movwf	RAM_newpos
	bcf	RP0		
	call	calcpos
	bsf	RP0
	movf	RAM_newpos,w
	subwf	RAM_p_stat
	btfsc	ZERO		; already on position
	return	    
	bsf	RAM_ext_stat, 6	; set new position move
	btfss	CARRY
	goto	go_dn
 
go_up:	bcf	RP0
	bsf	RAM_ext_stat, 5	; direction is up
	bsf	RAM_ext_stat, 0 
	return
 
go_dn:	bcf	RP0
	bcf	RAM_ext_stat, 5	; direction is down
	bsf	RAM_ext_stat, 1 	
	return    
 
; --------------------------------------------------------------------	
; Multiplication * 100
; Author: Generator
; From: http://www.piclist.com/techref/piclist/codegen/constdivmul.htm
;
; ALGORITHM:
; Clear accumulator
; Add input * 64 to accumulator
; Add input * 32 to accumulator
; Add input * 4 to accumulator
; Move accumulator to result
; Approximated constant: 100, Error: 0 %
;    Input: RAM_div16_tmp1_l/EB, 16 bits
;    Output: RAM_div16_tmp1_l/EB + RAM_div16_tmp1_mm, 23 bits
; Code size: 48 instructions
mul100:
;shift accumulator left 2 times
	bcf	CARRY
	rlf	RAM_div16_tmp1_l, f
	rlf	RAM_div16_tmp1_m, f
	clrf	RAM_div16_tmp1_mm
	rlf	RAM_div16_tmp1_mm, f
	rlf	RAM_div16_tmp1_l, f
	rlf	RAM_div16_tmp1_m, f
	rlf	RAM_div16_tmp1_mm, f
;copy accumulator to temporary
	movf	RAM_div16_tmp1_mm, w
	movwf	RAM_div16_tmp2_mm
	movf	RAM_div16_tmp1_m, w
	movwf	RAM_div16_tmp2_m
	movf	RAM_div16_tmp1_l, w
	movwf	RAM_div16_tmp2_l
;shift temporary left 3 times
	bcf	CARRY
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
;add temporary to accumulator
	movf	RAM_div16_tmp2_l, w
	addwf	RAM_div16_tmp1_l, f
	movf	RAM_div16_tmp2_m, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_m, w
	addwf	RAM_div16_tmp1_m, f
	movf	RAM_div16_tmp2_mm, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_mm, w
	addwf	RAM_div16_tmp1_mm, f
;shift temporary left 1 times
	bcf	CARRY
	rlf	RAM_div16_tmp2_l, f
	rlf	RAM_div16_tmp2_m, f
	rlf	RAM_div16_tmp2_mm, f
;add temporary to accumulator
	movf	RAM_div16_tmp2_l, w
	addwf	RAM_div16_tmp1_l, f
	movf	RAM_div16_tmp2_m, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_m, w
	addwf	RAM_div16_tmp1_m, f
	movf	RAM_div16_tmp2_mm, w
	BTFSC	CARRY
	incfsz	RAM_div16_tmp2_mm, w
	addwf	RAM_div16_tmp1_mm, f
	return
; --------------------------------------------------------------------		
; Division 24bit by 16bit
; Author: Nikolai Golovchenko
; From: http://www.piclist.com/techref/microchip/math/div/24by16.htm
;
;Inputs:
;   Dividend - RAM_div16_tmp1_mm:RAM_div16_tmp1_m:RAM_div16_tmp1_l
;   Divisor  - RAM_div16_tmp2_m:RAM_div16_tmp2_l
;Temporary:
;   Counter  - RAM_div16_lp
;   Remainder- RAM_div16_rem0:RAM_div16_rem1
;Output:
;   Quotient - RAM_div16_tmp1_mm:RAM_div16_tmp1_m:RAM_div16_tmp1_l
div16bit:
        CLRF	RAM_div16_rem0
        CLRF	RAM_div16_rem1
        MOVLW	24
        MOVWF	RAM_div16_lp
LOOPU2416:
        RLF	RAM_div16_tmp1_l, W	;shift dividend left to move next bit to remainder
        RLF	RAM_div16_tmp1_m, F	;
        RLF	RAM_div16_tmp1_mm, F	;
 
        RLF	RAM_div16_rem1, F	;shift carry (next dividend bit) into remainder
        RLF	RAM_div16_rem0, F
 
        RLF	RAM_div16_tmp1_l, F	;finish shifting the dividend and save  carry in RAM_div16_tmp1_l.0,
                                ;since remainder can be 17 bit long in some cases
                                ;(e.g. 0x800000/0xFFFF). This bit will also serve
                                ;as the next result bit.
 
        MOVF	RAM_div16_tmp2_l, W	;substract divisor from 16-bit remainder
        SUBWF	RAM_div16_rem1, F	;
        MOVF	RAM_div16_tmp2_m, W	;
        BTFSS	CARRY	    	;
        INCFSZ	RAM_div16_tmp2_m, W	;
        SUBWF	RAM_div16_rem0, F	;
;here we also need to take into account the 17th bit of remainder, which
;is in RAM_div16_tmp1_l.0. If we don't have a borrow after subtracting from lower
;16 bits of remainder, then there is no borrow regardless of 17th bit 
;value. But, if we have the borrow, then that will depend on 17th bit 
;value. If it is 1, then no final borrow will occur. If it is 0, borrow
;will occur. These values match the borrow flag polarity.
        BTFSC	CARRY           ;if no borrow after 16 bit subtraction
        BSF	RAM_div16_tmp1_l, 0	;then there is no borrow in result. Overwrite
                                ;RAM_div16_tmp1_l.0 with 1 to indicate no
                                ;borrow.
                                ;if borrow did occur, RAM_div16_tmp1_l.0 already
                                ;holds the final borrow value (0-borrow,
                                ;1-no borrow)
        BTFSC	RAM_div16_tmp1_l, 0	;if no borrow after 17-bit subtraction
        GOTO	UOK46LL		;skip remainder restoration.
 
        ADDWF	RAM_div16_rem0, F	;restore higher byte of remainder. (w 
                                ;contains the value subtracted from it
                                ;previously)
        MOVF	RAM_div16_tmp2_l, W	;restore lower byte of remainder
        ADDWF	RAM_div16_rem1, F	;
UOK46LL:
        DECFSZ	RAM_div16_lp, f	;decrement counter
        GOTO	LOOPU2416	;and repeat the loop if not zero.
        RETURN
 
; ===========================================================================	
 
	ENDIF
 
	END

After the patch of the original software, the GW60 behaves like an IKEA roller blind. It can be operated using the TRÅDFRI buttons and the gateway. It shows the current position and can also move to a specific position. If the gateway is connected to e.g. Siri, a “ Hey Siri, open all shutters ” is enough in the morning and the apartment will be bright.

Conclusion

The conversion has been in use with me since April 2020 without any problems. There is only a small restriction with manual actuation: then the current position is only updated with the next radio command.
If you have to update the software again, I've put the programming interface outside. But it is only nice-to-have…

Downloads

If you like my articles feel to donate a cappuccino or so…

en/tech/gw60.txt · Last modified: 2022/09/26 09:59 by 127.0.0.1