Scales.c

code format="c" //Includes
 * 1) include 
 * 2) include 
 * 3) include "define.h"
 * 4) include 
 * 5) include 
 * 6) include 
 * 7) include "stringconst.h"

//Global Variables

char recvbuf[I_BUFSIZE]; //All received commands are saved here (both serial and keypress), to be processed later char transbuf[O_BUFSIZE]; //Output to the serial port is buffered here for the serial_transmit to send char dispbuf[O_BUFSIZE]; //Output to the LCD screen is buffered here for the LCD to display

volatile char *recvbuf_iPtr = &recvbuf[0];//insertion pointer volatile char *recvbuf_ePtr = &recvbuf[0];//removal pointer volatile char transbufi = 0;            // buffer insertion index from string volatile char transbufr = 0;            // buffer removal index to transmit shift register

unsigned int ADSamples[VBUFSIZE]; //Buffer of the N most recent voltage samples (in millivolts)

//massConvertLookup[i][j] is converting unit i to unit j float massConvertLookup[][2] = {{1.0, 0.0352739619}, {28.349523125, 1.0}}; /* y = mx + b, where y is the result mass in unit i, x is the voltage, m is the gradient voltageToMass[i][0], b is the y-intercept voltageToMass[i][1]. */ float voltageToMassEquation[][2] = {{60.98824, -53.51852}, {1700.0, -1.0}}; //The ounce y-intercept value is a bit inaccurate, but it's round here or later so I don't think it matters in the end

unsigned int tareVoltage = 0;   //The tare, as a pure voltage unsigned int tareMass = 0; char voltageH = 0;   //The value from ADRESH char voltageL = 0; //The value from ADRESL

unsigned int sampleInterval = 12500;   //The increment count for the A/D sample rate. = 125 * period in ms. char newADValue = FALSE;   //Is there a new ADValue? unsigned char unit = GRAMS;   //The unit currently being used int mass = 0;   //The current mass, in the current unit int varMass = 0; //Variance in mass; int rawMass = 0; // mass for raw weight mode

//Strings unsigned char suffixGrams[] = " grams\r\n"; //the compiler treats initialised strings as const unsigned char suffixOunces[] = " ounces\r\n"; //If anyone works out a more elegant way of doing the suffixes, do so unsigned char unitError[] = " <>\r\n"; //An error message

unsigned char romstring[64]; //Space to dump strings when fetching from ROM (stops the memory being eaten by other bits of the program)

// Keypad Lookup Table

const char keypad_lookuptable[] = {'1','2','3','n','4','5','6','n','7','8','9','n','*','0','#'};

extern unsigned char LCDWantsToSend;

//Prototypes void low_ISR(void); void high_ISR(void); extern void setup(void); void transmit(unsigned char* message); void serial_receive(void); void keypad_receive(void);

void store_sample(unsigned int voltage); unsigned int getVoltage(void);

unsigned int getAverage(unsigned int* set, int size); unsigned int getDeviation(unsigned int* set, int size, unsigned int mean); int getMass(unsigned int voltage, char massUnit, char applyTare, char forDev); void int_to_string(int rawNumber, unsigned char* string);

void transchar(void); unsigned char* get_rom_string(const rom unsigned char* txPtr);

float convert_unit(float convertMass, char inputUnit, char outputUnit); void calibrate(unsigned int zeroVoltage, unsigned int secondVoltage, int secondMass, char massUnit); void setTare(void); void printInstructions(char mode, char func); void generateLine1(char key, unsigned char* function); void generateLine2(char key, unsigned char* function);

extern void checkState (void);

extern unsigned char isBusy(void); extern void sendNext(void);


 * 1) pragma config WDT = OFF //Stops the watchdog timer from restting the PIC

//Interrupt Jump Vectors
 * 1) pragma code high_vector = 0x0008

void high_interrupt(void) {   _asm goto high_ISR _endasm }


 * 1) pragma code low_vector = 0x0018

void low_interrupt(void) {   _asm goto low_ISR _endasm }

/*The actual code begins here!*/
 * 1) pragma code

/** * High priority interrupt service routine (ISR). * Central functions triggered here are inputs by Serial Receive and Keypad. * /

void high_ISR(void) {   char rcdebug = 0; static unsigned char LVDstring[] = "LOW VOLTAGE DETECTED"; /*All interrupts will check the mask (PIE) before checking the request (PIR)*/ //Disable ALL interrupts INTCONbits.GIEH = 0;

//Check for receive int if(PIE1bits.RCIE == 1 && PIR1bits.RCIF == 1) {       serial_receive; }

//Check for external keypress if(INTCON3bits.INT1IF ==1) {       keypad_receive; INTCON3bits.INT1IF = 0; }

if(PIE2bits.LVDIE == 1 && PIR2bits.LVDIF == 1) {       transmit(LVDstring); }

INTCONbits.GIEH = 1; }

/** * Low priority interrupt service routine (ISR). * Central functions triggered here are Serial Transmit and receiving A/D value. * / void low_ISR(void) {   /*All interrupts will check the mask (PIE) before checking the request (PIR)*/ //Disable ONLY LOW interrupts INTCONbits.GIEL = 0;

//Check for transmit empty int if(PIE1bits.TXIE == 1 && PIR1bits.TXIF == 1) {       transchar; }

if(PIR1bits.ADIF == 1 && PIE1bits.ADIE == 1) //If A/D interrupt enabled and triggered {       voltageH = ADRESH; voltageL = ADRESL; newADValue = TRUE; PIR1bits.ADIF = 0; }

INTCONbits.GIEL = 1; }

/** * Main function loop. * Each loop it will call the checkState function to determine the next action to be taken by the program. * It will also test to see if the A/D has collected a new voltage value. * If so, it will store this value into the A/D buffer, and increment the buffer counter j. * When j reaches the size of the buffer, the system will call getMass and getDeviation to calculate * the mass and variance values based on the current A/D buffer. * The main loop also sends characters to the LCD if it is ready and there is data waiting to be sent. * /

void main(void) {   unsigned int voltage = 0; unsigned int aveVoltage = 0; unsigned int varianceVolt = 0; int varianceMass = 0; unsigned char i = 0, j = 0;

//Clear transmit buffer at start of program for(i = 0; i < O_BUFSIZE; i++) transbuf[i] = 0;

setup; //Initialise all registers

while(1) {

if(newADValue) {           newADValue = FALSE; voltage = getVoltage; store_sample(voltage);

j++; if (j == VBUFSIZE) {               j = 0; aveVoltage = getAverage(ADSamples, VBUFSIZE); mass = getMass(aveVoltage, unit, TRUE, FALSE); //apply tare

varianceVolt = getDeviation(ADSamples, VBUFSIZE, aveVoltage); varMass = getMass(varianceVolt, unit, FALSE, TRUE); }       }

checkState; if(!isBusy && LCDWantsToSend) {       sendNext; }   }

}

/** * Gets the average of an array of values of pre-defined size. * The function adds all the values in the array then divides by the size. * @param set The array of unsigned integers to average. It need not be null-terminated in any way. * @param size The number of integers in the array. * @return mean Returns the mean value of the array as an unsigned integer. */

unsigned int getAverage(unsigned int* set, int size) {   int i = 0; long int total = 0; int mean = 0; for(i = 0; i < size; i++) {       total += set[i]; }

mean = total/size;

return mean; }

/** * Gets the variance of an array of values of pre-defined size, with a pre-calculated mean. * The function used is a simple finite-population variance equation: * variance = sqrt( 1/size * sum of (xi - mean)^2 ) for each sample xi * @param set The array of unsigned integers to calculate variance from. It need not be null-terminated in any way. * @param size The number of integers in the array. * @param mean The average value of the numbers in the array. * @return variance Returns the variance of the mumbers in the array as an unsigned integer. */

unsigned int getVariance(unsigned int* set, int size, unsigned int mean) {   // variance = sqrt( 1/size * sum of (xi - mean)^2 ) int i = 0; float totalf = 0.0, incf = 0.0, sizef = 0.0, meanf = 0.0, valf = 0.0, subf = 0.0, subf2 = 0.0;

unsigned int variance = 0;

meanf = (float)mean; sizef = (float)size;

for(i = 0; i < size; i++) {       valf = (float)set[i]; subf = valf - meanf; subf2 = subf * subf; incf = subf2/sizef; if (incf < 9999.0) // Exclude values that are clearly incorrect totalf += incf; }

variance = (unsigned int)totalf;

return variance; }

/** * Stores values from the A/D converter in a small circular buffer (VINSERT = 8 values long) * No processing is done. It is up to the getMean and getVariance functions to interperet the data * Values are stored in the global array "ADSamples[]". * @param voltage The value to be stored. The oldest values will be dropped to make way for new ones. * @return void. */ void store_sample(unsigned int voltage) {   static char vinsert = 0;

ADSamples[vinsert] = voltage;

/*advance the position in the buffer, loop around at the end*/ vinsert++; if(vinsert >= VBUFSIZE) {     vinsert = 0; } }

/** * A function to convert a signed integer to a string of ascii numerical characters. * By progressively dividing the integer passed by 10s, 100s, 1000s etc, the value of * each digit in the number can be determined. This is then used as an index to look * up the value of the appropriate character. Each character is placed into the output * string in the correct location so that it represents a decimal number. * This function also checks for negative values, and affixes a '-' sign if appropriate * @param rawNumber A signed integer less than +- ten thousand.. * @param string The return string, which is modified by this function. It will be overwritten and null terminated. * @return void. */ void int_to_string(int rawNumber, unsigned char* string) {   char digits[11] = "0123456789"; //the 11th character is /0 unsigned int thousands = 0, hundreds = 0, tens = 0, ones = 0; int t1 = 0, t2 = 0, t3 = 0; int number;

if(rawNumber < 0) {       number = 0 - rawNumber; string[0] = '-'; }   else {       number = (int)rawNumber; string[0] = ' '; }

thousands = number / 1000; hundreds = (number - 1000*thousands)/100; tens = (number - 1000*thousands - 100*hundreds)/10; ones = (number - 1000*thousands - 100*hundreds - 10*tens);

string[1] = digits[thousands]; string[2] = digits[hundreds]; string[3] = digits[tens]; string[4] = digits[ones]; if(thousands == 0) {       string[1] = ' '; if(hundreds == 0) {           string[2] = ' '; if(tens == 0) string[3] = ' '; }   }    string[5] = '\0'; }

/** * Called when the serial receive interrupt is triggered, this will read the RCREG * and then copy the character onto the input stream (recvbuf). This function will * modify the insertion and extraction pointers recvbuf_iPtr and recvbuf_ePtr. * @post The character from RCREG will be copied to recvbuf, and RCREG is cleared */ void serial_receive(void) {   PIR1bits.RCIF = 0;    // Clear RC flag

if (RCREG == 0x0D | RCREG == 0x0A) {   // Place special handling for 'enter' here. }

*recvbuf_iPtr = RCREG;   // Place recieved char into insertion location

recvbuf_iPtr = recvbuf_iPtr++;               // Increment insertion pointer. if (recvbuf_iPtr > recvbuf + I_BUFSIZE) {       recvbuf_iPtr = &recvbuf[0]; } }

/** * Converts the input receive in PORTB from the MM74C922 encoder chip * into a decimal number by right shifting and bit masking * Uses #define LOW_NIBBLE for bit masking * Uses the const char keypad_lookup table to receieve the value of * which button entered by the user * Usage example: * User presses the '1' key = b00000000( in binary) = 0( in decimal)= '1' in lookup table * @param temp Temporary value used to receieve PORTB values * @param key_pressed A string value representing the button pressed * @param recvbuf Used to store the string value * @return void */

void keypad_receive(void) {   char temp; char key_pressed;

temp = (PORTB>>2)&LOW_NIBBLE;                // 2x Right shifting and bit masking key_pressed = keypad_lookuptable[temp]       // Getting string valur from table *recvbuf_iPtr = key_pressed; recvbuf_iPtr = recvbuf_iPtr++;               // Increment insertion pointer. if (recvbuf_iPtr > recvbuf + I_BUFSIZE) {       recvbuf_iPtr = &recvbuf[0]; }

}

/** * Some strings are stored in the ROM. This function * will fetch the string from ROM, place it in RAM, * and return a pointer to the start of the RAM string. * @param txPtr This is a pointer to the address of the start of the string in ROM. Do not pass RAM strings to this! * @return romstring romstring is a block of (RAM) memory set aside to dump ROM strings into when they are being fetched. */ unsigned char* get_rom_string(const rom unsigned char* txPtr) {   strcpypgm2ram(romstring,(far rom unsigned char*)txPtr); return romstring; }

/** * Copies a string into the transmit buffer, for later sending. * @param trans_string The null-terminated string of ASCII characters for transmission. */

void transmit(unsigned char* trans_string) // Transmit function, writes string into buffer and starts sending {   char i = 0; Delay1KTCYx(10); while(trans_string[i] != '\0')       // until null termination of string reached {       transbuf[transbufi] = trans_string[i];        // copy string into transmit buffer transbufi++; if(transbufi >= O_BUFSIZE)// catch insertion index overflow {           transbufi = 0; }       i++; }   PIE1bits.TXIE = 1;                 // enable transmit interrupt //Because the transmit interrupt is enabled, the output string will start being sent immediately }

/** * Copies a character into the transmit shift register for transmission. * There are no inputs as it accesses the global transmit buffer. */

void transchar(void) // Transmit function, sends next char to TXREG as long as transmission buffer has more material to write {   TXREG = transbuf[transbufr];         // load character into transmit shift register

transbufr++;               // increment removal index if(transbufr >= O_BUFSIZE) {       transbufr = 0;         // catch removal index overflow }

if (transbufr == transbufi)         // if removal index catches insertion index, end of transmission {       PIE1bits.TXIE = 0;         // disable transmit interrupt } }

/** * Clears the HyperTerminal screen by transmitting the screen feed control character. */ void clearScreen(void) {   transmit(get_rom_string(_cls)); }

/** * This is a very simple function that will make sure only allowable * characters get passed to the state machine. Only the chars representing: * WEIGH, COUNT, TARE, CALIBRATE, STATS, RAW and SAMPLES are valid. * @param iChar A character which is to be checked for validity. * @return oChar Valid characters are passed through. Invalid characters return 0. unsigned char parseFunc(unsigned char iChar) {   if(iChar == WEIGH|iChar == COUNT|iChar == TARE|iChar == CALIBRATE|iChar == STATS|iChar == RAW|iChar == SAMPLES) {       return iChar; }   else {       return 0; } }

/** * Similar to parseFunc, but for the active mode. See parseFunc documentation also. * Ensures only characters valid for LOCAL, REMOTE or FACTORY modes are passed to the state machine. * @param iChar A character which is to be checked for validity. * @return oChar Valid characters are passed through. Invalid characters return 0. */ unsigned char parseMode(unsigned char iChar) {   if(iChar == LOCAL|iChar == REMOTE|iChar == FACTORY) {       return iChar; }   else {       return 0; } }

/** * Prints the user menu to HyperTerminal using serial transmit. * The menu changes depending on the active mode (USER vs. FACTORY) and function, since different options become available. * @param mode Either LOCAL or REMOTE, which will indicate where the instructions should be streamed to * @param func The function for which the instructions should be displayed. */

void printInstructions(char mode, char func) {   unsigned char asterisks[] = "*****";

transmit(get_rom_string(_outro)); transmit(asterisks); transmit(get_rom_string(_outro));

transmit(get_rom_string(_intro)); switch (mode) {       case LOCAL: transmit(get_rom_string(_ul)); break;

case REMOTE: transmit(get_rom_string(_ur)); break;

case FACTORY: transmit(get_rom_string(_fy)); break;

}   transmit(get_rom_string(_cs)); switch (func) {       case WEIGH: transmit(get_rom_string(_wh)); break;

case COUNT: transmit(get_rom_string(_ct)); break;

case STATS: transmit(get_rom_string(_ss)); break;

case RAW: transmit(get_rom_string(_rw)); break;

case SAMPLES: transmit(get_rom_string(_sr));; break;

case CALIBRATE: transmit(get_rom_string(_ce)); break;

}   transmit(get_rom_string(_fs));

transmit(get_rom_string(_outro));

switch (mode) {       case LOCAL: generateLine1(REMOTE,_ur); break;

case REMOTE: generateLine1(LOCAL,_ul); break; }

if(func != WEIGH) {       generateLine1(WEIGH,_wh); }

if(func != COUNT) {       generateLine1(COUNT,_ct); }

transmit(get_rom_string(_outro)); generateLine2(TARE,_te); generateLine2(CHANGEUNIT,_ts);

if (mode == FACTORY) {       transmit(get_rom_string(_outro)); transmit(get_rom_string(_fac1)); generateLine1(STATS,_ss); generateLine1(RAW,_rw); generateLine1(SAMPLES,_sr); generateLine1(CALIBRATE,_ce); transmit(get_rom_string(_outro)); }

transmit(asterisks); transmit(get_rom_string(_outro)); }

/** * Generates a menu instruction line for changing the active function. The line is transmitted via serial. * @param key A character corresponding to the key to be pressed to enter the desired function. * @param function A string with the name of the function referred to, such as "Count" or "Weigh". * Constant text elements "Press __ to enter __ mode." are copied from program memory with get_rom_string. * Usage example: generateLine1('w', "Weigh") will return "Press w to enter Weigh mode." */ void generateLine1(char key, unsigned char* function) {   unsigned char keystr[2]; // assemble null-terminated string from character passed to function keystr[0] = key; keystr[1] = '\0';

transmit(get_rom_string(_11)); transmit(keystr); transmit(get_rom_string(_12)); transmit(get_rom_string(_13)); transmit(get_rom_string(function)); transmit(get_rom_string(_14)); transmit(get_rom_string(_outro)); }

/** * Generates a menu instruction line for performing supplementary tasks (tare, change unit type). The line is transmitted via serial. * @param key A character corresponding to the key to be pressed to perform the task. * @param function A string with the name of the task referred to, such as "Toggle Units" or "Tare". * Constant text elements "Press __ to __." are copied from program memory with get_rom_string. * Usage example: generateLine2('u', "Toggle Units") will return "Press u to Toggle Units." */ void generateLine2(char key, unsigned char* function) {   unsigned char keystr[2]; // assemble null-terminated string from character passed to function keystr[0] = key; keystr[1] = '\0';

transmit(get_rom_string(_11)); transmit(keystr); transmit(get_rom_string(_12)); transmit(get_rom_string(function)); transmit(get_rom_string(_fs)); transmit(get_rom_string(_outro)); }

/** * Returns an unsigned 16 bit integer, containing 10 bits of A/D result. The 6 most * significant bits will be 0. * Precondition: The A/D Converter is on and initialised. * Precondition: The special event trigger has enabled the A/D converter, and then an interrupt * has moved the ADRESH and ADRESL values to voltageH and voltageL, respectively. * @return 10 bit A/D result */ unsigned int getVoltage(void) {   unsigned int result = 0; unsigned int lhold = 0;

result = voltageH * 256; lhold = voltageL & 0x00FF; result = result + lhold;

return result;                   //Result is 10 bits } /** * Gets the current mass, as ounces or grams, as specified by the parameters. * It uses either the predefined y-intercept and gradient, or one set by using * the calibrate function. * @param voltage The voltage corresponding to the desired mass. * @param massUnit The unit of mass to use, either GRAMS or OUNCES (#defined above). * @param applyTare A boolean value. A value evaluating to true will mean that the mass is adjusted for tare. * @param forVar A boolean value. A value evaluating to true will mean that no offset is applied, since this is a variance calculation. */ int getMass(unsigned int voltage, char massUnit, char applyTare, char forVar) {   //y = mx + b to get base result float mx = 0.0; float b = 0.0; int resultmass = 0;

mx = voltage * 64.0 / voltageToMassEquation[massUnit][0]; if (forDev == TRUE) // this is a standard deviation conversion and no offset is required b = 0.0; else if (forDev == FALSE) b = voltageToMassEquation[massUnit][1];

//mass = voltageToMassEquation[massUnit][0] * voltage + voltageToMassEquation[massUnit][1]; resultmass = (int)(mx + 0.5) + (int)(b + 0.5);

//   rawMass = resultmass;

if(applyTare) resultmass = resultmass - tareMass;

return resultmass; }

/** * Sets the tare value. * Sets the global value tareMass to the current value of global mass. * This is subtracted within the getMass function if a tared value is requested by the user. */ void setTare(void) {   tareMass += mass; // May give error if unit type switched after performing tare, but can be fixed by taring again. }

/** * Converts a mass from one unit to another. * Uses the #define conventions of masses, that is, GRAMS = 0, * and OUNCES = 1. * Usage example: * ounceValue = convertUnit(gramValue, GRAMS, OUNCES); * @param convertMass The input mass * @param inputUnit A numerical char representing the input mass type * @param outputUnit A numerical char represeting the output mass type * @return float The converted mass. */ float convert_unit(float convertMass, char inputUnit, char outputUnit) {   convertMass = convertMass * massConvertLookup[inputUnit][outputUnit]; return convertMass; }

/** * Use two points, and simple maths, to work out a calibration line. * If the input values are invalid, for any reason, the function will end. * @param zeroVoltage The voltage at a zero mass point * @param secondVoltage The voltage at some positive non-zero mass point. * @param secondMass The positive non-zero mass point. The value is in either grams or ounces * @param massUnit Specifies what unit secondMass is in, either GRAMS or OUNCES. */ void calibrate(unsigned int zeroVoltage, unsigned int secondVoltage, int secondMass, char massUnit) {   //Algorithm: //m = v * 64 / x - y   //For v1, 0 and v2, m:    //y = v1 * 64 / x    //y = v2 * 64 / x - m    //Thus: //v1 * 64 / x = v2 * 64 / x - m   //Therefore: //x = 64 * (v2 - v1) / m   //y = - v1 * m / (v2 - v1) //Also: //x(otherUnit) = convert_unit(x, otherUnit, massUnit);   //Note order. Equivalent to division //y(otherUnit) = convert_unit(y, massUnit, otherUnit);

float x = 0.0; float y = 0.0; char otherMassUnit = 1 - massUnit;   //0 or 1 float voltageDiff = secondVoltage - zeroVoltage; if(voltageDiff == 0)   //Error handling return; x = 64 * voltageDiff / secondMass; y = zeroVoltage * secondMass / voltageDiff; y = - y;

voltageToMassEquation[massUnit][0] = x;   voltageToMassEquation[massUnit][1] = y;

voltageToMassEquation[otherMassUnit][0] = convert_unit(x, otherMassUnit, massUnit); voltageToMassEquation[otherMassUnit][1] = convert_unit(y, massUnit, otherMassUnit); }

code