Skip to content

Instantly share code, notes, and snippets.

@ThomasCBrannan
Last active July 24, 2017 17:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ThomasCBrannan/98692c7da412e303db35b4c1bff8c4b0 to your computer and use it in GitHub Desktop.
Save ThomasCBrannan/98692c7da412e303db35b4c1bff8c4b0 to your computer and use it in GitHub Desktop.
/*
* 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
*/
float 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)); // Wait while measuring.
float result = ADC; // Determine result; Vcc in volts.
ADMUX = tempADMUX; // Reset the value of the ADMUX register.
result = 1125.3F / result; // Calculate Vcc (in V); 1125.3 = 1.1*1023.
return result;
}
// readADC() loosely based on function @ http://maxembedded.com/2011/06/the-adc-of-the-avr/
uint16_t readADC(uint8_t c) {
c &= 0b00000111;
uint8_t tempADMUX = ADMUX;
ADMUX = ((ADMUX & 0xF8) | c);
_delay_ms(2);
ADCSRA |= _BV(ADSC);
while (bit_is_set(ADCSRA,ADSC)) {};
uint16_t result = ADC;
ADMUX = tempADMUX;
return result;
}
// 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(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;
float 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);
// Calculate the temperature in celcius.
vcc = readVcc();
tempReading = readADC(0);
volts = (tempReading * vcc) / 1023.0F;
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