Lets Make Tech

Simulated Neural Network Robot

So you may be wondering what a Simulated Neural Network Robot is. To explain what it is first you need to understand how neurons work. So in addition to learning about electronics/code/robotics, you are going to learn some biology/neuroscience. 

Neurons

Alright so the main components of neurons are the dendrites, the soma (cell body), the axon, and the terminal buttons. To show you what they look like I stole a photo off google image. Note, this image shows two neurons. The arrows show the direction information flows. You can see that the dendrites are the input, and terminal buttons are the output. If you want to think about what this does in terms of electronics, you can image the soma as a capacitor with a transistor at the end and the axon as a wire. The electricity won't flow down the axon unless it reaches a certain voltage. So (thinking about it as an electronic device) the dendrites receive pulses of electricity that charge the capacitor, once it reaches a certain voltage the transistor sends electricity down the axon and a pulse of electricity goes to the next neuron.

Please note the electronics analogy above isn't what neurons actually do, it is an analogy.   

undefined 

 

If you are satisfied with the electronics analogy, skip the next few paragraphs I will let you know where to read with some bold text. If you want to know what neurons do, read it. 

Neurons have what is called a sodium potassium pumps embedded in their cell membrane. As you might imagine, it pumps sodium and potassium. More precisely it pumps three sodium ions out of the cell and two potassium ions in. Both have a positive charge, but more will end up outside the cell membrane. Some of the potassium ions will diffuse out to the lower concentration outside the cell membrane through a leaky gate, however the positive charge will keep some from diffusing out. Other ions and anions are also involved too, but we won't get into that. If you are interested in more details on the other players there are some good youtube videos that animate the process. So now there is a ion gradient across the neurons membrane, it will have a positive voltage outside the membrane and a negative charge inside the membrane.

When a neurotransmitter bonds to a receptor site on the dendrites it opens up an ion channel, it either does this directly (ionatropic), or indirectly (metabotropic) with a G protein/enzyme. For this purpose just know that a neurotransmitter opens an ion channel (gate/pathway). These channels are specific to certain molecules and the neurotransmitter is specific to a type of receptor. Depending on which type of ion channels the neurotransmitter opens, it can be excitatory or inhibitory.

So as an example, Glutamate is a neurotransmitter. The neuroreceptor specific to glutamate causes an ion gate specific to sodium to open. The the high concentration of sodium and voltage difference outside the cell membrane causes sodium to rush into the cell once the gate is open. The sodium rushing into the cell changes the voltage across the cell membrane. If the change in voltage is significant enough other ion gates near it (controlled by voltage) will open. This will continue down the axon until it reaches the terminal button. Here through a series of processes vacuoles filled with neurotransmitters bind to the cell membrane then release their contents into the synapse (gap between terminal button and another neuron's dendrites). You might be wondering what makes this process stop, why doesn't the voltage change just keep all the ion channels open. Well they only open briefly, after that they are in a refractory state where they won't open. This limits the firing rate of neurons and it prevents the propagation of the signal from going in reverse.

As an example of how a neurotransmitter can be inhibitory we will talk about GABA. When GABA binds to a neuroreceptor specific to it, it opens up chloride ion channels. Chloride is an anion, meaning that it is negatively charged. When the chloride channels open, chloride enters the neuron contributing to the negative charge that was created by the sodium potassium pumps. By contributing to the negative charge inside the neuron, it makes it more difficult to reach a significant enough voltage to cause the voltage dependent ion channels to open, thus the signal can't propagate down the axon.

Start here if you skipped the neuron mumbo jumbo

So in short:

1. A voltage difference maintained across the cell membrane with a sodium potassium pump.

2. Neurotransmitters change this voltage either up or down by opening ion gates. 

3. The message propagates down the neuron. 

4. Neurotransmitters are released to other neurons. 

Code: 

For complete code and documentation please check out my github

Alright so this isn't too hard to over simplify with code. Make a neuron object, give it some properties, write some functions. 

First we well make a variable for the voltage in the neuron object. Then a variable for excitatory gates and a variable for inhibitory gates. Finally a Boolean variable for for whether or not it has fired.

So make a setup function to set your pins, then initialize the neuron data by setting the neurons to their resting potential.

Alright, so all of the neurons have to work at the same time. So we are just going to do each step for each neuron one at a time.

In my code it looks like this:

void loop()
{
  //gather sensory information
  ping();
  //connections betweeen neurons
  synapses();
  //calculate the state of each neuron
  for (int i=0;i<(number_of_neurons);i++)
  {
    neuron(i);
  }
  //fire all active neurons
  fire();
}

So first ping, we get all of the data from proximity sensors.

Then open excitatory or inhibitory gates based on the proximity sensor's values by changing the value of the variable representing the gates.  

Then check to see which neurons fired previously by reading the fire variable and open excitatory or inhibitory gates based on which neurons previously fired.

Set a new concentration for each neuron based on the number and types of gates open. Run a function that represents the sodium potassium pump by changing the concentration again. Return all the gates values to 0.

Next check to see if the new concentration/voltage meets the threshold, if so set the fire variable to true.

Finally check to see which neurons fired. Depending on which neurons fired set wheel direction, or add or subtract to a variable representing wheel speed (use that speed as pwm). The neurons that fired will need to return to their resting concentration leave the values of other neuron's concentrations alone that way you can use the rate of fire to control them, much like pwm charging a capacitor slowly. Set fire to false for all neurons and let it loop back to the beginning. 

So after doing that you have to make the connections between neurons meaningful. It is much more difficult than you think to form the connections to make something behave the way you want. First you have to design the circuit, then tinker around with the values you give to the number of gates that open. 

To see what this circuit looks like I made a few charts. 

 

 undefined

 This complete chart can be a bit difficult to understand so I broke it up into smaller charts for each function. 

undefined

 This chart controls the speed of each wheel. By speeding up on the same side as when an object is close and slowing down on the opposite side, it will turn away from the obstacle.

undefined 

 Direction is just like a latch circuit, it stays in its current correction until the opposite sensor senses it is too close to something. 

undefined

 To prevent objects behind the robot (relative to its current direction) this circuit inhibits the rear proximity sensors from altering the speed. 

 

 

 

 

 

The final product, a robot that explores. 

If you want to see how the robot was made check out my previous post on How To Make a Simple Robot.

 

 

How To Make A Simple Robot

If you ever wanted to make a robot but didn't know where to start or have limited electrical/code knowledge here is a tutorial for you. 

 

undefined

 

First gather up these supplies:

BOM

-Arduino mega 

-Arduino motor shield

-Premade robot chassis of your choice I recommend one with two motorized wheels and one swivel wheels or two tank treads. I used this. Or if you want you can make your own chassis. 

-Jumper wires

-Soldering supplies & shrink tube. 

-Sensors of your choice. I used six proximity sensors. If you want you could use just one and have it rotate on a stepper motor. 

-Power Supply: 9v battery and clip, 9V worth of AA, what ever you have available just be reasonable. 

Connections

Just plug the motor shield into the arduino pins and hook the motor pins up to the terminal. Add a 9v battery to the power terminal if that's how you want to power it. 

For the proximity sensors solder together some wires so that it goes from 5v on the arduino to the + pin on each of the proximity sensors. Do the same for ground. 

The motor shield uses some of the pins by default so you will need to use different pins for the proximity sensors. So use whatever pins you want but keep pins A0, A1, 8, 9, 3, 11, 12, and 13 open for the motor shield. 

Pin Functions

In the setup set the pin mode to be either input or output for the pins you use

Pins A0 and A1 are for current sensing, I didn't use these but you can if you want. (Good for preventing stalls) If you use these pins set them as an input.

Pins 8 and 9 are the brakes, make sure you disengage these in the setup by setting it as an output and to LOW. After that using these is optional. I make sure they are disengaged by including it in the motor control function 

Pins 3 and 11 are for setting the speed 0-255. They use PWM set these pins as an output. 

Pins 12 and 13 set the direction, these pins should also be set as an output.

For the proximity sensor the trigger pin is used to activate the sensor and should be set as an output and the echo pin to gather data and should be set as an input. 

 

Code

Declare Variables


//SPEEDS
const int FAST = 200;
const int SLOW = 100;
//declare sensor pins
//sensor 1

const int pingPin1 = 22;
const int echoPin1 = 23;
//sensor2
const int pingPin2 = 24;
const int echoPin2 = 25;
//sensor 3
const int pingPin3 = 26;
const int echoPin3 = 27;
//sensor 4
const int pingPin4 = 28;
const int echoPin4 = 29;
//sensor 5
const int pingPin5 = 30;
const int echoPin5 = 31;
//sensor 6
const int pingPin6 = 32;
const int echoPin6 = 33;
//store distance read
long cm1, cm2, cm3, cm4, cm5, cm6;
//The direction of both motors and speed of each motor
long DIRECTION, speeda, speedb;

 

Setup: Set the pin mode of each pin. 

void setup() {
//Setup Motor A
pinMode(12, OUTPUT); 
pinMode(9, OUTPUT); 
//Setup Motor B
pinMode(13, OUTPUT); 
pinMode(8, OUTPUT); 
//Setup ping pins
pinMode(pingPin1, OUTPUT);
pinMode(echoPin1, INPUT);
pinMode(pingPin2, OUTPUT);
pinMode(echoPin2, INPUT);
pinMode(pingPin3, OUTPUT);
pinMode(echoPin3, INPUT);
pinMode(pingPin4, OUTPUT);
pinMode(echoPin4, INPUT);
pinMode(pingPin5, OUTPUT);
pinMode(echoPin5, INPUT);
pinMode(pingPin6, OUTPUT);
pinMode(echoPin6, INPUT);
}

 

 Now for the main loop

void loop()
{
//read sensors
ping();
//interpret data from sensors
logic();
//use data from sensors
motorcontrol();
}

 

Now we write the functions that we have called in the main loop. 

Ping: Turn the trigger pin off, on, off. Delay 2 microseconds after the first off, then 5 microseconds after turning it on. Read the signal with pulseIn() and convert to cm using the conversion function we will write later. 

long ping()
{
//variables
long duration;
//quick burst of electricity to the proximity sensor to activate it
digitalWrite(pingPin1, LOW);
delayMicroseconds(2);
digitalWrite(pingPin1, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin1, LOW);
//reads signal that bounces back
duration= pulseIn(echoPin1,HIGH);
//converts duration to cm using function below
cm1 = microsecondsTocm(duration);
digitalWrite(pingPin2, LOW);
delayMicroseconds(2);
digitalWrite(pingPin2, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin2, LOW);
//reads signal that bounces back
duration= pulseIn(echoPin2,HIGH);
//converts duration to cm using function below
cm2 = microsecondsTocm(duration);
Serial.print(cm2);
digitalWrite(pingPin3, LOW);
delayMicroseconds(2);
digitalWrite(pingPin3, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin3, LOW);
//reads signal that bounces back
duration= pulseIn(echoPin3,HIGH);
//converts duration to cm using function below
cm3 = microsecondsTocm(duration);
digitalWrite(pingPin4, LOW);
delayMicroseconds(2);
digitalWrite(pingPin4, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin4, LOW);
//reads signal that bounces back
duration= pulseIn(echoPin4,HIGH);
//converts duration to cm using function below
cm4 = microsecondsTocm(duration);
digitalWrite(pingPin5, LOW);
delayMicroseconds(2);
digitalWrite(pingPin5, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin5, LOW);
//reads signal that bounces back
duration= pulseIn(echoPin5,HIGH);
//converts duration to cm using function below
cm5 = microsecondsTocm(duration);
digitalWrite(pingPin6, LOW);
delayMicroseconds(2);
digitalWrite(pingPin6, HIGH);
delayMicroseconds(5);
digitalWrite(pingPin6, LOW);
//reads signal that bounces back
duration= pulseIn(echoPin6,HIGH);
//converts duration to cm using function below
cm6 = microsecondsTocm(duration);
}

 

Convert to cm

//converts ping duration to cm
long microsecondsTocm(long microseconds)
{
return microseconds / 29 / 2;
}

 

Logic: This is what determines what the robot does based on the sensor input. This assumes we have 6 sensors: three in the back, three in the front. The middle sensors will be straight while the side sensors will be diagonal. When something triggers the center sensors it will reverse direction, if something is closer on one side sensor than the other, it will increase speed on one side and decrease speed on the other resulting in it turning away from the object it sensed. Please note that the placement of the sensors matters.  

 

void logic()
{
/*
Direction Shifting:
The difference in distance between switching from forward to reverse (30) and
switching from reverse to forward (50) results in the robot having a tenancy
to go forward.
*/

//if it is going forward and gets close to something in front of it, switch to reverse
if (DIRECTION==LOW && cm1 > cm4 && cm4<30)
{
DIRECTION=HIGH;
}
//if it is in reverse and something is moderatly close switch to forward
if (DIRECTION==HIGH && cm4 > cm1 && cm1<30)
{
DIRECTION=LOW;
}
/*
Speed Control:
The speeds between the two wheels will always be different resulting in the robot having
a wavy motion as it moves thus "looking around". The difference in speeds also results
in the robot having a tenancy to move towards whichever side has the greatest amount of distance
thus moving away from walls and other objects. When in reverse the wheel speed difference is also
reversed.
*/
if (DIRECTION ==LOW)
{
if (cm5 > cm6)
{
speeda=FAST;
speedb=SLOW;
}
if (cm6 > cm5)
{
speeda=SLOW;
speedb=FAST;
}
}
if (DIRECTION ==HIGH)
{
if (cm2 > cm3)
{
speeda=FAST;
speedb=SLOW;
}
if (cm3 > cm2)
{
speeda=SLOW;
speedb=FAST;
}
}
}

 

Motor Control: Alright so now the robot knows what it want it wants to do and needs to tell the motor shield what to do.

//motor control
void motorcontrol()
{
digitalWrite(12, DIRECTION); 
digitalWrite(9, LOW); 
analogWrite(3, speeda); 
digitalWrite(13, DIRECTION); 
digitalWrite(8, LOW); 
analogWrite(11, speedb); 
}
Home