The Netduino forums have been replaced by new forums at community.wildernesslabs.co.
This site has been preserved for archival purposes only
and the ability to make new accounts or posts has been turned off.
This is a very early showcase of the code I wrote for the Netduino to control an R2D2. To build the R2 this far, it took over 2 years of research, wood, plastic, and aluminum work. I'm now back in my wheelhouse with programming and am loving the Netduino Plus 2.
That's brilliant! What an amazing job. Where can I more information on your Netdunio R2D2? You mentioned a builders log in the video but I didn't see any links to a website. Fantastic job sjmill01. Very impressive.
That is super cool, sjmill01. We just liked the video and tweeted about it.
Is this something you'd even bring to an event like MakerFaire?
What are your next steps with the design?
Chris
The site astromech.net houses a forum for R2 Builders. It was started around 1999. There are many, many approaches for building one using many different types of materials.
Most active posters use RC control only for their R2, but I wanted to make choreographed routines to add a little magic. With the autonomy, you feel like you have a toddler in the house - any moment he is going to bump his head or hurt a pet.
I started on him 3 years ago, but its research that slows it down mostly. It only took about a month from purchasing the Netduino to make him run on his own.
For events, my intent is to go to schools to promote science and visit kids at hospitals. What is neat about R2 is that he is a happy machine. He could roll through a prison and all the inmates would be smiling. I don't have any experience with MakerFaire or know much about it.
One thing to note, a lot of folks in various forums said the Netduino Plus 2 couldn't handle this scope, but the specs of the card indicated differently. So, I gave it a try. I think the nay saying is probably related to people not being comfortable in C#. I couldn't have pulled this off without 4 threads running simultaneously.
The next phases in design are to add two more proximity sensors, add a voice recognition card, and then add servos to his panels. The panels in the dome will flip up comically on queue. I'll offload the servo control to another board and communicate to it with serial from the Netduino Plus 2.
Thanks for the encouragement and help in prior posts.
1) Are you using the Ping ultra-sonic sensor for the proximity sensor?
2) Are you using a Netduino to control your speed controllers (sending RC controller signal to Netduino and then Netduino to speed controllers) and that is how you are able to swap between RC and autonomous control?
SEN-00639[color=rgb(51,51,51);font-family:Arial, 'MS Sans Serif', Geneva, sans-serif;] [/color] [color=rgb(51,51,51);font-family:Arial, 'MS Sans Serif', Geneva, sans-serif;]Ultrasonic Range Finder - Maxbotix LV-EZ1[/color]
[color=rgb(51,51,51);font-family:Arial, 'MS Sans Serif', Geneva, sans-serif;]2) Yep. It takes 4 threads to bring it all together. I attached the code below..[/color]
[color=rgb(63,63,63);font-family:verdana, geneva, lucida, 'lucida grande', arial, helvetica, sans-serif;font-size:13.63636302947998px;background-color:rgb(199,218,235);]These are the current features:[/color]
Selectable Modes: Stick Control, Autonomous Movement
Sound: triggers on distance in either mode
4 Threads running at all times: Determining/playing a sound based on distance from an object or time, determining distance from objects in front, setting preferred speed and direction based on the current mode and distance from an object, sending motor drivers pulses based on the preferred speed and direction
Object in zone 3, R2 goes forward and turns in the same direction as his last turn. If he doesn't find a clear path in a few seconds, he stops and yells.
Object in zone 2, R2 stops and spins in a circle in a random direction. If he doesn't find a clear path in a few seconds, he stops and yells.
Objects in zone 1, R2 stops and yells. He then backs up and turns 90 degrees.
If object is in zone 14 and the pitch knob is turned up, he'll do a dance to the 8-bit sounding Cantina song.
All other zones, R2 drives forward...the further from an object, the faster he goes.
using System;using System.Net;using System.Net.Sockets;using System.Threading;using Microsoft.SPOT;using Microsoft.SPOT.Hardware;using SecretLabs.NETMF.Hardware;using SecretLabs.NETMF.Hardware.Netduino;using System.IO.Ports;namespace R2D2Netduino{ public class Program { //v1.0 by Sean J. Miller static AnalogInput pin_ProximitySensorSignal_Front=new AnalogInput(AnalogChannels.ANALOG_PIN_A0); //a short name for a critical variable I'll be using. static bool sound_playing = false; //this is used to prevent clipping a sound that is playing if the person moves from zone to zone. const bool INTERRUPT_TRACK=true; const bool DONT_INTERRUPT_TRACK = false; static SerialPort serial_MP3TriggerBoard = new SerialPort(SerialPorts.COM1, 38400, Parity.None, 8, StopBits.One); static double temp_front_pin_voltage = 0; static bool thumbstick_controlled = true;//The default mode where he beeps based on the proximity sensor static bool pitch_knob_set_on = false;//If autonomous mode is off, pitch_knob_set_on can be flipped on to beep randomly static bool choreography_routine = false;//This is used to check to see if a pre-programmed movement & sound routine is currently playing to prevent interrupt by another thread. static int preferred_dome = 0;//The prefferred direction and speed of the dome as determined by the thumbstick or method static int current_dome = 0;//The last set speed and direction of the dome for comparison logic. static int preferred_straight_speed = 0;//The preferred direction and speed of the robot in a straight line. 0 is stop -100 is full backwards. 100 is full forwards. static int current_straight_speed = 0;//The last set speed and direction of the robot in a straight line. static int preferred_direction = 0;//The preferred direction and speed of the robot turn. 0 is stop -100 is full clockwise. 100 is full counterclockwise. static int current_direction = 0;//The last set speed and direction of the robot turn. static double K = 1; //for ramping of motor control static System.Threading.Timer timer_sound_reset;//this is to auto reset sound_playing to false after 20 seconds just in case I miss the serial RX event. static TimerCallback myTimerCallbackMethod; static double distance_increment = .06; //distance increment is an analog voltage increment. Used Debug.Print to assess values for the zones. static int front_distance_zone = 10;//this is to track which increment zone the object is in per the proximity sensor. It starts out at 10 arbitrarily. static Random random_number = new Random(); static InterruptPort inputPort5_Gear, inputPort6_PitTrim, inputPort7_LeftThumbHorizontalChannel3, inputPort12_RightThumbChannel1, inputPort13_RightThumbChannel2; static long leadingEdge5 = 0;//Used to time the PWM from Channel 5 on pin 5 static long leadingEdge6 = 0;//Used to time the PWM from Channel 6 on pin 6 static long leadingEdge7 = 0;//Used to time the PWM from Channel 3 on pin 7 static long leadingEdge12 = 0;//Used to time the PWM from Channel 1 on pin 12 static long leadingEdge13 = 0;//Used to time the PWM from Channel 2 on pin 13 static long currentPulseWidth7, currentPulseWidth12, currentPulseWidth13; //used to pulse out PWM on pins 7, 12, and 13 (dome, speed, direction) static SecretLabs.NETMF.Hardware.PWM PWM_DomeMovementToPin9, PWM_StraightMovementToPin10, PWM_TurnMovementToPin11; //Dome, Straight Line, and Turn pulses to motor drivers static Thread movementThread, soundThread, distanceThread, pulseThread;//One thread to handle dome and feet motors and the other is to handle playing sounds. public static void Main() { //This initiates the program and starts the forever looping routine named update. //R2D2Netduino.Program my_program = new R2D2Netduino.Program(); //my_program.init(); myTimerCallbackMethod = new TimerCallback(clearTime); timer_sound_reset = new System.Threading.Timer(myTimerCallbackMethod, null, 20000, 60000);//used to reset audio the Netduino misses the X sent back by the MP3Trigger Board serial_MP3TriggerBoard.DataReceived += new SerialDataReceivedEventHandler(DataReceivedHandler_TriggerBoard); serial_MP3TriggerBoard.Open(); serial_MP3TriggerBoard.Flush(); inputPort5_Gear = new InterruptPort(Pins.GPIO_PIN_D5, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth); inputPort5_Gear.OnInterrupt += new NativeEventHandler(inputPort_OnInterrupt5); inputPort6_PitTrim = new InterruptPort(Pins.GPIO_PIN_D6, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth); inputPort6_PitTrim.OnInterrupt += new NativeEventHandler(inputPort_OnInterrupt6); inputPort7_LeftThumbHorizontalChannel3 = new InterruptPort(Pins.GPIO_PIN_D7, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth); inputPort7_LeftThumbHorizontalChannel3.OnInterrupt += new NativeEventHandler(inputPort_OnInterrupt7); inputPort12_RightThumbChannel1 = new InterruptPort(Pins.GPIO_PIN_D12, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth); inputPort12_RightThumbChannel1.OnInterrupt += new NativeEventHandler(inputPort_OnInterrupt12); inputPort13_RightThumbChannel2 = new InterruptPort(Pins.GPIO_PIN_D13, true, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth); inputPort13_RightThumbChannel2.OnInterrupt += new NativeEventHandler(inputPort_OnInterrupt13); PWM_DomeMovementToPin9 = new SecretLabs.NETMF.Hardware.PWM(Pins.GPIO_PIN_D9); PWM_StraightMovementToPin10 = new SecretLabs.NETMF.Hardware.PWM(Pins.GPIO_PIN_D10); PWM_TurnMovementToPin11 = new SecretLabs.NETMF.Hardware.PWM(Pins.GPIO_PIN_D11); soundThread = new Thread(performSound); soundThread.Start();//start the thread that beeps and bloops movementThread = new Thread(performMovements); movementThread.Start();//start the thread that handles movement distanceThread = new Thread(checkAndSetDistance); distanceThread.Start();//start the thread that categorizes distance from objects into zones which is used to rome autonomously or fire interlocks on thumbstick control pulseThread = new Thread(pulseToMotorDrivers); pulseThread.Start(); Thread.Sleep(Timeout.Infinite);//sleep and let the threads do their magic } static void checkAndSetDistance() { //Identify what zone the object is that is infront of the robot. It runs in the distanceThread, so //we will always be aware of the distance from the sensors. while (true) { temp_front_pin_voltage = pin_ProximitySensorSignal_Front.Read(); if (temp_front_pin_voltage < distance_increment) front_distance_zone = 1; else if (temp_front_pin_voltage < 2 * distance_increment) front_distance_zone = 2; else if (temp_front_pin_voltage < 3 * distance_increment) front_distance_zone = 3; else if (temp_front_pin_voltage < 4 * distance_increment) front_distance_zone = 4; else if (temp_front_pin_voltage < 5 * distance_increment) front_distance_zone = 5; else if (temp_front_pin_voltage < 6 * distance_increment) front_distance_zone = 6; else if (temp_front_pin_voltage < 7 * distance_increment) front_distance_zone = 7; else if (temp_front_pin_voltage < 8 * distance_increment) front_distance_zone = 8; else if (temp_front_pin_voltage < 9 * distance_increment) front_distance_zone = 9; else if (temp_front_pin_voltage < 10 * distance_increment) front_distance_zone = 10; else if (temp_front_pin_voltage < 11 * distance_increment) front_distance_zone = 11; else if (temp_front_pin_voltage < 12 * distance_increment) front_distance_zone = 12; else if (temp_front_pin_voltage < 13 * distance_increment) front_distance_zone = 13; else if (temp_front_pin_voltage < 14 * distance_increment) front_distance_zone = 14; Thread.Sleep(250); } } static void performSound() { //This is the main program that monitors sensors and loops infinitely. int last_front_distance_zone = -1;//set initially to something that will ensure sound is triggered initially. int ticker = 0;//used to trigger on time. while (true) { if (last_front_distance_zone!=front_distance_zone) { //trigger on distance PlayTrack(randomInRange(front_distance_zone),DONT_INTERRUPT_TRACK); last_front_distance_zone=front_distance_zone;//this keeps him from chattering too much. } if (ticker++>30) { //trigger on time PlayTrack(random_number.Next(182),DONT_INTERRUPT_TRACK); ticker = 0; } Thread.Sleep(500); } } static void setDomeStickPreferredSpeedAndDirection() { if (currentPulseWidth7 > 14810) { preferred_dome = (int)(100 - 100 * (18960 - currentPulseWidth7) / (18960 - 14810)); } else if (currentPulseWidth7 > 14800 && currentPulseWidth7 < 14820) { preferred_dome = 0; } else { preferred_dome = (int)(100 * (currentPulseWidth7 - 14820) / (14820 - 10640)); } } static void setRobotStickPreferredSpeed() { if (currentPulseWidth12 > 14810) {//Forward if (front_distance_zone==1) { preferred_straight_speed = 0;//this is to interlock forward movement if something is super close. PlayTrack(132, DONT_INTERRUPT_TRACK); } else preferred_straight_speed = (int)(100 - 100 * (18960 - currentPulseWidth12) / (18960 - 14810)); } else if (currentPulseWidth12 > 14800 && currentPulseWidth12 < 14820) {//stopped preferred_straight_speed = 0; } else { //reverse preferred_straight_speed = (int)(100 * (currentPulseWidth12 - 14820) / (14820 - 10640)); } } static void setRobotStickPreferredDirection() { if (currentPulseWidth13 > 14810) { preferred_direction = (int)(100 - 100 * (18960 - currentPulseWidth13) / (18960 - 14810)); } else if (currentPulseWidth13 > 14800 && currentPulseWidth12 < 14820) { preferred_direction = 0; } else { preferred_direction = (int)(100 * (currentPulseWidth13 - 14820) / (14820 - 10640)); } } static void pulseToMotorDrivers() { while(true) { //Pulse Straight Speed if (preferred_straight_speed == 0) { PWM_StraightMovementToPin10.SetPulse(20000, (uint)(1500)); current_straight_speed = 0; } else if (System.Math.Abs(preferred_straight_speed - current_straight_speed) >5) { current_straight_speed = current_straight_speed + (int)(K * (preferred_straight_speed - current_straight_speed)); PWM_StraightMovementToPin10.SetPulse(20000, (uint)(1500 + (5 * current_straight_speed))); } //Pulse Direction if (preferred_direction == 0) { PWM_TurnMovementToPin11.SetPulse(20000, (uint)(1500)); current_direction = 0; } else if (System.Math.Abs(preferred_direction - current_direction) >5) { current_direction = current_direction + (int)(K * (preferred_direction - current_direction)); PWM_TurnMovementToPin11.SetPulse(20000, (uint)(1500 + (3 * current_direction))); } //Pulse dome if (System.Math.Abs(preferred_dome - current_dome) > 5) { PWM_DomeMovementToPin9.SetPulse(20000, (uint)(1500 + (5 * preferred_dome))); current_dome = preferred_dome; } Thread.Sleep(100); } } static void performMovements() { while (true) { if (thumbstick_controlled) { //If the gear switch is down, then it will turn off the choreography routine and //use the sticks on the controller for input. if (!choreography_routine)//this lets the choreography routine finish up before taking over with stick controll. { setDomeStickPreferredSpeedAndDirection(); setRobotStickPreferredDirection(); setRobotStickPreferredSpeed(); } } else if (!choreography_routine) { //if the gear switch is flipped up and the pitch is turned up, it will trigger //the choreography if not already triggered. choreography_routine = true; //performCantinaDance(); someday, I need to add another remote trigger romeAroundAutonomously(); choreography_routine = false; sound_playing = false; PlayTrack(2, DONT_INTERRUPT_TRACK); Thread.Sleep(1000);//give some time to remember to flip to thumbstick control if desired. } else stopRobot(); Thread.Sleep(100); } } static private void stopRobot() { //this is written with a Dimension Engineering Sabertooth 2x.. Motor Driver in Mind preferred_straight_speed=0; preferred_direction=0; } static private void romeAroundAutonomously() { PlayTrack(177, INTERRUPT_TRACK); int ii = 0; int last_direction = 50; stopRobot();//if already moving...stop it. Thread.Sleep(500);//give his bones a rest before taking off again. while (true) { if (thumbstick_controlled) { stopRobot(); return; } if (front_distance_zone == 1) { stopRobot();//just about hit something, so stop and yell! PlayTrack(132, INTERRUPT_TRACK); Thread.Sleep(1000); preferred_straight_speed = -55; preferred_direction = last_direction; Thread.Sleep(1500); stopRobot(); Thread.Sleep(3000);//give the owner time to flip to thumbstick control return; } else if (front_distance_zone == 2) { //if we are in Zone 2, we need to just stop and turn until we find a hole to drive through. preferred_straight_speed=0; preferred_direction=80-160*(random_number.Next(2)); last_direction = preferred_direction; ii = 0; while (front_distance_zone < 3 && ii < 55) { //if we don't find a hole to drive through in a few seconds of turning, just yell for help. ii++; Thread.Sleep(250); if (thumbstick_controlled) { stopRobot(); return; } } stopRobot(); if (ii >= 55) {//couldn't find a hole, so yell it up to have the owner take control. PlayTrack(132, INTERRUPT_TRACK); Thread.Sleep(4000);//give the owner time to respond with flipping back to control. return;//get out of this movement. } preferred_straight_speed=30;//if we made it here, then we found a hole...move slowly ahead. } else if (front_distance_zone == 3) { //if we are in zone 3, we need to slow down and turn sharp until we find a hole to drive through. preferred_straight_speed=30; preferred_direction = (int)((double)last_direction*1.1); if (thumbstick_controlled) { stopRobot(); return; } ii = 0; while (front_distance_zone < 4 && ii < 55) { //if we don't find a hole to drive through in a few seconds of turning, just yell for help. ii++; if (front_distance_zone < 3) { stopRobot(); break; } Thread.Sleep(100); if (thumbstick_controlled) { stopRobot(); return; } } stopRobot(); if (ii >= 55) {//couldn't find a hole, so yell it up to have the owner take control. PlayTrack(132, INTERRUPT_TRACK); Thread.Sleep(2000);//give the owner time to respond with flipping back to control. return;//get out of this movement. } preferred_straight_speed=35; } else if (front_distance_zone == 4) { preferred_direction=0; preferred_straight_speed=35; } else if (front_distance_zone == 5) { preferred_direction=0; preferred_straight_speed=38; } else if (front_distance_zone == 6) { preferred_direction = 0; preferred_straight_speed = 40; } else if (front_distance_zone == 7) { preferred_direction = 0; preferred_straight_speed = 42; } else if (front_distance_zone == 8) { preferred_direction = 0; preferred_straight_speed = 45; } else if (front_distance_zone == 9) { preferred_direction = 0; preferred_straight_speed = 48; } else if (front_distance_zone == 10) { preferred_direction = 0; preferred_straight_speed = 50; } else if (front_distance_zone == 11 || front_distance_zone == 12 || front_distance_zone == 13) { preferred_direction = 0; preferred_straight_speed = 52; } else if (pitch_knob_set_on) { stopRobot(); Thread.Sleep(1000); performCantinaDance(); Thread.Sleep(2000); } Thread.Sleep(100); } } static private void performCantinaDance() { stopRobot(); PlayTrack(13,INTERRUPT_TRACK); preferred_direction=(80+random_number.Next(20)); Thread.Sleep(1000 + random_number.Next(2000)); preferred_direction=(0); if (thumbstick_controlled) return; Thread.Sleep(500 + random_number.Next(250)); if (thumbstick_controlled) return; preferred_direction=(-(80 + random_number.Next(20))); Thread.Sleep(1000 + random_number.Next(2000)); preferred_direction=(0); if (thumbstick_controlled) return; Thread.Sleep(500+random_number.Next(250)); preferred_direction=(80 + random_number.Next(20)); Thread.Sleep(1000 + random_number.Next(2000)); preferred_direction=(0); if (thumbstick_controlled) return; Thread.Sleep(500 + random_number.Next(250)); preferred_direction=(-(80 + random_number.Next(20))); Thread.Sleep(1000 + random_number.Next(2000)); stopRobot(); if (thumbstick_controlled) return; Thread.Sleep(500 + random_number.Next(250)); preferred_direction = (-(80 + random_number.Next(20))); Thread.Sleep(1000 + random_number.Next(2000)); stopRobot(); if (thumbstick_controlled) return; Thread.Sleep(500 + random_number.Next(250)); preferred_direction = (-(80 + random_number.Next(20))); Thread.Sleep(1000 + random_number.Next(2000)); stopRobot(); if (thumbstick_controlled) return; Thread.Sleep(2000); } static void inputPort_OnInterrupt5(uint data1, uint data2, DateTime time) { //RC Channel 5 Reading in (Gear) if (data2 == 1) leadingEdge5 = time.Ticks; else if ((long)((time.Ticks - leadingEdge5)) >= 17000) thumbstick_controlled = false; else thumbstick_controlled = true; } static void inputPort_OnInterrupt6(uint data1, uint data2, DateTime time) {//RC Channel 6 Reading in (Pit Trim) if (data2 == 1) leadingEdge6 = time.Ticks; else if ((long)((time.Ticks - leadingEdge6)) > 16000) pitch_knob_set_on = false; else pitch_knob_set_on = true; } static void inputPort_OnInterrupt12(uint data1, uint data2, DateTime time) { //RC Channel 12 if (data2 == 1) leadingEdge12 = time.Ticks; else currentPulseWidth12 = (time.Ticks - leadingEdge12); } static void inputPort_OnInterrupt13(uint data1, uint data2, DateTime time) { //RC Channel 13 if (data2 == 1) leadingEdge13 = time.Ticks; else currentPulseWidth13 = (time.Ticks - leadingEdge13); } static void inputPort_OnInterrupt7(uint data1, uint data2, DateTime time) {//RC Channel 3 if (data2 == 1) leadingEdge7 = time.Ticks; else currentPulseWidth7 = (time.Ticks - leadingEdge7); } static private void clearTime(object state) { //This is called by timer_sound_reset thread every 20 seconds as a safety net for the serial event handler not catching the "track ended" response from the trigger board. sound_playing = false; timer_sound_reset.Change(20000, 60000);//resets the timer. } static private void DataReceivedHandler_TriggerBoard(object sender, SerialDataReceivedEventArgs e) { //This is triggered whenever some TTL is piped into Digital pin 0. if (((SerialPort)sender).ReadByte() == 88) sound_playing = false;//check for the Letter X from MP3Triggerboard. This signals the track is finsihed playing. } static private int randomInRange(int the_distance) { return (the_distance * 10 + random_number.Next(10));//This gives a random number to select random tracks within a zone. } static private void PlayTrack(int ii, bool interrupt) { //Sparkfun MP3TriggerBoard Only! //first, we ensure we don't interrupt a playing track if so desired. if (!sound_playing||interrupt==INTERRUPT_TRACK) { sound_playing = true; timer_sound_reset.Change(20000, 60000);//resets the timer. byte the_track = (byte)(ii); serial_MP3TriggerBoard.Write(new byte[] { 0x74, the_track }, 0, 2);//write out the serial to the MP3Trigger board which is the letter t followed by a byte value of 1-255. } } static private void setVolume(int ii) {//Sparkfun MP3TriggerBoard Only! byte the_setting = (byte)(ii);//according to the MP3Trigger Board, 0 is the loadest and 64 can't be heard. serial_MP3TriggerBoard.Write(new byte[] { 0x76, the_setting }, 0, 2); } }}
I am not as experienced as you, but I am working on an automated Holo-emitter movement for all 3 holo's. A servo for each x and a servo for each y and I am doing a random number generator to randomly adjust servo position and sleep duration, so the holo-emitters will move randomly. I'll post here once I get it tested and give more details on servo mechanics.
I see you are using a Netduino Plus 2. I am assuming I will need to upgrade from my Netduino std V1?
Ah, looking at the pin outs between the Std V1 and the Plus V2, there is a difference.
V2 has 6 PWM pins while the Std only has 4.
V2 has 4 Serial UART while Std has 2
V2 has 22 D I/O while the std only has 20.
So there are some differences. This tells me that my holo-emitter idea won't work on a std (I need 6 PWM outs), to have it all on one board, I'll have to have a plus, and then no other PWM at all. But I can at least make 2 emitters work.
But looking at the V2 std, it looks like it may work. It seems the major difference is the ethernet connections, SD card, and extra storage and processor speed.
@Vader, if all you need the PWM for is to control your servo, then I suggest you get a breakout broad servo controller, there some that can control up to 8 servo. you conect it to you Netduino Std via SPI or I2C
@sjmill01, Am encouraged by your success cause am working on similar project ( ...well still sourcing component ), Mine is mostly for out door but will work in two modes
in this mode if you send a GPS coordinate ( Lat, Long ) to the Robot ( not as fancy as your, just a flat bed on wheels) through a WiFi Connection, the robot will try to navigate to the location avoiding any ultrasonic detectable object in it path
Remote Mode:
In remote mode, the robot will be controlled remotely over a WiFi connection using a Joystick attached to a Windows 8 Laptop ( I hope to be able to control it with a Windows 8 Phone using the in-built accelerometer )
In both mode the robot will be sending back video from an on-broad camera, most off the components are currently on order, but I still have problem sourcing an electric motor that will be give me enough power to mover a 100 kg payload, I noticed you used an NPC Wheel chair motor that why i got interested in your project
so, how much power where able to get from the Motor, and which ESC did you use, am thnking of using ESC from RoboteQ SDC2130
My NPC motors are driven by a Dimension Engineering Sabertooth 2x25. They have dip switches that allow one to take microcontroller input or a failsafe autocalibrated RC mode (although I don't use that mode). My robot weighs probably around 100 pounds, but I hear the motors are fine for 160 lb droids in my club. They are 12V.
Here is what I have gotten for the servo control for the holo-emitters:
using System;using System.Threading;using Microsoft.SPOT;using Microsoft.SPOT.Hardware;using SecretLabs.NETMF.Hardware;using SecretLabs.NETMF.Hardware.Netduino; namespace holo_emitter_control{ public class Program { static PWM servo1 = new PWM(Pins.GPIO_PIN_D5); static PWM servo2 = new PWM(Pins.GPIO_PIN_D6); static PWM servo3 = new PWM(Pins.GPIO_PIN_D9); static PWM servo4 = new PWM(Pins.GPIO_PIN_D10); public static void Main() { // write your code here Thread s1 = new Thread (Servo1); s1.Start(); Thread s2 = new Thread (Servo2); s2.Start(); Thread s3 = new Thread (Servo3); s3.Start(); Thread s4 = new Thread (Servo4); s4.Start(); Thread.Sleep(Timeout.Infinite); } static void Servo1() { Random randomGenerator = new Random(); while(true) { uint pos1 = (uint) ((randomGenerator.NextDouble()*1000)+1000); int sleep1 = (int) ((randomGenerator.NextDouble()*120000)+1000); servo1.SetPulse(20000, pos1); Thread.Sleep(sleep1); } } static void Servo2() { Random randomGenerator = new Random(); while(true) { uint pos2 = (uint) ((randomGenerator.NextDouble()*1000)+1000); int sleep2 = (int) ((randomGenerator.NextDouble()*120000)+1000); servo2.SetPulse(20000, pos2); Thread.Sleep(sleep2); } } static void Servo3() { Random randomGenerator = new Random(); while(true) { uint pos3 = (uint) ((randomGenerator.NextDouble()*1000)+1000); int sleep3 = (int) ((randomGenerator.NextDouble()*120000)+1000); servo3.SetPulse(20000, pos2); Thread.Sleep(sleep3); } } static void Servo4() { Random randomGenerator = new Random(); while(true) { uint pos4 = (uint) ((randomGenerator.NextDouble()*1000)+1000); int sleep4 = (int) ((randomGenerator.NextDouble()*120000)+1000); servo4.SetPulse(20000, pos4); Thread.Sleep(sleep4); } } }}
The sleep commands will have the servo sleep for anywhere from 1 second to 2 minutes. The position will be randomly from 0 to 90 degrees. These servo positions were based out of the book "Getting Started with Netduino" This only does 2 emitters (one for each x axis and one for each y axis). Adding a 3rd wouldn't be hard at all, as long as the board has the extra 2 PWM outputs.
I'm not as adept as you are Sean yet, but hopefully this can help you.
Sean, here is my code for an entire dome controller. Tell me what you think.
It is built on a Netduino 1, so I don't have all the PWM pins I need. But what I do have is setup for the logic function display and the logic diagnostic display on the back. These use (6) MAX7219 daisy chained with an 8x8 led matrix per chip (like what i showed in my video above) via SPI. It has (2) led's on a random PWM to adjust intensity (for use for the 1" color spot next to the holo emitter) and (2) servo controls (for random movement of one of the holo emitters).
Notes are built into the code, so hopefully it makes sense. I have been testing on my desk, just have to get all the pretty parts to assemble in my dome. One word of warning, the Netduino board can only power 4 of the MAX7219. After 4, it drags the power supply down and the processor just continually reboots. I would have the MAX chips and the servos powered from a separate 5VDC source other than the Netduino.
The project does have (3) *.cs files to make everything work. I am still working on shaving it down as much as I can. My goal is to get rid of 2 of the 3 files. I have someone working with me on that now.
Any suggestions would be greatly appreciated. Thanks.
Thanks for sharing. I'm awaiting LEDs to finish my Max7219 drivers. I'm using PCBs a guy from Astromech designed. (Teeces) They are designed for the Arduino pro mini/micro, but actually just need 3 pins to drive them (D, L, C of the Max).
Did you design the PCBs for your Max7219's?
I tried opening your solution with VB C# 2010 Express, but it failed. Did you use VB2008? I'm a decent coder of 30 years using notepad, but clumsy with IDE's and which to use. If I can open the code, I might be able to help refine it. It would be awesome to make a fully autonomous R2 with just one Netduino including driving lights.
I'm new to PWM, too. Can you explain how PWM pulses an LED? I don't see how this physically works. I've seen demos show a slow increase in light. I don't see how pulsing thousands of times a second does this.
I have taken my R2 apart to set up his dome rotation. Rotation is already covered in my code, but I don't have the hardware in place to do it, yet. He is in pieces and its kind of depressing seeing him like he looked a year of effort ago.
I use Visual Studio Pro 2012. I have converted the cs files to txt files and uploaded them here. Now you can open them in notepad and just copy and paste into a new project. Keep in mind, all 3 files are needed to make it work. You have to have them all in the one project. And keep the name of the files the same.
My boards, I cheated. Bought a bunch of MAX7219 and 8x8 led matrix off eBay. Got 5 units for $15. I need 6 total to make my dome work, so I ordered 2 batches. Comes with all the parts and pieces, all you have to do is assemble. Here is the link.
PWM to an LED works on percentages. It is fairly simple, yet a little tricky. Here is the best way I can explain it. If you set your PWM duty cycle to 50% (that means for every cycle, the signal is on 50% and off 50%) the led will be 50% bright. If you set it to 75% duty cycle (75% on, 25% off) the led will be 75% bright. 100% = 100% bright (on at all times).
To try and explain why it works, I'll use 10 VDC (easy numbers). We want to dim an LED but all we have is 10 VDC on or off. No adjustment. So what we do is send 10 VDC in a PWM output. This makes the output pulse. With a 50% duty cycle, we will turn on the 10 VDC for half the time, and the other half it will be off. On an oscilloscope, you will see the square wave on and off. But to the LED, it sees "effectively" 5VDC (50% of 10 VDC is 5 VDC). So now the LED is working on only 5 VDC instead of the full 10. At a 25% duty cycle, the signal is on only 25% of the time, so the "effective" voltage is 2.5 VDC. The trick is to do it so fast the LED doesn't have time to fully react to the full 10 VDC signal. If we did 1 cycle every second, we would see full on, full off. No dimming. But at a cycle rate in the KHz range, we "fool" the led into dimming. If it would help, I think I have an image that may help give a visual of what is going on.
To explain the time, let's look at a 100 ms cycle(1 Kilohertz). In a 25% duty cycle, from t=0 to t=24 ms, we will be outputting 10 VDC. from t=25 to t=99 ms, we will output 0 VDC. Then at 100 ms, it starts all over. That is a definition of a cycle. And the PWM output is done in the multiple Kilohertz range. RC servos run on a cycle of 20 KHz. Using 20 Khz, that is a cycle ever 50 microseconds. Pretty darn fast.
I hope I didn't go too basic on my explanation. I can assume that with your programming background you know about cycles, but I put it in there for anyone else who is unfamiliar.