Heater
Posts: 13266
Joined: Tue Jul 17, 2012 3:02 pm

Re: ScriptBasic

Fri Jun 07, 2019 5:39 pm

ScriptBasic,
If hippy or Richard can explain how polling a pin results to humidity and temperature readings in an array I would appreciate it.
I'm not really sure what those guys are up to but my guess is...

If you can poll an input pin at a regular and hopefully accurate period that is at least twice the rate you expect the input signal to change, preferably more, then you can capture an array full of samples in one go.

That is just a bucket full of ones and zero's but you can then analyze whatever you have in your capture buffer at your leisure and decode whatever it contains.

All of which sounds a bit tricky to me when we are talking about using interpreted languages running on a non-realtime operating system where it's really hard to know what the time is and when you should do stuff. Not to mention the apparent variability in timing of the input device.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 6:16 pm

Python uses a C library (rpi.gpio) but polls the pin native and it works (with retrys). I don't see this as much different than what we are doing with WiringPi and scriba.
That is just a bucket full of ones and zero's but you can then analyze whatever you have in your capture buffer at your leisure and decode whatever it contains.
That is what I did in my last test. Build a string of 1/0's polling pin 7 85 times with a 1 microsecond delay. The resulting string looks real to me but what do I know?

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Fri Jun 07, 2019 6:36 pm

ScriptBasic wrote:
Fri Jun 07, 2019 4:48 pm
If hippy or Richard can explain how polling a pin results to humidity and temperature readings in an array I would appreciate it.
For me, "polling" is part of a bit-banging process, reading a pin, doing something active depending on its value. "Sampling" would be grabbing data at a constant(is) rate into an array.

So, when sampling one just grabs the signal level as often as one can over a period which is long enough to have received a full transmission. Then one sets about deciding what that grabbed data in the array means.

For the DHT11 the data bits are encoded as high pulses in the signal, short is zero, long is one. So one can scan the array and determine what distinguishes a short from a long pulse. In the example below it's notionally 5 consecutive high samples for a short, 9 for as long. So which side of 7 it's on gives us a good indicator of short or long -

Code: Select all

Sensor Value     0010

Bit value              0         0           1           0
                      ____      ___      _________      ____ 
Signal            ___|    |____|   |____|         |____|    |____

Sampled array     00001111100000111100000111111111100000111110000

Count of ones          5         4          10           5

Above 7 ?              0         0           1           0

Result            0010  
Bingo; our result has the same bit value as what the sensor sent.

The brilliant thing here is we don't need to know the notional short or long periods, or what the median is. We can determine that from whatever is in the array. We can intrinsically cope with the system clock being fast or slow, can handle a good degree of jitter and variances from how things should be, so even if we're stalled we should only lose one sample every so often and that shouldn't affect the overall result.

Richard appears AIUI to be using additional tricks, using the number of zeroes as well as the number of one's so he has the full width of each gap and pulse which can be used to determine where something was missed, more accurately determine what the data should be.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 6:45 pm

Thanks!

That is a VERY helpful explanation. The diagram brought it home.

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Fri Jun 07, 2019 6:47 pm

ScriptBasic wrote:
Fri Jun 07, 2019 6:16 pm
That is just a bucket full of ones and zero's but you can then analyze whatever you have in your capture buffer at your leisure and decode whatever it contains.
That is what I did in my last test. Build a string of 1/0's polling pin 7 85 times with a 1 microsecond delay. The resulting string looks real to me but what do I know?
Not sure what you are referring to but your 01010101 efforts were merely noting levels had changed, that is not 'sampling the raw data'.

The DHT11 outputs a bit-stream signal of 5ms or so duration per reading when triggered. If you sample every 1us you would end up with around 5000 samples in a 5000 item array / buffer. You then need to determine what the 40 data bits are within those 5000 samples as per above.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 6:56 pm

PLEASE send a SB script showing what you mean.

I like your idea of building a string / array of bits and process it after the reading has been done. Forensic analysis. 8-)

Can I assume the faster the pin sample rate the better the chance an accurate reading will result?

Pass 1. Determine how many 1's in a row on an average constitutes a HIGH.

Pass 2. Parse out 1/0's. LIKE could be useful here.

Pass 3. Convert bits to values in the 4 element result array.

When the DHT11 is done sending its data, does it leave the pin in one state or the other?
Last edited by John_Spikowski on Fri Jun 07, 2019 7:17 pm, edited 2 times in total.

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Fri Jun 07, 2019 7:04 pm

hippy wrote:
Fri Jun 07, 2019 6:36 pm
So, when sampling one just grabs the signal level as often as one can over a period which is long enough to have received a full transmission.
And looking at the diagram above, if you did get 5 zeroes for a gap, 5 ones for a short pulse, and we know those are meant to last 50us; you know your sample rate is nominally every 10us.

That may not be accurate, we're assuming the gap and short pulse is 50us, but it will be in the right ballpark.

Maybe I should have though of this earlier !

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Fri Jun 07, 2019 7:10 pm

ScriptBasic wrote:
Fri Jun 07, 2019 6:56 pm
PLEASE send a SB script showing what you mean.
I don't have an SB script, don't particularly intend to create one. But the general idea is -

Code: Select all

DIM array(5000)
FOR index = 0 TO 5000
  array[index] = WPI::DigitalRead(7)
NEXT
ScriptBasic wrote:
Fri Jun 07, 2019 6:56 pm
Can I assume the faster the pin sample rate the better the chance an accurate reading will result?
You will more accurately be able to determine what the DHT11 sent and what that means.

But it won't mean you are able to avoid the issues of corrupt readings which seem to even afflict those which are 'reading the data perfectly' in assembler or C.

You'll still need to check the checksum and decide if the data sent is good or bad.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 7:21 pm

That gives me something to run with. The magic is going to be how I process the bit stream. I'm going with a string rather than an array. Much faster.

Is the DHT11 data rata independent of the CPU's ability to sample pin status?

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 8:40 pm

Here is my data capture results using 99 for priority and no delays between reading the pin. It seems it's all over around 200 - 210 reads and then it's all one's after that. (5000 was overkill running interpretively)

Code: Select all

IMPORT wpi.bas

WPI::wiringPiSetup()
WPI::piHiPri(99)

valid = 0
WHILE valid = 0
  bits = ""
  WPI::pinMode(7, 1)
  WPI::digitalWrite(7, 0)
  WPI::delay(18)
  WPI::digitalWrite(7, 1)
  WPI::delayMicroseconds(40)
  WPI::pinMode(7, 0)
  FOR i = 0 TO 210
    thisstate = WPI::digitalRead(7)
    bits &= thisstate
'   WPI::delayMicroseconds(1)
  NEXT
  PRINT bits,"<*>\n"
  WPI::delay(1500)
WEND
WPI::piHiPri(0)  
Output

Code: Select all

[email protected]:~/sbrpi/examples $ scriba allbits.sb
0011110001111000100011100011001111000100010001000110011000100010001000100010001000111100010001111001100011100011001100010001000100010001000111100110011110001000100011110001000111100011100011111111111111111111111<*>
0111000100010001111000111100010001111001100011100011000100010001000100010001100010001000100010001111000100011110001000111100110001000100010001000100011110001000100011110001000100011110001111001100010001111111111<*>
0111000100010001111000111100010001110001100111100010001000110011000100010001000100010001100110001110001000111100010001111000100010001000100010001100111100010001000111100010001000111111100010001100111111111111111<*>
0111000100011001111000111100010001111001110001000100011001100010001000100010001000110011000111000100011110001000111100010001000100010001000110011000111000100011110001000100011110001000111100111100011111111111111<*>
0111000100010001111000111100010001110001100111100010001000110011000100010001000100010001100110011110001000111100010001111000100010001000100010001000111100010001000111100010001000111100011100010001100111111111111<*>
0111000100011000111000111100010001111001100011100011001100010001000100010001000110001000100010001111000100011100011001111000100011001100110001000100011110001000100011110011000100011100011110001000100011111111111<*>
0111000100010001111000111100110001110001000111100010001000110011001100010001000100010001000110011110001000111100010001111000100010001000100010001111100010001000111100010001000111100111100010001100111111111111111<*>
0111100010001111000111100110001110001100111100010001000110001000100010001000100011001100010001110001100111100010001111000100010001000110011000100010001111001100011100010001100111100010001111000111000111111111111<*>
0111100110011000111000111100010001111001100011100011001100010001000100010001000110011001100010001110001100111100010001111000100010001000100011001100111100010001000111100010001000111100011100010001100111111111111<*>
0111000100011001111000111100010001111001100011100011001100010001000100010001100110001000100010001111000100011110001000111000110011000100010001000100011110001000100011110001000100011110001110001000110011111111111<*>
0111000100010001111000111000110011110001000111100010001000100010001100110001000100010001000100011110001000111100010010010001100110001000100010001111000100010001111001100010001111001111000100010001111111111111111<*>
0111000110001000111000111100010001111000100011110001000100010001000100011001100010001000100010001111000100011110001000111100010001000100010001000110011110001000110011110001000110011110001111000100010001111111111<*>
0111000100010001111000111000110011110001000111100010001000100011001100010001000100010001100110001110001000111100010001111000100010001000100010001100110001110001000111100010001000111100010001111001111000111111111<*>
0111000100010001111000111100010001111001100011100011001100010001000100010001100010001000100010001111000100011110001000111100010001000100010001100110001110001100110001110001000110011110001111000100010001111111111<*>
0111000100010001111000111100110001110001000111100010001000100011001100010001000100010100010001110001000111100010001111000100010001000100010001100111100010001000111100010001000111100011100011001100111111111111111<*>
0111100110001000111000111100010001111000100011110001000100010001000100011001100010001000100010001111000100011110001000111100110001000100010001000100011110001000100011110001000100011110001110001100110001111111111<*>
0111000100010001111000111100110001110001000111100010001000100011001100010001000100010001000110011110001000111100010001111000100010001000100010001100111100010001000111100010001000111100011110011000100011111111111<*>
1110001001001110011110010001111000100011110011000100010001000100011001100010001000100010001111000100011110001000111100110001000100010001000100011110001111000100011100011001100000010001111000111111111111111111111<*>
0111000100011001111000111100011100010001111000100010001000110011000100010001000100011001100111100010001111000100011110001000100010001000100011001111000100010001111000100010001111000111000110011000111111111111111<*>
0111000100010001111000111000110011110001000111100010001000100011001100010001000100010001000110011110001000111100010001111000100010001000100010001100111100010001000111100010001000111100011100011001100011111111111<*>
0111000100010001111000111100110001110001000111100010001000100011001100010001000100010001000110011110001000111100010001111000100010001000100010001100111100010001000111100010001000111100011100011001100011111111111<*>
1110001100110001110001111000100011110001000111100110001000100010001000100011001100010001000100011110001000111000110011110001010011000100110011100001011100100100011000111100010001000111111111111111111111111111111<*>
0111000100010001111000111000110011110001000111100010001000100010001100110001000100010001000100011110001000111100110001110001100110011000100010001000111100011100010001111000100010001111000111000110011110001111111<*>
^C
[email protected]:~/sbrpi/examples $ 

User avatar
RichardRussell
Posts: 578
Joined: Thu Jun 21, 2012 10:48 am

Re: ScriptBasic

Fri Jun 07, 2019 8:46 pm

Heater wrote:
Fri Jun 07, 2019 5:39 pm
All of which sounds a bit tricky to me when we are talking about using interpreted languages running on a non-realtime operating system
Tricky in the sense of getting reliable results, yes - as we have seen. But not tricky in the sense of writing the code, indeed quite straightforward; I think my BBC BASIC program to do it worked pretty much first time.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 8:58 pm

Is there a sync HIGH pulse sent or is that noise?

In scriba's case it looks like "1111" is the device HIGH state.

User avatar
RichardRussell
Posts: 578
Joined: Thu Jun 21, 2012 10:48 am

Re: ScriptBasic

Fri Jun 07, 2019 9:02 pm

ScriptBasic wrote:
Fri Jun 07, 2019 8:40 pm
Here is my data capture results using 99 for priority and no delays between reading the pin. It seems it's all over around 200 - 210 reads
Only 200 reads in 5 milliseconds? That's 25 μs per sample, which isn't going to be fast enough (as Heater said the theoretical minimum sampling rate is twice the highest frequency in the signal, as per the Nyquist criterion). Get it down to 10 μs and you might have a fighting chance.

It looks from your code that rather than storing the samples in a numeric array you're concatenating them onto the end of a string. I don't know anything about the internal workings of ScriptBasic, but in BBC BASIC that would be much slower.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 9:13 pm

Thanks Richard!

That is what I needed to know before investing anymore time trying to do the pin read natively.

I might create a C function that reads a pin continually based on the number of iterations requested and returns it in a string.

ScriptBasic has a fast memory manager that is thread safe. Arrays (linked lists) are much slower.

FYI: Arrays in ScriptBasic are geared towards flexibility not speed. You can create powerful matrix structures with no practical limitations.

User avatar
RichardRussell
Posts: 578
Joined: Thu Jun 21, 2012 10:48 am

Re: ScriptBasic

Fri Jun 07, 2019 10:24 pm

ScriptBasic wrote:
Fri Jun 07, 2019 9:13 pm
Arrays (linked lists) are much slower.
How do you manage if ScriptBasic needs to call an API function which takes an array as a parameter? For example suppose you want to call the GDI32 PolyLine API to draw a polygon. Windows will assume that the array elements are in consecutive memory locations, but from your description that's not the case with SB arrays. Is there a workaround?

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 10:29 pm

I can create/access SB arrays at the C level and pass them back and forth through a function argument. If I need a C array to pass to a C function I can create / dimension it by the passed arguments in the call.

Under Windows the DLLC extension module has functions for dealing with C structures and variable types including COM. It also has a dynamic FFI that is great.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Fri Jun 07, 2019 10:39 pm

I would like to thank both @hippy and Richard for sharing their knowledge about GPIO and interfacing with a sensor like the DHT11.

I'm happy with my C version of the DHT11 interface and will continue down this road with other sensors that need this kind of granularity.

I still have a box of sensors I would like to discover how they work so that is where I'm going to be spending my time going forward.

I will be creating a WiringPi and sensor interface thread on the ScriptBasic board on the https://RaspberryBASIC.org forum.

Join us if you would like to follow along with the sensor interface project.

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Sat Jun 08, 2019 12:29 am

This is my proof of concept code for DHT11 decoding. You can ignore the lines with "# Ignore" as those are all there to fake a bit-stream and utility code. The rest is what you would have to implement in ScriptBasic.

It's not optimised. It doesn't include a check that 40, and only 40, pulses were detected. The finding the 'min' and 'max' pulses and calculating the 'mid' median could probably be better implemented.

Code: Select all

# .-------------------------------------------------------------------------.
# |  Simulate DHT11 Sapling and Decoding                                    |
# `-------------------------------------------------------------------------'

def Dim(size,default=0):                          # Ignore
  return [default] * size                         # Ignore
                                                  # Ignore
def Hex(n,w=2):                                   # Ignore
  s = hex(n)[2:].upper()                          # Ignore
  if len(s) < w:                                  # Ignore
    s = ("0" * (w-len(s))) + s                    # Ignore
  return "0x" + s                                 # Ignore
                                                  # Ignore
def Bin(n,w=8):                                   # Ignore
  s = bin(n)[2:]                                  # Ignore
  if len(s) < w:                                  # Ignore
    s = ("0" * (w-len(s))) + s                    # Ignore
  return "0b" + s                                 # Ignore
                                                  # Ignore
def ReadBit():                                    # Ignore
  try:                                            # Ignore
    b = ReadBit.bitStream[0]                      # Ignore
  except:                                         # Ignore
    chcksum     = HUMI_HI + HUMI_LO               # Ignore
    chcksum    += TEMP_HI + TEMP_LO               # Ignore
    chcksum    &= 0xFF                            # Ignore
    sensorData  = Bin(HUMI_HI)[2:]                # Ignore
    sensorData += Bin(HUMI_LO)[2:]                # Ignore
    sensorData += Bin(TEMP_HI)[2:]                # Ignore
    sensorData += Bin(TEMP_LO)[2:]                # Ignore
    sensorData += Bin(chcksum)[2:]                # Ignore
    bitStream = ""                                # Ignore
    while sensorData != "":                       # Ignore
      if sensorData.startswith("0"):              # Ignore
        bitStream += "0" * int(50/SAMPLE_EVERY)   # Ignore
        bitStream += "1" * int(26/SAMPLE_EVERY)   # Ignore
      else:                                       # Ignore
        bitStream += "0" * int(50/SAMPLE_EVERY)   # Ignore
        bitStream += "1" * int(70/SAMPLE_EVERY)   # Ignore
      sensorData = sensorData[1:]                 # Ignore
    ReadBit.bitStream = bitStream                 # Ignore
    b = ReadBit.bitStream[0]                      # Ignore
  ReadBit.bitStream = ReadBit.bitStream[1:] + "0" # Ignore
  return int(b)                                   # Ignore

# .-------------------------------------------------------------------------.
# |  Define sampling criteria                                               |
# `-------------------------------------------------------------------------'

HUMI_HI      = 0x12                               # Faked sensor reading
HUMI_LO      = 0x34
TEMP_HI      = 0x56
TEMP_LO      = 0x78

SAMPLE_EVERY = 10                                 # Sample every 10us
SAMPLE_TIME  = 6000                               # Sample time is 6ms

SAMPLE_SIZE  = SAMPLE_TIME // SAMPLE_EVERY        # How many samples we need

samples      = Dim(SAMPLE_SIZE)                   # The samples
pulses       = Dim(40)                            # The pulse widths
bits         = Dim(40)                            # The bit valuse
byte         = Dim(5)                             # the byte values

# .-------------------------------------------------------------------------.
# |  Grab a sample                                                          |
# `-------------------------------------------------------------------------'

for index in range(SAMPLE_SIZE):
  samples[index] = ReadBit()

# .-------------------------------------------------------------------------.
# |  Extract the pulse lengths                                              |
# `-------------------------------------------------------------------------'

pulseSize = 0
last = 0
for index in range(SAMPLE_SIZE):
  if samples[index] == 1:
    if last == 0:
      pulseSize += 1
    if pulseSize <= 40:
      pulses[pulseSize-1] += 1
  last = samples[index]

# .-------------------------------------------------------------------------.
# |  Determine min and max pulse sizes, and mid-way between                 |
# `-------------------------------------------------------------------------'

min = pulses[0]
max = pulses[0]

for index in range(40):
  if pulses[index] < min : min = pulses[index]
  if pulses[index] > max : max = pulses[index]

mid = ((max - min) // 2) + min

# .-------------------------------------------------------------------------.
# |  Determine bit values                                                   |
# `-------------------------------------------------------------------------'

for index in range(40):
  if pulses[index] > mid : bits[index] = 1
  else                   : bits[index] = 0

# .-------------------------------------------------------------------------.
# |  Form the bytes                                                         |
# `-------------------------------------------------------------------------'

for index in range(5):
  byte[index] = 0

for index in range( 0, 7) : byte[0] += bits[index] << (7-(index & 7))
for index in range( 8,15) : byte[1] += bits[index] << (7-(index & 7))
for index in range(16,23) : byte[2] += bits[index] << (7-(index & 7))
for index in range(24,31) : byte[3] += bits[index] << (7-(index & 7))
for index in range(32,39) : byte[4] += bits[index] << (7-(index & 7))

# .-------------------------------------------------------------------------.
# |  Print results                                                          |
# `-------------------------------------------------------------------------'

if ((byte[0] + byte[1] + byte[2] + byte[3]) & 0xFF) == byte[4]:
  print "Checksum matches"
else:
  print "Checksum does not match"

print Hex(byte[0])
print Hex(byte[1])
print Hex(byte[2])
print Hex(byte[3])
print Hex(byte[4])

# .-------------------------------------------------------------------------.
# |  End of Program                                                         |
# `-------------------------------------------------------------------------'

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Sat Jun 08, 2019 12:45 am

Does your Python script work with my generated bit streams?

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Sat Jun 08, 2019 1:36 am

ScriptBasic wrote:
Sat Jun 08, 2019 12:45 am
Does your Python script work on my generated bit streams?
No. The first only has 38 pulses, second has 41, and while the third has 40 pulses, samples show consecutive ones of length 1, 2, 3, 4 and 7. There's not enough consistency to cleanly differentiate between short and long pulses and that 7 suggests a gap between pulses was missed.

As Richard said; "Only 200 reads in 5 milliseconds? That's 25 μs per sample, which isn't going to be fast enough".

You need to speed up that capture rate and the easiest route, as suggested, is to store the samples in an array, build your concatenated 'bit' string from that array after the capture.

Updated : Seems you have the response pulse in there. Taking that into account and hand rigging the differentiator between short and long, the second sample does pass. But not sure the data is meaningful but that could be an error in my PoC code. Others don't pass so the verdict is still too slow.

User avatar
John_Spikowski
Posts: 1382
Joined: Wed Apr 03, 2019 5:53 pm
Location: Anacortes, WA USA
Contact: Website Twitter

Re: ScriptBasic

Sat Jun 08, 2019 1:47 am

Here is the Python version for comparison,

I'm surprised Python is fast enough to do meaningful sampling. With BBC BASIC and Python both able to sample natively, it blurs the line between interpreters and compilers.
If the data transmission is correct, the check sum should equals to the lower 8bit of the result of “8bit integral RH data + 8bit decimal RH data + 8bit integral T data + 8bit decimal T data”.

Image

2) MCU sends out start signal to DHT/ DHT responses to MCU
The default status of the DATA pin is high. When the communication between MCU and DHT11 starts, MCU will pull down the DATA pin for least 18ms. This is called “Start Signal” and it is to ensure DHT11 has detected the signal from MCU. Then MCU will pull up DATA pin for 20-40us to wait for DHT11’s response.

Image

Once DHT11 detects the start signal, it will pull down the DATA pin as “Response Signal”, which will last 80us. Then DHT11 will pull up the DATA pin for 80us, and prepare for data sending.

3)During the data transition

During the data transition, every bit of data begins with the 50us low-voltage-level and ends with a high-voltage-level signal. The length of the high-voltage-level signal decides whether the bit is “0” or “1”.

Data bit “0” has 26-28us high-voltage length:

Image

While data bit “1” has 70us high-voltage length:

Image
dht11_example.py

Code: Select all

import RPi.GPIO as GPIO
import dht11
import time
import datetime

# initialize GPIO
GPIO.setwarnings(False)
GPIO.setmode(GPIO.BCM)
GPIO.cleanup()

# read data using pin 4
instance = dht11.DHT11(pin=4)

# while True:
result = instance.read()
if result.is_valid():
  print("Last valid input: " + str(datetime.datetime.now()))
# print("Temprature: %d C" % result.temperature) 
  print("Temprature: %d F" % ((result.temperature * 9) / 5 + 32))
  print("Humidity: %d %%" % result.humidity)

time.sleep(1)

dht11.py (import)

Code: Select all

import time
import RPi


class DHT11Result:
    'DHT11 sensor result returned by DHT11.read() method'

    ERR_NO_ERROR = 0
    ERR_MISSING_DATA = 1
    ERR_CRC = 2

    error_code = ERR_NO_ERROR
    temperature = -1
    humidity = -1

    def __init__(self, error_code, temperature, humidity):
        self.error_code = error_code
        self.temperature = temperature
        self.humidity = humidity

    def is_valid(self):
        return self.error_code == DHT11Result.ERR_NO_ERROR


class DHT11:
    'DHT11 sensor reader class for Raspberry'

    __pin = 0

    def __init__(self, pin):
        self.__pin = pin

    def read(self):
        RPi.GPIO.setup(self.__pin, RPi.GPIO.OUT)

        # send initial high
        self.__send_and_sleep(RPi.GPIO.HIGH, 0.05)

        # pull down to low
        self.__send_and_sleep(RPi.GPIO.LOW, 0.02)

        # change to input using pull up
        RPi.GPIO.setup(self.__pin, RPi.GPIO.IN, RPi.GPIO.PUD_UP)

        # collect data into an array
        data = self.__collect_input()

        # parse lengths of all data pull up periods
        pull_up_lengths = self.__parse_data_pull_up_lengths(data)

        # if bit count mismatch, return error (4 byte data + 1 byte checksum)
        if len(pull_up_lengths) != 40:
            return DHT11Result(DHT11Result.ERR_MISSING_DATA, 0, 0)

        # calculate bits from lengths of the pull up periods
        bits = self.__calculate_bits(pull_up_lengths)

        # we have the bits, calculate bytes
        the_bytes = self.__bits_to_bytes(bits)

        # calculate checksum and check
        checksum = self.__calculate_checksum(the_bytes)
        if the_bytes[4] != checksum:
            return DHT11Result(DHT11Result.ERR_CRC, 0, 0)

        # ok, we have valid data, return it
        return DHT11Result(DHT11Result.ERR_NO_ERROR, the_bytes[2], the_bytes[0])

    def __send_and_sleep(self, output, sleep):
        RPi.GPIO.output(self.__pin, output)
        time.sleep(sleep)

    def __collect_input(self):
        # collect the data while unchanged found
        unchanged_count = 0

        # this is used to determine where is the end of the data
        max_unchanged_count = 100

        last = -1
        data = []
        while True:
            current = RPi.GPIO.input(self.__pin)
            data.append(current)
            if last != current:
                unchanged_count = 0
                last = current
            else:
                unchanged_count += 1
                if unchanged_count > max_unchanged_count:
                    break

        return data

    def __parse_data_pull_up_lengths(self, data):
        STATE_INIT_PULL_DOWN = 1
        STATE_INIT_PULL_UP = 2
        STATE_DATA_FIRST_PULL_DOWN = 3
        STATE_DATA_PULL_UP = 4
        STATE_DATA_PULL_DOWN = 5

        state = STATE_INIT_PULL_DOWN

        lengths = [] # will contain the lengths of data pull up periods
        current_length = 0 # will contain the length of the previous period

        for i in range(len(data)):

            current = data[i]
            current_length += 1

            if state == STATE_INIT_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # ok, we got the initial pull down
                    state = STATE_INIT_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_INIT_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # ok, we got the initial pull up
                    state = STATE_DATA_FIRST_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_FIRST_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # we have the initial pull down, the next will be the data pull up
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_UP:
                if current == RPi.GPIO.HIGH:
                    # data pulled up, the length of this pull up will determine whether it is 0 or 1
                    current_length = 0
                    state = STATE_DATA_PULL_DOWN
                    continue
                else:
                    continue
            if state == STATE_DATA_PULL_DOWN:
                if current == RPi.GPIO.LOW:
                    # pulled down, we store the length of the previous pull up period
                    lengths.append(current_length)
                    state = STATE_DATA_PULL_UP
                    continue
                else:
                    continue

        return lengths

    def __calculate_bits(self, pull_up_lengths):
        # find shortest and longest period
        shortest_pull_up = 1000
        longest_pull_up = 0

        for i in range(0, len(pull_up_lengths)):
            length = pull_up_lengths[i]
            if length < shortest_pull_up:
                shortest_pull_up = length
            if length > longest_pull_up:
                longest_pull_up = length

        # use the halfway to determine whether the period it is long or short
        halfway = shortest_pull_up + (longest_pull_up - shortest_pull_up) / 2
        bits = []

        for i in range(0, len(pull_up_lengths)):
            bit = False
            if pull_up_lengths[i] > halfway:
                bit = True
            bits.append(bit)

        return bits

    def __bits_to_bytes(self, bits):
        the_bytes = []
        byte = 0

        for i in range(0, len(bits)):
            byte = byte << 1
            if (bits[i]):
                byte = byte | 1
            else:
                byte = byte | 0
            if ((i + 1) % 8 == 0):
                the_bytes.append(byte)
                byte = 0

        return the_bytes

    def __calculate_checksum(self, the_bytes):
        return the_bytes[0] + the_bytes[1] + the_bytes[2] + the_bytes[3] & 255
Output

Code: Select all

[email protected]:~/sensors/DHT11_Python-master $ python dht11_example.py
Last valid input: 2019-06-07 18:46:59.763028
Temprature: 71 F
Humidity: 52 %
[email protected]:~/sensors/DHT11_Python-master $ 

User avatar
RichardRussell
Posts: 578
Joined: Thu Jun 21, 2012 10:48 am

Re: ScriptBasic

Sat Jun 08, 2019 9:53 am

ScriptBasic wrote:
Sat Jun 08, 2019 1:47 am
I'm surprised Python is fast enough to do meaningful sampling. With BBC BASIC and Python both able to sample natively, it blurs the line between interpreters and compilers.
A sample period of 1μs isn't really 'fast' when you consider that, at a 1.2 GHz CPU clock speed, it amounts to 1,200 clock cycles; you can do an awful lot in that number of cycles! Your 25μs period corresponds to 30,000 cycles; what on earth is the ScriptBasic interpreter doing in a tight loop, reading and storing samples, which takes that long?

FOR loops are the fastest type in BBC BASIC (and probably other BASICs too) because there is less 'per loop' overhead than with a REPEAT or WHILE. Specifically, the termination condition is determined in the FOR statement and doesn't need to be 'interpreted' each time around the loop as it does with the others. To give you a feel for the speed (and remember that BBC BASIC is a 'classic' interpreter with no pre-processing shortcuts) this empty FOR loop:

Code: Select all

      FOR I% = 1 TO 1000000
      NEXT
takes just under 0.2 seconds on my RPi 3B (not +) which is 0.2μs per loop or 240 cycles at 1.2 GHz. That's obviously a lot compared with a compiled language (assuming it doesn't optimise the loop away entirely!) but sounds in the right ball park for a 'pure' interpreter compiled from C. I expect an interpreter coded in assembly language, like Sophie Wilson (CBE)'s ARM BBC BASIC, would be considerably faster.

hippy
Posts: 5932
Joined: Fri Sep 09, 2011 10:34 pm
Location: UK

Re: ScriptBasic

Sat Jun 08, 2019 11:50 am

On my Pi 3B (non-plus) ...

Code: Select all

import RPi.GPIO as GPIO
pin = 0
GPIO.setmode(GPIO.BCM)
GPIO.setup(pin, GPIO.IN, GPIO.PUD_UP)
for n in range(1000000):
  bit = GPIO.input(pin)

Code: Select all

real    0m2.250s
user    0m2.181s
sys     0m0.060s
So for RPI.GPIO that's a polling interval of under 2.25us. appending each read to a list has an interval of 2.85us.

My own Python extension is, unsurprisingly, near exactly the same.

So 'less than 3us' and a non-optimal 'count consecutive levels which are the same' on-the-fly it is under 5us.

I can reduce my library execution time overhead by not passing a pin number every call, avoiding all the parsing, converting from Python type to C type, validity checking, every time. Just setting the pin once and reading whatever pin was set, gets it down to 1.6us, and there's scope for further reduction.

And it should be pointed out that I'm locked at 600MHz because I have /dev/serial0 enabled.

Using my library for ScriptBasic -

Code: Select all

IMPORT gpio.bas
pin = 0
gpio::enter()
gpio::setAllow(pin)
gpio::inputWithPullUp(pin)
FOR n = 1 TO 1000000
  this = gpio::getPin(pin)
NEXT
gpio::leave()

Code: Select all

real    0m6.619s
user    0m6.606s
sys     0m0.010s

So that's 'under 7us' where the Python was 'under 3us'. But that should be good enough, taking 3 samples during the shortest 26us period.

Changing to "bit = "X" & gpio::getPin(pin)" moved us up towards 10us. But the time does depend on how much is already in the string being added to. Limited to 2000 chars it was still 'under 11us'.

Using "array[n] = gpio:getPin(pin)" is 'under 8us'. Not passing the pin every time reduces that to 'under 4us'

So the conclusion is, ScriptBasic should be able to do it natively. ScriptBasic seems somewhat slower than Python for calls into the library, seems to have significant overhead in handling arguments passed, but it should be achievable.

User avatar
RichardRussell
Posts: 578
Joined: Thu Jun 21, 2012 10:48 am

Re: ScriptBasic

Sat Jun 08, 2019 11:56 am

hippy wrote:
Sat Jun 08, 2019 11:50 am
And it should be pointed out that I'm locked at 600MHz because I have /dev/serial0 enabled.
I wasn't aware of that side-effect, I wonder why. I don't know for sure what the CPU clock was when I ran my FOR loop test but I'm assuming probably 1.2 GHz because of the 100% load on one core for a significant time.

User avatar
RichardRussell
Posts: 578
Joined: Thu Jun 21, 2012 10:48 am

Re: ScriptBasic

Sat Jun 08, 2019 1:43 pm

RichardRussell wrote:
Sat Jun 08, 2019 9:53 am
I expect an interpreter coded in assembly language, like Sophie Wilson (CBE)'s ARM BBC BASIC, would be considerably faster.
That's been confirmed by somebody at the StarDot forum. Sophie's BASIC runs a 1 million iteration empty FOR loop in 35 ms compared with my 190 ms or so. So that's 35 nanoseconds per loop or only 42 clock cycles at 1.2 GHz, showing what an impressive assembly-language programmer she is (or at least was).

Return to “Other programming languages”