Manchester lib once more

Hi,

different project now, but again Manchester code hassles with an ATtiny85. I am using the ATtiny85 to read some sensor values (BME280 pressure/humidity/temperature and GY-30 light intensity, both via I²C) plus battery level and send it about every minute to an ESP32.

Since there is no compatible Manchester library to mchr3k's on Github for the ESP32, I have to roll my own.

I noticed I was not able to properly decode the signals from one of my two sensor boards and started digging into it. What I found was a difference in encoding HIGH and LOW bits in terms of phase lengths. The sensor board showed a significant shift of the HIGH signal towards shorter phase lengths, despite the fact the code in the Manchester lib is using the very same delays:

void Manchester::sendZero(void)
{
  delayMicroseconds(delay1);
  digitalWrite(TxPin, HIGH);

  delayMicroseconds(delay2);
  digitalWrite(TxPin, LOW);
}//end of send a zero


void Manchester::sendOne(void)
{
  delayMicroseconds(delay1);
  digitalWrite(TxPin, LOW);

  delayMicroseconds(delay2);
  digitalWrite(TxPin, HIGH);
}//end of send one

On the receiver side the signals show up as follows. I grouped the signal lengths in larger categories to keep it manageable. In the table you will find from left to right: Category#, Base phase length of that category, number of hits for a '0' signal, number of hits for an '1' signal:

Cat Phas     0    1
-------------------
  0    0:    1    0
  1   59:    0    0
  2  118:    0    0
  3  177:    0    0
  4  236:    0    0
  5  295:    0    4
  6  354:    0   49
  7  413:   47    0
  8  472:    5    0
  9  531:    0    0
 10  590:    0    0
 11  649:    0    0
 12  708:    0    1
 13  767:    0   25
 14  826:   20    0
 15  885:    6    0

You can see the '1' signal is shifted at least one category down towards shorter phase lengths.

How can that happen? I already changed the ATtiny85, the 433MHz sender and put fresh batteries in (the board is driven by 2 AAA batteries and a step-up converter to 3.3V) with no change at all.

The ATtiny will wake up every 8s and start reading the sensors and sending the data every 7th turn.

Please find the ATtiny85 sketch below.

/*
 ATtiny85  sensor
*/
#include <avr/sleep.h>
#include <avr/interrupt.h>
#include <TinyWireM.h>
#include "MyAttinyBME280.h"

#define _BH1750FVI

#ifdef _BH1750FVI
#define BH1750_ADDRESS 0x23
#endif

// include Manchester code library to drive 433MHz sender
#include <Manchester.h>

// Sensor data structure
#include "SensorData.h"

// *********** Define I/O Pins **********************
const int BatteryLvl = A2;         // Battery input
const int DATA_433 = PIN_B1;       // 433MHz sender data wire

//************ USER PARAMETERS***********************
// Number of sleep cycles between actions
const uint8_t SLEEPCYCLES = 7;
SensorData s;

// Variables for the Sleep/power down modes:
volatile uint8_t f_wdt = SLEEPCYCLES;

//************* MyAttinyBMP280 object ***************
MyAttinyBME280 bme(0x76);

// Routines to set and clear bits (used in the sleep code)
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

// the setup routine runs once when you press reset:
void setup()  { 
  // Set up IO pins
  pinMode(BatteryLvl, INPUT);
  pinMode(DATA_433, OUTPUT);   // 433 MHz sender
  analogReference(INTERNAL);
  // Initialize 433MHz sender
  man.setupTransmit(DATA_433, MAN_1200);
  s.data.len = 11;
  s.data.magic = 0x72;
  s.data.typeID = 0x11;
  TinyWireM.begin();

  // Sets BME280 forced mode, no upsampling
  bme.setWeatherMonitoring();
  bme.init();

  setup_watchdog(9); // approximately 8 seconds sleep
} 

// the loop routine runs over and over again forever:
void loop()  { 
  if (f_wdt>=SLEEPCYCLES) {  // wait for timed out watchdog / flag is set when a watchdog timeout occurs
    f_wdt=0;       // reset cycles

    bme.startSingleMeas();
    float    fval = 0.0;
    uint16_t val = 0;
    
    // Temperature
    fval = bme.readTemp();
    val = (fval+50)*500;
    s.data.payload[0] = (val>>8)&0xff;
    s.data.payload[1] = val&0xff;

    // Pressure
    fval = bme.readPress();
    val = (fval-600)*100;
    s.data.payload[2] = (val>>8)&0xff;
    s.data.payload[3] = val&0xff;

    // Humidity
    fval = bme.readHumidity();
    val = fval * 655;
    s.data.payload[4] = (val>>8)&0xff;
    s.data.payload[5] = val&0xff;

#ifdef _BH1750FVI
    TinyWireM.beginTransmission(BH1750_ADDRESS);             // I2C address
    TinyWireM.write(0x20);                                   // Switch to H-Mode2 1Lux resolution, one time
    TinyWireM.endTransmission();  
    delay(150);
    
    TinyWireM.requestFrom(BH1750_ADDRESS, 2);     // Request 2 bytes
    if(TinyWireM.available())     // Request 2 bytes
    {
      s.data.payload[6] = TinyWireM.read();    // Read byte into array
      s.data.payload[7] = TinyWireM.read();    // Read byte into array
    }
#endif

    // Batteriestatus
    sbi(ADCSRA,ADEN);                // switch Analog to Digitalconverter ON, in case we will need it

    int volt = 0;
    for(uint8_t i=8; i; i--) volt += analogRead(BatteryLvl);
    volt >>= 3;
    s.data.battery = (volt >> 2);    
    cbi(ADCSRA,ADEN);                // switch Analog to Digitalconverter OFF again
        
    man.transmitArray(12, s.packet);   // Send data
 }
 // now sleep well :-)
 system_sleep();
}


// set system into the sleep state 
// system wakes up when wtchdog is timed out
void system_sleep() {

  set_sleep_mode(SLEEP_MODE_PWR_DOWN); // sleep mode is set here
  sleep_enable();

  sleep_mode();                        // System actually sleeps here

  sleep_disable();                     // System continues execution here when watchdog timed out 
  
}

// 0=16ms, 1=32ms,2=64ms,3=128ms,4=250ms,5=500ms
// 6=1 sec,7=2 sec, 8=4 sec, 9= 8sec
void setup_watchdog(int ii) {

  byte bb;
  int ww;
  if (ii > 9 ) ii=9;
  bb=ii & 7;
  if (ii > 7) bb|= (1<<5);
  bb|= (1<<WDCE);
  ww=bb;

  MCUSR &= ~(1<<WDRF);
  // start timed sequence
  WDTCR |= (1<<WDCE) | (1<<WDE);
  // set new watchdog timeout value
  WDTCR = bb;
  WDTCR |= _BV(WDIE);
}
  
// Watchdog Interrupt Service / is executed when watchdog timed out
ISR(WDT_vect) {
  f_wdt++;  // set global flag
}

Addendum: I can even see the difference on the logic analyzer, attached directly to the DATA pin of the transmitter on the sensor board:

LOW phase:

HIGH phase:

The LOW phase is 54µs longer obviously.

I looked up the DALI library i use, and saw 2 things noInterrupts(); and

#define TE_LOW 390
#define TE_HIGH 823-TE_LOW

, so first off all with interrupts still turned on your timing may be off. Second, from memory , a 1 is a high followed by a low and a 0 is a low followed by a high, and the periods by definition are equal in time and the polarity is defined by the startbit. Manchester but the dali library has a longer TX-HIGH... so obviously there must be a reason for this that we don't know about.

DALI is running at 1200 bps, what are you running at ? Something similar i guess, how about just using the DALI library ?

Anyway you must be doing something wrong in the de-coding, since Manchester should actually be really precision in-sensitive.and margins of 50uS should not give any cause for incorrect data reception.

Thank you for your comments.

In fact my sensor is transmitting at 1200bd, too. I chose the Manchester lib on purpose, as it is small enough to fit into the ATtiny85's 8kB of memory together with the other stuff. I have done several projects with it already, but always with the same lib on both sender and receiver sides. I am in fact using timer interrupts to wake the MCU from deep sleep, but those are not active any more when it comes to sending, so I would not expect complications there.

Of course I can fix my receiver code to cope with the phase length difference - I simply wondered how the difference is caused and wanted to understand it. Interestingly your DALI library seems to correct the same effect, as you mentioned. Again, this is something I would like to understand.

To extend my story a bit: I would like the ESP32 being able to pick up several different 433MHz protocols, as I have some more sensors around that I did not build myself and are using completely different transmission mechanisms. So I am trying the ESP32 to detect the encoding type from the data itself, regardless what protocol or speed it will have. Having the luxury of an 80MHz MCU with lots of memory, I can afford to do some statistics on incoming messages to do so. The categorization shown in my first post is part of that - Manchester encoding always will have only two category ranges for single and double bits for instance.

My naïve approach was to take the average phase length from the leading "...101010" sequence to detect the timing for a single bit and double that for double bits. It in fact did work until I encountered my second sensor that had a larger difference between 0 and 1 phases, so that I found fake 3-bit sequences then.

I always figured that for reception you look for the first pulse length, and if the next edge occurs within 3/4 of that, that means the next bit is the same as the current, if it occurs beyond the 3/4 it is the opposite, and you correct the (half) length of the pulse just from the last pulse received all the time.