Arduino PID seesaw: A ball balancing machine

Arduino PID seesaw: A ball balancing machine

prusaprinters

<p>A take on norwegiancreation's PID ball balancing seesaw using an Arduino.</p><p>Video of this working:&nbsp;</p><p><a href="https://gyazo.com/a7e7b8c4ef73c828a3449bddea8c76b7">https://gyazo.com/a7e7b8c4ef73c828a3449bddea8c76b7</a></p><p><a href="https://gyazo.com/194d471b278426e594c36b10ec67122b">https://gyazo.com/194d471b278426e594c36b10ec67122b</a></p><p><a href="https://gyazo.com/78c361639dc5b48876ff313b620b8ae2">https://gyazo.com/78c361639dc5b48876ff313b620b8ae2</a></p><p>&nbsp;</p><p>&nbsp;</p><p>Parts:</p><ul><li>Arduino UNO</li><li>Mopei MG 996R or Hitec HS-422 Servo (most similar RC servos are fine)</li><li>Sharp GP2Y0A41SK0F IR sensor (8-30cm) (analog output)</li><li>2x Samsung 30Q 18650 batteries (anything that can power the servo is fine)</li><li>Standard 9v battery</li><li>10k potentiometer</li><li>Perforated electrical board</li><li>Ball bearing to balance or 3d print your own ball</li><li>2s 18650 battery case</li><li>9v → power barrel connector</li><li>x14 3mm bolts/screws for attaching the parts</li><li>x14 3mm nuts for attaching the parts</li><li>x14 washers for attaching the parts</li><li>Male XT30 plug (XT 60 or 90 would be better but I used XT30)</li><li>Female XT30 plug (XT 60 or 90 would be better but I used XT30)</li><li>6x Jumper pins</li><li>Spare wire for perf board</li><li>4x 3mm threaded hex standoff spacers</li><li>Aluminium servo arm (or print your own)</li><li>x5 rubber feet (these are not needed)</li></ul><p>This is pretty simple to put together. <strong>This can be done with a breadboard to avoid soldering; using the perf board.</strong></p><p>Here is the Arduino Code.</p><figure class="table" style="height:auto !important;width:1278.69px;"><table style="border-color:!important;border-width:0px;"><tbody><tr><td style="border-color:!important;border-width:0px;height:auto !important;padding:0px !important;width:auto !important;"><p>1</p><p>2</p><p>3</p><p>4</p><p>5</p><p>6</p><p>7</p><p>8</p><p>9</p><p>10</p><p>11</p><p>12</p><p>13</p><p>14</p><p>15</p><p>16</p><p>17</p><p>18</p><p>19</p><p>20</p><p>21</p><p>22</p><p>23</p><p>24</p><p>25</p><p>26</p><p>27</p><p>28</p><p>29</p><p>30</p><p>31</p><p>32</p><p>33</p><p>34</p><p>35</p><p>36</p><p>37</p><p>38</p><p>39</p><p>40</p><p>41</p><p>42</p><p>43</p><p>44</p><p>45</p><p>46</p><p>47</p><p>48</p><p>49</p><p>50</p><p>51</p><p>52</p><p>53</p><p>54</p><p>55</p><p>56</p><p>57</p><p>58</p><p>59</p><p>60</p><p>61</p><p>62</p><p>63</p><p>64</p><p>65</p><p>66</p><p>67</p><p>68</p><p>69</p><p>70</p><p>71</p><p>72</p><p>73</p><p>74</p><p>75</p><p>76</p><p>77</p><p>78</p><p>79</p><p>80</p><p>81</p><p>82</p><p>83</p><p>84</p><p>85</p><p>86</p><p>87</p><p>88</p><p>89</p><p>90</p><p>91</p><p>92</p><p>93</p><p>94</p><p>95</p><p>96</p><p>97</p><p>98</p><p>99</p><p>100</p><p>101</p><p>102</p><p>103</p><p>104</p><p>105</p><p>106</p><p>107</p><p>108</p><p>109</p><p>110</p><p>111</p><p>112</p><p>113</p><p>114</p><p>115</p><p>116</p><p>117</p><p>118</p><p>119</p><p>120</p><p>121</p><p>122</p><p>123</p><p>124</p><p>125</p><p>126</p><p>127</p><p>128</p></td><td style="border-color:!important;border-width:0px;height:auto !important;padding:0px !important;width:1231.59px;"><p><code>#include &lt;Servo.h&gt;</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*SERVO POSITION CONSTANTS*/</code></p><p><code>#define SERVO_OFFSET 1425&nbsp;&nbsp; //center position</code></p><p><code>#define SERVO_MIN 1000</code></p><p><code>#define SERVO_MAX 1900</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*REFERENCE RANGE*/</code></p><p><code>#define REFERENCE_MIN 4.5</code></p><p><code>#define REFERENCE_MAX 7.0</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*DELTA T*/</code></p><p><code>#define DT 0.001&nbsp; //seconds</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*PID PARAMETERS*/</code></p><p><code>#define Kp 0.7&nbsp;&nbsp;&nbsp; //proportional coefficient</code></p><p><code>#define Ki 0.2&nbsp;&nbsp;&nbsp; //integral coefficient</code></p><p><code>#define Kd 0.2&nbsp;&nbsp;&nbsp; //derivative coefficient</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*UPSCALING TO Servo.writeMilliseconds*/</code></p><p><code>#define OUTPUT_UPSCALE_FACTOR 10</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*EMA ALPHAS*/</code></p><p><code>#define SENSOR_EMA_a 0.05</code></p><p><code>#define SETPOINT_EMA_a 0.01</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*SENSOR SPIKE NOISE HANDLING*/</code></p><p><code>#define SENSOR_NOISE_SPIKE_THRESHOLD 15</code></p><p><code>#define SENSOR_NOISE_LP_THRESHOLD 300</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>float</code> <code>mapfloat(float</code> <code>x, float</code> <code>in_min, float</code> <code>in_max, float</code> <code>out_min, float</code> <code>out_max);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>Servo myservo;</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*ARDUINO PINS*/</code></p><p><code>int</code> <code>sensor_pin = 0;</code></p><p><code>int</code> <code>pot_pin = 1;</code></p><p><code>int</code> <code>servo_pin = 3;</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*EMA VARIABLE INITIALIZATIONS*/</code></p><p><code>float</code> <code>sensor_filtered = 0.0;</code></p><p><code>int</code> <code>pot_filtered = 0;</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*GLOBAL SENSOR SPIKE NOISE HANDLING VARIABLES*/</code></p><p><code>int</code> <code>last_sensor_value = analogRead(sensor_pin);</code></p><p><code>int</code> <code>old_sensor_value = analogRead(sensor_pin);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>/*GLOBAL PID VARIABLES*/</code></p><p><code>float</code> <code>previous_error = 0;</code></p><p><code>float</code> <code>integral = 0;</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>void</code> <code>setup() {</code></p><p><code>&nbsp; Serial.begin(115200);</code></p><p><code>&nbsp; myservo.attach(servo_pin);</code></p><p><code>}</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>void</code> <code>loop() {</code></p><p><code>&nbsp; /*START DELTA T TIMING*/</code></p><p><code>&nbsp; unsigned long</code> <code>my_time = millis();</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*READ POT AND RUN POT EMA*/</code></p><p><code>&nbsp; int</code> <code>pot_value = analogRead(pot_pin);</code></p><p><code>&nbsp; pot_filtered = (SETPOINT_EMA_a*pot_value) + ((1-SETPOINT_EMA_a)*pot_filtered);</code></p><p><code>&nbsp;&nbsp;&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*MAP POT POSITION TO CM SETPOINT RANGE*/</code></p><p><code>&nbsp; float</code> <code>setpoint = mapfloat((float)pot_filtered, 0.0, 1024.0, REFERENCE_MIN, REFERENCE_MAX);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*READ SENSOR DATA*/</code></p><p><code>&nbsp; int</code> <code>sensor_value = analogRead(sensor_pin);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*REMOVE SENSOR NOISE SPIKES*/</code></p><p><code>&nbsp; if(abs(sensor_value-old_sensor_value) &gt; SENSOR_NOISE_LP_THRESHOLD || sensor_value-last_sensor_value &lt; SENSOR_NOISE_SPIKE_THRESHOLD){&nbsp; //everything is in order</code></p><p><code>&nbsp;&nbsp;&nbsp; old_sensor_value = last_sensor_value;</code></p><p><code>&nbsp;&nbsp;&nbsp; last_sensor_value = sensor_value;</code></p><p><code>&nbsp; }</code></p><p><code>&nbsp; else{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //spike detected - set sample equal to last</code></p><p><code>&nbsp;&nbsp;&nbsp; sensor_value = last_sensor_value;</code></p><p><code>&nbsp; }</code></p><p><code>&nbsp;&nbsp;&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*LINEARIZE SENSOR OUTPUT TO CENTIMETERS*/</code></p><p><code>&nbsp; sensor_value = max(1,sensor_value);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //avoid dividing by zero</code></p><p><code>&nbsp; float</code> <code>cm = (2598.42/sensor_value) - 0.42;</code></p><p><code>&nbsp;&nbsp;&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*RUN SENSOR EMA*/</code></p><p><code>&nbsp; sensor_filtered = (SENSOR_EMA_a*cm) + ((1-SENSOR_EMA_a)*sensor_filtered);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*PID CONTROLLER*/</code></p><p><code>&nbsp; float</code> <code>error = setpoint - sensor_filtered;</code></p><p><code>&nbsp; integral = integral + error*DT;</code></p><p><code>&nbsp; float</code> <code>derivative = (error - previous_error)/DT;</code></p><p><code>&nbsp; float</code> <code>output = (Kp*error + Ki*integral + Kd*derivative)*OUTPUT_UPSCALE_FACTOR;</code></p><p><code>&nbsp; previous_error = error;</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*PRINT TO SERIAL THE TERM CONTRIBUTIONS*/</code></p><p><code>&nbsp; //Serial.print(Kp*error);</code></p><p><code>&nbsp; //Serial.print(' ');</code></p><p><code>&nbsp; //Serial.print(Ki*integral);</code></p><p><code>&nbsp; //Serial.print(' ');</code></p><p><code>&nbsp; //Serial.println(Kd*derivative);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*PRINT TO SERIAL FILTERED VS UNFILTERED SENSOR DATA*/</code></p><p><code>&nbsp; //Serial.print(sensor_filtered);</code></p><p><code>&nbsp; //Serial.print(' ');</code></p><p><code>&nbsp; //Serial.println(cm);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*PREPARE AND WRITE SERVO OUTPUT*/</code></p><p><code>&nbsp; int</code> <code>servo_output = round(output) + SERVO_OFFSET;</code></p><p><code>&nbsp;&nbsp;&nbsp;</code>&nbsp;</p><p><code>&nbsp; if(servo_output &lt; SERVO_MIN){ //saturate servo output at min/max range servo_output = SERVO_MIN; } else if(servo_output &gt; SERVO_MAX){</code></p><p><code>&nbsp;&nbsp;&nbsp; servo_output = SERVO_MAX;</code></p><p><code>&nbsp; }</code></p><p><code>&nbsp;&nbsp;&nbsp;</code>&nbsp;</p><p><code>&nbsp; myservo.writeMicroseconds(servo_output);&nbsp; //write to servo</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*PRINT TO SERIAL SETPOINT VS POSITION*/</code></p><p><code>&nbsp; //Serial.print(sensor_filtered);</code></p><p><code>&nbsp; //Serial.print(' ');</code></p><p><code>&nbsp; //Serial.println(setpoint);</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>&nbsp; /*WAIT FOR DELTA T*/</code></p><p><code>&nbsp; //Serial.println(millis() - my_time);</code></p><p><code>&nbsp; while(millis() - my_time &lt; DT*1000);</code></p><p><code>}</code></p><p><code>&nbsp;</code>&nbsp;</p><p><code>float</code> <code>mapfloat(float</code> <code>x, float</code> <code>in_min, float</code> <code>in_max, float</code> <code>out_min, float</code> <code>out_max)</code></p><p><code>{</code></p><p><code>&nbsp;return</code> <code>(x - in_min) * (out_max - out_min) / (in_max - in_min) + out_min;</code></p><p><code>}</code></p></td></tr></tbody></table></figure><p>Original design I modified:&nbsp;</p><p><a href="https://www.norwegiancreations.com/2016/08/pid-seesaw-part-1-the-design/">https://www.norwegiancreations.com/2016/08/pid-seesaw-part-1-the-design/</a></p><p><a href="https://www.norwegiancreations.com/2016/08/the-seesaw-part-2-basic-pid-theory-and-arduino-implementation/">https://www.norwegiancreations.com/2016/08/the-seesaw-part-2-basic-pid-theory-and-arduino-implementation/</a></p><p>&nbsp;</p><h4>&nbsp;</h4>

Download Model from prusaprinters

With this file you will be able to print Arduino PID seesaw: A ball balancing machine with your 3D printer. Click on the button and save the file on your computer to work, edit or customize your design. You can also find more 3D designs for printers on Arduino PID seesaw: A ball balancing machine.