/************************************************************************ * * Sketch Name: feb_04_homework.ino * * Development Environment: Arduino 1.6.5 * * Description: Arduino training homework set by Brian G0UKB * for the Itchen Valley Amateur Ratio Club * on 4th Feb 2016 - * * Build a "Voltage Protector" to the following specification: * * (1) Display a "Welcome" screen for 5 seconds. * (2) Read and display the ADC value on Line 1 of the LCD * Display the corresponding analogue voltage on Line 2 of the LCD * (3) Illuminate Amber LEDs if voltage > 4 Volts or < 2 Volts * (4) Illuminate Red LEDs if voltage > 4.5 Volts or < 1.5 Volts * (5) Illuminate Green LED if voltage between 2 and 4 Volts * * (6) Option 1 - Red LEDs should flash * (7) Option 2 - Red LED latches on if voltage ever goes over or under range * * Additions to the original specification implemented: * (a) Perform a "Lamp Test" at the end of the "welcome" LCD screen * (b) If the red LED(s) are latched on, pressing a push button * clears the latched LED(s) if the voltage is now in the normal range. * * All using the Arduino UNO hardware and only the 'C' features * and functions learned so far: * * const int TEST_LED = 13; * pinMode(TEST_LED, OUTPUT); * digitalWrite(TEST_LED, HIGH); * digitalWrite(TEST_LED, LOW); * Serial.begin(9600); * Serial.print("Something"); * Serial.println(Semething Else"); * analogRead(A0); * analogWrite(9,0); * analogWrite(9,127); * analogWrite(9,255); * map(); * #include * LiquidCrystal_I2C lcd(); * lcd.begin(16,2); * lcd.print("Something"); * lcd.setCursor(0,1); * if (something) * { * // do some action * } * else * { * // do some other action * } * pinMode(13,INPUT); * pinMode(13,INPUT_PULLUP); * digitalRead(13); * * * * ,-----------------, * GND -- IIC LCD Module GND --| | * Arduino UNO Rev 3 | IIC LCD Module | * .-------------------------------------------, +5V -- IIC LCD Module VCC --| | * | | | 2 Lines | * | (A5) SCL |(SCL)----------- IIC LCD Module SCL --| x | * | | | 16 Characters | * | (A4) SDA |(SDA)----------- IIC LCD Module SDA --| | * | | '-----------------' * | AREF|(AREF)-- * | | * --(Unused)| GND |(GND)-- * | | * --(IOREF)| 5V [on board LED] (SCK) digital_pin_13 |(13)--- * | | * --(Reset)| RESET (MISO) digital_pin_12 |(12)---## 1K ##-----|>|---- GND (RED_OVER_VOLTAGE_LED) * | | * --(3.3V)| 3.3V (MOSI/PWM) digital_pin_11 |(11)---## 1K ##-----|>|---- GND (YELLOW_OVER_VOLTAGE_LED) * | | * --(5V)| 5V (SS/PWM) digital_pin_10 |(10)---## 1K ##-----|>|---- GND (GREEN_LED) * | | * --(GND)| 0V (PWM) digital_pin_9 |(9)----## 1K ##-----|>|---- GND (YELLOW_UNDER_VOLTAGE_LED) * | | * --(GND)| 0V digital_pin_8 |(8)----## 1K ##-----|>|---- GND (RED_UNDER_VOLTAGE_LED) * | | * --(Vin)| 9V->12V | * | digital_pin_7 |(7)-- * | | * | (PWM) digital_pin_6 |(6)-- * | | * voltage_under_test --(A0)| Analogue in (PWM) digital_pin_5 |(5)-- * | | * --(A1)| Analogue in digital_pin_4 |(4)-- * | | * --(A2)| Analogue in (PWM) digital_pin_3 |(3)-- * | | ____ * --(A3)| Analogue in (INT_0) digital_pin_2 |(2)------------------o o-------, * | | PUSH_BUTTON | * --(A4)| Analogue in (SDA) (TX) digital_pin_1 |(1)-- | * | | GND * --(A5)| Analogue in (SCL) (RX) digital_pin_0 |(0)-- * | | * '-------------------------------------------' * * +5V -------------------, * | * | * # * # * 10K <----------- voltage_under_test * # * # * # * | * GND -------------------' * * * * Revision History: 1.0 - 05 Feb 2016 - S.Q. - First Draft * 1.1 - 12 Feb 2016 - S.Q. - Fixed some typos and added * a few comments * * Original Author: Sean Quinn (M0MMR) * ************************************************************************/ /************************************************************************ * * Libraries * ************************************************************************/ #include #include /************************************************************************ * * Constants * ************************************************************************/ const int adc_input_pin = A0; const int red_over_voltage_led_pin = 12; const int yellow_over_voltage_led_pin = 11; const int green_led_pin = 10; const int yellow_under_voltage_led_pin = 9; const int red_under_voltage_led_pin = 8; const int push_button_pin = 2; // ,------------,-------------------------------------------------------------------, // | | =========================== LED ================================= | // | Voltage | RED UNDER V | YELLOW UNDER V | GREEN | YELLOW OVER V | RED OVER V | // |------------|-------------|----------------|-------|---------------|------------| // | > 4.5 | OFF | OFF | OFF | OFF | ON * | // | 4.0 to 4.5 | OFF | OFF | OFF | ON * | OFF | // | 2.0 to 4.0 | OFF | OFF | ON *| OFF | OFF | // | 1.5 to 2.0 | OFF | ON * | OFF | OFF | OFF | // | < 1.5 | ON * | OFF | OFF | OFF | OFF | // '------------'-------------'----------------'-------'---------------'------------' const int over_voltage_red_limit = 4500; // 4.5V = 4500 mV Note: See later in the program const int over_voltage_yellow_limit = 4000; // 4.0V = 4000 mV why everything is in const int normal_voltage_high_limit = 4000; // 4.0V = 4000 mV millivolts const int normal_voltage_low_limit = 2000; // 2.0V = 2000 mV const int under_voltage_yellow_limit = 2000; // 2.0V = 2000 mV const int under_voltage_red_limit = 1500; // 1.5V = 1500 mV // Optional functionality - set none, one or both to true to activate the function, // then upload to the Arduino UNO Board const boolean latching_red_led = false; // true or false const boolean flashing_red_led = false; // true or false /************************************************************************ * * Global Variables * ************************************************************************/ // none /************************************************************************ * * Creators * ************************************************************************/ LiquidCrystal_I2C lcd(0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE); // Set up the I2C address and I2C-HD44780 pin mapping /************************************************************************ * * Title: setup() * * Inputs: none * * Outputs: none * * Purpose: Setup the Arduino environment (runs once) * ************************************************************************/ void setup() { Serial.begin(9600); // initialize USB serial output - read via Tools -> Serial Monitor on PC. lcd.begin(16,2); // send initialisation sequence to the LCD Serial.println(" "); // Start with a new line to finish off any junk on the serial monitor Serial.println("=== Itchen Valley Amateur Radio Club - Arduino Homework by Sean (M0MMR) ==="); pinMode(adc_input_pin, INPUT); pinMode(push_button_pin, INPUT_PULLUP); pinMode(red_over_voltage_led_pin, OUTPUT); pinMode(yellow_over_voltage_led_pin, OUTPUT); pinMode(green_led_pin, OUTPUT); pinMode(yellow_under_voltage_led_pin, OUTPUT); pinMode(red_under_voltage_led_pin, OUTPUT); digitalWrite(red_over_voltage_led_pin, LOW); digitalWrite(yellow_over_voltage_led_pin, LOW); digitalWrite(green_led_pin, LOW); digitalWrite(yellow_under_voltage_led_pin, LOW); digitalWrite(red_under_voltage_led_pin, LOW); lcd_welcome_message(); } /************************************************************************ * * Title: loop() * * Inputs: none * * Outputs: none * * Purpose: runs over and over again forever * ************************************************************************/ void loop() { int adc_reading; // static variable, only accessible inside this function int millivolts; // static variable, only accessible inside this function adc_reading = adc_get_raw_reading(); millivolts = convert_adc_reading_to_millivolts(adc_reading); usb_logging(adc_reading, millivolts); lcd_show(adc_reading, millivolts); set_leds(millivolts); } /************************************************************************ * * Title: lcd_welcome_message() * * Inputs: none * * Outputs: none * * Purpose: Display the "Welcome" message on the LCD * and perform a "Lamp Test" - illuminate all LEDs * for 2 seconds to make sure they are all connected * and working * ************************************************************************/ void lcd_welcome_message() { lcd.print("Arduino Homework"); // LCD Top Line of Text // LCD Line 2 // "by Sean M0MMR" one character at a time from alternate ends delay(100); lcd.setCursor( 0, 1); lcd.print("b"); delay(100); lcd.setCursor(12, 1); lcd.print("R"); delay(100); lcd.setCursor( 1, 1); lcd.print("y"); delay(100); lcd.setCursor(11, 1); lcd.print("M"); delay(100); lcd.setCursor( 2, 1); lcd.print(" "); delay(100); lcd.setCursor(10, 1); lcd.print("M"); delay(100); lcd.setCursor( 3, 1); lcd.print("S"); delay(100); lcd.setCursor( 9, 1); lcd.print("0"); delay(100); lcd.setCursor( 4, 1); lcd.print("e"); delay(100); lcd.setCursor( 8, 1); lcd.print("M"); delay(100); lcd.setCursor( 5, 1); lcd.print("a"); delay(100); lcd.setCursor( 7, 1); lcd.print(" "); delay(100); lcd.setCursor( 6, 1); lcd.print("n"); delay(1700); // Do the Lamp Test here ! lcd.setCursor( 0, 1); lcd.print("Lamp Test "); digitalWrite(red_over_voltage_led_pin, HIGH); digitalWrite(yellow_over_voltage_led_pin, HIGH); digitalWrite(green_led_pin, HIGH); digitalWrite(yellow_under_voltage_led_pin, HIGH); digitalWrite(red_under_voltage_led_pin, HIGH); delay(2000); digitalWrite(red_over_voltage_led_pin, LOW); digitalWrite(yellow_over_voltage_led_pin, LOW); digitalWrite(green_led_pin, LOW); digitalWrite(yellow_under_voltage_led_pin, LOW); digitalWrite(red_under_voltage_led_pin, LOW); lcd.clear(); } /************************************************************************ * * Title: adc_get_raw_reading() * * Inputs: none * * Outputs: Raw ADC reading (between 0 to 1023) * * Purpose: Get the raw ADC reading * ************************************************************************/ int adc_get_raw_reading() { int raw_adc_reading; raw_adc_reading = analogRead(adc_input_pin); // TO DO - Add filtering return raw_adc_reading; } /************************************************************************ * * Title: convert_adc_reading_to_millivolts() * * Inputs: Raw ADC reading (between 0 to 1023) * * Outputs: ADC reading converted to millivolts (0 to 5000) * * Purpose: Convert the raw ADC reading to a millivolts value * between 0mV and 5000mV * ************************************************************************/ int convert_adc_reading_to_millivolts(int raw_adc_reading) { int adc_converted_to_millivolts; /* Using fixed point (integer) maths is much quicker and less likely to * give unusual behaviour, given a few hints & tips. * * In this case, the maximum ADC reading will be 1023, which represents * an input analogue voltage of 5V, so 1 ADC step represents 5V / 1023 * = 4.89mV. In that case it makes sense to work in millivolts, where * 5V = 5000mV and an accuracy of + or - 1 millivolt is acceptable. * * To convert from ADC reading to millivolts, multiply the ADC reading * by 5000, and divide by 1023. * * Now check the range of the numbers in question: * Max ADC reading = 1023 x 5000mV = 5115000 * in this example adc_converted_to_millivolts is an "int" with a range * of -32768 to +32767, so our answer of 5115000 is too big to fit in * an int (in fact it wraps around and gives an incorrect answer of 3192). * * It's better to multiply first, then divide. * If we do the 5000mV / 1023 first, we get 4.89mV * but we are using integer maths and the .89 is lost, giving 4mV. * Using 4mV rather than 4.89mV as the scale factor intruduces * a large error in our final adc_converted_to_millivolts result. * * A "long" has the range -2147483648 to 2147483647 and is * capable of holding our 1023 x 5000mV = 5115000 without any problem. * * So, how about the "int" adc_converted_to_millivolts, will the final * answer fit in an "int" (i.e. is it between -32768 and +32767) ? * Test it and see: * 1023 x 5000mV = 5115000 * 5115000 / 1023 = 5000mV * Yes, 5000 is less than 32767, so adc_converted_to_millivolts can be * an int. * * The 'C' language allows us to "promote" a variable to another type * temporarily, so we can make the calculations to the right hand side * of the equation happen in the world of "long"s and the final answer * converted back to "int" before it is stored in adc_converted_to_millivolts. * * Hint 1: Always use (lots of) round brackets to make 'C' do the * calculation you want and not how the language behaves by default. * * Hint 2: Use "(long)" immediately before the variable name you want to promote * to a "long" * * Hint 3: Use an "L" immediately after a constant value you want 'C' * to use as a "long" * * Hint 4: Always work out the maximum expected result of a calculation * before you decide if the variable needs to be an "int" or a "long" * * See the formula below as an example: * */ adc_converted_to_millivolts = (((long)raw_adc_reading * 5000L) / (1023L)); return adc_converted_to_millivolts; } /************************************************************************ * * Title: usb_logging() * * Inputs: adc_reading (between 0 to 1023) * millivolts (between 0 and 5000) * * Outputs: none * * Purpose: Log the adc_reading and corresponding Voltage * to the USB port * ************************************************************************/ void usb_logging(int adc_reading, int millivolts) { int whole_volts; int fractional_volts; /* * Split the millivolts into whole volts (the volts to the left of the decimal point) * and the fractional volts (the parts of volts to the right of the decimal point) * Getting whole volts from millivolts in integer maths is easy, divide by 1000. * Getting the fractional part is just as easy using "%" * i.e. "%" in 'C' is the Modulus Operator (remainder after an integer division). */ whole_volts = millivolts / 1000; // e.g. 1234mV / 1000 = 1 fractional_volts = millivolts % 1000; // e.g. 1234mV % 1000 = 234 Serial.print("Raw ADC Reading = "); Serial.print(adc_reading); Serial.print(" Voltage = "); Serial.print(whole_volts); Serial.print("."); Serial.println(fractional_volts); } /************************************************************************ * * Title: lcd_show() * * Inputs: adc_reading (between 0 to 1023) * millivolts (between 0 and 5000) * * Outputs: none * * Purpose: Display the raw ADC reading on the top line * of the LCD and the corresponding voltage * on the second line of the LCD * ************************************************************************/ void lcd_show(int adc_reading, int millivolts) { int whole_volts; int fractional_volts; whole_volts = millivolts / 1000; // e.g. 1234mV / 1000 = 1 fractional_volts = millivolts % 1000; // e.g. 1234mV % 1000 = 234 // see usb_logging() above // for details of how this works. lcd.home(); lcd.print("Raw ADC = "); lcd.print(adc_reading); lcd.print(" "); lcd.setCursor ( 0, 1 ); lcd.print("Voltage = "); lcd.print(whole_volts); lcd.print("."); lcd.print(fractional_volts); lcd.print(" "); } /************************************************************************ * * Title: set_leds() * * Inputs: millivolts (between 0 and 5000) * * Outputs: none * * Purpose: Set the LEDs as per G0UKB's specification * ************************************************************************/ void set_leds(int millivolts) { boolean red_led_flashed = false; int push_button_state; if( (millivolts >= normal_voltage_low_limit) && (millivolts <= normal_voltage_high_limit) ) { /* Voltage is in range */ if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_over_voltage_led_pin, LOW); } digitalWrite(yellow_over_voltage_led_pin, LOW); digitalWrite(green_led_pin, HIGH); digitalWrite(yellow_under_voltage_led_pin, LOW); if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_under_voltage_led_pin, LOW); } /* Check for push button - if latching_red_led true, * then clear the red LEDs on pressing the button */ push_button_state = digitalRead(push_button_pin); // TO DO - add push button debouncing if(push_button_state == LOW) // button pressed { // Turn OFF the red LED(s) (in case we are in latched_red_led mode) digitalWrite(red_under_voltage_led_pin, LOW); digitalWrite(red_over_voltage_led_pin, LOW); } } else /* Voltage is out of range - check if it is under or over voltage */ { if(millivolts < under_voltage_red_limit) { /* Voltage is below the LOW Limit */ if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_over_voltage_led_pin, LOW); } digitalWrite(yellow_over_voltage_led_pin, LOW); digitalWrite(green_led_pin, LOW); digitalWrite(yellow_under_voltage_led_pin, LOW); if(flashing_red_led) { digitalWrite(red_under_voltage_led_pin, LOW); delay(200); digitalWrite(red_under_voltage_led_pin, HIGH); delay(200); red_led_flashed = true; } else { digitalWrite(red_under_voltage_led_pin, HIGH); } } else if(millivolts < under_voltage_yellow_limit) { /* Voltage is below the Operating Limit */ if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_over_voltage_led_pin, LOW); } digitalWrite(yellow_over_voltage_led_pin, LOW); digitalWrite(green_led_pin, LOW); digitalWrite(yellow_under_voltage_led_pin, HIGH); if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_under_voltage_led_pin, LOW); } } else if(millivolts > over_voltage_red_limit) { /* Voltage is above the HIGH Limit */ if(flashing_red_led) { digitalWrite(red_over_voltage_led_pin, LOW); delay(200); digitalWrite(red_over_voltage_led_pin, HIGH); delay(200); red_led_flashed = true; } else { digitalWrite(red_over_voltage_led_pin, HIGH); } digitalWrite(yellow_over_voltage_led_pin, LOW); digitalWrite(green_led_pin, LOW); digitalWrite(yellow_under_voltage_led_pin, LOW); if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_under_voltage_led_pin, LOW); } } else if(millivolts > over_voltage_yellow_limit) { /* Voltage is above the Operating Limit */ if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_over_voltage_led_pin, LOW); } digitalWrite(yellow_over_voltage_led_pin, HIGH); digitalWrite(green_led_pin, LOW); digitalWrite(yellow_under_voltage_led_pin, LOW); if(!latching_red_led) { // Keep the red LED ON if we are in latched_red_led mode digitalWrite(red_under_voltage_led_pin, LOW); } } else /* Trap any unexpected conditions here */ { Serial.println("set_leds() run time error 666"); /* * This is an example of what we call "Defensive Programming". * when we have accounted for all the conditions that could happen, * we add a trap here for all the ones we missed or don't know about. * * There's a deliberate "hole" in the "if", "else if" logic above * as an example. * * Hint: What happens if millivolts is * exactly equal to under_voltage_red_limit ? */ } } if(red_led_flashed) { // No need to delay() - the LED flashing took 400ms } else { /* No delay as LED did not flash, * so add a 400ms delay() * to maintain the same timing * whether the LED flashed or not */ delay(400); } } // The block below is here so when you print out the program, you know // you've got it all and the printer didn't run out of paper etc :-) /************************************************************************ * * end feb_04_homework.ino * ************************************************************************/