Current Lua downloadable firmware will be posted here

User avatar
By lepido
#45066 Here is my first hit at it (without the HTTP part and the blocking issue solved):

Code: Select allhcsr04 = {}

function hcsr04.init(trig_pin, echo_pin, average)
    local self = {}
    self.trig_pin = trig_pin or 7
    self.echo_pin = echo_pin or 6
    self.average = average or 3
    gpio.mode(self.trig_pin, gpio.OUTPUT)
    gpio.mode(self.echo_pin, gpio.INT)

    self.time_start = 0
    self.time_end = 0
    self.dist = {}
    self.count = 0

    function self.echo_int(level)
        if level == 1 then
            self.time_start = tmr.now()
            gpio.trig(self.echo_pin, "down")
        else
            self.time_end = tmr.now()
        end   
    end

    function self.trigger()

        self.time_start = 0
        self.time_end = 0
   
        -- register echo interrupt
        gpio.trig(self.echo_pin, "up", self.echo_int)
        -- start trigger
        gpio.write(self.trig_pin, gpio.HIGH)
        -- end trigger impuls after 100us
        tmr.delay(20)
        gpio.write(self.trig_pin, gpio.LOW)
       
        -- start the watchdog
        tmr.alarm(2,100,tmr.ALARM_SINGLE, function ()
            if (self.time_end - self.time_start) < 0 then
                self.dist = -1
                print("measurement failed - " .. self.count)
            else
                self.count = self.count + 1
                self.dist[self.count] = (self.time_end - self.time_start) / 5800
                if (self.count < self.average) then
                    self.trigger()
                else
                    local dist = 0
                    for loop, value in ipairs(self.dist) do
                        dist = dist + value
                    end
                    dist = dist / self.count
                    print("distance: " .. dist)
                end
            end
        end)
    end
    return self
end


The problem is that it seems the trigger signal is not send properly or the interrupt is not fired. Unfortunately I do not have a scope to figure out which one is the issue. When resetting the nodeMCU, the measurement works fine. Afterwards, however, the measurement fails (usually on the very first iteration). It fails because the interrupt (registered on the ECHO pin) isn't fired.

Any ideas what could be the problem here?

---
HC-SR04 background:
One needs to send a pulse on the TRIGGER pin (at least 10us) to trigger the measurement. The measured distance is given back by the impulse length on the ECHO pin.
User avatar
By TerryE
#45136 You haven't read my FAQ yet have you? You have to think in an event driven paradigm not a procedural one. If you sit in long loops then the WDT fires or the wifi / tcp stack dies. The measurement process is like a set of beads on a necklace. Each bead is a separate SDK task triggered by an event. You gain control by passing a completion callback to add at the end of the necklace, e.g.
Code: Select alllocal sensor = require "hcsr04"
sensor.init()
sensor.measure_avg(average_cb)
The measure isn't completed om the line after the measure_avg call (it's only just begun) its complete when average_cb() is passed the average measurement. To give you an idea of how to approach this, I've hacked together this -- compiles clean but untested. I leave that to you :)
Code: Select allmodule = ...;
local self = {}
_G[module] = self

-- keep all privated as locals and use upval binding to make their scope module wide
-- tailcalls are effectively function gotos

local gpio, time_start, time_end, trigger, echo = gpio, 0, 0
local mode,write,OUTPUT,INT,HIGH,LOW= gpio.mode,gpio.OUTPUT,gpio.INT,gpio.HIGH,gpio.LOW
local now, delay, alarm, timer = tmr.now, tmr.delay, tmr.alarm

local function echo_cb(level)
  if level == 1 then
    time_start = now()
    trig(echo, "down")
  else
    time_end = now()
  end
end

local function measure(meaure_cb)
  trig(echo, "up", echo_cb)
  write(trigger, HIGH); delay(100);write(trigger, LOW)
  alarm(timer, 100, 0, function()
    local elapsed = time_end - time_start
    return measure_cb(elapsed < 0 and -1 or elapsed / 5800)
  end)
end

local average

local function measure_avg(average_cb)
  local total, i = 0, 0
  local function measure_sample(distance)
    -- if any sample is invalid them abort
    if distance < 0 then return average_cb(1) end
    if i > 0 then total = total + distance end
    if i == average then
      tmr.unregister(timer)
      return average_cb(total / average)
    end
    i = i + 1
    alarm(timer, 30, 0, function() measure(measure_sample) end)
  end
  measure(measure_sample)
end

function self.init(pin_trig, pin_echo, avg, timer_id)
  trigger, echo, average, timer = pin_trig or 4, pin_echo or 3, avg or 3, timer_id or 1
  mode(trig, OUTPUT); mode(echo, INT)
  self.measure = measure
  self.measure_avg = measure_avg
end 
 
return self
User avatar
By lepido
#45151 TerryE, thanks for your help so far! Really appreciate it!

I took your code and tested it together with a simple init.lua (see below). The problem, however, is basically the same as before. The interrupt (level = down) is not fired at all for short distances and longer distances (> 40cm) do not return constant results.

I had the sensor on a table with a distance to the wall about roughly 1m (~ 3 feet). I did not move the sensor but got very differing results (from a couple of cm up to 17m). Pointing the sensor towards another surface (different measurement series) didn't change anything.

Here is the output (using only the single measurement function so far):

Code: Select allNodeMCU custom build by frightanic.com
   branch: dev
   commit: 7d576efed94092916e1a84bccba6319d62b9dedf
   SSL: true
   modules: dht,file,gpio,mqtt,net,node,sntp,tmr,uart,wifi
 build    built on: 2016-04-08 18:35
 powered by Lua 5.1.4 on SDK 1.5.1(e67da894)
--- starting measurement ---
> rx INT
Measurement result: 0.42741379310345
print(uart.setup(0, 115200, 8, 0, 1, 1 ))
115200
>
Communication with MCU...
Got answer! AutoDetect firmware...

NodeMCU firmware detected.
=node.heap()
40800
> dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 3.8486206896552
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 0.59120689655172
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 4.0860344827586
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 3.6062068965517
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 1.04
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 3.761724137931
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 1.8551724137931
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 8.2537931034483
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 3.5643103448276
dofile("init.lua")
--- starting measurement ---
> rx INT
Measurement result: 17.503275862069


It seems that the call to the interrupt callback function is not executed timely (meaning, it gets stalled). Of course, I do understand that the SDK is not a real time OS, but it worked with the 1.4.0 release. Did something in the interrupt handling change as well between 1.4.0 and 1.5.1?

Here are the lua files I used:

init.lua
Code: Select allprint("--- starting measurement ---")

function measure_cb(distance)
    print("Measurement result: " .. distance)
end

hcrs04_new = require "hcsr04_new"

hcrs04_new.init(7,6,3,1)
hcrs04_new.measure(measure_cb)


hcrs04_new.lua
Code: Select allmodule = ...;
local self = {}
_G[module] = self

-- keep all privated as locals and use upval binding to make their scope module wide
-- tailcalls are effectively function gotos

local gpio, time_start, time_end, trigger, echo = gpio, 0, 0
local now, delay, alarm, timer = tmr.now, tmr.delay, tmr.alarm

local function echo_cb(level)
  if level == 1 then
    time_start = now()
    gpio.trig(echo, "down")
  else
    time_end = now()
    print("rx INT")
  end
end

local function measure(measure_cb)
  gpio.trig(echo, "up", echo_cb)
  gpio.write(trigger, gpio.HIGH); delay(100); gpio.write(trigger, gpio.LOW)
  tmr.alarm(timer, 500, 0, function()
    local elapsed = time_end - time_start
    return measure_cb(elapsed < 0 and -1 or elapsed / 5800)
  end)
end

local average

local function measure_avg(average_cb)
  local total, i = 0, 0
  local function measure_sample(distance)
    -- if any sample is invalid then abort
    if distance < 0 then return average_cb(1) end
    if i > 0 then total = total + distance end
    if i == average then
      tmr.unregister(timer)
      return average_cb(total / average)
    end
    i = i + 1
    alarm(timer, 30, 0, function() measure(measure_sample) end)
  end
  measure(measure_sample)
end

function self.init(pin_trig, pin_echo, avg, timer_id)
  trigger, echo, average, timer = pin_trig or 7, pin_echo or 6, avg or 3, timer_id or 1
  gpio.mode(trigger, gpio.OUTPUT); gpio.mode(echo, gpio.INT)
  self.measure = measure
  self.measure_avg = measure_avg
end 
 
return self
User avatar
By TerryE
#45160 OK, I've had a look at the data sheet. The range in cm is Δt/58 where t is in µSec, so maximum range of 10m say suggests that you do your sampling pings every 60mSec or so; if you want to take an average of 4 pings then this will be 4 seconds. You should avoid all I/O and memory allocations during the measurement so I would keep it all really simple as follows . You only need one method: measure so you can even strip this down to a unction require instead of a class one:
Code: Select alllocal measure = require "hc-sr04-v2" measure(7,6,3,1,function(d) print(d) end))

where the require code is something like:
Code: Select alllocal gpio, time_start, time_end, trigger, echo = gpio, 0, 0
local now, delay, alarm, timer = tmr.now, tmr.delay, tmr.alarm

return function(trig_pin, echo_pin, sample_cnt, timer_id,report_cb)
    trig_pin, echo_pin   = trig_pin or 7, echo_pin or 6
    sample_cnt, timer_id = sample_cnt or 3, timer_id or 1   

    local total, i, result = 0, 0, {}

    local function echo_cb(level)
      if level == 1 and result[i] = 0 then
        result[i] = -now()
        trig(echo, "down")
      elseif level == 0 and result[i] < 0 then
        result[i] = now() + result[i]
        trig(echo, "none")
        i = i + 1
      else
        trig(echo, "none") -- anything else turn off interrupts and restart at next semaple
      end
    end
   
    local function measure()
      if i > 0 then -- process last sample
        if result[i] < 0 then 
          result[i] = 0
          i = i - 1
          return -- skip a beat to allow the sonar to settle down
        else
          total = total + result[i]
        end
        if i == sample_cnt then
          tmr.unregister(timer)
          for j = 1, sample_count do print(("Sample %u is %u"):format(j,result[i])) end
          return report_cb(total / (58*sample_count))
        end
      end

      mode(echo, INT); gpio.trig(echo, "up", echo_cb)
      trig(echo, "up", echo_cb)
      write(trigger, HIGH); delay(110); write(trigger, LOW)
      i = i + 1
    end
   
    for j = 0, sample_cnt do results[j] = 0 end -- pre-allocate result array

    mode(trigger, OUTPUT)
    alarm(timer, 60, 1, measure))
    measure()
  end

In essence, what I am doing is to just use a repeating timer to pulse the trigger pin then time the up/down transitions to add a new sample to results. You don't actually need to store an array for the averages, but doing so will help you to debug this. Note that I don't even start to output to the UART until the time critical stuff is completed. The error logic isn't quite correct, but I've got jobs to do and I've got to dash. This gives you the idea. I'll leave you to sort it out. //Terry