How/When to update bias in RPROP neural network? - neural-network

I am implementing this neural network for some classification problem. I initially tried back propagation but it takes longer to converge. So I though of using RPROP. In my test setup RPROP works fine for AND gate simulation but never converges for OR and XOR gate simulation.
How and when should I update bias for RPROP?
Here my weight update logic:
for(int l_index = 1; l_index < _total_layers; l_index++){
Layer* curr_layer = get_layer_at(l_index);
//iterate through each neuron
for (unsigned int n_index = 0; n_index < curr_layer->get_number_of_neurons(); n_index++) {
Neuron* jth_neuron = curr_layer->get_neuron_at(n_index);
double change = jth_neuron->get_change();
double curr_gradient = jth_neuron->get_gradient();
double last_gradient = jth_neuron->get_last_gradient();
int grad_sign = sign(curr_gradient * last_gradient);
//iterate through each weight of the neuron
for(int w_index = 0; w_index < jth_neuron->get_number_of_weights(); w_index++){
double current_weight = jth_neuron->give_weight_at(w_index);
double last_update_value = jth_neuron->give_update_value_at(w_index);
double new_update_value = last_update_value;
if(grad_sign > 0){
new_update_value = min(last_update_value*1.2, 50.0);
change = sign(curr_gradient) * new_update_value;
}else if(grad_sign < 0){
new_update_value = max(last_update_value*0.5, 1e-6);
change = -change;
curr_gradient = 0.0;
}else if(grad_sign == 0){
change = sign(curr_gradient) * new_update_value;
}
//Update neuron values
jth_neuron->set_change(change);
jth_neuron->update_weight_at((current_weight + change), w_index);
jth_neuron->set_last_gradient(curr_gradient);
jth_neuron->update_update_value_at(new_update_value, w_index);
double current_bias = jth_neuron->get_bias();
jth_neuron->set_bias(current_bias + _learning_rate * jth_neuron->get_delta());
}
}
}

In principal you don't treat the bias differently than before when you did backpropagation. It's learning_rate * delta which you seem to be doing.
One source of error may be that the sign of the weight change depends on how you calculate your error. There's different conventions and (t_i-y_i) instead of (y_i - t_i) should result in returning (new_update_value * sgn(grad)) instead of -(new_update_value * sign(grad)) so try switching the sign. I'm also unsure about how you specifically implemented everything since a lot is not shown here. But here's a snippet of mine in a Java implementation that might be of help:
// gradient didn't change sign:
if(weight.previousErrorGradient * errorGradient > 0)
weight.lastUpdateValue = Math.min(weight.lastUpdateValue * step_pos, update_max);
// changed sign:
else if(weight.previousErrorGradient * errorGradient < 0)
{
weight.lastUpdateValue = Math.max(weight.lastUpdateValue * step_neg, update_min);
}
else
weight.lastUpdateValue = weight.lastUpdateValue; // no change
// Depending on language, you should check for NaN here.
// multiply this with -1 depending on your error signal's sign:
return ( weight.lastUpdateValue * Math.signum(errorGradient) );
Also, keep in mind that 50.0, 1e-6 and especially 0.5, 1.2 are empirically gathered values so they might need to be adjusted. You should definitely print out the gradients and weight changes to see if there's something weird going on (e.g. exploding gradients->NaN although you're only testing AND/XOR). Your last_gradient value should also be initialized to 0 at the first timestep.

Related

formula to pick every pixel in a bitmap without repeating

I'm looking for an algorithm, I am programming in swift now but pseudocode or any reasonably similar "C family" syntax will do.
Imagine a large list of values, such as pixels in a bitmap. You want to pick each one in a visually random order, one at a time, and never pick the same one twice, and always end up picking them all.
I used it before in a Fractal generator so that it was not just rendering line by line, but built it up slowly in a stochastic way, but that was long ago, in a Java applet, and I no longer have the code.
I do not believe it used any pseudo-random number generator, and the main thing I liked about it is that it did not make the rendering time take longer than the just line by line approach. Any of the shuffling algorithms I looked at would make the rendering take longer with such a large number of values to deal with, unless I'm missing something.
EDIT: I used the shuffling an array approach. I shuffle once when the app loads, and it does not take that long anyway. Here is the code for my "Dealer" class.
import Foundation
import Cocoa
import Quartz
class Dealer: NSObject
{
//########################################################
var deck = [(CGFloat,CGFloat)]()
var count = 0
//########################################################
init(_ w:Int, _ h:Int)
{
super.init()
deck.reserveCapacity((w*h)+1)
for y in 0...h
{
for x in 0...w
{
deck.append((CGFloat(x),CGFloat(y)))
}
}
self.shuffle()
}
//########################################################
func shuffle()
{
var j:Int = 0
let total:Int = deck.count-1
for i:Int in 0...total
{
j = Int(arc4random_uniform(UInt32(total)))
deck.swapAt(i, j)
}
}
//########################################################
func deal() -> (CGFloat,CGFloat)
{
let result = deck[count]
let total:Int = deck.count-1
if(count<total) { count=count+1 } else { count=0 }
return(result)
}
//########################################################
}
The init is called once, and it calls shuffle, but if you want you can call shuffle again if needed.
Each time you need a "card" you call Deal. It loops to the beginning when the "deck" is done.
if you got enough memory space to store all the pixel positions you can shuffle them:
const int xs=640; // image resolution
const int ys=480;
color pixel[sz]; // image data
const int sz=xs*ys; // image size
int adr[sz],i,j;
for (i=0;i<sz;i++) adr[i]=i; // ordered positions
for (i=0;i<sz;i++) // shuffle them
{
j = random(sz); // pseudo-randomness with uniform distribution
swap(pixel[i],pixel[j]);
}
this way you got guaranteed that each pixel is used once and most likely all of them are shuffled ...
You need to implement a pseudo-random number generator with a theoretically known period, which is greater than but very close to the number of elements in your list. Suppose R() is a function that implements such a RNG.
Then:
for i = 1...N
do
idx = R()
while idx > N
output element(idx)
end
If the period of the RNG is greater than N, this algorithm is guaranteed to finish, and never output the same element twice
If the period of the RNG is close to N, this algorithm will be fast (i.e. the do-while loop will mostly do 1 iteration).
If the RNG has good quality, the visual output will look pleasant; here you have to do experiments and decide what is good enough for you
To find a RNG that has an exactly-known period, you should examine theory on RNGs, which is very extensive (maybe too extensive); Wikipedia has useful links.
Start with Linear congruential generators: they are very simple, and there is a chance they will be of good enough quality.
Here's a working example based on linear feedback shift registers. Since an n-bit LFSR has a maximal sequence length of 2n−1 steps, this will work best when the number of pixels is one less than a power of 2. For other sizes, the pseudo-random coordinates are discarded until one is obtained that lies within the specified range of coordinates. This is still reasonably efficient; in the worst case (where w×h is a power of 2), there will be an average of two LSFR iterations per coordinate pair.
The following code is in Javascript, but it should be easy enough to port this to Swift or any other language.
Note: For large canvas areas like 1920×1024, it would make more sense to use repeated tiles of a smaller size (e.g., 128×128). The tiling will be imperceptible.
var lsfr_register, lsfr_mask, lsfr_fill_width, lsfr_fill_height, lsfr_state, lsfr_timer;
var lsfr_canvas, lsfr_canvas_context, lsfr_blocks_per_frame, lsfr_frame_rate = 50;
function lsfr_setup(width, height, callback, duration) {
// Maximal length LSFR feedback terms
// (sourced from http://users.ece.cmu.edu/~koopman/lfsr/index.html)
var taps = [ -1, 0x1, 0x3, 0x5, 0x9, 0x12, 0x21, 0x41, 0x8E, 0x108, 0x204, 0x402,
0x829, 0x100D, 0x2015, 0x4001, 0x8016, 0x10004, 0x20013, 0x40013,
0x80004, 0x100002, 0x200001, 0x400010, 0x80000D, 0x1000004, 0x2000023,
0x4000013, 0x8000004, 0x10000002, 0x20000029, 0x40000004, 0x80000057 ];
nblocks = width * height;
lsfr_size = nblocks.toString(2).length;
if (lsfr_size > 32) {
// Anything longer than about 21 bits would be quite slow anyway
console.log("Unsupposrted LSFR size ("+lsfr_size+")");
return;
}
lsfr_register = 1;
lsfr_mask = taps[lsfr_size];
lsfr_state = nblocks;
lsfr_fill_width = width;
lsfr_fill_height = height;
lsfr_blocks_per_frame = Math.ceil(nblocks / (duration * lsfr_frame_rate));
lsfr_timer = setInterval(callback, Math.ceil(1000 / lsfr_frame_rate));
}
function lsfr_step() {
var x, y;
do {
// Generate x,y pairs until they are within the bounds of the canvas area
// Worst-case for an n-bit LSFR is n iterations in one call (2 on average)
// Best-case (where w*h is one less than a power of 2): 1 call per iteration
if (lsfr_register & 1) lsfr_register = (lsfr_register >> 1) ^ lsfr_mask;
else lsfr_register >>= 1;
y = Math.floor((lsfr_register-1) / lsfr_fill_width);
} while (y >= lsfr_fill_height);
x = (lsfr_register-1) % lsfr_fill_width;
return [x, y];
}
function lsfr_callback() {
var coords;
for (var i=0; i<lsfr_blocks_per_frame; i++) {
// Fetch pseudo-random coordinates and fill the corresponding pixels
coords = lsfr_step();
lsfr_canvas_context.fillRect(coords[0],coords[1],1,1);
if (--lsfr_state <= 0) {
clearInterval(lsfr_timer);
break;
}
}
}
function start_fade() {
var w = document.getElementById("w").value * 1;
var h = document.getElementById("h").value * 1;
var dur = document.getElementById("dur").value * 1;
lsfr_canvas = document.getElementById("cv");
lsfr_canvas.width = w;
lsfr_canvas.height = h;
lsfr_canvas_context = lsfr_canvas.getContext("2d");
lsfr_canvas_context.fillStyle = "#ffff00";
lsfr_canvas_context.fillRect(0,0,w,h);
lsfr_canvas_context.fillStyle = "#ff0000";
lsfr_setup(w, h, lsfr_callback, dur);
}
Size:
<input type="text" size="3" id="w" value="320"/>
×
<input type="text" size="3" id="h" value="240"/>
in
<input type="text" size="3" id="dur" value="3"/>
secs
<button onclick="start_fade(); return 0">Start</button>
<br />
<canvas id="cv" width="320" height="240" style="border:1px solid #ccc"/>

Sand 3D Printer Slicing Issue

For my doctoral thesis I am building a 3D printer based loosely off of one from the University of Twente:
http://pwdr.github.io/
So far, everything has gone relatively smoothly. The hardware part took longer than expected, but the electronics frighten me a little bit. I can sucessfully jog all the motors and, mechanically, everything does what is supposed to do.
However, now that I am working on the software side, I am getting headaches.
The Pwder people wrote a code that uses Processing to take an .STL file and slice it into layers. Upon running the code, a Processing GUI opens where I can load a model. The model loads fine (I'm using the Utah Teapot) and shows that it will take 149 layers.
Upon hitting "convert" the program is supposed to take the .STL file and slice it into layers, followed by writing a text file that I can then upload to an SD card. The printer will then print directly from the SD card.
However, when I hit "convert" I get an "Array Index Out of Bounds" error. I'm not quite sure what this means.. can anyone enlighten me?
The code can be found below, along with a picture of the error.
Thank you.
// Convert the graphical output of the sliced STL into a printable binary format.
// The bytes are read by the Arduino firmware
PrintWriter output, outputUpper;
int loc;
int LTR = 0;
int lowernozzles = 8;
int uppernozzles = 4;
int nozzles = lowernozzles+uppernozzles;
int printXcoordinate = 120+280; // Left margin 120
int printYcoordinate = 30+190; // Top margin 30
int printWidth = 120; // Total image width 650
int printHeight = 120; // Total image height 480
int layer_size = printWidth * printHeight/nozzles * 2;
void convertModel() {
// Create config file for the printer, trailing comma for convenience
output = createWriter("PWDR/PWDRCONF.TXT"); output.print(printWidth+","+printHeight/nozzles+","+maxSlices+","+inkSaturation+ ",");
output.flush();
output.close();
int index = 0;
byte[] print_data = new byte[layer_size * 2];
// Steps of 12 nozzles in Y direction
for (int y = printYcoordinate; y < printYcoordinate+printHeight; y=y+nozzles ) {
// Set a variable to know wheter we're moving LTR of RTL
LTR++;
// Step in X direction
for (int x = 0; x < printWidth; x++) {
// Clear the temp strings
String[] LowerStr = {""};
String LowerStr2 = "";
String[] UpperStr = {""};
String UpperStr2 = "";
// For every step in Y direction, sample the 12 nozzles
for ( int i=0; i<nozzles; i++) {
// Calculate the location in the pixel array, use total window width!
// Use the LTR to determine the direction
if (LTR % 2 == 1){
loc = printXcoordinate + printWidth - x + (y+i) * width;
} else {
loc = printXcoordinate + x + (y+i) * width;
}
if (brightness(pixels[loc]) < 100) {
// Write a zero when the pixel is white (or should be white, as the preview is inverted)
if (i<uppernozzles) {
UpperStr = append(UpperStr, "0");
} else {
LowerStr = append(LowerStr, "0");
}
} else {
// Write a one when the pixel is black
if (i<uppernozzles) {
UpperStr = append(UpperStr, "1");
} else {
LowerStr = append(LowerStr, "1");
}
}
}
LowerStr2 = join(LowerStr, "");
print_data[index] = byte(unbinary(LowerStr2));
index++;
UpperStr2 = join(UpperStr, "");
print_data[index] = byte(unbinary(UpperStr2));
index++;
}
}
if (sliceNumber >= 1 && sliceNumber < 10){
String DEST_FILE = "PWDR/PWDR000"+sliceNumber+".DAT";
File dataFile = sketchFile(DEST_FILE);
if (dataFile.exists()){
dataFile.delete();
}
saveBytes(DEST_FILE, print_data); // Savebytes directly causes bug under Windows
} else if (sliceNumber >= 10 && sliceNumber < 100){
String DEST_FILE = "PWDR/PWDR00"+sliceNumber+".DAT";
File dataFile = sketchFile(DEST_FILE);
if (dataFile.exists()){
dataFile.delete();
}
saveBytes(DEST_FILE, print_data); // Savebytes directly causes bug under Windows
} else if (sliceNumber >= 100 && sliceNumber < 1000){
String DEST_FILE = "PWDR/PWDR0"+sliceNumber+".DAT";
File dataFile = sketchFile(DEST_FILE);
if (dataFile.exists()){
dataFile.delete();
}
saveBytes(DEST_FILE, print_data); // Savebytes directly causes bug under Windows
} else if (sliceNumber >= 1000) {
String DEST_FILE = "PWDR/PWDR"+sliceNumber+".DAT";
File dataFile = sketchFile(DEST_FILE);
if (dataFile.exists()){
dataFile.delete();
}
saveBytes(DEST_FILE, print_data); // Savebytes directly causes bug under Windows
}
sliceNumber++;
println(sliceNumber);
}
What's happening is that print_data is smaller than index. (For example, if index is 123, but print_data only has 122 elements.)
Size of print_data is layer_size * 2 or printWidth * printHeight/nozzles * 4 or 4800
Max size of index is printHeight/nozzles * 2 * printWidth or 20*120 or 2400.
This seems alright, so I probably missed something, and it appears to be placing data in element 4800, which is weird. I suggest a bunch of print statements to get the size of print_data and the index.

Calculate IRR (Internal Rate Return) and NPV programmatically in Objective-C

I am developing a financial app and require IRR (in-built functionality of Excel) calculation and found such great tutorials in C here and such answer in C# here.
I implemented code of the C language above, but it gives a perfect result when IRR is in positive. It is not returning a negative value when it should be. Whereas in Excel =IRR(values,guessrate) returns negative IRR as well for some values.
I have referred to code in above C# link too, and it seems that it follows good procedures and returns errors and also hope that it returns negative IRR too, the same as Excel. But I am not familiar with C#, so I am not able to implement the same code in Objective-C or C.
I am writing C code from the above link which I have implemented for helping you guys.
#define LOW_RATE 0.01
#define HIGH_RATE 0.5
#define MAX_ITERATION 1000
#define PRECISION_REQ 0.00000001
double computeIRR(double cf[], int numOfFlows)
{
int i = 0, j = 0;
double m = 0.0;
double old = 0.00;
double new = 0.00;
double oldguessRate = LOW_RATE;
double newguessRate = LOW_RATE;
double guessRate = LOW_RATE;
double lowGuessRate = LOW_RATE;
double highGuessRate = HIGH_RATE;
double npv = 0.0;
double denom = 0.0;
for (i=0; i<MAX_ITERATION; i++)
{
npv = 0.00;
for (j=0; j<numOfFlows; j++)
{
denom = pow((1 + guessRate),j);
npv = npv + (cf[j]/denom);
}
/* Stop checking once the required precision is achieved */
if ((npv > 0) && (npv < PRECISION_REQ))
break;
if (old == 0)
old = npv;
else
old = new;
new = npv;
if (i > 0)
{
if (old < new)
{
if (old < 0 && new < 0)
highGuessRate = newguessRate;
else
lowGuessRate = newguessRate;
}
else
{
if (old > 0 && new > 0)
lowGuessRate = newguessRate;
else
highGuessRate = newguessRate;
}
}
oldguessRate = guessRate;
guessRate = (lowGuessRate + highGuessRate) / 2;
newguessRate = guessRate;
}
return guessRate;
}
I have attached the result for some value which are different in Excel and the above C language code.
Values: Output of Excel: -33.5%
1 = -18.5, Output of C code: 0.010 or say (1.0%)
2 = -18.5,
3 = -18.5,
4 = -18.5,
5 = -18.5,
6 = 32.0
Guess rate: 0.1
Since low_rate and high_rate are both positive, you're not able to get a negative score. You have to change:
#define LOW_RATE 0.01
to, for example,
#define LOW_RATE -0.5

Round to "beautiful" value

guys, I'm making simple graph drawer and want to find beautiful values for horizontal lines.
For example, if I have value equals to 72089.601562, beautiful is 70000, or 75000. So, I think that beautifulNumber%5 = 0.
Have you any ideas?
How about this?
#import <math.h>
#import <stdio.h>
#define ROUNDING 5000
int beautify(float input)
{
// Cast to int, losing the decimal value.
int value = (int)input;
value = (value / ROUNDING) * ROUNDING;
if ((int)input % ROUNDING > ROUNDING / 2 )
{
value += ROUNDING;
}
return value;
}
int main()
{
printf("%d\n", beautify(70000.601562)); // 70000
printf("%d\n", beautify(72089.601562)); // 70000
printf("%d\n", beautify(76089.601562)); // 75000
printf("%d\n", beautify(79089.601562)); // 80000
printf("%d\n", beautify(70000.601562)); // 70000
return 0;
}
It depends whether you want a floor value, a ceiling value or just to round to the nearest 5000.
For a floor value:
int beautiful = (int)(floor(ugly / 5000.0) * 5000.0);
For a ceiling value:
int beautiful = (int)(ceil(ugly / 5000.0) * 5000.0);
For rounding:
int beautiful = (int)(round(ugly / 5000.0) * 5000.0);
For making graph lines, I'd probably find the minimum and maximum values you have to graph, start with a floor value for the minimum value and then add your desired interval until you have surpassed your maximum value.
For instance:
float minValue = 2.34;
float maxValue = 7.72;
int interval = 1;
NSMutableArray *horizLines = [NSMutableArray array];
int line = (int)(floor(minValue / interval) * interval);
[horizLines addObject:[NSNumber numberWithInt:line]];
do {
line = (int)(ceil(minValue / interval) * interval);
[horizLines addObject:[NSNumber numberWithInt:line]];
if (minValue >= maxValue) break;
minValue = minValue + interval;
}
Use as needed!
Well, it seems like you'd want it to scale based on the size of the number. If the range only goes to 10, then obviously rounding to the nearest 5,000 doesn't make sense. There's probably a really elegant way to code it using bit shifting but just something like this will do the trick:
float value = 72089.601562
int beautiful = 0;
// EDIT to support returning a float for small numbers:
if (value < 0.2) beautiful = int(value*100)/100.;
else if (value < 2.) beautiful = int(value*10)/10.;
// Anything bigger is easy:
else if (value < 20) beautiful = (int)value;
else if (value < 200) beautiful = (int)value/10;
else if (value < 2000) beautiful = (int)value/100;
else if (value < 20000) beautiful = (int)value/1000;
// etc
Sounds like what you want to do is round to 1 or perhaps 2 significant digits. Rounding to n significant digits is pretty easy:
double roundToNDigits(double x, int n) {
double basis = pow(10.0, floor(log10(x)) - (n-1));
return basis * round(x / basis);
}
This will give you roundToNDigits(74518.7, 1) == 70000.0 and roundToNDigits(7628.54, 1) == 8000.00
If you want to round to 1 or 2 digits (only 2 where the second digit is 5), you want something like:
double roundSpecial(double x) {
double basis = pow(10.0, floor(log10(x))) / 2.0;
return basis * round(x / basis);
}

PID controller in C# Micro Framework issues

I have built a tricopter from scratch based on a .NET Micro Framework board from TinyCLR.com. I used the FEZ Mini which runs at 72 MHz. Read more about my project at: http://bit.ly/TriRot.
So after a pre-flight check where I initialise and test each component, like calibrating the IMU and spinning each motor, checking that I get receiver data, etc., it enters a permanent loop which then calls the flight controller method on each loop.
I'm trying to tune my PID controller now using the Ziegler-Nichols method, but I am always getting a progressively larger overshoot. I was eventually able to get a [mostly] stable oscillation using proportional control only (setting Ki and Kd = 0); timing the period K with a stopwatch averaged out to 3.198 seconds.
I came across the answer (by Rex Logan) on a similar question by chris12892.
I was initially using the "Duration" variable in milliseconds which made my copter highly aggressive, obviously because I was multiplying the running integrator error by thousands on each loop. I then divided it by another thousand to bring it to seconds, but I'm still battling...
What I don't understand from Rex's answer is:
Why does he ignore the time variable in the integral and differential parts of the equations? Is that right or is it a typo?
What he means by the remark
In a normal sampled system the delta term would be one...
One what? Should this be one second under normal circumstances? What
if this value fluctuates?
My flight controller method is below:
private static Single[] FlightController(Single[] imuData, Single[] ReceiverData)
{
Int64 TicksPerMillisecond = TimeSpan.TicksPerMillisecond;
Int64 CurrentTicks = DateTime.Now.Ticks;
Int64 TickCount = CurrentTicks - PreviousTicks;
PreviousTicks = CurrentTicks;
Single Duration = (TickCount / TicksPerMillisecond) / 1000F;
const Single Kp = 0.117F; //Proportional Gain (Instantaneou offset)
const Single Ki = 0.073170732F; //Integral Gain (Permanent offset)
const Single Kd = 0.001070122F; //Differential Gain (Change in offset)
Single RollE = 0;
Single RollPout = 0;
Single RollIout = 0;
Single RollDout = 0;
Single RollOut = 0;
Single PitchE = 0;
Single PitchPout = 0;
Single PitchIout = 0;
Single PitchDout = 0;
Single PitchOut = 0;
Single rxThrottle = ReceiverData[(int)Channel.Throttle];
Single rxRoll = ReceiverData[(int)Channel.Roll];
Single rxPitch = ReceiverData[(int)Channel.Pitch];
Single rxYaw = ReceiverData[(int)Channel.Yaw];
Single[] TargetMotorSpeed = new Single[] { rxThrottle, rxThrottle, rxThrottle };
Single ServoAngle = 0;
if (!FirstRun)
{
Single imuRoll = imuData[1] + 7;
Single imuPitch = imuData[0];
//Roll ----- Start
RollE = rxRoll - imuRoll;
//Proportional
RollPout = Kp * RollE;
//Integral
Single InstanceRollIntegrator = RollE * Duration;
RollIntegrator += InstanceRollIntegrator;
RollIout = RollIntegrator * Ki;
//Differential
RollDout = ((RollE - PreviousRollE) / Duration) * Kd;
//Sum
RollOut = RollPout + RollIout + RollDout;
//Roll ----- End
//Pitch ---- Start
PitchE = rxPitch - imuPitch;
//Proportional
PitchPout = Kp * PitchE;
//Integral
Single InstancePitchIntegrator = PitchE * Duration;
PitchIntegrator += InstancePitchIntegrator;
PitchIout = PitchIntegrator * Ki;
//Differential
PitchDout = ((PitchE - PreviousPitchE) / Duration) * Kd;
//Sum
PitchOut = PitchPout + PitchIout + PitchDout;
//Pitch ---- End
TargetMotorSpeed[(int)Motors.Motor.Left] += RollOut;
TargetMotorSpeed[(int)Motors.Motor.Right] -= RollOut;
TargetMotorSpeed[(int)Motors.Motor.Left] += PitchOut;// / 2;
TargetMotorSpeed[(int)Motors.Motor.Right] += PitchOut;// / 2;
TargetMotorSpeed[(int)Motors.Motor.Rear] -= PitchOut;
ServoAngle = rxYaw + 15;
PreviousRollE = imuRoll;
PreviousPitchE = imuPitch;
}
FirstRun = false;
return new Single[] {
(Single)TargetMotorSpeed[(int)TriRot.LeftMotor],
(Single)TargetMotorSpeed[(int)TriRot.RightMotor],
(Single)TargetMotorSpeed[(int)TriRot.RearMotor],
(Single)ServoAngle
};
}
Edit: I found that I had two bugs in my code above (fixed now). I was integrating and differentiating with the last IMU values as opposed to the last error values. That got rid of the runaway sitation completely. The only problem now is that it seems to be a bit slow. When I perturb the system, it responds very quickly and stop it from continuing, but it takes a long time to get back to the setpoint (0), about 10 seconds or more. Is this now just down to tuning the PID? I'll give the suggestions below a go, and let you know if any of them make a difference.
One question I have is:
being a .NET board, I don't want to bank on any kind of accurate timing, so instead of trying to work out at what frequency I am executing that method, surely if I calculate the actual time and factor that into the equations, it should be better, or am I misunderstanding something?