Created
July 24, 2017 06:08
-
-
Save ThomasCBrannan/fcce2d5c95813b04feacc80dbabd226d to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* GccApplication2.cpp | |
* | |
* Created: 7/15/2017 11:32:42 PM | |
* Author : Thomas Brannan | |
* | |
* This is starter kit project 3, the 'Love-O-Meter' (temperature sensor). | |
* ... Note that this version uses an EXTERNAL temperature sensor. | |
* | |
* References: | |
* AVRFreaks guide to the ADC for a faster analogRead() @ http://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-adc?page=all | |
* AVRFreaks guide to Serial communications (USART) @ http://www.avrfreaks.net/forum/tut-soft-using-usart-serial-communications?page=all | |
* qeewiki USART guide @ https://sites.google.com/site/qeewiki/books/avr-guide/usart | |
* Jame's blog Sample Code for atmega328p Serial Communication @ http://jamesgregson.blogspot.com/2012/07/sample-code-for-atmega328p-serial.html | |
* Max Embedded guide to AVR USART @ http://maxembedded.com/2013/09/the-usart-of-the-avr/ | |
* [TUT] [C] Bit manipulation (AKA "Programming 101") @ http://www.avrfreaks.net/forum/tut-c-bit-manipulation-aka-programming-101?page=all | |
* Transmitting 32 bit float in 8 bit UART @ https://e2e.ti.com/support/dsp/tms320c6000_high_performance_dsps/f/115/t/12204 | |
* Unions by cpluscplus.com @ http://www.cplusplus.com/doc/tutorial/other_data_types/ | |
* Guide to USART, including redirecting STD I/O @ https://appelsiini.net/2011/simple-usart-with-avr-libc/ | |
* Using Standard IO streams in AVR GCC @ http://www.embedds.com/using-standard-io-streams-in-avr-gcc/ | |
* Newbie's Guide to AVR timers (see: ctc mode) @ http://www.avrfreaks.net/forum/tut-c-newbies-guide-avr-timers?name=PNphpBB2&file=viewtopic&t=50106 | |
* Using the EEPROM memory in AVR-GCC pdf: ~/desktop/m/arduino | |
* Secret Arduino Voltmeter @ https://provideyourown.com/2012/secret-arduino-voltmeter-measure-battery-voltage/ | |
* ADC on atmega328 part 1 @ http://www.embedds.com/adc-on-atmega328-part-1/ | |
* The ADC of the AVR @ http://maxembedded.com/2011/06/the-adc-of-the-avr/ | |
*/ | |
/* F_CPU..............Speed of ATmega328P is 8E6Hz | |
* FOSC...............Clock speed is 2*F_CPU | |
* USART_BAUDRATE.....9600 bits/s data transfer rate | |
* BAUD_PRESCALE......Equation for BAUD_PRESCALE from qeewiki | |
* With our 16E6 clock frequency, this equation sets a ~103 prescale, which results in an acceptable 0.2% error | |
*/ | |
#define F_CPU 8000000UL | |
#define FOSC 16000000UL | |
#define USART_BAUDRATE 9600 | |
#define BAUD_PRESCALE FOSC/16/USART_BAUDRATE - 1 | |
/* avr/io.h is standard | |
* stdio.h is used for printf() | |
* avr/eeprom.h is used to read/write from nonvolatile memory | |
*/ | |
#include <avr/io.h> | |
#include <stdio.h> | |
#include <avr/eeprom.h> | |
#include <util/delay.h> | |
// uart_putchar sends a single byte over USART. | |
/* @return int ... Error if not 0. | |
* @param char c ... The byte of data to write to USART. | |
* @param FILE* stream ... The direction of the data? | |
*/ | |
int uart_putchar(char c, FILE* stream) { | |
if (c == '\n') { | |
uart_putchar('\r', stream); | |
} | |
loop_until_bit_is_set(UCSR0A, UDRE0); | |
UDR0 = c; | |
return 0; | |
} | |
// uart_getchar writes a single byte over USART | |
/* @retrun UDR0 ... The I/O register, returns this because it is the data that has been recieved | |
* @param FILE* stream ... | |
*/ | |
int uart_getchar(FILE* stream) { | |
loop_until_bit_is_set(UCSR0A, RXC0); /* Wait until data exists. */ | |
return UDR0; | |
} | |
// readVcc() is taken from the 'Secret Voltmeter' article | |
/* I (Thomas Brannan) have made the following three modifications to readVcc(): | |
* I have tried modifying the function to reset the ADMUX MUX[3:1] bits | |
* I have made it return the number of volts instead of millivolts | |
* I have made it read ADCL and ADCH at the same time with result = ADC | |
*/ | |
long readVcc() { | |
// Save the state of the ADMUX register so we can reset it when we're done here | |
uint8_t tempADMUX = ADMUX; | |
// Read 1.1V reference against AVcc | |
// set the reference to Vcc and the measurement to the internal 1.1V reference | |
#if defined(__AVR_ATmega32U4__) || defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) | |
ADMUX = _BV(REFS0) | _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); | |
#elif defined (__AVR_ATtiny24__) || defined(__AVR_ATtiny44__) || defined(__AVR_ATtiny84__) | |
ADMUX = _BV(MUX5) | _BV(MUX0); | |
#elif defined (__AVR_ATtiny25__) || defined(__AVR_ATtiny45__) || defined(__AVR_ATtiny85__) | |
ADMUX = _BV(MUX3) | _BV(MUX2); | |
#else | |
ADMUX = _BV(REFS0) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1); | |
#endif | |
_delay_ms(2); // Wait for Vref to settle | |
ADCSRA |= _BV(ADSC); // Start conversion | |
while (bit_is_set(ADCSRA,ADSC)); // measuring | |
long result = ADC; | |
ADMUX = tempADMUX; // reset the value of the ADMUX register. | |
result = 1125.3L / result; // Calculate Vcc (in V); 1125.3 = 1.1*1023 | |
return result; // Vcc in volts | |
} | |
// readADC() loosely based on function @ http://maxembedded.com/2011/06/the-adc-of-the-avr/ | |
uint16_t readADC(char c) { | |
c &= 0b00000111; | |
uint8_t tempADMUX = ADMUX; | |
ADMUX = (ADMUX & 0xF8)|c; | |
ADCSRA |= _BV(ADSC); | |
while(ADCSRA & _BV(ADSC)) {}; | |
ADMUX = tempADMUX; | |
return (ADC); | |
} | |
// Main() | |
int main(void) { | |
// Set up analog input | |
/* Refer to section 24.9 of the datasheet for a discussion of registers! | |
* For the ADMUX register, see section 24.5 'Changing Channel or Reference Selection'... | |
* ... Setting REFS0 without REFS1 will make voltage reference be AVCC with external capacitor at AREF pin. | |
* For the ADCSRA register, see section ???... | |
* ... Setting the ADATE bit will put the ADC in auto triggering mode, so that we do not have to set a bit for every conversion. | |
* ... Setting the ADSC bit will start ADC conversions. | |
* ... Setting the DPS2, ADPS1 and ADPS0 bits of the ADCSRA register will set the prescaler to 128; ADCfrequency = F_CPU/prescaler = 8E6Hz/128 = 125kHz | |
* ... Setting the ADEN bit in the ADSCRA register activates the ADC. | |
*/ | |
ADMUX |= _BV(REFS0); | |
ADCSRA |= (/*_BV(ADATE) | _BV(ADSC) |*/ _BV(ADPS2) | _BV(ADPS1) | _BV(ADPS0) | _BV(ADEN)); | |
// Set up timer | |
/* For the TCCR1A regsiter... | |
* ... Setting WGM12 without WGM11 or WGM10 will put the counter in CTC mode. | |
* For the TCCR1B register... | |
* ... Setting CS12 without CS11 or CS10 will set the prescale to 256. | |
* For the OCR1A register... | |
* ... Setting this register to 31249 will mean that each second the timer will set a flag in TIFR1 bit OCF1A. ERROR: It updates too slowly! | |
*/ | |
TCCR1A |= _BV(WGM12); | |
OCR1A = 31249; | |
TCCR1B |= (_BV(CS12)); | |
// Set up Serial communication | |
/* The UBRRnH and UBRRnL registers must be set with the baud prescale. | |
* There are three rgisters that control USART settings: UCSRnA, UCSRnB, UCSRnC. | |
* For the UCSR0A register... | |
* ... Setting the TXEN0 bit enables the USART Transmitter. | |
* ... Setting the RXEN0 bit enables the USART Receiver. | |
* For the UCSR0B register... | |
* ... Setting the TXEN0 bit enables the USART Transmitter. | |
* ... Setting the RXEN0 bit enables the USART Receiver. | |
* For the UCSR0C register... | |
* ... Setting the UCSZ01 bit and the UCSZ00 bit will set the frame size to 9 bits in a frame the Receiver and Transmitter use. | |
*/ | |
UBRR0H = ((BAUD_PRESCALE) >> 8); | |
UBRR0L = BAUD_PRESCALE; | |
UCSR0B |= (_BV(TXEN0) | _BV(RXEN0)); | |
UCSR0C |= (_BV(UCSZ01) | _BV(UCSZ00)); | |
// Redirect STDOUT to USART | |
fdevopen(&uart_putchar, &uart_getchar); | |
// Configure GPI/O pins 2, 3, and 4 as OUTPUT. | |
DDRD |= (_BV(DDD4) | _BV(DDD3) | _BV(DDD2)); | |
// Declare some variables | |
uint16_t tempReading; | |
float volts; | |
float degreesCelcius; | |
char input; | |
long vcc; | |
// Main While Loop | |
/* Once per second... | |
* ... Calculate the temperature. | |
* ... Update Serial communication | |
* ... Control 3 LEDs | |
*/ | |
while (1) { | |
// TODO: Get input from the terminal. | |
// Check to see if one second has passed. | |
if (TIFR1 & _BV(OCF1A)) { | |
// Clear the OCF1A flag in TIFR by setting it (this is an unusual bx common to interrupt flags in the AVR) | |
TIFR1 = _BV(OCF1A); | |
// Measure Vcc | |
vcc = readVcc(); | |
// Calculate the temperature in celcius 0 <= tempReading <= 1023 --> 0 <= volts <= 5.0 --> -50 <= degreesCelcius <= 450 | |
tempReading = readADC('0'); | |
volts = (tempReading * vcc) / 1023; | |
degreesCelcius = (volts - 0.5) * 100; | |
// Update Serial communication | |
printf("Vcc = %E\ttempReading = %E\tvolts = %E\tdegreesCelcius = %E\n", vcc, tempReading, volts, degreesCelcius); | |
// Turn LEDs ON/OFF to visually represent degreesCelcius. | |
if (degreesCelcius < 15) { | |
PORTD = 0x00; | |
} else if (degreesCelcius < 25) { | |
PORTD = (_BV(PORTD3) | _BV(PORTD2)); | |
} else { | |
PORTD = (_BV(PORTD4) | _BV(PORTD3) | _BV(PORTD2)); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment