Showing posts with label avr. Show all posts
Showing posts with label avr. Show all posts

Wednesday, May 13, 2009

LCD Digital Clock

This clock is pretty similar in terms of effort to my previous seven-seg LED based digital clock -- but the outcome is just not comparable. See for yourself.

The only difference between this from my previous clock, is that the display logic now drives the standard 16x2 alpha numeric LCD instead of multiplexing around those 4 seven segment LEDs (infact I don't have to do multiplexing now, so it is even simpler with only one timer as opposed to the earlier clock with 2 timers). I'm not going to talk about the driver code for the 16x2 alphanumeric LCD for two reasons. First, it is pretty complicated to be put in here and would not really fit the audience. Second, this info is available all around the web, it is just the matter of coding the protocol between the uC and the LCD chip.

Here is the LCD clock in action:

Sunday, May 10, 2009

Digital Clock

I have finally managed to build my own digital clock. This is basically 4 seven segment LEDs put together and driven by my micro-controller (an ATMega8).

I had been working on this for few weeks now. The difficult part about making this clock was multiplexing 4 seven segment LEDs. Soldering 4 LEDs to suit the multiplexing circuit was a nightmare. Having a printed circuit on a PCB would be the right way to go; but without it, it is clumsy to build clumsier to debug. I spent a considerable amount of time to get this soldering done -- as it has to be really firm, accurate all within a limited space. Me, not being an experienced guy, it was tough for me. See it for yourself.





Other than this there are only two more hurdles to the problem:

1. Timing a second -- this is the crucial part of the project, although not that difficult. Will explain shortly.

2. Multiplexing 4 seven segments -- previously I had done only two; Also to make that dot (separator between hour and minute digits) to blink every second.

Timing a second:
Usually I clock the uC to run at 1MHz, this time I had clocked it to run at 2MHz (though it wasn't necessary, I thought it might be useful to have precise control and more power to drive 4 seven-segs along with running the clock.).

Anyways, I used a 16-bit counter to measure a second. This counter gets incremented on every cycle. ie., on a 2MHz clock, this counter would get incremented 2 million times a second. This was a bit too much for timing, so I configured the prescaler to bring down the clock for the timer by 1/8th (smallest possible) which is 256KHz (2 ^ 18). Incidentally, it is possible to program the uC to notify you on every overflow of this 16bit counter instead of you checking for an overflow everytime. So the overflow routine would get called for every 2 ^ 16 increments of the counter. With the current clock configuration, the overflow routine should get notified 4 times a second -- this seems good enough to time a second. So for every 4th call on this routine, it increments the seconds counter. The rest is obvious.

Here is the code for the overflow routine:

// g_* are global variables.
ISR(TIMER1_OVF_vect)
{
static int t = 0; // no. of times overflow has happened.

t++;
g_dot_point = (t/2); // dot point stays on for half a second and off for half.

if(4 >= t) {
t = 0;

g_ss++; // increment the seconds
if(g_ss > 59) {
g_ss = 0; g_mm++;
}
if(g_mm > 59) {
g_mm = 0; g_hh++;
}
if(g_hh > 23) g_hh = 0;
}
}
Multiplexing the 4 seven-segs:
If you do not know how multiplexing displays work and if you have not read my earlier post, please consider reading it.

This is pretty similar to my earlier multiplexing code -- just an extension. Now there are 8 data pins (one extra now for dot point) and 4 control lines one per 7segment. The multiplexing is done on the overflow interrupt of a different timer (as 4Hz of timer1 is too slow to multiplex 4 seven-segs). The following code should be self-explanatory.


ISR(TIMER0_OVF_vect)
{
static int n = 0; // decides which digit to update now.(right to left, 0 -> 3)
static int tp[4] = {1, 10, 100, 1000};

int cur_time = g_hh*100 + g_mm;

PORTC = 0;

seg7_write_digit_dot( (cur_time / tp[n]) % 10, // manipulate the appropriate digit
(g_dot_point && n == 2)); // 3rd digit -> print dot if req.

PORTC = 1 << n; // select the right digit by sending the correct control line.

n++; // next digit on next overflow.
if(n >= 4) n = 0;
}

One missing piece in this project is the means to configure the time. The amount of benefit that gives did not excite me for the amount of work required to do that. It was kind of boring stuff. So I have now configured the clock to always start at 13.25 (that is the time I was testing this today), so I can just choose to start the clock at the right time, and then on it just runs fine. Anyways, I can reprogram the clock to whatever time I want to start with. :)

Here is the digital clock in action:

Monday, March 30, 2009

Digital Thermometer

This is where I was heading to. With the last module, I was ready with 2 digit 7segment LED which could be used to show the current ambient temperature.

The only remaining part is to integrate the temperature sensor into the system, read, decode, and display the reading. I used the LM35 temperature sensor which is quite simple and handy to use (at the size of a transistor). LM35 is a centigrade temperature sensor and has 3 terminals -- VCC, Vout and GND. Connect VCC and GND with a 5V across, and you can calculate the current ambient temperature based on the potential available at Vout. Based on the datasheet of LM35, Vout is set to (0mV + 10 mV/degree). So a 100mV at Vout means a 10 degree centigrade temperature sensed.

Now, the remaining task is to make the micro controller (I use ATMega8) read this reading. uC deals only with digital data. This being an analog data, it has to ideally be fed through a Analog-to-Digital-Convertor (ADC). Incidentially, ATMega8 has an inbuilt ADC (with 6 channels in PDIP package). For the ADC to decode the analog data properly, the ARef (Pin 21 in PDIP) terminal has to be set to a reference voltage. To give an example, if the reference voltage is 5V, one unit in a 10bit ADC is defined as (5/1024) volt ie., ~5mV. So for every 5mV from the analog input (in our case, LM35), the reading from ADC goes up by 1 unit.

In my case, the ARef is set to 4.85V. Hence one ADC unit is (4.85/1024) volt ie., 4.736 mV. As discussed earlier LM35 outputs 10mV per degree Centigrade; so my temperature reading is (adc_reading * 4.736 / 10) or (adc_reading * 0.4736) deg. Centigrade.

Hardware:
The hardware part is just connecting the LM35 to my previous module. The output of LM35 is connected to ADC channel-2 (PIN 25 in PDIP) -- as channel 0,1 are shared with PORTC's 0-1 bits which I have been using as control bits for selecting the 7segment digit in TDM mode.

Software:
After enabling ADC channel-2, the ADC's current value is read and the temperature is calculated using the above derived formula. The value is stored in a global volatile variable which is displayed in the 7segs as in my previous module. The temperature is read every 2 seconds (just arbitrary).

Here is the code:

// Author : Gerald Naveen A (ageraldnaveen at gmail dot com)

#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 1000000

#include <util/delay.h>

static volatile uint16_t g_temp_c = 99;

// insert TDM based seven segment code here... interrupt handling etc.,
// didn't want to bloat the codespace while publishing.

void initialize_adc()
{
ADMUX = (1 << REFS0);
ADCSRA = (1 << ADEN) | 7; // enable && prescaler /128
}

uint16_t read_adc_channel(unsigned int ch)
{
uint16_t result;

ADMUX |= (ch & 0x07); // enable ADC channel 7

ADCSRA |= (1 << ADSC); // start conversion

while(!(ADCSRA & (1 << ADIF))); // wait for conversion to complete

result = ADC; // read the result

ADCSRA |= (1 << ADIF); // signal done to ADC

return result;
}

int main()
{
sei();

initialize_adc();
// initialize timer etc., as my previous module

while(1) {
// reading from channel 2
uint16_t val = (uint16_t) read_adc_channel(2) * 0.4736;
if(val < 100) // just to avoid noise, have a upper limit (100 too big?)
g_temp_c = val; // send it for display

_delay_ms(2000);
}
return 0;
}
Here is a snapshot of my setup showing the temperature inside my refrigerator :) I could not shoot a meaningful video, as the project shows an almost constant number. The temperature was actually showing 8 degrees when I opened the fridge after putting my project inside for around 5 minutes; when I opened the door and while I was trying to place the breadboard upright for the digits to be visible and clicked, the temperature had shot up by a few degrees due to the door being open :D


Friday, March 27, 2009

Multiplexing two 7segment LEDs

This is a follow up on my previous post on 7segment LED display.

When it comes to displaying 2 digits, there are at least 2 choices. The simplest choice is: In addition to the existing 7bits for the first digit, add 7 more data bits and let them drive the second digit. The obvious drawback with this approach is the need for large number of data lines. With increase in the number of digits, you need 7bits for each additional digit. At some point the idea does not scale and goes impractical.

The second choice is to use Time Division Multiplexing (TDM). In this approach the same data bus (7bit always) is used to show the digits across all the 7segment LEDs. A separate control signal is added (1 bit per digit -- simple appraoch; ideally 'log (base 2) n' control lines are enough for n digits). The control signal is used as 'chip-select' to select the appropriate digit and the data at the data bus at that moment is used to light up that segment appropriately. An important caveat in TDM is that, the 7Seg LEDs will not retain the digit when the control transfers to the next segment (obvious?). As a result only one 7seg will be lit at any point in time. Thanks to the persistence-of-vision property of the human eye, by switching the control between the LEDs at a fast pace, it is possible to "virtually" light up more than one 7seg at the same time. And that's the idea behind this project.

Hardware:
The 7bit data bus control the digit to be displayed (as in my previous post with single 7seg). Additionally, 2 control lines, each connected to the common anode of each of the 7seg select the digits by supplying the positive voltage(+5V). It is actually a good idea to connect the control signals to the base of a transistor and use the transistor as a switching device to turn on/off the positive voltage to the LED -- I don't have transistor at the moment; given that the 7seg does not draw too much current, it was safe to drive them directly from the uC's output pins. I would not recommend this though.

Software:
The software part is little complicated. The idea of the program is to display 2 digits of a running counter. The counter has to be incremented at a slow pace (once per second?) so human eye can follow the counter. However, the 7segs have to be refreshed at a very high rate otherwise we would see flickering of digits (remember only one of them is lit at any moment). To implement this, it is possible to run a loop with few ms sleep interval and keep refreshing the digits; and increment the counter only after every 100 iterations (so in effect the counter is incremented only after a second or so). This is naive and may not scale when there is more functionality than just incrementing the counter. So the ideal method is to make use of timer interrupts. ATMega8 has 3 timers. I have made use of timer0. Once enabled, whenever the counter belonging to the timer (in this case TCNT0) overflows beyond its size (in this case 8 bit), the uC invokes the appropriate interrupt handler. In the interrupt handler, I've written code to update one digit at every invocation.

Here is the code:

/* Author: Gerald Naveen A (ageraldnaveen at gmail dot com) */

// Write the digit on PORTD (0-7 bits)
// Select the digit on PORTC (0-1 bits)
#include <avr/io.h>
#include <avr/interrupt.h>

#define F_CPU 1000000 // 1MHz
#include <util/delay.h>

//my implementation that wraps writing a digit to 7seg
//implements seg7_write_digit
#include <gerald/7seg.h>

// volatile makes sense
volatile int g_cur_val = 0;

void initialize_timer0()
{
TCCR0 |= (1 << CS01); // configure the prescaler for timer0
TIMSK |= (1 << TOIE0); // enable timer0 interrupt
TCNT0 = 0; // initialize timer0 counter to 0
}

// the TIMER0 overflow interrupt handler
ISR(TIMER0_OVF_vect)
{
static int n = 0; // decides which digit to update now.

if(!n) {
// make sure you disable the control signal before changing
// the data bits. otherwise you can notice small leakage of
// data onto other digit.
PORTC = 0;
seg7_write_digit(g_cur_val % 10); // output ones
PORTC = 0x1;
}
else {
PORTC = 0;
seg7_write_digit((g_cur_val/10) % 10); // output tens
PORTC = 0x2;
}
n = !n; // toggle the digit selection
}

int main()
{
DDRD = 0x7F;
DDRC = 0x03;
PORTD = 0xFF;
PORTC = 0; // disable control signals by default

sei(); // enable global interrupts

initialize_timer0();

while(1)
{
g_cur_val++; // just keep incrementing the counter
_delay_ms(100);
}
return 0;
}
Here is the project in action:


Monday, March 23, 2009

7Segment LED Display

After getting the micro controller (uC) work, now it is time to start building small modules for later use in bigger projects. 7Segment LED is one of the common ways of output when the data is numerical.

I have a common anode 7Segment LED (Red). The 7Seg has seven segments each having to be separately lit up by grounding the appropriate cathode for that segment (actually not necessarily ground, any potential lesser than anode by ~1.5-5V). So, to control 7 segments (using a simple enough circuit), we need to have 7 bits of info, each driving one segment. As the segments are controlled through the cathode, the uC has to sink current from the 7Seg to light up a segment. This is achieved by outputting a logical 0 at the corresponding bit in the uC's output port.

The ideal thing is to connect each of those 7 cathodes to their corresponding output pins through a current limiting resistor of 330ohms. For ease of use and testing, I've positioned the resistor between the 5V supply and the anode. This is much simpler for the proof of concept and easy to wire on the breadboard. The drawback of this approach however is that, the current gets split into each of the lit segments, as a result the brightness of the segments vary based on the number of segments lit (1 being the brightest and 8 being the dimmest). I don't really care at this moment, given that I know the reason.

That's all the about the hardware side. The software needs to output the correct bits at the output port to display a digit on the 7Seg. Each digit is displayed by lighting 2 or more segments in the 7Seg. I've created a static mapping between the digits (0-9) and their corresponding segments-to-be-lit. Now, based on the number to be shown, the software outputs the bits and the digits appear on the 7seg. To keep it appealing, I've made the program to display the last digit of a running counter (as usual, a sleep between the increments to keep it visible to the eye).

Here is the code:

/* Author: Gerald Naveen A (ageraldnaveen at gmail dot com) */

#include <avr/io.h>

#define F_CPU 1000000 // 1MHz
#include <util/delay.h>
#define G_SEGA (1 << 0)
#define G_SEGB (1 << 1)
#define G_SEGC (1 << 2)
#define G_SEGD (1 << 3)
#define G_SEGE (1 << 4)
#define G_SEGF (1 << 5)
#define G_SEGG (1 << 6)

uint8_t seg7_map[10]= {
G_SEGA | G_SEGB | G_SEGC | G_SEGD | G_SEGE | G_SEGF, // 0
G_SEGB | G_SEGC, // 1
G_SEGA | G_SEGB | G_SEGG | G_SEGE | G_SEGD, // 2
G_SEGA | G_SEGB | G_SEGG | G_SEGC | G_SEGD, // 3
G_SEGF | G_SEGG | G_SEGB | G_SEGC, // 4
G_SEGA | G_SEGF | G_SEGG | G_SEGC | G_SEGD, // 5
G_SEGA | G_SEGF | G_SEGG | G_SEGC | G_SEGD | G_SEGE, // 6
G_SEGA | G_SEGB | G_SEGC, // 7
G_SEGA | G_SEGB | G_SEGC | G_SEGD | G_SEGE | G_SEGF | G_SEGG, // 8
G_SEGA | G_SEGB | G_SEGC | G_SEGD | G_SEGF | G_SEGG // 9
};

void seg7_write_digit(uint8_t d)
{
if(d > 9)
d = d % 10;

PORTD = 0xFF ^ (seg7_map[d] & 0xFF); // output logical 0 to light that segment
}

int main()
{
DDRD = 0xFF;
int i = 0;
while(1)
{
seg7_write_digit(i++);
_delay_ms(400);
}
return 0;
}
Here is the circuit in action:



This code I wrote is useful to drive one 7Seg LED; the next job is to drive more than one 7Seg LED -- yes it is different. See you then.

Saturday, March 21, 2009

Hello AVR!

Finally, my first AVR micro-controller based project is ON! I always had a great passion for embedded electronics, but never had a chance and guidance to improve. This is a first step towards that -- thanks to the Internet for a handful of articles.

After a week's struggle to setup the whole environment, I managed to successfully flash my first program into my ATMega8 micro-controller and use it to drive 2 LEDs. The power of the ATMega8 is just amazing; with very little power consumption, the features it provides for embedded applications is just too good (In a 28pin PDIP packaging, it has around 23 I/O pins, 6 channel ADC, Pulse Width Modulation, Programmable USART, ISP, 3 Timers and clocking at 8-16MHz).

Why the struggle:

This shouldn't have been a struggle, if I wasn't unlucky to get a faulty ATMega8. This is my first AVR project and I had bought tonnes of electronic goods starting from multimeter, soldering iron to AVR ISP programmer, ATMega8, crystals, resistors, capacitors, inductors, LEDs....(I've actually bought more stuff which I'm yet to use). After setting up the circuit as required, connecting the micro controller (uC) to the ISP programmer and the programmer to the computer, I was not able to flash my controller at all and that was the problem :( I struggled struggled and struggled to debug every portion of this chain; tried a different ISP programmer (built my own serial ISP programmer) but no use; after achieving no success, the final and the only option was to suspect my ATMega8 uC -- the hero of this project. Anyone would think why it took me so long to suspect this; True. But I did suspect this earlier, however I wished this wasn't the issue because I didn't have a spare one with me and I cannot get this in the nearby electronics shops. Finally I had to personally go to SP road in Bangalore (Bangalore's version of the Chennai's ritchie street) and get a ATMega8. Sigh!!! All said and done, it is finally working :D

This is pretty much a 'Hello World' nothing else. The uC just drives the 2 LEDs I have connected over PORTC through the 330ohm current limiting resistors. To keep it a bit fancy, I made the 2 LEDs to represent the last 2 bits of a running integer counter. So basically the LEDs glow in the following pattern as the integer keeps incrementing -- 00, 01, 10, 11. A 500ms delay between the increments, to keep it visible to the eye.

The code would look something like this (I use the WinAVR cross compiler).

#include <avr/io.h>
#include <util/delay.h>

int main()
{
DDRC = 0xFF; // Enable output on PORT C
uint8_t c = 1;
while(1) {
PORTC = c++; // output the integer on PORT C, whose 0-1bits drive the LEDs
_delay_ms(500);
}
return 0;
}
Here is the Hello AVR! in action: