Tag: NodeMCU

Build your binary for NodeMCU

Build your binary for NodeMCU

Thanks to frightanic who replied to my post on my onewire problem, I learned about building your own binary with just the modules you need. Of course, I need that share that.

As you know, NodeMCU is constantly being improved. The version I used initially are, as it turns out, hopelessly outdated. On the building the firmware page on the NodeMCU documentation site, there are 3 options to get a new custom build for the NodeMCU. I might investigate the docker option later but for now, I’ve used the cloud build service to get 2 binaries, one with all the modules that I think I will need and one with just the following: file, gpio, net, node, tmr, wifi and pwm. It’s actually so straight forward that it feels silly to write about it.

Fill in your email address (this is where you will get the link to download the binaries once created) and again and select the modules you expect to use and than click on start your build and wait for the email. When you receive the email, it will contain 2 links, one to the float version and one to the integer version which you can than download. Be sure to click on Donate with PayPal if you use and appreciate this service.

Advertisements
NodeMCU Web Lights

NodeMCU Web Lights

After experimenting a lot with the NodeMCU, I’ve soldered the first one to a board together with some leds, microswitches and a button. What this contribution does is what I call Web Lights. Basically it is a webserver which controls the set of 3 color leds. You can do an http call to set the lights to a color or give it a range of colors and it will loop through them.

The microswitches are connected to pins D1 and D2 which also have a pull up resistor (next to it in the picture). These are generally off and are only on, to prevent the main program to start or to start the config server (see my post on my init.lua). The button is connected to D6. The four 3 color leds are connected to pins D3, D4 and D5. The connector you see at the top is for easy connection of my USB to UART device.

Apart from the init.lua and accompanying files described in my earlier post on my init.lua and the one for managing the 3 color led this project requires 2 more files, one for the functions to allow for color switching and one for the http server.

The functions for color switching:

cl = {}
clpos = 0
clc = ""
clrpt = 0
cltmr = 0
mcltmr = 0

function setclrpos ()
  if clrpt>0 then
    cltmr = cltmr+1
    if cltmr>=mcltmr then
      cltmr = 0
      clpos = clpos+1
      if clpos>#clc then
        clrpt = clrpt-1
        if clrpt<=0 then
          resetcolor()
        else
          clpos = 1
        end
      end
      if clpos<=#clc then
        rgbcolor(cl[tonumber(clc:sub(clpos,clpos))])
      end
    end
  end
end

function multicol (xcllist,xclscheme,xclrpt,xcltmr)
  clrpt = 0
  cl = {}
  local k
  clc = ""
  if (xcllist~=nil) then
    for k in string.gmatch(xcllist,"(%w+),*") do
      cl[#cl+1] = k
      clc = clc..tostring(#cl)
    end
  end
  mcltmr = 2
  if xcltmr then
    mcltmr = tonumber(xcltmr)
    if cltmr<1 then
      mcltmr = 1
    elseif mcltmr>10 then
      mcltmr = 10
    end
  end
  if xclscheme then
    clc = xclscheme
  end
  cltmr = 0
  clpos = 0
  if xclrpt then
    clrpt = tonumber(xclrpt)
  else
    clrpt = 1
  end
end

function resetcolor (level)
  clrpt = 0
  rgbcolor("off")
end

tmr.alarm(2,250,1,setclrpos)

I know, quite a lot of code to dump on you but it’s not as bad as it looks. The mechanics behind this are as follows:

The function multicol accepts four parameters:

  • A list of comma seperated colors
  • A list of numbers in which order the colors provided in the first parameter should appear
  • A number value how many times the sequence should repeat
  • A timer value

Each of these, except the first one, has a default value but let’s first look at an example:

multicol("red,blue,green","1213",9999,200)

This call will give you the following colors (changed every 200 ms) red,blue,red,green and repeats until you set a different color scheme or call resetcolor (or if you wait 9999 cycles).

The function’s default values are such that if you just supply a range of colors, that range is cycled through once with a timer value of 500 milliseconds.

The function sets the global variables:

  • cl, a numbered array of colors, based on the first parameter
  • clc, the list of numbers referencing the color array cl to cycle through
  • clrpt, the number of times the cycle should repeat
  • clpos, the position of the character in clc where we are at

and uses one local variable:

  • cltmr, the timer value

After the variables are set, the timer is set with the function setclrpos.

The function setclrpos moves to the next character in the variable clc by increasing clpos. If clpos has moved beyond the end of the clc string, move it back to the start if you still need to do a cycle (checked by reducing clrpt). If there is no cycle repeat left, turn off the leds and stop the timer. The last set of lines in the function actually set the color.

The logistics behind this is a continuous timer, which triggers every 250 milliseconds, and runs setclrpos. If there is nothing to do, than the function will simple just stop.

 

My init.lua

My init.lua

The file init.lua is the one that gets executed automatically everytime your NodeMCU boots. What I wanted to do is have a generic init.lua that does the basics like connecting to wifi but also allow for an abort or a config option.

Please note that this is my development init.lua. If I get around to calling some product final, I might consider updating this. The reason is that it is my development init.lua is because it allows for an abort of all program execution (by connecting D1 to ground) or start a config http server (by connecting D2 to ground) instead of the main program. In the final product I might need the extra pins and the software should be stable enough that I do not need an abort or a config option.

There are 3 files which stay the same, 1 config file and finally the main program file. The program flow is as follows:

init.lua starts
check if input D1 is low. if so, abort the rest of the program and goto end
init_functions.lc is executed
init_vars.lua is executed
if an array of ssids is defined, check which one of the available ssids matches one
  in the list. If you find a match, connect to it and goto wait for ip
if just one ssid is defined, connect to it
wait for an ip
if within a certain time, you do not have an ip, print a message and goto end
check if input D2 is low. if so, start the config server by executing
  httpserver_cfg.lc, send an ifttt trigger and goto end
check if main.lc exists. if not, print a message and goto end
send an ifttt trigger
start main.lc
end

That’s the basics. You will see that I have used compiled LUA code to save on heap (*.lc instead of *.lua) except for init_vars.lua which can be changed by the config server.

Let’s go through the code, starting with the easiest one, init_vars.lua

this_node = "Example"
ssids = {}
ssids["ssid1"] = "pass1"
ssids["ssid2"] = "pass2"
ssids["ssid3"] = "pass3"
srvtype = "HTTP"

this_node is the name of the device which is used in IFTTT triggers.
ssids is a list of possible ssids. Convenient if your device travels and will have multiple possible wifi networks.
srvtype is the type of device it is. It is only used in IFTTT triggers.

The next up is init_functions.lua:

function geturl (h, u)
  local conn = net.createConnection(net.TCP, false)
  conn:on("connection", function(cn, answer)
    cn:send("GET "..u.." HTTP/1.1\r\n"
    .."Host: "..h.."\r\n"
    .."Connection: keep-alive\r\n"
    .."Accept: */*\r\n\r\n")
  end)
  conn:connect(80,h)
end  

function ifttt (ev, v1, v2, v3)
  geturl("maker.ifttt.com",
"/trigger/"..ev.."/with/key/---yourkeyhere----?value1="..v1.."
&value2="..v2.."&value3="..v3)
end

function unescape(s)
  local rt, i, len = "", 1, #s
  s = s:gsub('+', ' ')
  local j, xx = s:match('()%%(%x%x)', i)
  while j do
    rt = rt .. s:sub(i, j-1) .. string.char(tonumber(xx,16))
    i = j+3
    j, xx = s:match('()%%(%x%x)', i)
  end
  return rt .. s:sub(i)
end

Three functions, geturl, which does an api call to host h with path u. Notice that this function does not return any page info. It just does the call which is sufficient for IFTTT and a lot of other API calls. The function unescape is exactly what the name suggest, it unescapes url request, turning %3A into : etc.

The following file to look at is httpserver_cfg.lua. This file starts a config server on port 81 which allows you to modify the init_vars.lua file without hooking up the device to a computer.

if srvcfg~=nil then
  srvcfg:close()
end
srvcfg = net.createServer(net.TCP)
srvcfg:listen(81,function(conn)
  conn:on("receive",function(cn,request)
--
-- extract the request uri
--
    local cmd = ""
    local method,path,vars
    _,_,method,path,vars = string.find(request,"([A-Z]+) (.+)?(.+) HTTP")
    if(method==nil) then
      _, _, method, path = string.find(request,"([A-Z]+) (.+) HTTP")
    end
    print(request)
    local httpget = {}
--
-- convert the request uri into httpget variables
--
    local k,v
    if (vars~=nil) then
      for k,v in string.gmatch(vars,"(%w+)=([%w%s%%%.%,%-%+]+)&*") do
        httpget[k] = unescape(v)
      end
    end
--
-- reboot if requested
--
    if httpget["cmd"]=="reboot" then
      node.restart()
--
-- rewrite the init_vars.lua file
--
    elseif httpget["change"]=="Change" then
      file.open("init_vars.lua","w")
      if httpget["node"] then
        this_node = httpget["node"] 
      else
        this_node = "New"
      end
      file.writeline("this_node = \""..this_node.."\"")
      file.writeline("")
      if httpget["srvt"] then
        srvtype = httpget["srvt"] 
      else
        srvtype = "New"
      end
      file.writeline("srvtype = \""..srvtype.."\"")
      file.writeline("")
      local c
      if httpget["ssid2"] then
        file.writeline("ssids = {}")
        ssids = {}
        c = 1
        while c<=5 do
          if httpget["ssid"..c] and httpget["ssid"..c]~="" then
            if httpget["pass"..c] then
              k = httpget["ssid"..c] 
              v = httpget["pass"..c] 
            else
              k = httpget["ssid"..c] 
              v = ""
            end
            file.writeline("ssids[\""..k.."\"] = \""..v.."\"")
            ssids[k] = v
          else
            c = 100
            break
          end
          c = c+1
        end
      else
        ssids = nil
        if httpget["ssid1"] then
          ssid = httpget["ssid1"] 
        else
          ssid = "None"
        end
        if httpget["pass1"] then
          pass = httpget["pass1"] 
        else
          pass = ""
        end
        file.writeline("ssid = \""..ssid.."\"")
        file.writeline("pass = \""..pass.."\"")
      end
      file.close()
    end
--
-- create the webpage
--
    local buf = "HTTP/1.1 200 OK\r\nContent-type: text/html\r\nConnection: close\r\n\r\n"
    buf = buf.."Config NodeMCU "..this_node..""
    buf = buf.."

Config NodeMCU: “..this_node..”


    buf = buf..”

This node


    buf = buf..”Reboot this node


    buf = buf..”

Node number/name:


    buf = buf..”Server type:


    buf = buf..”

Wifi networks


    buf = buf..”Enter list of wifi SSIDs and passwords:

    if ssids then
      c = 1
      for k,v in pairs(ssids) do
        buf = buf..”Wifi “..c..”, SSID: ”
        buf = buf..”password:

        c = c+1
      end
      while c<=5 do
        buf = buf..”Wifi “..c..”, SSID: ”
        buf = buf..”password:

        c = c+1
      end
    else
      buf = buf..”Wifi 1, SSID: password:

      c = 2
      while c<=5 do
        buf = buf..”Wifi “..c..”, SSID: ”
        buf = buf..”password:

        c = c+1
      end
    end
    buf = buf..”


    buf = buf..”

Available wifi networks


    if listaps then
      buf = buf..listaps
    end
    buf = buf..”
“..node.heap()
    buf = buf..”

"
    cn:send(buf)
    buf = ""
    cn:close()
    collectgarbage()
  end)
end)

Maybe a bit too big to just publish here, but the logic behind it is quite simple. The first part of the webfunction checks for input and creates a new init_vars.lua file if it receives a change request. The second part creates the webpage and send it to the requestor.

The file main.lua is where you code your actual program or where you load the different modules. This will be different per application.

This leaves the final file, init.lua.

local pin_error = 0
local pin_abort = 1
local pin_startconfig = 2
gpio.mode(pin_error,gpio.OUTPUT)
gpio.mode(pin_abort,gpio.INPUT)
gpio.mode(pin_startconfig,gpio.INPUT)
gpio.write(pin_error,gpio.HIGH)
--
-- check if the abort pin is high
-- (this means no abort is requested)
--
if gpio.read(pin_abort)==1 then
--
-- load the functions and variables
-- and print the mac address
--
  wifi.setmode(wifi.STATION)
  dofile("init_vars.lua")
  dofile("init_functions.lc")
  print("\n\nNodeMCU:   "..this_node)
  print("\nMAC address: "..wifi.sta.getmac())
  if not this_node then
    this_node = "New"
  end
  listaps = ""
--
-- if multiple ssids have been defined
-- loop through the available APs and choose
-- the first one for which we have credentials
--
  if ssids then
    ssid = ""
    pass = ""
    wifi.sta.getap(function(t)
      if t then 
        local k,v
        for k,v in pairs(t) do
          listaps = listaps..k.."
"
          if ssid=="" and ssids then
            local k2,v2
            for k2,v2 in pairs(ssids) do
              if k2..""==k.."" then
                ssid = k2
                pass = ssids[k2]..""
                print("Connecting to:"..ssid)
              end
            end
          end
        end
--
-- if we did not find an ssid broadcasted that
-- we know, the first ssid is selected
-- (so this could be a hidden ssid)
--
        if ssid=="" and ssids then
          for k,v in pairs(ssids) do
            ssid = k
            pass = v
            break
          end
        end
        wifi.sta.config(ssid,pass)
      end
    end)
  else
--
-- there is either one or no ssid defined
-- connect to the defined ssid or to None
--
    if not ssid then
      ssid = "None"
    end
    if not pass then
      pass = ""
    end
    wifi.sta.getap(function(t)
      if t then
        local k,v
        for k,v in pairs(t) do
          listaps = listaps..k.."
"
        end
        wifi.sta.config(ssid,pass)
      end
    end)
    wifi.sta.config(ssid,pass)
  end
--
-- define a timer to wait for an IP
-- it tries this at most "cnt" times
-- in the timer, you check whether an IP is assigned
-- which means succes connecting to an AP
--
  local cnt = 15
  tmr.alarm(0,500,1,function()
    if wifi.sta.getip()==nil then
      cnt = cnt-1
      if cnt<=0 then
        tmr.stop(0)
        gpio.write(pin_error,gpio.LOW)
        print("Not connected to wifi")
      end
    else
--
-- We have a connection to an AP
-- Get our IP address and check whether we need
-- to start the main program or the config server
-- (determined whether port 2 is low (config)
-- or high (main program))
--
      tmr.stop(0)
      ip, __, __ = wifi.sta.getip()
      print("My IP:"..ip)
      if gpio.read(pin_startconfig)==1 then
        if srvtype then
          ifttt("NodeMCU"..srvtype,this_node,ip,"")
          iot("&ip="..ip.."&ssid="..ssid.."&type="..srvtype)
        else
          ifttt("NodeMCUBoot",this_node,ip,"")
          iot("&ip="..ip.."&ssid="..ssid.."&type=Boot")
        end
        if file.open("main.lc","r") then
          file.close()
          print("\nStarting main")
          dofile("main.lc")
        else
          print("Could not find main.lc")
        end
      else
        print("Starting config server")
        ifttt("NodeMCUConfig",this_node,ip,"")
        iot("&ip="..ip.."&ssid="..ssid.."&type=Config")
        dofile("httpserver_cfg.lc")
      end
    end
  end)
else
  gpio.write(pin_error,gpio.LOW)
  print("Abort button pressed. Stopping execution of init.lua")
end

I have put comments in the text to show which section does what. Most should be self explanatory based on the high level description in the beginning. The most tricky part is probably the waiting for IP. This is based on a solution I found somewhere else. What it does is define a trigger which triggers every 500 milliseconds. The function it triggers is defined in-line. Every time the function is triggered, it checks whether an IP is assigned. If it is, the timer is stopped and, depending on input 2 either the main program or the config server is started. If an IP has not yet been assigned, the counter cnt is decreased. If it reaches 0 it means that we assume we will not get an IP and the program execution is stopped.

The whole idea behind waiting for an IP is the assumption that you need an IP for this device to be effective. If you do not need that you can of course skip those steps and immediately start the main program (it would also not make sense to start the config server since that needs an IP as well).

I hope that my development init.lua makes sense together with my previous articles on LUA. If not, please let me know in the comments.

NodeMCU LUA & the heap

NodeMCU LUA & the heap

Once you have gone past the simple programs, reading a sensor, turning on a led etc you will notice that there is something that you will run out of, heap memory.

The ESP8266 does not come with a lot of memory and unfortunately, the LUA interpreter for it comes with not a whole lot of heap and heap is what you need while executing programs, storing variables etc. And if you run out of it, the NodeMCU reboots…..

While there is plenty written on how to use little memory, I did want to give you two tips that I look at:

use local variables where possible

If you do not declare something local in LUA, it is automatically a global variable which means it stays in memory (and uses heap) after the program or function stops. If you execute the same program/function again, the variable (and it’s value) are still there. To conserve heap, you should use local variables if possible. An example of this:

function countsteps()
  local i
  for i = 1,10 do
    print("Step "..tostring(i))
  end
end

In the function above, if you do not include the line local i, the variable i will be available after the function countsteps finishes. You can assign a value to a variable while declaring it local:

local s = "A string value"

compile your programs

The LUA interpreter on the NodeMCU comes with a compiler. You can compile your lua file into a lc (lua compiled) file which when executed uses less heap.

To compile you use compile(“myprogram.lua”) which creates myprogram.lc and to use it instead of executing dofile(“myprogram.lua”), you use dofile(“myprogram.lc”). That’s it.

I2C and MCP23017, part 2

I2C and MCP23017, part 2

After being able to set basic outputs and inputs in the previous post, the next step could be to be understand the inputs, internal pull ups and the triggers INTA and INTB.

Let’s start with internal pull ups. The MCP23017 has the ability to software define internal pull ups. This means that you do not have to build these outside the chip. The registers GPPUA (0x0C) and GPPUB (0x0D) are used to set or remove this internal pull up. As with the other registers, 8 bits can be set. 0 means do not use internal pull up while a 1 means use an internal pullup. For the remainder of this article, we will assume a range of buttons or other inputs on bank A.

Let’s tell the MCP23017 that all of bank A needs to have the internal pull up set:
(again pseudo code. The code will be different for the Raspberry Pi and the NodeMCU)

write_to_i2c_bus(device_address,GPPUA,0xFF)

Now our inputs only need to connect to ground or float to send a signal. Next let’s tell the chip that we want any change on an input in bank A to send a signal to INTA. We do this by setting the individual bits on GPINTENA (0x04) high. (The corresponding GPINTENB can be found on 0x05).

write_to_i2c_bus(device_address,GPINTENA,0xFF)

All set, now if a change happens on any port on bank A, INTA is triggered. On your device (Raspberry PI or NodeMCU) you can define a trigger to react on this.

VERY IMPORTANT!! After each trigger on INTA (or INTB) you will NOT receive another trigger until you clear this one. You do this by either reading from GPIOA (or GPIOB) or by reading INTCAPA (0x10) (or INTCAPB at 0x11). Since you don’t know whether or trigger has happened during the initial boot up of your program, I suggest doing a read of GPIOA and/or GPIOB after you define your trigger to make sure the trigger is cleared.

The values you read from INTCAPA (or INTCAPB) show (in bit order) which ports set off the trigger.

If you are short on input ports on your device, it is possible to OR both INT ports such that you only have one trigger to alert you that there is a change on EITHER bank A or B. To do this, you set bit 6 (0x40) of either IOCONA (0x0A) or IOCONB (oxoB). To do this, you read the value first and than set or unset bit 6 and write it back.

current_value = read_from_i2c_bus(device_address,IOCONA)
new_value = current_value OR 0x40 (to set the 6th bit, chose one line \/)
new_value = current_value AND 0xBF (to unset the 6th bit, chose one line /\)
write_to_i2c_bus(device_address,IOCONA,new_value)

Depending on whether you want to set or unset the bit, you chose one of the 2 lines in the example above.

NodeMCU & KY-032 obstacle avoidance

NodeMCU & KY-032 obstacle avoidance

The KY-32 obstacle avoidance module, part of the 37 sensors set, uses an infrared light and an infrared receiver to determine whether something is in front of it. Even though the specs say you can set the distance to between 2 and 40 cm’s, most have only managed to get a little over 10 cm. To do this you have to turn the blue/white potentiometers. Turn the one on the left all the way counter clockwise. Turn the other all the way clock wise. That should set the maximum distance.

To connect it to the NodeMCU, you treat it as a analog device. Connect GND to ground. Next to is Vcc which you connect to 3.3v and next to that is signal which you connect to A0. To read the value, you use the function adc.read(0).

I have not been able to use it yet as a digital sensor (using  a trigger) which would allow for having 2 sensors on 1 NodeMCU.

 

NodeMCU & KY-033 hunt sensor module

NodeMCU & KY-033 hunt sensor module

In the 37 sensors set there is a sensor called the KY-033 hunt sensor module, also referred to as white line hunter or black line hunter. This sensor consists of 2 main parts, an infrared light and an infrared receiver. What it does is shine the infrared light and measure how much is send back to the receiver. A high value means that almost no light is received by the sensor (black line), while a low light means that a value is being send back to the receiver.

Connecting it to the NodeMCU is straight forward. GND goes to ground, Vcc goes to 3.3v and OUT goes to the ADC port A0. Note that this sensor follows a different pin layout than most others in the 37 sensors set.

To read the value, you use a call to adc.read(0).

NodeMCU & KY-010 optical broken module

NodeMCU & KY-010 optical broken module

The KY-010 optical broken module that is part of the 37 sensors set is an analog sensor. On one side of the sensor there is a light and on the other end a receiver. The analog output shows how much of the light is coming through.

To connect it to the NodeMCU, connect – to ground, S to the analog input A0 and the middle pin to 3.3v.

To read the value of the analog output, you use adc.read(0). A low value means a lot of light is reaching the other end. A high value means that a lot of the light is being blocked.

I2C and the MCP23017

I2C and the MCP23017

Sometimes in your Raspberry PI or NodeMCU projects you need more inputs and/or outputs than your PI/NodeMCU is giving you. One way to do that is to add more ports using the MCP23017 (or multiple even). It is a chip that works on the I2C bus and gives you 16 new in- and outputs.

How to connect them to your Raspberry Pi or your NodeMCU, I will discuss seperately. However the general workings of the chip is the same for both, so in this post I will explain how to connect it and what the different addresses are.

I2C is a bus which means you can have multiple devices on the same bus as long as each has a different address. The address for the MCP23017 is set by connecting pins to ground or Vdd. Let’s look at the pin layout:

MPC23017

As you can see there are 16 in- and outputs: GPA0-7 (bank A) and GPB0-7 (bank B). These are where you connect your digital in- and outputs to. Vdd is where you connect the + to (5v or 3.3v). Vss is connected to ground. SCL and SDA are the I2C bus channels and these you connect to your Raspberry Pi or NodeMCU bus. Note that you connect SCL to SCL and SDA to SDA (as opposed to Txd and Rxd which you switch). A0, A1 and A2 is where you set your address anywhere between 0x20 and 0x27 (hex 20 and hex 27). If you connect all three to ground, your device address is 0x20. If you connect A0 to Vdd and A1 and A2 to ground, your device address will be 0x21 etc. Also connect RESET to Vdd.

Once you have connected the power, connected the I2C bus and set the address, you are good to go.

To start, you have to tell the MCP23017 whether the ports are in- or outputs. You do this by writing to addresses IODIRA (0x00) for bank A and IODIRB (0x01) for bank B. There are 8 ports per bank which correspond to 8 bits. Setting a bit high means it is an input, setting a bit low means it is an output. I assume you can convert bits to a hex number, but a quick example to help you out:

Let’s say we want the first 4 ports of bank A to be inputs and the last 4 ports of bank A to be outputs. In bits this would mean that the first 4 bits are 1 and the last 4 bits are 0. If we write this out (starting with port 7 at the left and port 0 at the right), we get 00001111 which is the value in binary. If we convert this to hexadecimal we get 0x0F (add 1 for the bit at the right, than 2, than 4 and lastly 7. This gives 15 which is F in hexidecimal).

Of couse you can do the same for bank B. Now let’s tell the MCP23017 in pseudo code (the actual code of course differs per hardware platform) that the first 4 ports of bank A are inputs and the last 4 ports of bank A are outputs. We are assuming A0, A1 and A2 are connected to ground so the device address is 0x20.

device_address = 0x20
IODIRA = 0x00
bankAInOutput = 0x0F

write_to_i2c_bus(device_address,IODIRA,bankAInOutput)

Next we want to write to the outputs. We do a similar exercise, except we need a different address and we need to determine which ports to set high. Let’s say that we wish to turn all our outputs high. We need to convert this to a hexadecimal number as well. The first 4 bits (starting at the right) are for input, so we leave these at 0. The last 4 bits we want to set high. In binary we want to write the value 11110000 which corresponds to 0xF0. The address we want to write to is called OLATA and has address 0x14. Assuming we have executed our pseudo code above, we just need to do this to set the 4 outputs high:

OLATA = 0x14
All4OutputsHigh = 0xF0

write_to_i2c_bus(device_address,OLATA,All4OutputsHigh)

Next, let’s see what our input is doing. These are the first 4 bits. To read our bank we need address GPIOA which has address 0x12. When we read this address we do not only get our inputs back but also the state of our current outputs, in other words, the status of every port in our bank. In pseudo code this will look something like this:

 GPIOA = 0x12

AllOfBankA = read_from_i2c_bus(device_address,GPIOA)

You will get a byte back which corresponds to 8 bits, the 8 ports. If you get back for example 0xF5 that means your 4 outputs are high (0xF0) which is what you would expect since we just set these and the 4 inputs have status 0x05 which means the first input (GPA0) is high (bit 0 which corresponds to a 1) and GPA2, bit 2 which corresponds to a 4. GPA1 and GPA3 are low in this example. If your are unfamiliar with hexadecimal and bits, there is plenty on the internet to learn this (example link).

Well, this covers the basics. We can determine which ports are input and which are output and we can set a value to each and read the value from them.

In the MCP23017 chip there are 2 banks, A and B and each have seperate addresses to use but remember the device address (in our example 0x20) is the same. To summarize the addresses:

Determine which ports are input and output:
IODIRA (0x00) and IODIRB (0x01)

Set the output ports:
OLATA (0x14) and OLATB (0x15)

To read both output and input ports:
GPIOA (0x12) and GPIOB (0x13)

 This covers the very basics of the MCP23017.

Read the next steps in I2C and MCP23017, part 2

NodeMCU & KY-002 vibration switch module

NodeMCU & KY-002 vibration switch module

The KY-002 Vibration switch module is part of the 37 sensors set which I am connecting to my NodeMCU.

The KY-002 (and some others) is a simple switch. To connect it, connect – (on mine on the left) to ground, S (on mine on the right) to your digital input (in my example D3) and + (the middle pin) to 3.3v.

Now to test whether it’s working, you can use the following code:

function click (level)
  gpio.write(0,level)
  print(level)
end

gpio.mode(0,gpio.OUTPUT)
gpio.trig(3,"both",click)

This code assumes you have nothing attached to D0 and uses the onboard red led (which is connected to D0) to show the status of the switch.

If there is a vibration that the sensor detects, you should see the NodeMCU respond.