From a92a378c0bff9f00b15b7959bf5aaa7941371b57 Mon Sep 17 00:00:00 2001 From: Alvie Rahman Date: Thu, 12 Oct 2023 17:26:07 +0100 Subject: [PATCH] add skeleton programs --- .../MotorEncoderSkeleton.ino | 300 ++++++++++++++++++ Lab_1/skeleton_programs/TestEncoder.c | 87 +++++ Lab_1/skeleton_programs/TestEncoder.ino | 116 +++++++ Lab_1/skeleton_programs/TwoSensorsSkeleton.c | 135 ++++++++ .../skeleton_programs/TwoSensorsSkeleton.ino | 143 +++++++++ 5 files changed, 781 insertions(+) create mode 100644 Lab_1/skeleton_programs/MotorEncoderSkeleton.ino create mode 100644 Lab_1/skeleton_programs/TestEncoder.c create mode 100644 Lab_1/skeleton_programs/TestEncoder.ino create mode 100644 Lab_1/skeleton_programs/TwoSensorsSkeleton.c create mode 100644 Lab_1/skeleton_programs/TwoSensorsSkeleton.ino diff --git a/Lab_1/skeleton_programs/MotorEncoderSkeleton.ino b/Lab_1/skeleton_programs/MotorEncoderSkeleton.ino new file mode 100644 index 0000000..fbfc05e --- /dev/null +++ b/Lab_1/skeleton_programs/MotorEncoderSkeleton.ino @@ -0,0 +1,300 @@ +/* Example of driving servomotor and reading encoder signals in various ways */ + +#include /* Needed to set up counter on pin 47 */ +#include /* Needed to communicate with LS7366R (Counter Click) */ + +/* Serial input aspects are based closely upon: + http://forum.arduino.cc/index.php?topic=396450 + Example 4 - Receive a number as text and convert it to an int + Modified to read a float */ + +/* LS7366R aspects very loosely based on concepts used in controlling + the Robogaia 3-axis encoder shield though implementation is very different + https://www.robogaia.com/3-axis-encoder-conter-arduino-shield.html */ + +/* Counting using Timer 5 (external counter input) based loosely on code from + https://forum.arduino.cc/index.php?topic=59396.0 written by bubuldino */ + +/* Pins used for L298 driver */ +#define enA 13 /* PWM output, also visible as LED */ +#define in1 8 /* H bridge selection input 1 */ +#define in2 9 /* H bridge selection input 2 */ +#define minPercent -100.0 +#define maxPercent 100.0 + +/* Encoder input pins (used for state machine and interrupts) */ +#define channelA 2 +#define channelB 3 + +/* Used to to initiate SPI communication to LS7366R chip (Counter click) */ +#define chipSelectPin 10 + +/* Size of buffer used to store received characters */ +#define numChars 32 + +/* Intervals in milliseconds for user-defined timed loops */ +#define printInterval 1000 + +/* Global variables used in serial input */ +char receivedChars[numChars]; // an array to store the received data +float dataNumber = 0; // new for this version +boolean newData = false; + +/* Global variables used for motor control and encoder reading */ +double percentSpeed; +double encoderValue; + +/* Used for state machine and encoder reading */ +typedef enum states{state1=1, state2, state3, state4}; +volatile long int count = 0; +volatile long int error = 0; +volatile states state; +bool channelAState, channelBState; + +/* Used for handling overflows in Timer 5 */ +volatile long int bigLaps; + +/* Global variables used for loop timing */ +unsigned long prevMillisPrint = 0; /* stores last time values were printed */ +unsigned long prevMillisControl = 0; /* stores last time control action was updated */ + +/* Overlapping regions of memory used to convert four bytes to a long integer */ +union fourBytesToLong +{ + long result; + unsigned char bytes [4]; +}; + +void setup() +{ + Serial.begin(9600); + Serial.println("Enter PWM duty cycle as a percentage (positive for forward, negative for reverse"); + + /* Set encoder pins as input but with pullup resistors to be compatible with various encoders */ + pinMode(channelA, INPUT_PULLUP); + pinMode(channelB, INPUT_PULLUP); + + channelAState = digitalRead(channelA); + channelBState = digitalRead(channelB); + + initialiseEncoderStateMachine(); /* Find initial state based on inputs */ + + /* Set up and initialise pin used for selecting LS7366R counter: hi=inactive */ + pinMode(chipSelectPin, OUTPUT); + digitalWrite(chipSelectPin, HIGH); + + SetUpLS7366RCounter(); + + delay(100); + + /* Configure Timer 5 to count pulses on pin 47 */ + pinMode(47, INPUT_PULLUP); // set pin to input with pullup resistor + + TCCR5A = 0; // No waveform generation needed. + TCCR5B = (1<= printInterval) { + // save the last time you printed output + prevMillisPrint = currentMillis; + printLoop(); + } + + recvWithEndMarker(); + if(convertNewNumber()) + // Update value read from serial line + { + percentSpeed=dataNumber; + driveMotorPercent(percentSpeed); + } + + updateEncoderStateMachine(); +} + +void driveMotorPercent(double percentSpeed) +/* Output PWM and H bridge signals based on positive or negative duty cycle % */ +{ + percentSpeed = constrain(percentSpeed, -100, 100); + int regVal = map(percentSpeed, -100, 100, -255, 255); + analogWrite(enA, (int)abs(regVal)); + digitalWrite(in1, regVal>0); + digitalWrite(in2, !(regVal>0)); +} + +void printLoop() +/* Print count and control information */ +{ + /* Sample all counters one after the other to avoid delay-related offsets */ + long encoderCountFromLS7366R = readEncoderCountFromLS7366R(); + long encoderCountFromStateMC = count; + long stateMCerror = error; + long timer5Count = TCNT5 + bigLaps*65536; + Serial.print("Count from LS7366R = "); + Serial.print(encoderCountFromLS7366R); + Serial.print(" from state m/c = "); + Serial.print(encoderCountFromStateMC); + Serial.print(" State m/c errors = "); + Serial.print(stateMCerror); + Serial.print(" Count from LS7366R/4 = "); + Serial.print(encoderCountFromLS7366R/4); + Serial.print(" from Timer 5 = "); + Serial.print(timer5Count); + Serial.print(" Percent speed = "); + Serial.print(percentSpeed); + Serial.print("\r\n"); +} + +long readEncoderCountFromLS7366R() +/* Reads the LS7366R chip to obtain up/down count from encoder. Reads four + bytes separately then concverts them to a long integer using a union */ +{ + fourBytesToLong converter; /* Union of four bytes and a long integer */ + + digitalWrite(chipSelectPin,LOW); /* Make LS7366R active */ + + SPI.transfer(0x60); // Request count + converter.bytes[3] = SPI.transfer(0x00); /* Read highest order byte */ + converter.bytes[2] = SPI.transfer(0x00); + converter.bytes[1] = SPI.transfer(0x00); + converter.bytes[0] = SPI.transfer(0x00); /* Read lowest order byte */ + + digitalWrite(chipSelectPin,HIGH); /* Make LS7366R inactive */ + + return converter.result; +} + + +void SetUpLS7366RCounter(void) +/* Initialiseds LS7366R hardware counter on Counter Click board to read quadrature signals */ +{ + /* Control registers in LS7366R - see LS7366R datasheet for this and subsequent control words */ + unsigned char IR = 0x00, MRD0=0x00; + + // SPI initialization + SPI.begin(); + //SPI.setClockDivider(SPI_CLOCK_DIV16); // SPI at 1Mhz (on 16Mhz clock) + delay(10); + + /* Configure as free-running 4x quadrature counter */ + digitalWrite(chipSelectPin,LOW); /* Select chip and initialise transfer */ + /* Instruction register IR */ + IR |= 0x80; /* Write to register (B7=1, B6=0) */ + IR |= 0x08; /* Select register MDR0: B5=0, B4=0, B3=1 */ + SPI.transfer(IR); /* Write to instruction register */ + /* Mode register 0 */ + MRD0 |= 0x03; /* 4x quadrature count: B0=1, B1=1 */ + /* B2=B3=0: free running. B4=B5=0: disable index. */ + /* B6=0: asynchronous index. B7: Filter division factor = 1. */ + SPI.transfer(MRD0); + digitalWrite(chipSelectPin,HIGH); + + /* Clear the counter i.e. set it to zero */ + IR = 0x00; /* Clear the instructino register IR */ + digitalWrite(chipSelectPin,LOW); /* Select chip and initialise transfer */ + IR |= 0x20; /* Select CNTR: B5=1,B4=0,B3=0; CLR register: B7=0,B6=0 */ + SPI.transfer(IR); /* Write to instruction register */ + digitalWrite(chipSelectPin,HIGH); + +} + +void recvWithEndMarker() +/* Receive data from serial port finishing with "newline" character. + Based on http://forum.arduino.cc/index.php?topic=396450 Example 4 */ +{ + static byte ndx = 0; + char endMarker = '\n'; + char rc; + + if (Serial.available() > 0) { + rc = Serial.read(); + + if (rc != endMarker) { + receivedChars[ndx] = rc; + ndx++; + if (ndx >= numChars) { + ndx = numChars - 1; + } + } + else { + receivedChars[ndx] = '\0'; // terminate the string + ndx = 0; + newData = true; + } + } +} + +bool convertNewNumber() +/* Converts character string to floating point number only if there are new + data to convert, otherwise returns false */ +{ + if (newData) { + dataNumber = 0.0; // new for this version + dataNumber = atof(receivedChars); // new for this version + newData = false; + return true; + } + else + { + return false; + } +} + +void initialiseEncoderStateMachine() +/* User written code to initialise state of state machine code based on input states */ +{ + if (channelAState) + { + if(channelBState) + { + state = state3; + } + /* else.... a lot of code goes here! */ + } +} + +void updateEncoderStateMachine() +/* User written code to update state and increment count of state machine */ +{ + channelAState = digitalRead(channelA); + channelBState = digitalRead(channelB); + + switch (state) + { + case state1: + if (channelAState && !channelBState) + { + count++; + state = state2; + } + /* else if .... a lot of code goes here! */ + /* don't forget "break" at end of each case. */ + } +} + +ISR(TIMER5_OVF_vect ) +{ + //when this runs, you had 65536 pulses counted. + bigLaps++; +} + diff --git a/Lab_1/skeleton_programs/TestEncoder.c b/Lab_1/skeleton_programs/TestEncoder.c new file mode 100644 index 0000000..fc7df2b --- /dev/null +++ b/Lab_1/skeleton_programs/TestEncoder.c @@ -0,0 +1,87 @@ +#include +#include +#include +#define bool int +#define byte unsigned char +#define numChars 32 + +void initialiseEncoderStateMachine(); +void updateEncoderStateMachine(); + +// Here we are using global variables simply to be compatible with the Arduino program structure +// This is REALLY bad practice so please don't do it otherwise! +long int count = 0; +long int error = 0; + +enum states {state1=1, state2, state3, state4}; +bool channelAState=0; +bool channelBState=0; + +enum states state; +char receivedChars[numChars]="00"; // an array to store the received data + +int main() +{ + // Replicates the setup in Arduino + printf("Enter a pair of characters representing initial states of channels A and B\n"); + scanf("%s",receivedChars); + channelAState = receivedChars[0]!='0'; + channelBState = receivedChars[1]!='0'; + + initialiseEncoderStateMachine(); + printf("State %d, count %ld, error %ld\n", state, count, error); + printf("Enter a pair of characters representing channels A and B, enter 99 to end\n"); + + // Replicates the loop in Arduino + do + { + scanf("%s",receivedChars); + if (strcmp(receivedChars, "99")==0) + { + break; + } + channelAState = receivedChars[0]!='0'; + channelBState = receivedChars[1]!='0'; + updateEncoderStateMachine(); + printf("State %d, count %ld, error %ld\n", state, count, error); + + } + while(1); + return 0; +} +void initialiseEncoderStateMachine() +{ + /* If initially A is 0 and B is 0, system starts in State 1 + If initially A is 1 and B is 0, system starts in State 2 + If initially A is 1 and B is 1, system starts in State 3 + If initially A is 0 and B is 1, system starts in State 4 */ + + if (channelAState) + { + if(channelBState) + { + state = state3; + } + /* else .... lots of code goes here */ + } +} + +void updateEncoderStateMachine() +{ + switch (state) + { + case state1: + /* If A is 0 and B is 0, do nothing and stay in State 1 + If A is 1 and B is 0, add 1 to main counter and go to State 2 + If A is 0 and B is 1, subtract 1 to main counter and go to State 4 + If A is 1 and B is 1, do nothing to main counter but add 1 to error counter and go to state 3 */ + + if (channelAState && !channelBState) + { + count++; + state = state2; + } + /* else .... lots of code goes here */ + break; /* don't forget break at the end of each case! */ + } +} diff --git a/Lab_1/skeleton_programs/TestEncoder.ino b/Lab_1/skeleton_programs/TestEncoder.ino new file mode 100644 index 0000000..b08808a --- /dev/null +++ b/Lab_1/skeleton_programs/TestEncoder.ino @@ -0,0 +1,116 @@ +/* Test program for incremental encoder state machine code */ +/* Serial input aspects are based closely upon: + http://forum.arduino.cc/index.php?topic=396450 + Example 4 - Receive a number as text and convert it to an int + Modified to read a float */ + +long int count = 0; +long int error = 0; +enum states{state1=1, state2, state3, state4}; +bool channelAState; +bool channelBState; + +int state; +const byte numChars = 32; +char receivedChars[numChars]; // an array to store the received data + +boolean newData = false; + +void setup() { + Serial.begin(9600); + Serial.println("Enter initial state as a 2-digit number e.g. 01 then hit return"); + do { + recvWithEndMarker(); + } while (!newData); + channelAState = receivedChars[0]!='0'; + channelBState = receivedChars[1]!='0'; + + initialiseEncoderStateMachine(); + Serial.print(state); + Serial.print('\n'); + + Serial.println("Now keep entering state as a 2-digit number e.g. 01 then hit return."); + newData = false; +} + +void loop() { + recvWithEndMarker(); + if (newData) + { + channelAState = receivedChars[0]!='0'; + channelBState = receivedChars[1]!='0'; + updateEncoderStateMachine(); + Serial.print("State: "); + Serial.print((int)state); + Serial.print(" Count: "); + Serial.print(count); + Serial.print(" Error: "); + Serial.print(error); + Serial.write('\n'); + newData = false; + } + +} + +void recvWithEndMarker() { + static byte ndx = 0; + char endMarker = '\n'; + char rc; + + while (Serial.available() > 0 && newData == false) { + rc = Serial.read(); + + if (rc != endMarker) { + receivedChars[ndx] = rc; + ndx++; + if (ndx >= numChars) { + ndx = numChars - 1; + } + } + else { + receivedChars[ndx] = '\0'; // terminate the string + ndx = 0; + newData = true; + } + } +} + +void initialiseEncoderStateMachine() +{ + /* If initially A is 0 and B is 0, system starts in State 1 + If initially A is 1 and B is 0, system starts in State 2 + If initially A is 1 and B is 1, system starts in State 3 + If initially A is 0 and B is 1, system starts in State 4 */ + + if (channelAState) + { + if(channelBState) + { + state = state3; + } + /* else .... lots of code goes here */ + } +} + +void updateEncoderStateMachine() +{ + switch (state) + { + case state1: + /* If A is 0 and B is 0, do nothing and stay in State 1 + If A is 1 and B is 0, add 1 to main counter and go to State 2 + If A is 0 and B is 1, subtract 1 to main counter and go to State 4 + If A is 1 and B is 1, do nothing to main counter but add 1 to error counter and go to state 3 */ + + if (channelAState && !channelBState) + { + count++; + state = state2; + } + /* else .... lots of code goes here */ + break; /* don't forget break at the end of each case! */ + /* other cases follow */ + } +} + + diff --git a/Lab_1/skeleton_programs/TwoSensorsSkeleton.c b/Lab_1/skeleton_programs/TwoSensorsSkeleton.c new file mode 100644 index 0000000..02585c1 --- /dev/null +++ b/Lab_1/skeleton_programs/TwoSensorsSkeleton.c @@ -0,0 +1,135 @@ +#include +#include + +// Forward TC function +float NISTdegCtoMilliVoltsKtype(float tempDegC); // returns EMF in millivolts + +// Inverse TC function +float NISTmilliVoltsToDegCKtype(float tcEMFmV); // returns temp in degC assuming 0 degC cold jcn + +int main() +{ + // Define VRef + + // Define Thermistor constants + + // User input for pins A0 and A1 + + // Calculate thermistor temperature in degrees C ( Part b, i,ii,iii & v) + + // Calculate thermocouple temperature in degrees C ( Part c, i - iv) + + // Output results + printf("Thermistor temperature (deg C): %f \n", *******); + printf("Thermocouple temperature with CJC (deg C): %f \n", ******); + + return 0; +} + +/* Write a function here to convert ADC value to voltages. (Part a, equation 1) +Call it from the main() function above */ + +/* Write a function to convert degrees K to degrees C (Part b, (iv)) +Call it from the main() function above */ + +/* returns EMF in millivolts */ +float NISTdegCtoMilliVoltsKtype(float tempDegC) +{ + int i; + float milliVolts = 0; + if(tempDegC >= -170 && tempDegC < 0) + { + const float coeffs[11] = + { + 0.000000000000E+00, + 0.394501280250E-01, + 0.236223735980E-04, + -0.328589067840E-06, + -0.499048287770E-08, + -0.675090591730E-10, + -0.574103274280E-12, + -0.310888728940E-14, + -0.104516093650E-16, + -0.198892668780E-19, + -0.163226974860E-22 + }; + for (i=0; i<=10; i++) + { + milliVolts += coeffs[i] * pow(tempDegC,i); + } + } + else if(tempDegC >= 0 && tempDegC <= 1372) + { + const float coeffs[10] = + { + -0.176004136860E-01, + 0.389212049750E-01, + 0.185587700320E-04, + -0.994575928740E-07, + 0.318409457190E-09, + -0.560728448890E-12, + 0.560750590590E-15, + -0.320207200030E-18, + 0.971511471520E-22, + -0.121047212750E-25 + }; + const float a0 = 0.118597600000E+00; + const float a1 = -0.118343200000E-03; + const float a2 = 0.126968600000E+03; + + for (i=0; i<=9; i++) + { + milliVolts += coeffs[i] * pow(tempDegC,i); + } + + milliVolts += a0*exp(a1*(tempDegC - a2)*(tempDegC - a2)); + } + else + { + milliVolts = 99E99; + } + return milliVolts; +} + +// returns temperature in deg C. +float NISTmilliVoltsToDegCKtype(float tcEMFmV) +{ + + int i, j; + float tempDegC = 0; + const float coeffs[11][3] = + { + {0.0000000E+00, 0.000000E+00, -1.318058E+02}, + {2.5173462E+01, 2.508355E+01, 4.830222E+01}, + {-1.1662878E+00, 7.860106E-02, -1.646031E+00}, + {-1.0833638E+00, -2.503131E-01, 5.464731E-02}, + {-8.9773540E-01, 8.315270E-02, -9.650715E-04}, + {-3.7342377E-01, -1.228034E-02, 8.802193E-06}, + {-8.6632643E-02, 9.804036E-04, -3.110810E-08}, + {-1.0450598E-02, -4.413030E-05, 0.000000E+00}, + {-5.1920577E-04, 1.057734E-06, 0.000000E+00}, + {0.0000000E+00, -1.052755E-08, 0.000000E+00} + }; + if(tcEMFmV >=-5.891 && tcEMFmV <=0 ) + { + j=0; + } + else if (tcEMFmV > 0 && tcEMFmV <=20.644 ) + { + j=1; + } + else if (tcEMFmV > 20.644 && tcEMFmV <=54.886 ) + { + j=2; + } + else + { + return 99E9; + } + + for (i=0; i<=9; i++) + { + tempDegC += coeffs[i][j] * pow(tcEMFmV,i); + } + return tempDegC; +} \ No newline at end of file diff --git a/Lab_1/skeleton_programs/TwoSensorsSkeleton.ino b/Lab_1/skeleton_programs/TwoSensorsSkeleton.ino new file mode 100644 index 0000000..564b93b --- /dev/null +++ b/Lab_1/skeleton_programs/TwoSensorsSkeleton.ino @@ -0,0 +1,143 @@ +/* Test program for reading of thermistor, thermocouple and LVDT. + K-type thermocouple functions written by Arthur Jones using + official NIST polynomial data from + https://srdata.nist.gov/its90/download/type_k.tab */ + +#include /* needed for exp() and pow() */ + +/* It is good practice to define things like pins used at the start + so that you avoid hard-coded values (magic numbers) in code */ +#define TCpin A0 +#define ThermistorPin A1 + +/* Similarly, define any constant values e.g. Vref, B, R0 here to avoid + need for "magic numbers" in code */ + +void setup() +{ + Serial.begin(9600); +} + +void loop() +{ + /* Put your code here to read ADCs and convert ADC voltages to + temperatures */ + + /* Display results. Don't use printf or formatting etc., they don't work on the Arduino. Just use + the serial print statements given here, inserting your own code as needed */ + Serial.print("Thermistor temperature (deg C): "); + Serial.println(.........); // Replace ... with your code, it won't compile until you do. + Serial.print(" Thermocouple temperature with CJC (deg C): "); + Serial.println(.........); // Replace ... with your code, it won't compile until you do. + Serial.println("\n"); + delay(1000); +} + +/* Write a function to convert ADC value to + voltage: put it here and use it in your code above*/ + +/* Write a function to convert degrees K to degrees C +Call it from the main() function above */ + +/* Under no circumstances change any of the following code, it is fine as it is */ +float NISTdegCtoMilliVoltsKtype(float tempDegC) +/* returns EMF in millivolts */ +{ + int i; + float milliVolts = 0; + if(tempDegC >= -170 && tempDegC < 0) + { + const float coeffs[11] = + { + 0.000000000000E+00, + 0.394501280250E-01, + 0.236223735980E-04, + -0.328589067840E-06, + -0.499048287770E-08, + -0.675090591730E-10, + -0.574103274280E-12, + -0.310888728940E-14, + -0.104516093650E-16, + -0.198892668780E-19, + -0.163226974860E-22 + }; + for (i=0; i<=10; i++) + { + milliVolts += coeffs[i] * pow(tempDegC,i); + } + } + else if(tempDegC >= 0 && tempDegC <= 1372) + { + const float coeffs[10] = + { + -0.176004136860E-01, + 0.389212049750E-01, + 0.185587700320E-04, + -0.994575928740E-07, + 0.318409457190E-09, + -0.560728448890E-12, + 0.560750590590E-15, + -0.320207200030E-18, + 0.971511471520E-22, + -0.121047212750E-25 + }; + const float a0 = 0.118597600000E+00; + const float a1 = -0.118343200000E-03; + const float a2 = 0.126968600000E+03; + + for (i=0; i<=9; i++) + { + milliVolts += coeffs[i] * pow(tempDegC,i); + } + + milliVolts += a0*exp(a1*(tempDegC - a2)*(tempDegC - a2)); + } + else + { + milliVolts = 99E9; + } + return milliVolts; +} + +float NISTmilliVoltsToDegCKtype(float tcEMFmV) +// returns temperature in deg C. +{ + + int i, j; + float tempDegC = 0; + const float coeffs[11][3] = + { + {0.0000000E+00, 0.000000E+00, -1.318058E+02}, + {2.5173462E+01, 2.508355E+01, 4.830222E+01}, + {-1.1662878E+00, 7.860106E-02, -1.646031E+00}, + {-1.0833638E+00, -2.503131E-01, 5.464731E-02}, + {-8.9773540E-01, 8.315270E-02, -9.650715E-04}, + {-3.7342377E-01, -1.228034E-02, 8.802193E-06}, + {-8.6632643E-02, 9.804036E-04, -3.110810E-08}, + {-1.0450598E-02, -4.413030E-05, 0.000000E+00}, + {-5.1920577E-04, 1.057734E-06, 0.000000E+00}, + {0.0000000E+00, -1.052755E-08, 0.000000E+00} + }; + if(tcEMFmV >=-5.891 && tcEMFmV <=0 ) + { + j=0; + } + else if (tcEMFmV > 0 && tcEMFmV <=20.644 ) + { + j=1; + } + else if (tcEMFmV > 20.644 && tcEMFmV <=54.886 ) + { + j=2; + } + else + { + return 99E9; + } + + for (i=0; i<=9; i++) + { + tempDegC += coeffs[i][j] * pow(tcEMFmV,i); + } + return tempDegC; +}