ESP8266 GPIO output performance

posted by on 2015.05.14, under collected, electronics, programming
14:

While building an extreme feedback device utilizing the ESP8266 and a bunch of WS2812B LEDs I missed some detailed information about the GPIO output performance of the ESP8266. This was a more general demand – but I ended up with my own WS2812 driver. And it was fun to use NOPs to achieve a nearly perfect timing (… cause I still remember the good old days when we used NOPs to create awesome rasterbars on the C64).

setup / basics

The XTENSA lx106 runs at 80 Mhz and has interrupts and watchdog disabled during measurement (see below).
Excerp from https://github.com/esp8266/esp8266-wiki/wiki/gpio-registers – I/O addresses used to control the GPIO hardware:

0x60000304 - set GPIO pin HIGH
0x60000308 - set GPIO pin LOW
0x60000310 - set GPIO pin direction output
0x60000314 - set GPIO pin direction input

Xtensa calling convention

*** this part is just here for completeness ***

The lx106 used in the ESP8266 implements the CALL0 ABI offering a 16-entry register file (see Diamond Standard 106Micro Controller brochure). By this we can apply the calling convention outlined in the Xtensa ISA reference manual, chapter 8.1.2 CALL0 Register Usage and Stack Layout:

a0 - return address
a1 - stack pointer
a2..a7 - arguments (foo(int16 a,long long b) -> a2 = a, a4/5 = b), if sizefof(args) > 6 words -> use stack
a8 - static chain (for nested functions: contains the ptr to the stack frame of the caller)
a12..a15 - callee saved (registers containing values that must be preserved for the caller)
a15 - optional stack frame ptr

Return values are placed in AR2..AR5. If the offered space (four words) does not meet the required amount of memory to return the result, the stack is used.

disabling interrupts

According to the Xtensa ISA (Instruction Set Architecture) manual the Xtensa processor cores supporting up to 15 interrupt levels – but the used lx106 core only supports two of them (level 1 and 2). The current interrupt level is stored in CINTLEVEL (4 bit, part of the PS register, see page 88). Only interrupts at levels above CINTLEVEL are enabled.

In esp_iot_sdk_v1.0.0/include/osapi.h the macros os_intr_lock() and os_intr_unlock() are defined to use the ets_intr_lock() and ets_intr_unlock() functions offered by the ROM. The disassembly reveals nothing special:

disassembly – ets_intr_lock() and ets_intr_unlock():

ets_intr_lock():
40000f74:  006320      rsil  a2, 3           // a2 = old level, set CINTLEVEL to 3 -> disable all interrupt levels supported by the lx106
40000f77:  fffe31      l32r  a3, 0x40000f70  // a3 = *mem(0x40000f70) = 0x3fffcc0
40000f7a:  0329        s32i.n  a2, a3, 0       // mem(a3) = a2 -> mem(0x3fffdcc0) = old level -> saved for what?
40000f7c:  f00d        ret.n

ets_intr_unlock():
40000f80:  006020      rsil  a2, 0           //enable all interrupt levels
40000f83:  f00d        ret.n

To avoid the overhead of the function call and the unneeded store operation the following macros to enable / disable interrupts can be used:

macros for disabling/enabling interrupts:

#define XT_CLI __asm__("rsil a2, 3");
#define XT_STI __asm__("rsil a2, 0");

NOTE: the ability to run rsil from user code without triggering a PriviledgedInstruction exception implies that all code is run on ring0. This matches the information given here https://github.com/esp8266/esp8266-wiki/wiki/Boot-Process

the watchdog

Just keep it disabled. I run into a lot of trouble with it – seemed the wdt_feed() didn’t work for me.
…and its not (well) documented at all.

Some pieces of information I found in the net:

gpio_output_set(uint32 set_mask, uint32 clear_mask, uint32 enable_mask, uint32 disable_mask)

declared in: esp_iot_sdk_v1.0.0/include/gpio.h
defined in: ROM (eagle.rom.addr.v6.ld -> PROVIDE ( gpio_output_set = 0x40004cd0 ))

C example:

gpio_output_set(BIT2, 0, BIT2, 0);  // HIGH
gpio_output_set(0, BIT2, BIT2, 0);  // LOW
gpio_output_set(BIT2, 0, BIT2, 0);  // HIGH
gpio_output_set(0, BIT2, BIT2, 0);  // LOW

disassembly – call to gpio_output_set(BIT2, 0, BIT2, 0):

40243247:       420c            movi.n  a2, 4                                     // a2 = 4
40243249:       030c            movi.n  a3, 0                                     // a3 = 0
4024324b:       024d            mov.n   a4, a2                                    // a4 = 4
4024324d:       205330          or      a5, a3, a3                                // a5 = 0
40243250:       f79001          l32r    a0, 40241090 <system_relative_time+0x18>  // a0 = *mem(40241090) = 0x40004cd0
40243253:       0000c0          callx0  a0                                        // call 0x40004cd0 - gpio_output_set

disassembly – gpio_output_set (thanks to By0ff for the ROM dump):

> xtensa-lx106-elf-objdump -m xtensa -EL  -b binary --adjust-vma=0x40000000 --start-address=0x40004cd0 --stop-address=0x40004ced -D 0x4000000-0x4011000/0x4000000-0x4011000.bin

0x4000000-0x4011000/0x4000000-0x4011000.bin:     file format binary

Disassembly of the .data section:

40004cd0 <.data+0x4cd0>:
40004cd0:       f0bd61          l32r    a6, 0x40000fc4  // a6 = *mem(0x40000fc4) = 0x60000200
40004cd3:       0020c0          memw                    // finish all mem operations before next op
40004cd6:       416622          s32i    a2, a6, 0x104   // mem(a6 + 0x104) = a2 -> mem(0x60000304) = 4 (SET)
40004cd9:       0020c0          memw
40004cdc:       426632          s32i    a3, a6, 0x108   // mem(a6 + 0x108) = a3 -> mem(0x60000308) = 0 (CLR)
40004cdf:       0020c0          memw
40004ce2:       446642          s32i    a4, a6, 0x110   // mem(a6 + 0x110) = a4 -> mem(0x60000310) = 4 (DIR -> OUTPUT)
40004ce5:       0020c0          memw
40004ce8:       456652          s32i    a5, a6, 0x114   // mem(a6 + 0x114) = a5 -> mem(0x60000314) = 0 (DIR -> INPUT)
40004ceb:       f00d            ret.n                   // return to the caller

> od -A x -j 0xfc4 -N 4 -x 0x4000000-0x4011000/0x4000000-0x4011000.bin
000fc4 0200 6000            // *mem(0x40000fc4) = 0x60000200
000fc8

gpio_output_set()

gpio_output_set()

The whole cycle of a HIGH-RISE/LOW/HIGH-RISE transition takes 1160 nano seconds – the execution of one call to gpio_output_set() takes ~580ns (~46 cycles@80Mhz). Since the clear operation is executed after the set operation (see the code above) the LOW period is slightly shorter then the HIGH period (HIGH: 675ns, LOW: 485ns). By setting an initial LOW GPIO to HIGH and LOW in the same command a short pulse of 88 nano seconds length (~7 cycles) is created.

The macro GPIO_OUTPUT_SET(gpio_no, bit_value) – defined in esp_iot_sdk_v1.0.0/include/gpio.h – is just a wrapper for gpio_output_set():

#define GPIO_OUTPUT_SET(gpio_no, bit_value) \
    gpio_output_set(bit_value<<gpio_no, ((~bit_value)&0x01)<<gpio_no, 1<<gpio_no,0)

WRITE_PERI_REG(addr, val)

Macro defined in esp_iot_sdk_v1.0.0/include/eagle_soc.h:

#define WRITE_PERI_REG(addr, val) (*((volatile uint32_t *)ETS_UNCACHED_ADDR(addr))) = (uint32_t)(val)

It boils down to the following assembly instructions:

4024323a:       080000          excw
4024323d:       600003          excw                // --> *mem(4024323c) -> 0x60000308

40243240:       000304          excw
40243243:       f03d60          subx8   a3, a13, a6 // --> *mem(40243240) -> 0x60000304

// set
40243252:       fffb21          l32r    a2, 40243240 <eagle_lwip_getif+0x28>    // a2 = 0x60000304
40243255:       230c            movi.n  a3, 4                                   // a3 = 4
40243257:       0020c0          memw
4024325a:       0239            s32i.n  a3, a2, 0                               // mem(a2 + 0) = a3 -> mem(0x60000308) = 4

// clear
4024325c:       fff821          l32r    a2, 4024323c <eagle_lwip_getif+0x24>    // a2 = 0x60000308
4024325f:       430c            movi.n  a3, 4                                   // a3 = 4
40243261:       0020c0          memw
40243264:       0239            s32i.n  a3, a2, 0                               // mem(a2 + 0) = a3 -> mem(0x60000308) = 4   

To avoid optimization by the compiler I used the following hand crafted code for the measurement:

...
__asm__("movi    a2, 0x60000304  \n" // will be converted to literal load - l32r    a2, 40243240 <eagle_lwip_getif+0x28>
    "movi.n  a3, 4       \n"
    "memw           \n"
    "s32i.n  a3, a2, 0     \n"
    );

__asm__("movi    a2, 0x60000308  \n"
    "movi.n  a3, 4       \n"
    "memw           \n"
    "s32i.n  a3, a2, 0     \n"
    );
...

The disassembly shows that the movi is converted to a literal load (as expected):

...
40243248:       fffd21          l32r    a2, 4024323c <eagle_lwip_getif+0x24>
4024324b:       430c            movi.n  a3, 4
4024324d:       0020c0          memw
40243250:       0239            s32i.n  a3, a2, 0
40243252:       fffb21          l32r    a2, 40243240 <eagle_lwip_getif+0x28>
40243255:       430c            movi.n  a3, 4
40243257:       0020c0          memw
4024325a:       0239            s32i.n  a3, a2, 0
...

write_peri_reg()

write_peri_reg()

The whole cyle of a HIGH-RISE/LOW/HIGH-RISE transition takes 237ns nano seconds with a HIGH-period
of 100 ns (8 cycles) and a LOW-period of 137 ns (11 cycles – the HIGH-period is three cycles shorter
then the LOW period – maybe caused by one additional instruction fetch).

By avoiding the mov and removing the memw operations I was able to generate pulses with a period time of 150ns (12 cycles, HIGH: ~52ns, LOW: ~98ns).


__asm__("movi    a2, 0x60000304  \n"
    "movi    a4, 0x60000308  \n" 
    "movi.n  a3, 4       \n"  // GPIO2
    "memw           \n"
    "s32i.n  a3, a2, 0     \n"
    "s32i.n  a3, a4, 0     \n"
    "s32i.n  a3, a2, 0     \n"
    "s32i.n  a3, a4, 0     \n"
    "s32i.n  a3, a2, 0     \n"
    "s32i.n  a3, a4, 0     \n"
  );

fastest - T: ~150ns

fastest – T: ~150ns

WS2812B timing

…this was the starting point that forced me to have a deeper look into that topic. There are already some implementations for controlling the WS2812B. They work – just use them. This part is for fun and education … ahh, lets do it in plain assembly.

static inline void WS2812B_SEND_1(int port) 
{
  //800ns HIGH & 450ns LOW
  __asm__ volatile ("movi    a2, 0x60000304  \n"
            "movi    a3, 0x60000308  \n"
            "movi    a4, %0      \n"
            "s32i    a4, a2, 0     \n"
            "memw            \n"
            "movi    a5, 14         \n"
            "3:            \n"
            "addi    a5, a5, -1    \n"
            "bnez    a5, 3b      \n"
            "nop.n           \n"
            "nop.n           \n"
            "nop.n           \n"
            "nop.n           \n"
            "nop.n           \n"
            "s32i    a4, a3, 0     \n"
            "memw            \n"
            "movi    a5, 2         \n"
            "4:            \n"
            "addi    a5, a5, -1    \n"
            "bnez    a5, 4b      \n"
           :: "g" (port)
           : "a2", "a3", "a4", "a5"
           );
}

ws2812b - send logical 1

ws2812b – send logical 1

SEND_1_HIGH: 796ns
SEND_1_LOW:  454ns

static inline void WS2812B_SEND_0(int port) 
{
  //400ns HIGH & 850ns LOW
  __asm__ volatile ("movi    a2, 0x60000304  \n"
            "movi    a3, 0x60000308  \n"
            "movi    a4, %0      \n"
            "s32i    a4, a2, 0     \n"
            "memw            \n"
            "movi    a5, 7         \n"
            "1:            \n"
            "addi    a5, a5, -1    \n"
            "bnez    a5, 1b      \n"
            "nop.n           \n"
            "s32i    a4, a3, 0     \n"
            "memw            \n"
            "movi    a5, 10         \n"
            "2:            \n"
            "addi    a5, a5, -1    \n"
            "bnez    a5, 2b      \n"
            "nop.n           \n"
            "nop.n           \n"
              "nop.n           \n"
           :: "g" (port)
           : "a2", "a3", "a4", "a5"
           );
}

ws2812b - send logical 0

ws2812b – send logical 0

SEND_0_HIGH: 397ns
SEND_0_LOW: 855ns

experiences

  1. Add a ~400 ohm resistor into the WS2812 data input line to avoid oscillation caused by reflections of the input of the first LED.

    write_peri_reg() - no resistor

    write_peri_reg() – no resistor

  2. Use decoupling capacitors – without I got strange noise on the power supply line.

    pwr supply - noise bursts

    pwr supply – noise bursts


    pwr supply - single bursts

    pwr supply – single bursts

  3. Ensure the WS2812 data line uses a proper signal level. According to the data sheet DATA_IN is treated as HIGH above 3.5V (0.7*Vdd) and LOW below 1.5V (0.3*Vdd)). The Vhigh of 3.3V offered by the ESP9266 is not enough for the WS2812B to be detected as a clean HIGH signal (I got some strange flickers – after adding a 4050 as level shifter everything was fine).
  4. Disable interrupts during bit-banging the WS2812. Avoid disabled interrupts for more then 10ms or wireless connections will act wired. Keeping that in mind you will be able to write ~300 LEDs at once (1250ns per bit, 8 bit per color, 3 colors = 30us, internal overhead when switching to the next pixel = 225ns, keep 50us between each write for reset condition). If you need to run the code from above from flash check the timing using an oscilloscope. I have seen NOPs taking more then one cycle…
  5. Wear sunscreen.

Useful links

door alarm

posted by on 2013.05.13, under electronics, sicherheit
13:

Our cellar and our briefcase was „emptied“ by some strangers and the main door has fresh marks of a crowbar… The criminal statistic for housebreaking has climbed a new peak – time to get paranoid. Since I don’t like psycho pharmacy  I created a little circuit around a ATtiny45 that is able to handle the following external sensors and actors – to act as a door alarm system:

  • hidden arm/disarm switch – just use the door bell to get a knocking sequence
  • hall sensor, triggered by a little magnet (3 mm diameter, 4 mm long) embedded in our wooden door
  • red/green LED to signal the state of the alarm system
  • switch a external horn on/off to create a fu*** (nny) loud noise

To run the system I decided to take some juice from the door opener. He offers a 12V AC line – so I used an recycled bridge rectifier and some capacitors to create a rudimentary 12V DC source…

bridge rectifier

bridge rectifier

The controller board was created with Eagle in THT old school style (schematic + board: dooralarm – yes, I increased the diameter of the drill holes for the resistors and capacitors a little…). The brain of the board is an ATtiny45. The hall sensors part is played by a TLE4905L. It is quite easy to use – just connect Q to an input of the uC and use the internal pull up to create a clean logic 1 if no magnetic field is applied to the sensor – that’s all. In conjunction with that little neodymium cylinder magnet I got a detection range of 5..7 mm – far enough to sense the state of the door safely.

hot glue to keeps the sensor in place

hot glue to keeps the sensor in place

The disarm hall sensor is placed inside the housing of the controller board. Just place a magnet on the housing – the alarm state is overridden – and the horn is off. The horn (piezo, 120dB@12V/0.2A) is controlled by a BUZ11 so I could extend the system easily by heavier loads.

schematic of the controller board

schematic of the controller board

As I run out of pins (I kept the reset pin for easy programming) I used that funny way of driving the status LEDs with only one port pin… so don’t worry about that.

controller board

controller board

By accident I stumbled over the AD-2000M – a RFID/keypad door opener panel. I found a seller here in Germany (reduces shipping time a lot) that sold the device for a reasonable price: including 10 key chain tags and shipping only 12 € – nice. The devices arrived 2 days later – and I tried to understand the manual arrrgh! … in short:

  • you can have 3 types of users
    • activate with RFID tag only
    • activate with RFID tag + 6 digit PIN
    • 6 digit PIN only
  • every user is identified by a unique 4 digit user id
  • a user id can only be used with one type of user
    (its not possible to use the same user id for a user with card or PIN only access)

The AD-2000M Rev. 3 works according to the following state machine:

programming states ad2000-m rev 3.0

programming states ad2000-m rev 3.0

The AD2000-M is able to detect the RFID tags through the (wooden) door – a nice replacement for the hidden arm/disarm switch formed by bell knocking… good. The controller board also fits inside the housing of the device so no further enclosing is needed. Very good. During the first tests of the setup I recognized that the 7805 inside the AD2000-M creates an enormous heat. Bad. Reason: even in the „ready“- state the device draws 60mA (for what?!?) – makes ~0.4W power dissipation over the 7805… way to much over the time. I also disliked the idea having 2 7805 sitting around and wasting energy. The moment to use a very nice drop in replacement from TI: the PTH08080W. It comes in form of a ready to use wide input range step down regulator – just kick out the 7805, solder a resistor to the module (to select the desired output voltage – ~350 Ohm for 5V output)  – and thats it… many thanks to TI for the samples! ***advertisement finished***

controller inside the ad2000-m

controller inside the ad2000-m

PTH08080W - drop in replacement

PTH08080W – drop in replacement

Note 1: the little jumper soldered to the via holes on the left side of the controller prevents me from the cover open alarm

Note 2: the shrink wire covered red blob contains the PTH08080W

Note 3: I used the two vias on the left side of the relay to connect my switch input (see schematic + board – input K1 +/-). The relay is switched on if the door opener is activated. I simply detect the state of the relay – voila – an RFID arm/disarm switch.

The alarm system is based on a simple state machine. If the system is armed by using a known identification token (RFID, PIN) and the door is opened a short sequence of pulses is given. After that, the horn sound continuously for 30, 45, 2x 60 seconds with little breaks. Finally the horn is deactivated – to protect my neighbors from screaming sounds during my holidays… (for easier drawing the „move from trigger-state-to-disarm“ transition is not shown).

states of the alarm system

states of the alarm system

The __LOCK-states are used for filtering bouncing contacts of the relay. To prevent a complete alarm cycle caused by a „hard knock“ against the door the system reverts to the armed state if the door is closed within the first 1.5 seconds. Programming the controller was straight forward… I just translated the state machine from the picture above. Since the code deals with a security device no optimization was applied – just easy to read and maintain code (dooralarm – firmware). Sorry! for not posting any picture of the finished system. But that would be like posting my bank cards PIN… That’s it.

cheap etching machine for small pcb’s

posted by on 2012.10.26, under collected, electronics
26:

I quit my last job where I had access to great labs – including the one for making PCBs the easy way (yeah, they have all the nice/expensive tools from Bungard … and a CNC miller/cutter). So now  I had to decide how to setup the needed infrastructure for making PCBs@home. Here are some of the results.

For putting the layout on the PCB I decided to use the direct toner transfer method with a modified laminator like described here. Cause of the chicken-and-egg-problem I used an Arduino to control the heater instead of a dedicated board (what I create after the setup for making PCBs is done so I can create more boards…). It works very well – thanks to all the folks that invented/improved that technology.

Since I do all the fun at home I choose sodium per sulfate as etchant – its a clean and safe solution. First I thought about buying a commercial etching machine. But after some reading it turns out that all the inexpensive and/or cheap machines are … to expensive. I moved to the hardware store and got all I needed for less then 50 euro:

  • round glass vase, 25 cm hight,  9 cm  diameter ~ 5 €
  • aquarium heater, 100 W – 20 €
  • aquarium membrane air pump ~ 10 €
  • fizzy stone ~ 2 €
  • 2 m silicone tube ~ 2 €
  • return valve ~ 3 €
  • a sheet of 4 mm acrylic glass (30 cm x 20 cm) ~ 5 €
  • pack of 4 cupping vessels ~ 2 €

I also ordered a 24 cm long tee thermometer that goes till 110 degree Celsius for ~8 €. But a simple 2-euro window thermometer with a range from -x..50 degree Celsius also does that job. But the tee thermometer looks better. Much!

First I created an holder for the fizzy stone by heating a small strip (5 cm x 10 cm) of the acrylic glass with my kitchen gas torch in the middle and bend it by 90 degrees. Two holes later a cupping vessel and the stone are mounted and put in place on the ground of the vase:

fizzy stone holder

fizzy stone holder

Cause of the air bubbles the etchant could spray out of the upper end. A quadratic cover of 10×10 cm acrylic glass makes a good seal. To keep it in place I glued a smaller piece (5.5×5.5 cm) that fits (nearly) the inner diameter of the glass vase in the center of the larger one. The width of hole for the heater is aligned to the diameter of his glass rod so the little bigger head could keep down the top plate. The tee thermometer is plugged through a 8 mm hole and fixed by a rubber band.

top plate

top plate

The PCB holder is made from a strip of the acrylic glass and two cupping vessels so only one-sided PCBs are possible for now. In a first run I mold the top cover and the PCB holder together by heating the ends and the plate with a gas torch. It holds bombproof till I tried to fix the angle of the holder a little by heating the junction again. After cooling down it breaks of – I think the heat applied to much stress to the material. A little of cyan acrylic glue fixed that mistake. Lesson learned: Don’t mold. Just glue!

PCB holder, thermometer and heater

PCB holder, thermometer and heater

heater - removed limitter

heater – removed limiter

 

 

 

 

 

 

 

 

 

 

 

 

I had to remove the mechanical temperature limiter to get the needed 45..50 degree Celsius for the etchant. I simply used a rasp for this purpose. A first test with 1 liter of water shows that the machine needs ~25 minutes to heat up from 16 to 48 degree.The hysteresis of the mechanical temperature switch is 6 degree so the etchant cools down till 42 degrees – 3 degree below optimum. But cooling down takes that long that I could etch one PCB inside the temperature frame between 48 and 45 degree. For now that fits my needs (I do not plan to go into mass production).

working heater is indicated by yellow ligth

working heater is indicated by yellow light

Finally I filled in the etchant: 200 gram sodium per sulfate and 900 ml water. The finished etching machine looks not so bad:

finished etching machine

finished etching machine

A first run of the complete setup produces great results.  Etching of the complete board takes ~10 minutes. The area of the PCB that was in the stream of the air bubbles was ready after 7 minutes. 3 minutes more for the rest – no under-etching at all.  Some samples below…

TSSOP20

TSSOP20

SOIC08

And finally the one I’m most happy with (cause it shows that even that small structures are not the limit):

SOT23-8

SOT23-8

And yes, the numbers are not mirrored. Two bad things about glass vase: due to the  small diameter the size of the PCBs is limited to ~8 cm on one side.  The second is about filling the etchant from the vase into a bottle. It wont work very well. The vase has a cutted border so some of the liquid „hangs“ on the glass and flows down of the outside of the vase. But after that great success I ordered a bigger glass vase with a diameter of ~11 cm and a border that is round molten. I think version 0.2 will solve that problems.

multimeter GVA-18B protocol & dump tool

posted by on 2012.02.04, under electronics, programming
04:

A wile ago I bought an inexpensive multimeter on ebay: G VA 18 B. For ~30€ it comes with autorange measurement for voltage, current, frequency, resistant, capacity and … temperature (with an internal and external sensor). The meter has serial interface based on an infrared diode on the top.The connection to the pc is done with a CP2030-based serial-usb adapter cable (also in the package).

GVA18B, also sold as VA18

GVA18B, also sold as VA18

Unhappily the offered software won’t work for me – it does not see the virtual COM-port (and it only works on Windows). So I decided to write my own. I connected to the meter via putty – and got only binary crap. With the help of some lines C# (I decided to train my C#-„skills“ – can’t remember the reason) to dump the output in hex/binary and some sample data I figured out the protocol… great hardware but ugly protocol. It seems our friends in HongKong simply map the data of the display controller to some bytes… The facts:

Every sample is decoded into 14 bytes. The high-nibble of each byte contains the position within the stream (bits 4,5,6,7; P={16,32,48,64,..,224} or shifted by 4 bits P={1,..14}). The bytes 1, 10, 11, 12, 13, 14 containing control informations about the selected unit, the range and further functions. The table below shows witch bit in each byte is set for a specific function (high nibble set to zero, first number is decimal value, in brackets the relevant bits):

Each numeric position on the display is encoded into a pair of bytes [1,2], …, [7,8]. The association is given in the following graphic. Bytes 1,3,5,7 represent position „a“, 2,4,6,8 position „b“:

The meaning of Bit 3 in byte „a“ depends on the position of the digit. For the leftmost digit it indicates a leading minus. For the other positions it indicates that the digit is the first part of the fraction.

In the attached code you find a class that handles all the encoding stuff – GVA18BProtocolDecoder. You can drop the received data into it – it does the rest. For simple use it offers an interface for registering a handler that is called when new data arrives. The data can then be fetched by using some convenience methods. The dump2display does what is names – it simply puts the data on the screen (and shows how to use the decoder). And yes, the code is over sized and not very sexy – yet. But it is under the GPL3 – use it as you can: GVA18BDataDump

pagetop