5

Ok, I admit this a very odd issue I'm having.

My setup is that I have an ultrasonic sensor that senses the distance every five seconds, and sends the data to another Pi, (a Pi 3 in this case) via Bluetooth.

Now, the distance sensed varies wildly. The object is 150cm away, and I get readings of anything between 70cm and 150cm.

You probably think, the code is wrong, but this is where it gets interesting. If I use the exact same setup, but I use a Pi Zero with a Bluetooth dongle (as opposed to the Pi Zero W) it works perfectly fine! It's the same sensor, same setup, I even use the same SD Card (Running the March version of Jessie Lite)...

I have 2 Pi Zero's and 2 Pi Zero W's - both Zero W's sense the distance incorrectly, while both Zeros work fine.

Has anyone experienced something similar? Is there a remedy? Or do I have a faulty batch of Pi Zero W's?

Edit:

Here's the code for measuring the distance:

import RPi.GPIO as gpio
import time
import math

TEMPERATURE = 26
TRIGGER    = 38  #Default Pin 38 (BOARD) / gpio 20 (BCM)
ECHO       = 40  #Default Pin 40 (BOARD) / gpio 21 (BCM)


def startMeasurement():
    sample_size = 11
    sample_wait = 0.1

    sonar_signal_off = 0
    speed_of_sound = 331.3 * math.sqrt(1+(TEMPERATURE / 273.15))

    distance_reading = 0
    failed_reading = 0
    previous_reading = 0

    sample = []
    # setup input/output pins
    gpio.setwarnings(False)
    gpio.setmode(gpio.BOARD)
    gpio.setup(TRIGGER, gpio.OUT)
    gpio.setup(ECHO, gpio.IN)

    while distance_reading < sample_size:
        gpio.output(TRIGGER, gpio.LOW)
        time.sleep(sample_wait)
        gpio.output(TRIGGER, True)
        time.sleep(0.00001)
        gpio.output(TRIGGER, False)
        echo_status_counter = 1

        while gpio.input(ECHO) == 0:
            if echo_status_counter < 1000:
                sonar_signal_off = time.time()
                echo_status_counter += 1
            else:
                sonar_signal_off = 0      
                raise SystemError('Echo pulse was not received')

        while gpio.input(ECHO) == 1:
            sonar_signal_on = time.time()

        time_passed = sonar_signal_on - sonar_signal_off
        distance_cm = time_passed * ((speed_of_sound * 100) / 2)
        faulty_pair = previous_reading >= 2500 and distance_cm < 5

        if(distance_cm < 2500 and faulty_pair == False):
           failed_reading = 0
            distance_reading += 1
            sample.append(distance_cm)
        else:
            failed_reading += 1
            if(failed_reading > sample_size*2+1):
                return -666
        previous_reading = distance_cm

    sorted_sample = sorted(sample)

    # Only cleanup the pins used to prevent clobbering
    # any others in use by the program
    gpio.cleanup((TRIGGER, ECHO))
    return sorted_sample[sample_size // 2]

def main():
    while True:
        try:
            srt_time = time.time()
            this_dist =  startMeasurement()
            end_time = time.time()
            tot_time = (end_time-srt_time)

            time.sleep(max(0.1, 5-tot_time))

        except SystemError as ex:
            template = "[SENSOR] {0} error: {1!r}"
            message = template.format(type(ex).__name__, ex.args)
            print(message)
            pass


if __name__ == "__main__":
    main()

And this is the code for talking to the Pi 3 via Bluetooth. Essentially the Pi 3 acknowledges that it received the data, and every (roughly) 5 seconds a new measurement is taken:

# -*- coding: UTF-8 -*-
############################################################################
# IMPORTS
from bluetooth import *
from ast import literal_eval
import threading
import os
import sys
import time
import json

import pp_detect as detect
import pp_getmac as mac

if sys.version < '3':
    input = raw_input

############################################################################
#GLOBALS?
this_node_mac = mac.retrieve("hci0")
prev_node_mac = "AB:CD:EF:01:23:45"
prev_port = 4097

def handleJsonReceive(socket, name):
    data = socket.recv(1024)
    if len(data) == 0:
        data = '{"action":"noop"}'
    data = json.loads(data)
    return data

# SERVER SIDE
def my_server():
    print "[   SERVER] STARTED"

    sdata = None
    noopc = 0

    while True:
        connection_done = False
        try:
            while not connection_done:
                print "[   SERVER] Accepting connections from ",prev_node_mac
                print "[   SERVER] Accepting connections on port", prev_port

                this_sock=BluetoothSocket( L2CAP )
                port = prev_port
                this_sock.bind(("",port))
                this_sock.listen(1)
                prev_sock,address = this_sock.accept()
                print "[   SERVER] Accepted connection from ",address

                sdata = handleJsonReceive(prev_sock, "   SERVER1")
                cmd = sdata['action']

                print "[   SERVER] Connection successful"
                prev_sock.send('{"action":"ready"}')
                connection_done=True

            while True:
                sdata = handleJsonReceive(prev_sock, "   SERVER")
                cmd = sdata['action']

                noopc += 1 if cmd=="noop" else 0
                if(noopc > 250):
                    raise BluetoothError("(666, 'Custom connection timeout')")

                if(cmd == "sense"):
                    srt_time = time.time()

                    payload = senseDistance()
                    prev_sock.send(payload)

                    end_time = time.time()
                    tot_time = (end_time-srt_time)

                    time.sleep(max(0.1, 5-tot_time))

        except BluetoothError as ex:
            template = "[   SERVER] {0} error: {1!r}"
            message = template.format(type(ex).__name__, ex.args)
            print(message)
            time.sleep(2)
            pass
        except Exception as ex:
            template = "[   SERVER] {0} error: {1!r}"
            message = template.format(type(ex).__name__, ex.args)
            print(message)
            pass


def senseDistance():
    myDistance=detect.startMeasurement()
    mydata='{"action": "receive", "payload":"%.1f", "address":"%s"}'%(myDistance,this_node_mac)
    print myDistance
    return mydata


############################################################################
#START

if __name__ == "__main__":
    if(prev_node_mac != "__NONE__"):
        server = threading.Thread(name='server', target=my_server)
        server.start()

Now the code is not the best, since I just started learning Python. So please be gentle.

  • I think the code is wrong. – joan Apr 24 '17 at 15:51
  • 1
    Welcome -- but you need to include the code and details about how the sensor is attached for this question to be answered seriously. – goldilocks Apr 24 '17 at 16:12
  • @goldilocks - ok, thanks - I will add the code I have once I get home – Armalyte Apr 24 '17 at 16:25
  • 1
    in general, a distance sensor is handled by: 1) start ping 2) stop pin 3) start counting 4) poll or interrupt the ping response 5) if response taking too long, then consider the target to be too far away. 6) if response found then stop timer 7) calculate distance. From your question, is seems the next 'ping' is started too soon, resulting in the response being seen from the prior 'ping'/ Note: always wait (greater than) the max response time before starting another 'ping' sequence – user3629249 Apr 28 '17 at 17:40
  • Out of curiosity, have you ever tried using a bluetooth dongle with the RPi Zero W? I'm really curious as to if the bluetooth module on that isn't fast enough or isn't prioritized in the manner you need it to be. – Jacobm001 Jun 26 '17 at 20:16
1

Your code seems to do the timing entirely in software, in Python even. Since the Pi is a multitasking system (not a microcontroller like Arduino), your python program is not running all the time. This means that there is likely some time between the IO pin changing and your python script seeing the IO pin change. Then there is a bit more time between seeing the IO pin change and actually getting the timestamp from time.time().

An important thing to note is that the inaccuracy depends largely on how much other things the CPU is doing. It seems likely that keeping the wifi connection running on your Zero W takes up significant time (or at least regularly interrupts other processes for a short time), which could explain this behaviour. Even if there is another problem that causes the differences between the Zero and Zero W, doing this timing in software is still fairly limited.

A better approach is to let the hardware sample the IO pin and report any changes along with a hardware-generated timestamp. There seems to be a complicated trick using the DMA controller, but I can recommend the pigpio library which can set this up and hides all the complicated bits from you. There's even python bindings so you should be able to get started quickly. With pigpio, you can do very accurate timing (up to 1us precision IIRC).

0

If you're using the hardware clock for timing, there's a known clock-stretching issue with the BCM 2835.

  • 1
    But the Pi Zero and the Pi Zero W both use the same chip. That would seem to eliminate this as the root cause. – Steve Robillard Jun 26 '17 at 20:02

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.