Search This Blog
Sunday, February 24, 2013
Floating Point Arithmetic
If you want to multiply a number by a fixed floating point constant, here is a
pretty efficient method.
The example that brought this up was trying to measure the distance from the Ping ultrasonic sensor. On trigger, the Ping sends out a pulse. 200 us later, you can start timing, waiting for the trigger pin to go low. This yields a value between 0-18500 at 4MHz clock, and it corresponds to the number of microseconds ( 1 per clock pulse ) that the round trip of the ultrasonic burst took to hit and come back.
To get the distance, you first divide this by 2 to get the single trip value.
Sound travels about 343.2 meters/second. That is 34320 cm/sec. Invert this to 2.91375e-5 * 1e6 = 29.13 microseconds per cm. That means we need to divide the time value by 29.13 microseconds to get the distance. Another way to divide by 29.1375 is multiply by 1/29.1375 = .03432.
Well, that doesn't help much does it? Here's the trick. You can create something called a "fixed point" to simplify the math. To do this, we figure out how to represent .03432 in a 16 bit number and remember the offset. So, multiply .03432 * 65536 = 2249.19552. Round to 2249. We've used a 16 bit offset, so we will have to shift things back 16 places at the end.
Now, in C, you can assign things like this for our Ping sensor:
unsigned int sensorRaw = 18500; // the 2 way time measured. This is the max value.
unsigned long cm = 2249; // this is our fixed point, calculated above.
unsigned long distanceCm; // this will hold the distance in cm.
// first, get the single trip time by dividing by 2 ( shift right one ).
sensorRaw = sensor >> 1;
// now, multiply by our fixed point
distanceCm = sensorRaw * cm;
Doing it this way takes about 128 us at 4MHz. It seems consistent in time for all numbers I tried. Doing a raw multiply of sensorRaw * .03432 took anywhere from 220us to 450us. Pretty good improvement. You could probably get this down to under 30 us
if you did it in assembly with the hardware multiplier. The 16 bit shift just means you only use the two upper bytes of the 32 bit answer. The algorithm for this is in the datasheet for the 18f2455 and takes 28us at 4MHz. 128us was good enough for me!
Here is the original quote that helped me from the PicList from Byron A Jeff:
You may want to think about fixed point. You can create a fixed point number
simply by taking the original number and multiplying it by the range that
you wish to represent it. For example if you wanted to represent .1 in 16
bits, take .1 and multiply it by 65536 giving 6553.6. Round to 6554. Store
this in 16 bits and use a 16 bit multiply followed by a 16 bit right shift
(which means simply leave off the lower 16 bits).
Here an example. Say we wanted to multiply 20 by our .1. So multiply 20 by
6554 giving 131080. Then shift 16 bits, which effectively divides by 65536.
The result of 2.0001220703 is close enough for govt. work
Tuesday, February 12, 2013
Pic18F4550 Interface to Wii Nunchuck Over I2C in C
It was time to test out some of the C18 hardware libraries. First up was the USART. I wanted to be able to talk to the PC instead of using the rather large LCD routines I usually use. I also wanted a way to do output at 3.3V without having to buy a 3.3V LCD. Next up was the I2C libraries. They greatly simplify a lot of the work of transmission/reception, checking bytes for done, overflow, collision, etc. To pull these together, the Pic wants to initialize a Wii Nunchuck extension controller, enumerate the device type, and print that device type number over USART to the PC. It wants to take a reading of the nunchuck's values every second and print those as well. The final step required a program or script to receive the values and print them. Along the research road, several next steps became evident.
USART
I have a TTL to USART shifter from Sparkfun. It's essentially just a pair of transistors, some resistors and diodes. It connects to a serial to USB cable. Hooking it up was trivial. The C18 library is very simple to use. After configuring the USART, writing to it simply requires an fprintf command. The file in this case is a constant for the USART: _H_USART. Using stty on the command line followed by a cat </dev/ttyUSB proved that all was working.
After that, the Pic needed to be able to send converted data values as either decimals or hexidecimals. The built in conversion formats of fprintf made that a snap. For decimal, the command was simply:
unsigned char value = 0xC8;
fprintf( _H_USART, "Value: %d\n", value );
The commands for hex were:
unsigned char value = 0xC8;
fprintf( _H_USART, "Value: %X\n", value );
PC Script
The final program ended working as a simple bash script. It sets up a handler for when the user presses Ctrl + C to end the program. It then sets up a special file pointer to /dev/ttyUSB. This is the device file the PC listens to for serial signals from the serial to USB device. This is set up as a udev rule, granting full permissions to the regular user. The script then loops forever, reading from the serial device. If the device sends the magic word 'clear', it clears the screen. Otherwise, it prints the line verbatim.
Here is the script:
The only real magic in this script is the exec command, the trap, and the 'clear' signal. The exec sets up the /dev/ttyUSB as a file pointer. This allows the read command to work as expected. I also use this for reading files in simple scripts. The trap captures Ctrl+C, allowing the program to exit gracefully. The 'clear' command allows the Pic to control the output somewhat.
I2C
The C18 libraries seem to make working with I2C very easy. They handle all the flag checking and bit setting/clearing needed to communicate over I2C. There are a few caveats to this.
First, some Pics have quirks when using I2C. Some of these may be alleviated by following the following steps on start:
This snippet is from the main function. The Tris register is set to all output. The Latch of the register is cleared. The Tris is then set to all input. My code did not notice this addition. Then again, enough posts are provide enough evidence that these steps will stay right where they are.
The code ended up using the multi-byte read function, getsI2C. This worked well to read in the six ID bytes as well as the 6 data bytes. The code rejected the multi-byte write function, putsI2C. This routine exits prematurely when the code wants to write value zero ( 0x00 ). That was a pesky "feature" to discover!
Something else that may trip up beginners like me: you have to specify the speed of the communication after executing an OpenI2C function. The datasheets don't really tell you how to calculate the value. They do provide the values for some common speeds. In this case, the datasheet for the Pic18F4550 did not have a value for 48MHz. Hmmm. I found the answer in two great places. The first was on the PyroElectric website. They have a short tutorial on I2C: http://www.pyroelectro.com/2011/03/24/pic-i2c-interface-tutorial/ ( handy tip: click the read link in the top box to see the full tutorial. This trips everybody up. ). The other place was in Myke Predko's classic Programming and Customizing PICmicro Microcontrollers. They both have the same formula:
(FOsc / 4 / Desired Speed ) - 1
For example, to get 100KHz standard speed at 48MHz, I use:
(4.8e7 / 4 / 100000 ) - 1 = 119 or 0x77
Nunchuck
Not a lot to say here. The nunchuck is a pretty cool little gadget. It consists of a microcontroller, an accelerometer, two potentiometers for the joystick, and two buttons. The microcontroller handles reading the values and converting them. It communicates via I2C at 3.3V and (nominally) 400KHz speed.
The nunchuck used was a genuine Nintendo model. I did not take the initialization shortcut because of this. The code wants to read any nunchuck. In fact, it will eventually read any extension controller whatsoever. At least, it will read the nunchuck, the motion plus, the two together in pass-through mode, and the classic controller.
For the physical interface, a piece of the blade of an old PCI modem was used to make the connector. See the references below for ideas as well as pinouts. As a safer, more robust alternative, all the usual web sites sell adapter boards. Of them, Sparkfun's was the cheapest when I checked. Another great idea is to buy a nunchuck extension cable and cut off an end.
Research/References
A good amount of research preceded this project. No one site provided all the answers. The best site by far was the wiibrew.org site. Here are the relevant pages:
http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers
http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck
All of the following were also of great help:
http://www.settorezero.com/wordpress/interfacciarsi-con-il-nunchuck-della-nintendo-appunti/ ( Italian )
http://dangerousprototypes.com/docs/Wii_Nunchuck_quick_guide
http://www.windmeadow.com/node/42
The comments in the windmeadow.com page were very useful, providing insight and links to help with many small issues.
Approach
Since I am still new to C for microcontrollers, I started with the convertRawValues and printValues functions. These needed to be correct before the code got bogged down in the problems of talking to the nunchuck. The packet format from the research was consistent across all sites. The first bit of code set constant values for the data packets, focusing on the conversion and printing over USART. Once this worked, it was time to try talking to the nunchuck.
The research led to the following assumptions. First, the wiibrew.org's information seemed the most solid and consistent. It was assumed that the initialization and device type enumeration procedures were the most recent and correct. They would avoid any problems with encryption.
A decision had to be made regarding the address of the device. It was decided that the address was the seven bit value 0x52. This would need to be shifted up one bit. The lowest bit would hold a one or zero for reading(0xA5) or writing(0xA4) ( respectively ).
The approach would always require a successful enumeration of the device type. This would be crucial toward reading other devices. It would be impossible to read multiple devices on the same line without it.
Difficulties
The sites researched varied in many crucial points. The controllers used, libraries imported, modifed, etc. all varied greatly. There were too many variables to sort out. Even with what turned out to be some solid assumptions and really good SWAGs ( Scientific, Wicked Ass Guesses ), some puzzling difficulties still cropped up.
SPEED
The first of these was speed. All of the sites seemed to agree that the nunchuck operated at 400 KHz. There were some vague references to logic analyzers showing they always ran at 100 KHz. This project received very inconsistent results in initialization and device type enumeration at 400 KHz. The project was operating at that speed with 1k resistors on the clock and data lines. When this was changed to 100 KHz with 10k resistors, the results became much more consistent.
DELAYS BETWEEN START & STOP
This issue really slowed development. After many tries, it became apparent that a minimum of 100us was needed between an I2C Start and Stop to produce consistent results. The application was unable to break this barrier when operating at 48 MHz. Perhaps a slower speed controller could lower or drop this required delay?
SAMPLING RATE?
The current application samples the data once every second and then (slowy) writes the results over USART. Several posts and comments suggested that the highest sample rate was rather low. This makes sense given that the interface is a Human Interface Device. It would seem probable that the data could be sample every few hundred milliseconds. This may not be enough for a really delicate control device, but it could be enough for simple applications. The nunchuck has been used on some moderately successful self-balancing robots. More research is needed here.
Solutions
Here are the specs for what worked in the project. This has been a really long post. It warrants some pictures and/or video. These will be added as soon as time allows. [ I'm proud of you for hanging with me this far!]. 'Nuff said. Here are the specs:
Pic18F4550, running at 48 MHz via PLL from a 4 MHz ceramic resonator.
USART @ 19200 baud through a TTL to Serial converter to a Serial to USB cable.
Linux Laptop running OpenSuse - udev rule set up to map the serial/USB converter to /dev/ttyUSB.
Bash script to display output from /dev/ttyUSB, clear on command, exit gracefully on Ctrl+C.
Program written in C for Microchip C18 compiler(non-extended mode ) with built-in libraries.
I2C at 100 KHz with 10k resistors as pull-ups on data and clock lines.
Device Init with two writes:
StartI2C 0xA4 0xF0 0x55 StopI2C (delay100us) Start 0xA4 0xFA 0x00 StopI2C
Enumerate Device Type with:
StartI2c 0xA4 0xFB StopI2C (delay200us) Start 0xA5 [ read 6 bytes with Ack/Nack ] StopI2C
Program always reads device type immediately after init sequence.
Read data with:
StartI2C 0xA4 0x00 StopI2C (delay200us) StartI2C 0xA5 [read 6 bytes] StopI2C
The conversion and print functions are pretty straight forward and commented in the code.
Code
Here is the mainline code. The missing bits are the config fuses and the delay routines. The config fuses are pretty standard. They are set up to run on XS_PLL. There is a ceramic resonator of value 4 MHz. Phase Lock Loop drives that to a practical 48 MHz ( 12 MIPs ). The delays are a set of macros that use the Delay routines from delays.h. I can post them if anyone asks.
Code:
syntax highlighted by Code2HTML, v. 0.9.1
Next Steps
First step: Bask in the glow of having gotten it to work! [ yea, me!] Now, look to do some cool stuff. Next up will be a quick one to get the classic controller working. In theory, this should only require the data values arrays to expand in size, a handler based on the device type, and additional convert and print routines. Let's see how that one works out!
After that, it's time to tackle the Wii MotionPlus! First up on that is to get the gyro reporting by itself. That will mean a change to the addressing scheme ( maybe init, too. Gotta research that ). Then, add new get data bytes, convert, and print routines. Once all that is working ( if ever ), it's on to getting the nunchuck and Motion Plus on the same wire in pass through mode. To do anything useful with the Motion Plus, I'm going to need an extension cable. This will allow the Motion Plus to be moved around freely. It will also provide a much more stable interface cable. The hack job of the PCI pins currently in use is pretty shaky!
Thanks for reading. I hope that this was helpful in some small way. This deserves some eye candy. Pictures really wouldn't be exciting or helpful. A video might be cool. I'll try to make one soon.
USART
I have a TTL to USART shifter from Sparkfun. It's essentially just a pair of transistors, some resistors and diodes. It connects to a serial to USB cable. Hooking it up was trivial. The C18 library is very simple to use. After configuring the USART, writing to it simply requires an fprintf command. The file in this case is a constant for the USART: _H_USART. Using stty on the command line followed by a cat </dev/ttyUSB proved that all was working.
After that, the Pic needed to be able to send converted data values as either decimals or hexidecimals. The built in conversion formats of fprintf made that a snap. For decimal, the command was simply:
unsigned char value = 0xC8;
fprintf( _H_USART, "Value: %d\n", value );
The commands for hex were:
unsigned char value = 0xC8;
fprintf( _H_USART, "Value: %X\n", value );
PC Script
The final program ended working as a simple bash script. It sets up a handler for when the user presses Ctrl + C to end the program. It then sets up a special file pointer to /dev/ttyUSB. This is the device file the PC listens to for serial signals from the serial to USB device. This is set up as a udev rule, granting full permissions to the regular user. The script then loops forever, reading from the serial device. If the device sends the magic word 'clear', it clears the screen. Otherwise, it prints the line verbatim.
Here is the script:
#!/bin/bash
cleanUp()
{
echo ' '
echo 'The user has ended listening on ttyUSB.'
exec 3<&-
exit $?
}
# Set up to trap Ctrl-C
trap cleanUp SIGINT
# Port setting
stty -F /dev/ttyPIC raw ispeed 19200 ospeed 19200 cs8 -cstopb -parenb -echo
exec 3</dev/ttyPIC
clear
echo 'Listening on ttyUSB:'
while [ 1 ]
do
read line 0<&3
if [ "$line" = "clear" ]
then
clear
echo 'Listening on ttyUSB:'
else
echo $line
fi
done
The only real magic in this script is the exec command, the trap, and the 'clear' signal. The exec sets up the /dev/ttyUSB as a file pointer. This allows the read command to work as expected. I also use this for reading files in simple scripts. The trap captures Ctrl+C, allowing the program to exit gracefully. The 'clear' command allows the Pic to control the output somewhat.
I2C
The C18 libraries seem to make working with I2C very easy. They handle all the flag checking and bit setting/clearing needed to communicate over I2C. There are a few caveats to this.
First, some Pics have quirks when using I2C. Some of these may be alleviated by following the following steps on start:
239 // This sillyness prevents weird stuff on the Latch from screwing up I2C 240 TRISB = 0x00; 241 LATB = 0x00; 242 TRISB = 0xFF;
This snippet is from the main function. The Tris register is set to all output. The Latch of the register is cleared. The Tris is then set to all input. My code did not notice this addition. Then again, enough posts are provide enough evidence that these steps will stay right where they are.
The code ended up using the multi-byte read function, getsI2C. This worked well to read in the six ID bytes as well as the 6 data bytes. The code rejected the multi-byte write function, putsI2C. This routine exits prematurely when the code wants to write value zero ( 0x00 ). That was a pesky "feature" to discover!
Something else that may trip up beginners like me: you have to specify the speed of the communication after executing an OpenI2C function. The datasheets don't really tell you how to calculate the value. They do provide the values for some common speeds. In this case, the datasheet for the Pic18F4550 did not have a value for 48MHz. Hmmm. I found the answer in two great places. The first was on the PyroElectric website. They have a short tutorial on I2C: http://www.pyroelectro.com/2011/03/24/pic-i2c-interface-tutorial/ ( handy tip: click the read link in the top box to see the full tutorial. This trips everybody up. ). The other place was in Myke Predko's classic Programming and Customizing PICmicro Microcontrollers. They both have the same formula:
(FOsc / 4 / Desired Speed ) - 1
For example, to get 100KHz standard speed at 48MHz, I use:
(4.8e7 / 4 / 100000 ) - 1 = 119 or 0x77
Nunchuck
Not a lot to say here. The nunchuck is a pretty cool little gadget. It consists of a microcontroller, an accelerometer, two potentiometers for the joystick, and two buttons. The microcontroller handles reading the values and converting them. It communicates via I2C at 3.3V and (nominally) 400KHz speed.
The nunchuck used was a genuine Nintendo model. I did not take the initialization shortcut because of this. The code wants to read any nunchuck. In fact, it will eventually read any extension controller whatsoever. At least, it will read the nunchuck, the motion plus, the two together in pass-through mode, and the classic controller.
For the physical interface, a piece of the blade of an old PCI modem was used to make the connector. See the references below for ideas as well as pinouts. As a safer, more robust alternative, all the usual web sites sell adapter boards. Of them, Sparkfun's was the cheapest when I checked. Another great idea is to buy a nunchuck extension cable and cut off an end.
Research/References
A good amount of research preceded this project. No one site provided all the answers. The best site by far was the wiibrew.org site. Here are the relevant pages:
http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers
http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck
All of the following were also of great help:
http://www.settorezero.com/wordpress/interfacciarsi-con-il-nunchuck-della-nintendo-appunti/ ( Italian )
http://dangerousprototypes.com/docs/Wii_Nunchuck_quick_guide
http://www.windmeadow.com/node/42
The comments in the windmeadow.com page were very useful, providing insight and links to help with many small issues.
Approach
Since I am still new to C for microcontrollers, I started with the convertRawValues and printValues functions. These needed to be correct before the code got bogged down in the problems of talking to the nunchuck. The packet format from the research was consistent across all sites. The first bit of code set constant values for the data packets, focusing on the conversion and printing over USART. Once this worked, it was time to try talking to the nunchuck.
The research led to the following assumptions. First, the wiibrew.org's information seemed the most solid and consistent. It was assumed that the initialization and device type enumeration procedures were the most recent and correct. They would avoid any problems with encryption.
A decision had to be made regarding the address of the device. It was decided that the address was the seven bit value 0x52. This would need to be shifted up one bit. The lowest bit would hold a one or zero for reading(0xA5) or writing(0xA4) ( respectively ).
The approach would always require a successful enumeration of the device type. This would be crucial toward reading other devices. It would be impossible to read multiple devices on the same line without it.
Difficulties
The sites researched varied in many crucial points. The controllers used, libraries imported, modifed, etc. all varied greatly. There were too many variables to sort out. Even with what turned out to be some solid assumptions and really good SWAGs ( Scientific, Wicked Ass Guesses ), some puzzling difficulties still cropped up.
SPEED
The first of these was speed. All of the sites seemed to agree that the nunchuck operated at 400 KHz. There were some vague references to logic analyzers showing they always ran at 100 KHz. This project received very inconsistent results in initialization and device type enumeration at 400 KHz. The project was operating at that speed with 1k resistors on the clock and data lines. When this was changed to 100 KHz with 10k resistors, the results became much more consistent.
DELAYS BETWEEN START & STOP
This issue really slowed development. After many tries, it became apparent that a minimum of 100us was needed between an I2C Start and Stop to produce consistent results. The application was unable to break this barrier when operating at 48 MHz. Perhaps a slower speed controller could lower or drop this required delay?
SAMPLING RATE?
The current application samples the data once every second and then (slowy) writes the results over USART. Several posts and comments suggested that the highest sample rate was rather low. This makes sense given that the interface is a Human Interface Device. It would seem probable that the data could be sample every few hundred milliseconds. This may not be enough for a really delicate control device, but it could be enough for simple applications. The nunchuck has been used on some moderately successful self-balancing robots. More research is needed here.
Solutions
Here are the specs for what worked in the project. This has been a really long post. It warrants some pictures and/or video. These will be added as soon as time allows. [ I'm proud of you for hanging with me this far!]. 'Nuff said. Here are the specs:
Pic18F4550, running at 48 MHz via PLL from a 4 MHz ceramic resonator.
USART @ 19200 baud through a TTL to Serial converter to a Serial to USB cable.
Linux Laptop running OpenSuse - udev rule set up to map the serial/USB converter to /dev/ttyUSB.
Bash script to display output from /dev/ttyUSB, clear on command, exit gracefully on Ctrl+C.
Program written in C for Microchip C18 compiler(non-extended mode ) with built-in libraries.
I2C at 100 KHz with 10k resistors as pull-ups on data and clock lines.
Device Init with two writes:
StartI2C 0xA4 0xF0 0x55 StopI2C (delay100us) Start 0xA4 0xFA 0x00 StopI2C
Enumerate Device Type with:
StartI2c 0xA4 0xFB StopI2C (delay200us) Start 0xA5 [ read 6 bytes with Ack/Nack ] StopI2C
Program always reads device type immediately after init sequence.
Read data with:
StartI2C 0xA4 0x00 StopI2C (delay200us) StartI2C 0xA5 [read 6 bytes] StopI2C
The conversion and print functions are pretty straight forward and commented in the code.
Code
Here is the mainline code. The missing bits are the config fuses and the delay routines. The config fuses are pretty standard. They are set up to run on XS_PLL. There is a ceramic resonator of value 4 MHz. Phase Lock Loop drives that to a practical 48 MHz ( 12 MIPs ). The delays are a set of macros that use the Delay routines from delays.h. I can post them if anyone asks.
Code:
1 /* 2 * File: nunchuck.c 3 * Author: Tom Hunt 4 * 5 * Created on February 8, 2013, 9:51 PM 6 * 7 * Running Pic 18F4550 @48 MHz 8 * Communicate with Wii Nunchuck over I2C, convert values, 9 * and send them to PC over USART at 19,200 baud 10 * 11 * There seem to be several schools of thought on how to talk to Wii extension 12 * devices. This program eventually wants to talk to the nunchuck and the 13 * motion plus in pass-through mode. This will yield a 6 DoF IMU. To this 14 * end, the program will try to use the protocols as outlined in the wiiBrew.org pages: 15 * http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers 16 * http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers/Nunchuck 17 * http://www.wiibrew.org/wiki/Wiimote/Extension_Controllers/Wii_Motion_Plus 18 * 19 * It will attempt to use the universal initializations and run in unencrypted mode. 20 * 21 * The main routine will initialize and go into a forever loop. The loop will take 22 * a reading, convert it, send it, and delay about 1 second. 23 * 24 * This works pretty well with the following caveats. 25 * First, it works much better at 100 KHz I2C speed. 26 * [Other issues have been fixed. :) ] 27 * 28 * 29 */ 30 31 #include32 #include 33 #include 34 #include 35 #include 36 #include 37 38 #include "configFuses.h" 39 #include "mydelays.h" 40 41 // For 400 KHz: ( 48MHz / 4 / 400 KHz ) - 1 = 29 or 0x1D 42 // For 100 KHz: ( 48MHz / 4 / 100 KHz ) - 1 = 119 or 0x77 43 #define I2C_Speed 0x77 44 #define Slew SLEW_OFF 45 46 #define extensionWriteAddress 0xA4 47 #define extensionReadAddress 0xA5 48 49 #define extensionInitRegister 0xF0 50 #define extensionInitData 0x55 51 52 #define extensionTypeReadRegister 0xFA 53 #define extensionTypeWriteRegister 0xFB 54 #define extensionTypeData 0x00 55 56 #define extensionReadRegister 0x00 57 58 #pragma udata access accessRam 59 near unsigned char values[6]; 60 near unsigned int unpackedValues[7]; 61 62 #pragma romdata nunchuck 63 const far rom unsigned char fail[] = "Read data failed.\n"; 64 65 #pragma code 66 67 /** 68 * Convert the 6 raw bytes of data into the 7 nunchuck values. 69 * Store the converted values in finalValues. 70 * Raw Values: 71 * Byte Value 72 * --------------------------------------------------------------------------- 73 * 0 SX<7:0> 74 * 1 SY<7:0> 75 * 2 AX<9:0> 76 * 3 AY<9:0> 77 * 4 AZ<9:0> 78 * 5 PackedByte(see below) 79 * 80 * Bits: 7:6 5:4 3:2 1 0 81 *Value: AZ<1:0> AY<1:0> AX<1:0> BC BZ 82 * 83 * @param rawValues - the 6 bytes retrieved from the nunchuck 84 * @param finalValues - the array to hold the 7 converted values. 85 */1:0>1:0>1:0>9:0>9:0>9:0>7:0>7:0> 86 void convertRawValues( unsigned char* rawValues, unsigned int* finalValues ) 87 { 88 int i = 0; 89 unsigned char mask = 0x0C; 90 unsigned char shiftAmount = 0x02; 91 unsigned char packedByte = *(rawValues+5); 92 93 // SX - Joystick X value --- set the value and increment raw values pointer 94 *finalValues++ =*rawValues++ & 0x00FF; 95 // SY - Joystick Y value --- set the value and increment raw values pointer 96 *finalValues++ = *rawValues++ & 0x00FF; 97 // This loop takes care of the 3 acelerometer values. It gets the 7 High bits from each array value in turn. 98 // It shifts these up 2, casting to an int as it goes. C18 doesn't follow ANSI here, so the cast is needed. 99 // Then the LSbs are shifted in. As we go in order, all we need to do increment the shift by 2 and shift the 100 // mask by 2. 101 for( i = 0; i < 3; i++ ) { 102 *finalValues++ = ( ((unsigned int) *rawValues++) << 2 ) + ( ( packedByte & mask ) >> shiftAmount ); 103 mask = mask<<2; 104 shiftAmount += 2; 105 } 106 // Need to do the buttons 107 // if the button bit is set, the button is not pressed. 108 // N.B. The value is inverted here to be like normal C. Pressed = 1 = true 109 // The C button is bit 1 of the 6th byte 110 *finalValues++ = ( ( packedByte >> 1 ) & 1 ) ? 0x000 : 0x0001; 111 // The Z button is bit 0 of the 6th byte 112 *finalValues++ = ( ( packedByte >> 0 ) & 1 ) ? 0x000 : 0x0001; 113 114 } 115 116 void printDeviceType( unsigned char* values ) 117 { 118 unsigned char i; 119 120 fprintf( _H_USART, "clear\n" ); 121 fprintf( _H_USART, "Device Type:\n" ); 122 for( i = 0; i < 6; i++ ) { 123 fprintf( _H_USART, "0x%X\n", *values++ ); 124 } 125 } 126 127 /* 128 * Spew out the values over USART. 129 */ 130 void printValues( unsigned int* printValues ) 131 { 132 fprintf( _H_USART, "clear\n" ); 133 // Joystick: 134 fprintf( _H_USART, " X Joystick: %d\n", *printValues++ ); 135 fprintf( _H_USART, " Y Joystick: %d\n", *printValues++ ); 136 // Accelerometer: 137 fprintf( _H_USART, "X Accelerometer: %d\n", *printValues++ ); 138 fprintf( _H_USART, "Y Accelerometer: %d\n", *printValues++ ); 139 fprintf( _H_USART, "Z Accelerometer: %d\n", *printValues++ ); 140 141 // Now do the buttons 142 // C button 143 if( *printValues++ ) { 144 fprintf( _H_USART, " C Button: Pressed\n" ); 145 } else { 146 fprintf( _H_USART, " C Button: Not Pressed\n" ); 147 } 148 149 // Z button 150 if( *printValues ) { 151 fprintf( _H_USART, " Z Button: Pressed\n" ); 152 } else { 153 fprintf( _H_USART, " Z Button: Not Pressed\n" ); 154 } 155 156 } 157 158 /** 159 * initialize the extension controller with universal method. 160 * Ensure that the type identification block will be unencrypted. 161 * 162 * @return error - 0 on success, -2 on NotAck, -3 on WCOL 163 */ 164 unsigned char initExtension( void ) 165 { 166 unsigned char error = 0; 167 delay100us(); 168 StartI2C(); 169 error = putcI2C( extensionWriteAddress ); 170 if( error ) return error; 171 error = putcI2C( extensionInitRegister ); 172 if( error ) return error; 173 error = putcI2C( extensionInitData ); 174 if( error ) return error; 175 StopI2C(); 176 // introduce an artificial wait before sending new packet 177 delay100us(); 178 StartI2C(); 179 error = putcI2C( extensionWriteAddress ); 180 if( error ) return error; 181 error = putcI2C( extensionTypeWriteRegister ); 182 if( error ) return error; 183 error = putcI2C( extensionTypeData ); 184 if( error ) return error; 185 StopI2C(); 186 187 return 0; 188 } 189 190 unsigned char readSixBytes( unsigned char reg, unsigned char* values ) 191 { 192 unsigned char error = 0; 193 unsigned char i; 194 195 // write the register from which we will read 196 delay200us(); 197 StartI2C(); 198 error = putcI2C( extensionWriteAddress ); 199 if( error ) return error; 200 error = putcI2C( reg ); 201 if( error ) return error; 202 StopI2C(); 203 204 // Read the six data bytes at the read address 205 delay200us(); 206 StartI2C(); 207 error = putcI2C( extensionReadAddress ); 208 if( error ) return error; 209 // Now, read in the 6 bytes 210 error = getsI2C( values, 6 ); 211 if( error ) return error; 212 StopI2C(); 213 delay200us(); 214 return error; 215 } 216 217 // Get the type of the extension 218 unsigned char retrieveType( unsigned char* values ) 219 { 220 unsigned char error = 0; 221 // Read the six data bytes 222 error = readSixBytes( extensionTypeReadRegister, values ); 223 return error; 224 } 225 226 // Get the values for the extension and pack the raw data in values array. 227 unsigned char retrieveExtensionData( unsigned char* values ) 228 { 229 unsigned char error = 0; 230 // Read the six data bytes 231 error = readSixBytes( extensionReadRegister, values ); 232 return error; 233 } 234 235 void main( void ) 236 { 237 unsigned char error = 0; 238 239 // This sillyness prevents weird stuff on the Latch from screwing up I2C 240 TRISB = 0x00; 241 LATB = 0x00; 242 TRISB = 0xFF; 243 244 INTCON = 0x00; // turn off all interrupts 245 // make sure we are all digital 246 ADCON0 = 0x00; 247 ADCON1 |= 0x0F; 248 // initialize the USART to 19,200 baud 249 // spbrgh = ( 48,000,000 / 19200 / 16 ) - 1 = 155.25 250 OpenUSART( USART_TX_INT_OFF & USART_RX_INT_OFF 251 & USART_ASYNCH_MODE & USART_EIGHT_BIT 252 & USART_CONT_RX & USART_BRGH_HIGH, 253 155 ); 254 255 // initialize the I2C bus 256 OpenI2C( MASTER, Slew ); 257 // Now, set the speed in SSPADD. 258 SSPADD = I2C_Speed; 259 fprintf( _H_USART, "Starting.\n" ); 260 // initialize the extension device 261 error = initExtension(); 262 if( error == -2 ) { 263 fprintf( _H_USART, (const far rom char*) "Init Failed. NOTACK\n" ); 264 } else if( error ) { 265 fprintf( _H_USART, "Init Failed. Write Collision\n" ); 266 } else { 267 error = retrieveType( values ); 268 if( ! error ) { 269 printDeviceType( values ); 270 delay1sec(); 271 delay1sec(); 272 delay1sec(); 273 delay1sec(); 274 delay1sec(); 275 276 while( 1 ) { 277 // take reading, convert, and display every second 278 error = retrieveExtensionData( values ); 279 if( error ) { 280 fprintf( _H_USART, "clear\n" ); 281 fprintf( _H_USART, fail ); 282 } else { 283 convertRawValues( values, unpackedValues ); 284 printValues( unpackedValues ); 285 } 286 delay1sec(); 287 } 288 } 289 } 290 291 fprintf( _H_USART, "I'm not doing anything right now...\n" ); 292 // init error zone 293 while(1) { 294 Nop(); 295 } 296 297 }
syntax highlighted by Code2HTML, v. 0.9.1
Next Steps
First step: Bask in the glow of having gotten it to work! [ yea, me!] Now, look to do some cool stuff. Next up will be a quick one to get the classic controller working. In theory, this should only require the data values arrays to expand in size, a handler based on the device type, and additional convert and print routines. Let's see how that one works out!
After that, it's time to tackle the Wii MotionPlus! First up on that is to get the gyro reporting by itself. That will mean a change to the addressing scheme ( maybe init, too. Gotta research that ). Then, add new get data bytes, convert, and print routines. Once all that is working ( if ever ), it's on to getting the nunchuck and Motion Plus on the same wire in pass through mode. To do anything useful with the Motion Plus, I'm going to need an extension cable. This will allow the Motion Plus to be moved around freely. It will also provide a much more stable interface cable. The hack job of the PCI pins currently in use is pretty shaky!
Thanks for reading. I hope that this was helpful in some small way. This deserves some eye candy. Pictures really wouldn't be exciting or helpful. A video might be cool. I'll try to make one soon.
Subscribe to:
Posts (Atom)