Вы находитесь на странице: 1из 10

SSC PACIFIC

Embedded Systems Control


CPU Improvement
Replacing the LEGO Mindstorms NXT Brick with Arduino

Francesca Bragg
6/1/2014

Establishing an Arduino Uno as the master to a HiTechnic Motor controller to replace the NXT LEGO
Mindstorms brick. The existing processing unit is toy-like and hard to work with because it was made for
low level applications and has limited capabilities. In order to upgrade the system the Arduino Uno will
become the master device communicating over the I2C bus communication protocol.
Complete documentation of process;
detailing steps taken to achieve control over the motors
1) Testing out I2C communication: Arduino to act as an NXT I2C slave
a) Interfacing the Arduino and LEGO MINDSTORMS [1]
Code for Arduino [2]
Code for NXT Brick – Slave send (in RobotC) [3]

2) Switching Arduino to act as master rather than slave: Replacing NXT with Arduino and making motor
controller act as slave

HiTechnic FIRST Motor Controller Specification [4]


a) Determine I2C address of device as well as register address (device’s unique address)
As stated in [5] , the each transaction between the master/slave is treated as a read or write into
a memory location.
a. Stated in paper [4] the first motor controller in the daisy chain uses an I2C address of
02/03, and subsequent controllers use the addresses 04/05, 06/07, and 08/09 (only
compatible for a maximum of four controllers in the daisy chain). Therefore, our Device
address is 02/03. When addressing this location, we came to the issue of which location
exactly we should be addressing; 0x02 or 0x03? What exactly did the two numbers
mean? Those questions were answered here [6]; they indicate the shifted write/read
addresses (unshifted = 0x01).
b. We also knew from the paper [5] the register address, or the internal address to read
from, was 0x00. This tells the slave device (in this case the motor controller) the master
is interested in the address location 0x00 but not whether it is interested in performing
a read or write function. Thus far we believe this can be distinguished by the given I2C
address 02/03 (this will be discussed further later).
c. The memory location or register address of the device was still unclear because as
stated in [4], the sensor version number uses the addresses 00-07H. Because there is
little clear documentation about how to establish the Arduino as a master rather than
slave using the I2C bus, it was very difficult to determine this exact address. However,
after a lot of research and trial and error in the Arduino environment, we came to find
there was actually a really simple way to do this using an I2C scanner described on the
Arduino website [7] and used in the given sketch [8]. The scanner printed there was a
device found on the I2C bus at the following address: 0x05. Upon evaluation of this
number, we realized it was the equivalent the I2C address (0x02) shifted one bit to the
left –as described in [6]. This [0x05] is written in binary as 101. You may have caught that
those two values aren’t exactly equivalent, the I2C address in binary is 10, so shifted
once to the left the result should be 100. However, the last bit is used to indicate
whether the function is a read or write. If you recall the device address, 02/03, written
as 010/011 respectively in binary, you may notice that an even bit (0) represents a write
while an odd bit (1) represents a read. So, the output 0x05 or 0101 from the I2C scanner
represented it was communicating to the sensor as a read, leading us to believe the
address 0x04 or 0100 would indicate a write.
d. For some reason, we could not get all of these addresses to work the way we expected
them to. Given that, as the Arduino website explains, the wire library is using a 7 bit
version of an I2C address, where “7 bits identify the device, and the eighth bit
determines if it's being written to or read from,” we would expect our device address
0x01, written 001 in binary to become 010 in binary or 0x02. Because the last value
indicates a write or read instance, this address ending in a 0 would represent a write
and an identical one ending in a 1 (011 or 0x03) would represent a read. These were
consistent with the specifications given in the HiTechnic FIRST Motor Controller
Specification [4] for the first motor controller in the daisy chain. Now granted our motor
controller’s internal address is 0x05 or the device’s address (010) shifted again once to
the left, we would expect this to result in a new address of 10x where x is either a 0 or 1
to indicate a write or read. Thus an address of 100 or 0x04 should signify a write
function and 101 or 0x05 should indicate a read function. Because all of these findings
were in keeping with the information we were coming across, it was curious that these
addresses weren’t working when we actually went to use them in our program. This led
us to believe that the issue wasn’t with the information we had, but rather how the
Arduino was using it, in that it didn’t behave in a way similar enough to the LEGO NXT. In
order for it to work properly, we needed the Arduino to look as close to the NXT as
possible so it could replace the device without notice and be an accepted master to the
Motor controller(s). In order to make that happen, we wanted to make sure that the
voltage output to the I2C device from the Arduino was similar to that of the NXT, that
their signaling speeds were around the same, and that they used the same termination
or resistors in handling power. Looking at the LEGO® MINDSTORMS® NXT Hardware
Developer Kit [9] “there is no pull-up resistor mounted internally in the NXT,” so the
resistors on the I2C bus should be placed in the off position. From that document we
also learn that the NXT supplies 4.3 volts of power to the I2C bus’s fourth pin (assigned
to power) and that “the I2C communication is running 9600 bits per second. Now
following these three conditions we wanted to match, we turned the resistors on the
I2C bus off so the termination power would be identical. Then, we verified that the NXT
was outputting 4.3 volts to the I2C bus by re-hooking up the brick to the I2C bus and
measuring the voltage on pin 4 assigned to power, and pin 3 assigned to ground. We
discovered that 4.82 volts were being supplied to the I2C bus which was even more
fitting than the expected 4.3 volts because it was closer to the 5 volt supply we could,
and now would, run from the Arduino. After confirming the voltage, we moved on to
look at how the signaling speeds of the two processors compared. As aforementioned,
referencing the NXT Hardware Developer Kit [9] we learn that the NXT operates at 9600
baud or 9600 b/s; approximately equal to 9.6 KHz. Upon further research of the TWI bit
rate for the Arduino we found from a detailed reference of the Wire library [10] that the
serial communication operates at 100 KHz, the equivalent to 100,000 baud or b/s which
is significantly greater. This sizeable difference in communication speed could explain
why our program wasn’t working when, as far as we could tell, we were using all of the
proper addresses, and our messages were coming back scrambled. To address this issue,
we looked further still into the Wire library and into the TWI library [11]. There we found
and identified there was a command (#define TWI_FREQ 100000L) that was defaulting
the I2C/Wire/TWI operating speed to 100,000 b/s. So, we attempted to intercept this
default by defining a new speed above our access of the Wire library so it would use our
value rather than the library’s. We couldn’t simply define the speed as 9600 b/s
although that seems like the obvious solution, because there’s an interaction between
the software (TWI library) that sets a prescaler value of 1. This limit is used in the SCL
frequency rate calculation which limits the range of baud rates we can use so in order to
change these other values, we’d have to modify the code. We decided to make the new
speed 4 times what the NXT CPU operates at (9600 x4) or 38400 b/s. Unfortunately, this
value as well as a number of others we tried was still returning scrambled messages. So,
we decided to go directly into the TWI library and set the modifications there. Two
registers must be modified in order to set the SLC frequency for the Arduino: the TWSR
which is the prescalar and the TWBR which is the bit rate register. We initially reduced
the rate to 37390L in the TWI header file (which changed the TWBR) and changed the
prescaler value from a Cbi to Sbi or clear bit value (0) to set bit value (1). This allowed
our code to work because the way the TWI library previously setup the prescalar, you
could only set the baud rate so low. Rather than using the set prescaler value of one
that limited the range the baud rate could have, we changed the prescalar to four which
allowed for a greater scope of accepted values. While this fixed our code and allowed
our program to run consistently successfully, it was problematic that we were changing
the Wire/TWI library at its source. This would mean that these new values would be
used anytime the library was referenced which would cause it to not work in some
instances for the same reasons it wasn’t working for us (the signaling speeds vary
between devices). So, we wanted to go back to defining these values before referencing
the Wire library in our own program and make the values conditional in the original TWI
files. We did this by creating if else statements in both files that only called these new
values if they were defined, otherwise it would follow the original presets. This was
achievable in the twi.c Source File [12], however we were unable to create the condition
in the header file (where we would need to change the processing speed) because
although we were defining a new value before accessing the wire library, the compiler
kept accessing the library before acknowledging our definition.
e. Once we were able to make the Arduino “look” like the NXT brick, we were successfully
able to communicate with the motor controller and have our program retrieve the
device’s internal addresses. Originally, as we were playing around with the program and
trying to figure out how to access the information stored on the device, we wrote the
program to read one address. This could be any of the following: the product’s ID
(manufacturer), sensor type, or version number; because all three addresses were the
same length – 8 bits long– and used the same device address. Since our program was
only written to read one address at a time, we wanted to change it to print out all of the
information so we wouldn’t have to constantly change and re compile it to learn about a
new address. So, we changed our code to print all three specifications at once; first in
the loop function, and then in a new function we called getSensorInfo, so that the loop
function wouldn’t become cluttered and the function could be called multiple times if
desired.
f. With the program working and returning all three values, we realized minor changes
could be made to read other device’s information so long as we knew their I2C address,
and that the return addresses were also 8 bits long. Using the I2C scanner [7], we could
very easily find this information, and a consultation of their specifications could confirm
that the length of each address was indeed 8 bits. Now in our program, all we had to do
was change the device’s read address, and re-compile it. Here came another issue…
while our program worked fine with the motor controller, it wouldn’t return anything
when we hooked it up to other devices. At first, this had us stumped because we knew
we had all of the correct information, the addresses we were trying to read were of the
same length in all instances so changing the device’s I2C address should be the only
variable in making the program compatible with other devices, so why weren’t we
returning anything? Looking back on our code, we wavered over the
Wire.endtransmission command. This command can either stop or restart message after
the transmission. We initially had no parameters on the function. We then changed this
implementing a true Boolean –by putting a 1 inside the parentheses—that would send a
stop message after transmission, releasing the I2C bus. After recompiling the program,
we were able to get it to work with the Vision Subsystem v3 for NXT (Camera) and
Multi-Sensitivity Acceleration Sensor v3 for NXT seen here [13], however, for some
reason the ultrasonic sensor was not returning any values.
g. We were pretty stumped with what would make this sensor different from the others
and the only one unable to communicate with the Arduino in place of the NXT. I was
convinced it had something to do with the fact that the ultrasonic sensor was the only
NXT device that was returning two addresses when the I2C scanner was used. We tried
using these addresses in our code (0x01 & 0x03), as well as the address between them
and the addresses shifted both a bit to the left and right, but nothing worked. So we
kept researching and after a lot of prodding into the matter, we had a couple of ideas of
things we could follow. First of all, we looked into the fact that it was an active rather
than passive sensor, so perhaps it needed some start command to initiate it. However,
that didn’t make much sense since the scanner was recognizing the device existed, it
was just unable to gather information from it, and there was nothing online that
suggested this to be the case. So we looked into other possibilities. After countless
searches and little to follow, I found a forum that discussed someone stuck on a similar
issue. They said to “Please note - This sensor [the ultrasonic] is much more difficult than
other LEGO sensors to use,” because, “You must provide both 9V and 5V supplies.” They
also suggested that “I2C needs to be 'hacked' due to a feature in the ultrasonic i2c
firmware which makes it non-compliant with the industry standard i2c protocol.” So
now we had two possible culprits. Perhaps the device wasn’t receiving enough power (it
was only attached to the 5 volts transmitted from the Arduino) to operate, or perhaps it
didn’t fit the “norms” defined in the wire library and wasn’t working for the same
reason we couldn’t get the Arduino to work at first. To address the first issue, we
wanted to verify that the sensor did need an additional voltage supply, and that that
was even possible over the I2C bus. Tracking down the document referred to in forum,
the LEGO NXT Ultrasonic Sensor Specifications [14], we found that the only remaining pin
available on the I2C bus, the analog pin, was in fact capable of supplying voltage. But
just because it was capable of doing so, didn’t necessarily mean it should be doing so; sp
should it? We answered this question by seeing how the ultrasonic sensor behaved
when the NXT was communicating with it—or at least when the NXT thought it was
communicating with it. This was achieved by hooking up the NXT to the I2C bus, sending
a program to it that told it it was communicating with an ultrasonic sensor, and using a
voltage meter to measure the voltage output on the analog pin of the I2C bus. If it did
require the additional nine volts, we would expect the NXT to be supplying that at this
point. However, when we actually executed these steps, we found that it was only
outputting 4.95 volts. To verify this was a “standard” voltage, we tricked the NXT device
to believe it was communicating with other types of sensors (touch, light, and sound)
and again read the voltage output. In all instances, it was 4.95 volts, so we could
eliminate the need for additional voltage as the cause for trouble reading the ultrasonic
sensor. Many additional leads were followed and research done into what could make
the ultrasonic sensor unique and possibly unfit for the Wire library. We also found
additional sites/documents that suggested the sensor wasn’t compatible with the Wire
library, “the NXTshield advanced development guide from mindsensors says: ‘Software
I2C: The Software I2C protocol is slower than the Hardware I2C, however it is tailored to
work with NXT Digital sensors. Hardware I2C: This I2C protocol provides high speed
communication. But NXT digital sensors (e.g. Ultrasonic) do not work
with this protocol.’ However I couldn’t find any solid answers and as I was investing
more and more time into trying to figure out and solve the issue, it seemed like I was
spending more and more time going nowhere. I still think the issue has something to do
with the fact that the device was returning two addresses when the I2C scanner was run
rather than one; and/or that it is not suitable with the Wire library and there is a value
that needs to be redefined just as the speed the Arduino processed at had to be. But we
couldn’t find anywhere that suggested this new value and even if that value did exist
and was located, redefining it in our program would make it so the other devices would
no longer work since both the accelerometer and camera used the same device address
as the ultrasonic sensor.
b) Attempting to make the motors move now that we knew the motor controller’s address, 0x05.
a. This was probably the most challenging part of this process because nowhere out there
did there exist a clear example of how to communicate with the Arduino as a master to
slave devices over the I2C bus. More specifically, it seemed that no one had attempted
to do this with the HiTechnic motor controller, and those that had, only talked about
their work on forums where they asked for help because they too were stuck. We had
all the information, or so it seemed, so why weren’t we able to use it the way that the
motor controller’s specifications suggested we could? After becoming very familiar with
the I2C protocol and the Arduino and Motor controller, it was extremely boggling that
nothing was working. We should be able to follow the communication outline with the
addresses we have and we should get the motors to do something, but we couldn’t get
them to go! We tried the addressing the device address (0x05) in a multitude of ways
following different examples of things people had tried in the past either with the
HiTechnic controller, or with other slave devices, trying to understand what exactly they
were trying to do and how we could replicate it with what we had. As we went through
these examples and nothing was working, we tried using different addresses trying the
0x05 the I2C scanner had returned, 0x02 and 0x03 for the I2C bus’s write and read
bytes; 0x04 assuming that the 0x05 address returned was part of the shifted write/read
address 0x04/0x05 and additionally 0x02 again assuming that would be the correlating
unshifted address; 0x01 the unshifted address of 0x02/0x03, 0x0A and 0x0B which
would be the shifted address of 0x05, and a great deal of others (pretty much
everything between 0x00 and 0x07) but we couldn’t get anything to work. Then we
thought perhaps we weren’t following the correct procedure. We were beginning the
transmission at our device address to initiate that that was the device we were trying to
communicate with, then writing to that address and then ending the transmission. We
tried this simple structure with a combination of addresses, mainly starting the
transmission with the device, and then changing where we would write to. Next we
would end this transmission, and then request 8 bytes while it was available from that
device. Finally we would end the transmission. Our main loop looked something like:
void loop()
{
Wire.beginTransmission(DEVICE_ADDRESS);
Wire.read(); // This would be my command byte
Wire.endTransmission();
Wire.beginTransmission(DEVICE_ADDRESS); // Now I send my read command
Wire.write((byte)MOTOR_ADDRESS);
Wire.endTransmission();
Wire.requestFrom(DEVICE_ADDRESS, bytes_returned); // data returned
seems to be 24 bytes
Wire.endTransmission();
while (Wire.available()) {
c = Wire.read();
if (c != 0) {
Serial.print(c);
}
}
It seemed we were providing all the right information, the registers we wanted to
communicate with, the message size, etc., and were following this example of someone
also communicating with a device over the I2C bus:
{
// move the register pointer back to the first register
Wire.beginTransmission(cn75address); // "Hey, device @ 0x48! Message for
you"
Wire.write(0); // "move your register pointer back to 00h"
Wire.endTransmission(); // "Thanks, goodbye..."
// now get the data from the device
Wire.requestFrom(cn75address, 2); // "Hey, device @ 0x48 - please send me
the contents of your first two registers"
*a = Wire.read(); // first received byte stored here
*b = Wire.read(); // second received byte stored here
}
We were really stuck and again and again trying to work with the I2C protocol:
 1. Send the START bit (S).
 2. Send the slave address (ADDR).
 3. Send the Read(R)-1 / Write(W)-0 bit.
 4. Wait for/Send an acknowledge bit (A).
 5. Send/Receive the data byte (8 bits) (DATA).
 6. Expect/Send acknowledge bit (A).
 7. Send the STOP bit (P).
And the information we had. We had the start bit, the slave address, that we wanted to
read from that address; we put a delay in like it suggested and then sent a request from
to read those 8 bytes, sent another start byte and then the stop. So what were we
missing? Everything seemed to be in line with the tutorials we were finding, including
this next one:
A typical sequence used with the Arduino for requesting data from a device
usually looks like this:
Wire.beginTransmission(I2C_deviceAddress);
Wire.send(registerLocation);
Wire.endTransmission(); // This function sets the pointer to the registerLocation
from where we want to read the data
Wire.requestFrom(I2C_deviceAddress,howManyBytes);
These examples just name a couple of the many we compared and manipulated to try
and get to work but for a long time we were really stuck on how to communicate with
the motors. Then, we thought we came to a breaking point with this tutorial. Although
we were able to use it to get the motors to move, it didn’t solely utilize the wire library.
It called for a lot of its own definitions and functions and seemed to over complicate the
commands it was sending. After spending a good deal of the day trying to understand
what, why and how exactly it was doing what it was doing, I decided it was something
that wasn’t really worth perusing. The code was very over complicated, and its
parameters didn’t seem to make sense, any modifications I made did not have logical
results so the order I thought it followed, that would be in line with the motor
controller’s specs, just didn’t apply. So again we were racking our brains, trying to come
up with where to go next, and we had a sort of break through. All this time we had been
addressing and trying to read from the device address, however what we should have
been doing was beginning from that device address, and then reading from the
manufacturer address. When we changed our code to look like:
{
int i;
char data[128];
// Read
Wire.beginTransmission(DEVICE_ADDRESS);
Wire.write(0x08); // manufacturer
Wire.endTransmission(1);
Wire.requestFrom(DEVICE_ADDRESS, 8);
while (Wire.available())
{
data[i++] = Wire.read();
}
data[i] = '\0';
Serial.println(data);
// Write
Wire.beginTransmission(DEVICE_ADDRESS);
Wire.write(0x45); // motor 1 power
Wire.write(50); // 50%
Wire.endTransmission(0);
}
We had success! The problem was not only the fact that we weren’t using the
manufacturer address, but we were also just attempting to write to the slave device
when “a read of the slave actually starts off by writing to it.” Thanks to these two
tutorials, tutorial 1 & tutorial 2, we were able to make sense of things and begin writing
a new program and defining functions using the information they provided, and the
addresses we had established belonged to each register.
c) Successful Move!
a. With a little toying around to understand the exact results of our commands, and how
the motor controller’s specifications now fit into our parameters, we were able to
create a successful program Our Program [16]
Appendix
1. Interfacing the Arduino and LEGO MINDSTORMS[1]
2. Code for Arduino [2]
3. Code for NXT Brick – Slave send (in RobotC) [3]
4. HiTechnic FIRST Motor Controller Specification [4]
5. LEGO Mindstorms NXT I2C Communication [5]
6. I2C Sensor Addressing [6]
7. I2C Scanner [7]
8. I2C Scanner Program [8]
9. LEGO® MINDSTORMS® NXT Hardware Developer Kit [9]
10. Wire Library Detailed Reference [10]
11. Twi.h Header File [11]
12. Twi.c Source File [12]
13. Reading Device Addresses [13]
14. LEGO NXT Ultrasonic Sensor Specifications [14]
15. Links to tutorials:
a. Example 1: Someone communicating with a device over the I2C bus [15a]
b. Example 2: I2C Protocol [15b]
c. Example 3: Another communication attempt using I2C bus [15c]
d. Example 4: Successful Motor move, Wire library/addressing device from scratch [15d]
e. Example 5: Tutorial 1 [15e]
f. Example 6: Tutorial 2 [15f]
16. Successful Control: Our Program [16]

Вам также может понравиться