Month: February 2016

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.

NodeMCU & KY-023 joystick module

NodeMCU & KY-023 joystick module

The KY-023 joystick module is a small joystick on a print in the 37 sensors set. It has an integrated pushbutton. There are 3 outputs, 2 variables outputs (x and y axis) and the pushbutton. The NodeMCU only has one analog input (A0) so this module will need 2 NodeMCU’s to be used.

To connect it, you connect GND to ground and +5V to either 3.3v. Connect VRx or VRy to A0 and SW to a digital input (in the example below D3).

To read the push button 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)

The read the axis of the joystick, you use adc.read(0). The value will range between 0 and 1024. Around 512 is the center point of the joystick.

NodeMCU & microphone sensors

NodeMCU & microphone sensors

The 37 sensors set came with 2 microphone sensors:

  • KY-037 sensitive microphone sensor module
  • KY-038 microphone sound sensor

Both sensors work the same way so connecting them to the NodeMCU is also the same. Connect – to ground + to 3.3v and A0 to the ADC port. There is also a digital port D0 which you can use if you just want a trigger. In that case, connect it to a digital input.

To get the sensor ready, we first have to make sure we get a high enough signal. To do this we need to turn the wheel on the blue box until we have a proper signal. If you are using LUA Loader, you can select ADC TOUT in the GPIO box (blue) and click on the clock. It will continously display the ADC value. Try to get the value to around 500. Turning counter clockwise will make the value go up and clockwise will make it go down. Mine was initially at 0 and I had to give the screw several full turns counter clockwise to get to a value slightly above 500.

Basically, that’s it. You now continously read the ADC value and use that to determine what to do or use a gpio trigger if you want to rely on the digital input:

gpio.mode(3,gpio.INPUT)

function havesound (level)
  print(level)
end

gpio.trig(3,"both",havesound)
NodeMCU & KY-040 rotary encoder

NodeMCU & KY-040 rotary encoder

The KY-040 rotary encoder module is part of the 37 sensors set that I am hooking up to a NodeMCU. The rotary encoder works on the principle of 2 pins on a rotary blade where by detecting which connection is made first you know which the knob is turned. A very good explanation can be found here.

To connect the rotary switch you do the following. Connect GND to ground and + to 3.3v. Than connect SW to 3.3v with a 10k resistor and connect SW to a digital input (in my example 5). Connect DT to 3.3v with a 10k resistor and connect DT to a digital input (in my example 3). Connect CLK with a 10k resistor to 3.3v and connect CLK to a digital input (in my example 4). What the rotary encoder does is connect ground to pins DT and CLK in sequence and the sequence is determined by whether you rotate right or left.

I have made many attempts to create working code for the NodeMCU. Unfortunately, it seems a bit to slow to catch all the triggers. With every rotation you would expect ports 3 and 4 to send two triggers, 0 and 1 and by checking the value of the other pin you would know whether it is turning left or right.

The code that seems to work the best is this one:

function rotaryturn3 (level)
  if level~=gpio.read(4) then
    print("Turning right")
  end
end

function rotaryturn4 (level)
  if level~=gpio.read(3) then
    print("Turning left")
  end
end

function rotarybutton (level)
  if level==0 then
    print("Button pushed")
  else
    print("Button released")
  end
end

gpio.mode(3,gpio.INPUT)
gpio.mode(4,gpio.INPUT)

gpio.trig(3,"both",rotaryturn3)
gpio.trig(4,"both",rotaryturn4)

gpio.trig(5,"both",rotarybutton)

Unfortunately the code does not always give the right result, so you can get a left once in a while when you are turning the knob right or the other way around.

 

SG90 and the NodeMCU

SG90 and the NodeMCU

On my Raspberry Pi I have code the control the Tower Pro SG90, a servo motor that can rotate 180 degrees. It would of course be great if I can get this cheap servo motor working on the cheap NodeMCU. I found some code on the internet which I modified and made into code which is usable on the NodeMCU. This video was actually quite useful in understanding the SG90.

Let’s hook up the SG90 to the NodeMCU. The brown wire is attached to ground, the red to 3.3v and the yellow/orange is your control signal. This you should connect to a digital out. In my code I used D3.

The SG90 is not like a motor that runs if you apply current but instead it moves itself to a position based on the width of the pulse in a 20 ms cycle. A 0.5 ms pulse will turn it to 0 degrees, a 1.5 ms pulse will turn it to 90 degrees and a 2.5 ms pulse will turn it to 180 degrees. The SG90 moves a bit towards the desired position with every pulse so you cannot just send one pulse and expect it to reach it’s end position.

The base of the end functions is this setup:

tmr.alarm(tmrnum,20,1,function()
  if servovalue and servovalue>0 then
    gpio.write(pin, gpio.HIGH)
    tmr.delay(servovalue)
    gpio.write(pin, gpio.LOW)
  end
end

It moves the servo motor on pin pin in steps towards position servovalue where servovalue is a number between 0 and 2000. This works, but it would be nice if we could set an angle (0 to 180 degrees) instead of a value between 0 and 1023. Especially since the 0 does not match 0 degrees and nor does 2000 match 180 degrees. This is mainly because each SG90 needs to be calibrated. Also the timer does not stop and that is also something that we want. So let’s first make a function that moves the SG90 to a specific value and stops afterwards. For now, ignore the servotimer variable. I will get to that later on.

--
-- move the servo on the specified pin to value servovalue
-- this is done in 25 steps
--
function setservovalue (pin, svalue)
  local cnt = 25
  local tmrnum
  if not servotimer[pin] then
    tmrnum = 2
  else
    tmrnum = servotimer[pin]
  end
  servovalue = min(2000,max(svalue,0))
  tmr.alarm(tmrnum,20,1,function()
    if servovalue and servovalue>0 then
      gpio.write(pin, gpio.HIGH)
      tmr.delay(servovalue)
      gpio.write(pin, gpio.LOW)
    end
    cnt = cnt-1
    if cnt<=0 then
      tmr.stop(tmrnum)
    end
  end)
end

In short, you call the function with the pin number that your SG90 is attached to and the value to which you want to move the SG90. The value is automatically trimmed to be between 0 and 2000. Moving the servo is done in 25 steps (found that out by trying, moving from 0 to 180 degrees). After the 25 steps, the timer is stopped.

You can use this function to find at which value your SG90 is at 0 degrees and at 180 degrees. Next, you want a function which you can call with degrees instead of a servo value. So let’s take a look at the whole code:

 --
-- define a servo entry where left is the servo value
-- at which it is at 0 degrees and right is the value
-- at which it is at 180 degrees.
--
-- example:
--   defineservo(3,200,1850)
--
-- the parameters are saved in servoleft and servoright
-- and a timernumber is generated and remembered so you
-- can change multiple servo's at the same time
--
function defineservo (pin, left, right)
  if not servoleft then
    servoleft = {}
  end
  if not servoright then
    servoright = {}
  end
  if not servotimer then
    servotimer = {}
  end
  servoleft[pin] = left
  servoright[pin] = right
  servotimer[pin] = 2+#servotimer
  gpio.mode(pin,gpio.OUTPUT)
end

--
-- move the servo on the specified pin to value servovalue
-- this is done in 25 steps
--
function setservovalue (pin, svalue)
  local cnt = 25
  local tmrnum
  if not servotimer or not servotimer[pin] then
    tmrnum = 2
  else
    tmrnum = servotimer[pin] 
  end
  servovalue = math.min(2000,math.max(svalue,0))
  tmr.alarm(tmrnum,20,1,function()
    if servovalue and servovalue>0 then
      gpio.write(pin, gpio.HIGH)
      tmr.delay(servovalue)
      gpio.write(pin, gpio.LOW)
    end
    cnt = cnt-1
    if cnt<=0 then
      tmr.stop(tmrnum)
    end
  end)
end

--
-- after you've defined the servo, you can set the angle
-- with this function
--
function setservo (pin, angle)
  local servovalue
  if servoleft[pin] and servoright[pin] then
    servovalue = (servoright[pin]-servoleft[pin])/180*angle+servoleft[pin] 
  else
    servovalue = 2000/180*angle
  end
  setservovalue(pin,servovalue)
end

You can use the function setservovalue to find out at what value you get a nice 0 degrees (in my test SG90 this was at 200) and after that use setservovalue to test at which value we get a nice 180 degrees (my test SG90, 1850). Now you are ready to define the servo with the function defineservo(3,200,1850). The values are kept and a timer number specifically for this servo is generated. I usually start at timer 2 for generic purposes so that’s why I used 2+#servotimer. Now it’s setup up for you to call setservo(3,0) to set it at 0 degrees, or for example setservo(3,45) to set it at 45 degrees.