Friday 5 July 2013

Hacking Cheap Chinese Parking Sensors

Following in the vein that I think we are about to see a revival in tech design/manufacturing in the UK, I am sharing my DIY experiences and getting my hands dirty for the first time in a long time.

About 4 years ago I thought it would be cool to modify Chinese parking sensors and integrate it into the car. Since then I've bought a car with a built in reversing camera so the desire subsided but I still fancied doing it. Recently I stumbled across an article to do exactly that except for a robot project and this let me to discover a few brave individuals that have hacked these cheap parking sensors and integrated it into their car system

Since it's possible to buy these parking sensor systems for £10, I've decided to have a go and have enjoyed playing with hardware again - the first time in over a decade.

The protcols between these systems vary and the one I bought didn't have the same protocol as those who have hacked them. So I bought another one only to find it had exactly the same protocol despite being a different design. I think that kind of reveals something about the design ecosystem which exists in China.

The other people who have hacked these units have all used an arduino so I figured I would too. I have to say I'm really impressed with what you can make the arduino do. It's a really powerful device.

In order to decode the protocol I ended up buying a protocol analyser. It was just £12. Equally remarkable. After lots of detective work I figured out the protocol. 

Mine was more odd than others out there. They all use a form of Pulse Width Modulation (PWM).  What was odd about mine is it's the 0V pulse width which is important rather the +5V which we are classically taught. The other odd thing about mine is the display forms part of the timing chain. The controller shuts down if the display is not attached. I therefore needed to emulate the display timing to make it work.

The parking sensor protocol is based on bursts every 20.7mS with a period of 22.658mS
The high level frame timings are:
1/ Display sets +5V for 20.68 ms
2/ Display sets 0V for 0.138 ms
3/ Display pulls +5V for 1.57ms then goes high impedance
4/ Controller sends data during the 1.57ms duration
5/ Display pulls 0V for 0.27mS
6/ Cycle repeats

Data is sent by the controller during the 1.57ms period. Data pulses are sent by controller in inverted PWM format ie zero volts if where the logic 1 and 0 are and a logic 1 or 0 depends upon how long the period of silence lasts. (Took a while to figure that out).

A logic 0 is a 0V pulse of 34uS and a logic 1 isa 0V of 54uS . The burst starts with a 0V pulse of 78us. Pulses of +5V vary in duration

Personally I prefer interrupt driven code so that's what I've opted for. One of the hacked versions I looked at was polled.  Given the pulse widths are 34us and the arduino takes about 6us to do anything I opted to interrupt driven. It also means the board can do other things when events aren't happening for example it could be updating the screen with data.

I'm using timer1 which is 16 bits to orchestrate the display emulation and turning on and off the interrupts. The data burst is interrupt driven on the edges.

Here's my arduino code:

// Copyright Steve Roberts 2013
// Arduino based car parking sensor protocol decode
// There are plenty of cheap 4 sensor Chinese parking sensors available
// This decodes the protocol between the controller and the display
// The display is a 7 segment LED with left right distance indicators
// The protocols used vary by vendor and are proprietary
// The protocol here is one based on bursts every 20.7mS with a period of 22.658mS
// The controller and display are inter-linked and it will not work without the display
// attached
// The display sends the high level frame information to the controller.
// We therefore need to emulate the display timing frame.
// The controller plugs into an arduino digital pin
// The high level frame timings are:
// Display sets +5V for 20.68 ms
// Display sets 0V for 0.138 ms
// Display pulls +5V for 1.57ms then goes high impedance
// Controller sends data during the 1.57ms duration
// Display pulls 0V for 0.27mS
// Cycle repeats
// Data pulses are sent by controller in inverted PWM format
// A logic 0 is a 0V pulse of 34uS and a logic 1 isa 0V of 54uS
// The burst starts with a 0V pulse of 78uS
//  Pulses of +5V vary in duration
// Use arduino timers which are interrupt driven for the high level timing structure
// Use  timer1 since this is a 16 bit timer. Timers0 and timer2 are only 8 bits long so insufficient
// Use digital pins 2 and 3 which are mapped to interrupts for the digital signal detection
// The data in the burst period relates to one sensor. It is 16 bits long. It consists on an address
// and a 8bit value for distance
// Since the arduino is only working 1.6ms every 22mS we can stagger the timer to support 2 sets
// of parking sensor for front and rear parking sensors on a car (work in progress)

//storage variables
boolean toggle1 = 0;
unsigned long lastChange = 0L;
unsigned long  rearData =0;
int rearDataReady =0;

#define  rearParkingSensorPin 3 
// Pin 3 Interrupt 1
#define rearInterrupt 1
// Interrupt for pin 3

void setup(){
 
  //set pins as outputs

  pinMode(rearParkingSensorPin, OUTPUT);
  Serial.begin(115200);
  pinMode(13, OUTPUT);
 
cli();//stop interrupts

//set timer1 interrupt at duration of 20.6ms
  TCCR1A = 0;// set entire TCCR1A register to 0
  TCCR1B = 0;// same for TCCR1B
  TCNT1  = 0;//initialize counter value to 0
  // set compare match register for 20.6ms duration
  OCR1A = 41190;// = (16*10^6) / (1*1024) - 1 (must be <65536)
  // turn on CTC mode
  TCCR1B |= (1 << WGM12);
 // Set CS21 bit for 8 prescaler
  TCCR1B |= (1 << CS21);  
    // enable timer compare interrupt
  TIMSK1 |= (1 << OCIE1A);


sei();//allow interrupts

}//end setup


ISR(TIMER1_COMPA_vect){
// Timing comes from the display unit to control box
// Single wire working
// We need to emulate the timing sequence
// User interrupt driven timers for this

  switch (toggle1){
    case 0:
          digitalWrite(rearParkingSensorPin,HIGH);
          OCR1A = 41190;// 20.6 ms HIGH
          toggle1 = 1;
          break;
     case 1:
         digitalWrite(rearParkingSensorPin,LOW);
         OCR1A = 275;//138 us LOW
         toggle1 = 2;
         break;
     case 2:
         // Data burst period from controller
         rearData = 1; //Set the first bit
         rearDataReady = 0;
         digitalWrite(rearParkingSensorPin,HIGH);
    
 // We have a total of 140us before we need to start doing edge handling of pulses
         pinMode(rearParkingSensorPin, INPUT);

         // we will get an interrupt on both falling and rising edges:
         // Clear any pending interrupts we may have
        EIFR = 0xff;
        attachInterrupt(1, interrup_isr_onchange, CHANGE);
         OCR1A = 3148;//1.574 ms HIGH
         toggle1 = 3;
         break;
      case 3:
          EIFR = 0xff;
          detachInterrupt(1);
  
          digitalWrite(rearParkingSensorPin,LOW);
          pinMode(rearParkingSensorPin, OUTPUT);
          OCR1A = 548;//0.274ms LOW
          toggle1 = 0;
          break;
  }
}


void interrup_isr_onchange(void)
{

  // ISR latency from edge change 3-6 us
  // We are interested in how long the 0V state was
  // 34uS = 0
  // 56uS = 1
  // 78uS is the first pulse
  unsigned long timeNow;
  unsigned long pulseInterval;
  unsigned int bit;
  timeNow = micros();
  pulseInterval = timeNow - lastChange;
  lastChange = timeNow;
 // Serial.println(pulseInterval);
 
  if (digitalRead(rearParkingSensorPin)  == HIGH && rearData < 131072) {
    // We've had a low pulse
    if (pulseInterval < 45) {
       rearData = rearData << 1;
       rearDataReady++;
    }
    if (pulseInterval > 45 && pulseInterval < 68 ) {
        rearData = rearData ^1;
       rearData = rearData << 1;
      rearDataReady++;
    }
    // Ignore pulses longer than 68uS
  
  }else { // digitalWrite(13,!digitalRead(13));
                rearDataReady = 17;
            }
  // Rising edge only of interest as low pulse

}

void loop(){
 unsigned int address;
 unsigned  int distance;
 // Check we have enough bits of data
 if (rearDataReady >  15 && rearData > 0b1111111111111111) {
   rearData = rearData >> 1; // We're getting an extra 0 - can't figure out why so strip off
// Check bits 9,10,11,12 are all 1's
     address = rearData & 0b0111000000000000 ;
// Get rid of the least significant 12 bits
     address = address >> 12;
// Extract the distance information
     distance = rearData & 0b0000000011111111;
// Discard any dodgy data
// Valid sensor address aree 0,1,2,3,4
// Maximum distance value is 99 (9.9m). Reported in decimeters
     if (address < 5 && distance < 100) {
     Serial.print (address);
     Serial.print (",");
    Serial.println(distance);
     }

  digitalWrite(13,!digitalRead(13));  // Just show we are alive.
  }
}

Currently this just outputs to the Serial port but the plan is to use a MAXIM 7456 to do composite video overlay. I can then overlay the data from the parking sensors which I will install, onto my cars reversing camera. I've looked at my car and it's easy to hook into the camera. Currently the camera selection is basically a tap from the reversing lights (thankfully no CAN bus there) so I can use that and also take control of the screen display selection too. I'm also thinking about installing camera on the front of the car and intelligently switching between front and rear.  This will involve some custom hardware. The arduino allows extension boards.  These are called shields. I plan to create a design and get it made.






1 comment: