Chapter 6
Part 16 - Displaying Numbers on the LCD Display
So, we know how to put characters and even strings of information to the LCD. The most useful thing to display on the LCD would be a number. Now, I'm sure you are thinking, well, we could just pass a number in quotes, like this: "632" using the Send_A_String command (that we created in the last tutorial ! Well, sure, but what if it is a number from a sensor stored in a variable, or something like that! We actually do want the number in those quotes, but how we get it there is where the magic comes in... and it is REALLY simple.
If we want the numbers to be displayed at the same locations every time (and not just fill the entire screen with a list of numbers), we will need to move the cursor to the desired locations every time we want to send the number to the LCD. This can also be used to locate strings and characters.
The magic of moving the cursor to any position on the LCD display is in the 0x80 number. That number represents the first location on the display, at the top left corner. If you've been paying attention to the binay tutorial, you would know that 0x80 represents 0b10000000 in binary. The "1" is in the 8th place, leaving a full 7 digits left for something wonderful. 7 binary digits can hold 128 numbers (0-127).
As you already know, LCD screens are divided up into many columns and rows. For the LCD in the tutorial, there are 4 lines (rows) and 20 columns, hence the 20x4 that is usually shown in the description. Add them all up and you have 80 total positions on the LCD. Hmm... 80 is less than 128. This tells us that there may be LCDs out there that have more lines and columns, becuase there are that many possibilities using this command.
If you sent a command using Send_A_Command and inserted 0x80 to get processed by the LCD, the cursor position would appear at the top left. If you inserted 0x8A, then the cursor would be positioned 10 spaces right of the top left corner. It would actually be in the middle, since the LCD is 20 columns wide. But there is a catch! One would think that if this number kept increasing, the cursor would wrap to the next line, but it doesn't. The cursor wraps to the third line instead. This is due to how it uses the 128 reserved spaces. Because of the various LCD displays out there with different line and column counts, the first line will start from "0" and fill lines 1 and three up to number 63, which is 1 minum half of 128. Now it is starting to come together, isn't it. The 2nd and fourth line start from 64 and go on towards the end. So half of the 128 is reserved for the first and third line, and the other half is reserved for the second and fourth line. That way, all LCD will start from 0 on the first line, and start from 64 on the second line, even on 2 line LCDs.
This makes great sense, because if you have an LCD with only two lines that were 64 columns long (using all of the 128 reserved positions), then the LCD would still be compatible with an LCD with 2 lines but with only 20 or so columns. You could expect to see the same information on the LCD, at least as far as the selected LCD.
However, trying to put the cursor in a desired location using a number within 0 to 64 and 64 to 128 not really knowing which line you may be affecting is as easy as it could be. We can create a new routine that allows you to simply state the x and y coordinate locations would be much easier. If you wanted to display something on the 4th line and 6 spaces from the start of the line could be written link GotoLocation(6, 4); That's easier than stating the location in this form: Send_A_Command(90); 90 (in decimal, not hex) just happens to be that position on a 20x4 LCD display. 90 is derived this way: 64 is the 2nd line. The 2nd line wraps to the 4th line by adding 20 to 64 (20+64 = 84). knowing that you are on the 4th line, just add 6 and you have the numer 90. But forget all that, because we will implement the easy way.
Creating a routine that you can just enter the X and Y component is pretty easy. First, you will need to create a variable that stores all of the initial column numbers. In the case of the 20x4 LCD, the numbers are 0 for the first line, 64 for the second line, 20 for the third line and 84 for the forth line. The variable declaration might look like this:
char firstColumnPositionsForMrLCD[4] = {0, 64, 20, 84};
I am using the "char" data type because the numbers are not that big. The "char" data type can range from 0 to 128. The variable name "firstColumnPositionsForMrLCD" is LONG. This is actually intentional. First, for best programming practice and readability, and second, so later on, the possibility for this name to be used for another use would be very unlikely. Remember, we will have this varialble in another file as a library and we wont see it (while working in our main .c file), but it will be present. The numbers between the squiggly brackets are the initial positions for each line. This way, all we need to do is put the "Y" component in this array and it will tell us the initial position for that line. Then we add the "X" component to that number and we will have the correct position on the LCD.
The array could also be a bit more systematic and could look like this:
#define numberOfColumns 20;
char firstColumnPositionsForMrLCD[4] = {0, 64, numberOfColumns , 64 + numberOfColumns };
This way, all you would need to do is specify the LCD's number of columns and you would not need to go to that obfuscatious data sheet (or should I say data BOOK). So, armed with these glorious instructions and new knowledge that you thought you would never know, how might the actual GotoLocation routine look like? Well, here you go:
void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}
Notice the 0x80... That still needs to reside in the Send_A_Command since it is the command that represents the first position. then the array calls up the correct number and then the "X" is added. You may have also noticed the y-1 and x-1. This is added because the array is indexed from 0 and the screen positions are also indexed from 0 and not 1. The small subrraction is just to be able to call line #1 "1" instead of "0" and line #2 "2" instead of "1", and so on. For the "X", i like calling the first column "1" not "0". It's like being in Paris, all of the first floors are considered the ground, or "0" floor and the floor just above is considered the first floor. Not that it is wrong, but I'm just not accustomed to that. If you like your first column and first line to be "0", simple remove the subtraction and you are set.
Now, for the desert. Making a numeric variable appear on the LCD screen. The secret to doing this is to have a function that converts a number (numeric variable) into a string. We already know ow to put a string to the LCD, now all we need is to have a string that represents the number in a variable. Fortunately, there is a function that is already made for this. It's called itoaand dtostrf. The "i" in itoa means integer. The "d" in dtostrf means double. You already know what an integer is. If not, its just a number that doesn't have any decimal places. You might have guessed that double is a number that can contain decimal places. Double refers to double precision. Both of these functions convert a value to a string and they require the stdlib.h library to be included ( #include <stdlib.h> ). The following shows the proper usage:
itoa(integer value, string that will store the numbers, base);
dtostr(double precision value, width, precision, string that will store the numbers);
Value is either a direct value plugged into this place, or a variable to contains a value.
Width that is used with dtostrf is the number of characters in the number that includes the negative sign (-). For instance, if the number is -532.87, the width would be 7 including the negative sign and the decimal point.
Precision is how many numbers would be after the decimal point in the dtostrf usage.
Base is the maximum number of values per digit. For instance, 2 is for binary representation (2 possible values for each digit - 0 or 1); 10 is for the common human number system (10 possible values for each digit - 0, 1, 2, 3, 4, 5, 6, 7, 8, or 9); 16 is for hexadecimal where there are 16 possible values for each digit - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, or F. It should be understood that if the base is larger, then the alphabet will just get larger.
String is the variable that accepts alphanumeric characters such as a char array. After the function is executed, the string will have the number stored within the array. For instance, if an integer variable contains the number 643, then the string variable will contain "634".
This declaration would be somewhere in program.
char aNumberAsString[4];
int x = 432;
This would be where you needed to convert the number to the string:
itoa(x, aNumberAsString, 10);
This would be where you would want to display the number to the LCD:
Send_A_String(aNumberAsString);
Here is the entire program that pretty much has all of the routines that we need for the LCD:
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#define MrLCDsCrib PORTB
#define DataDir_MrLCDsCrib DDRB
#define MrLCDsControl PORTD
#define DataDir_MrLCDsControl DDRD
#define LightSwitch 5
#define ReadWrite 7
#define BiPolarMood 2
char firstColumnPositionsForMrLCD[4] = {0, 64, 20, 84};
void Check_IF_MrLCD_isBusy(void);
void Peek_A_Boo(void);
void Send_A_Command(unsigned char command);
void Send_A_Character(unsigned char character);
void Send_A_String(char *StringOfCharacters);
void GotoMrLCDsLocation(uint8_t x, uint8_t y);
int main(void)
{
DataDir_MrLCDsControl |= 1<<LightSwitch | 1<<ReadWrite | 1<<BiPolarMood;
_delay_ms(15);
Send_A_Command(0x01); //Clear Screen 0x01 = 00000001
_delay_ms(2);
Send_A_Command(0x38);
_delay_us(50);
Send_A_Command(0b00001110);
_delay_us(50);
char positionString[4];
while(1)
{
for(int y = 1; y<=4; y++)
{
for(int x = 1;x<=20;x++)
{
itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String("X = ");
Send_A_String(positionString);
itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String("Y = ");
Send_A_String(positionString);
GotoMrLCDsLocation(x, y);
Send_A_String("x");
_delay_ms(50);
GotoMrLCDsLocation(x, y);
Send_A_String(" ");
itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String(" ");
itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String(" ");
}
}
}
}
void Check_IF_MrLCD_isBusy()
{
DataDir_MrLCDsCrib = 0;
MrLCDsControl |= 1<<ReadWrite;
MrLCDsControl &= ~1<<BiPolarMood;
while (MrLCDsCrib >= 0x80)
{
Peek_A_Boo();
}
DataDir_MrLCDsCrib = 0xFF; //0xFF means 0b11111111
}
void Peek_A_Boo()
{
MrLCDsControl |= 1<<LightSwitch;
asm volatile ("nop");
asm volatile ("nop");
MrLCDsControl &= ~1<<LightSwitch;
}
void Send_A_Command(unsigned char command)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = command;
MrLCDsControl &= ~ ((1<<ReadWrite)|(1<<BiPolarMood));
Peek_A_Boo();
MrLCDsCrib = 0;
}
void Send_A_Character(unsigned char character)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = character;
MrLCDsControl &= ~ (1<<ReadWrite);
MrLCDsControl |= 1<<BiPolarMood;
Peek_A_Boo();
MrLCDsCrib = 0;
}
void Send_A_String(char *StringOfCharacters)
{
while(*StringOfCharacters > 0)
{
Send_A_Character(*StringOfCharacters++);
}
}
void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}
Part 17 - Separating Code from the Main to Form a Library
We've produced a bit of reusable code to talk to the LCD, sending commands and characters. We enabled as many functions for the LCD as we need at the moment (we will later implement the 4-wire - 4-bit - mode to use fewer pins on the microcontroller), so this is a good time to take the code and make a library out of it. There are a couple of ways we can make these libraries. The library can reside in a ".h" file and a ".c" file, or it can all reside in the ".h" file. The latter is a bit easier, but is not recommended if there is a huge amount of code.
First, a very important condition must be included in both methods. Sometimes, these libraries may be included in multiples files in the overall project. But, libraries cannot be included more than once. You will see that the #include <avr/io.h> will be included in the main file, and in the library files because both of these files need the information in the io.h file, but this file cannot be loaded twice, so a condition must be added so that the code within the ".h" file is not loaded twice. The #ifndef conditional statement is used in this case.
At the beginning of the ".h" file, we use a #define statement to essentially label the file. Let's say, the io.h file has a #define statement at the beginning like this: #define _IO_H_. The #define statement in this case is just telling the compiler that the label _IO_H_ is defined. That's it! now, if after this statement has been processed and _IO_H_ has been defined, you could ask the compiler: "has _IO_H_ been defined?". The compiler would say yes. We actually do the opposite. We ask if the _IO_H_ has not been defined, and then we tell the compiler to go ahead and process the code. This is where the #ifndef comes in. #ifndef is short for "if not defined". So, in the case of the io.h file, the following would exist:
#ifndef _IO_H_
#define _IO_H_
//All of the code in the .h file
#endif
The #endif would be at the very end of the file. If this file was processed once, you know that the code would be processed because _IO_H_ would not have been defined yet (hence the if not defined). If the code had been processed previously (from another file) then the #define _IO_H_ would have already been processed and the compiler would say to the #ifndef and say, yes it has been defined and I'm not going to process the following code. In fact, I'm going all the way to the end of the file, to the #end if statement and getting out of this file and continuing where I left off!!
We need to do this to our ".h" file. Since I am calling the .h and .c file MrLCD.h and MrLCD.c, we will call the label in the #define statement simply MrLCD, like this:
#ifndef MrLCD
#define MrLCD
//All of the code in our LCD library
#endif
The First Method:
So, now that we got that out of the way, let's talk about the first method, even though I will be using the second. As I said, in the first method, two files will be created, a ".h" file and a ".c" file. The ".h" file will contain all of the macros (#define) statements, and all of the prototypes. The ".c" file will contain all of the actual routines. The main reason the ".h" file is created with only the definitions and prototypes is to give you a convenient place to change configurations. In the case of the LCD library, the #define statements contain information that will most likely change, such as the port that is connected to the data lines of the LCD, and the port and pins that will receive the control lines of R/W (Read/Write), RS (Register Select) and E (enable).
In the tutorial, I name the ".c" and ".h" files MrLCD.c and MrLCD.h. We will create these file by clicking on File -> New -> C / C++. When you do this, you will see a new tab appear called "new". You will do this two times, one for MrLCD.c and MrLCD.h. To name the tabs, you will need to use the "Save As..." menu selection and you will be prompted to name the file. Optionally, you can name the files when you close the files since the program will ask you for the name of the file.
Now we need to copy the information from the main file into the ".h" file and ".c" file. For the .h file, you will need to copy the #include statements, #define statements and prototype information:
#ifndef MrLCD
#define MrLCD
//These are the Includes
#include <avr/io.h>
#include <util/delay.h>
//These are the define statements
#define MrLCDsCrib PORTB
#define DataDir_MrLCDsCrib DDRB
#define MrLCDsControl PORTD
#define DataDir_MrLCDsControl DDRD
#define LightSwitch 5
#define ReadWrite 7
#define BiPolarMood 2
//These are the prototypes for the routines
void Check_IF_MrLCD_isBusy(void);
void Peek_A_Boo(void);
void Send_A_Command(unsigned char command);
void Send_A_Character(unsigned char character);
void Send_A_String(char *StringOfCharacters);
void GotoMrLCDsLocation(uint8_t x, uint8_t y);
void InitializeMrLCD(void);
#endif
Notice the #ifndef and the #endif. This code pcb copy would only happen one time in the entire project, even if I included this file in many other files. This will come in handy in larger projects, but we are using include statements for other files more than once.
The ".c" file will receive the all of the routines (except the main routine) and the ".c" file will also receive any global variables that the routines use. We have one called: firstColumnPositionsForMrLCD which is declared at the top of the file, but this could have been defined just before the routine that uses it. The ".c" file also has the #include statements that the routines use.
The ".c" file may look like this:
//These are the Includes
#include <avr/io.h>
#include <util/delay.h>
#include "Mr.LCD"
//These are the Routines
void Check_IF_MrLCD_isBusy()
{
DataDir_MrLCDsCrib = 0;
MrLCDsControl |= 1<<ReadWrite;
MrLCDsControl &= ~1<<BiPolarMood;
while (MrLCDsCrib >= 0x80)
{
Peek_A_Boo();
}
DataDir_MrLCDsCrib = 0xFF; //0xFF means 0b11111111
}
void Peek_A_Boo()
{
MrLCDsControl |= 1<<LightSwitch;
asm volatile ("nop");
asm volatile ("nop");
MrLCDsControl &= ~1<<LightSwitch;
}
void Send_A_Command(unsigned char command)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = command;
MrLCDsControl &= ~ ((1<<ReadWrite)|(1<<BiPolarMood));
Peek_A_Boo();
MrLCDsCrib = 0;
}
void Send_A_Character(unsigned char character)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = character;
MrLCDsControl &= ~ (1<<ReadWrite);
MrLCDsControl |= 1<<BiPolarMood;
Peek_A_Boo();
MrLCDsCrib = 0;
}
void Send_A_String(char *StringOfCharacters)
{
while(*StringOfCharacters > 0)
{
Send_A_Character(*StringOfCharacters++);
}
}
void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}
void InitializeMrLCD()
{
DataDir_MrLCDsControl |= 1<<LightSwitch | 1<<ReadWrite | 1<<BiPolarMood;
_delay_ms(15);
Send_A_Command(0x01); //Clear Screen 0x01 = 00000001
_delay_ms(2);
Send_A_Command(0x38);
_delay_us(50);
Send_A_Command(0b00001110);
_delay_us(50);
}
You might notice a few differences. First, there is no need to use the #ifndef since the ".h" file already has that. Second, there is a need to add an include to the "Mr.LCD.h" to get the prototypes. And finally, there is a new routine called InitializeMrLCD. This routine just packages up the first few lines of code pertaining to the LCD. We don't want that code taking up any room in our main file. fib聚焦离子束分析 Using it this way, all we need to do it add a line in the main routine:
InitializeMrLCD();
The first method also requires you to inform the makefile of the ".c" file that will be included. In this file, you will have the remark: "# List C source files here." Just under that statement, you will see "SRC = $(TARGET).c ". The new ".c" file that you just created will be included here. This is what that are might look like:
# List C source files here. (C dependencies are automatically generated.)
SRC = $(TARGET).c MrLCD.c
That is all you need to do in the makefile.
The Second Method:
The second method is the easiest, but as the program gets very long, it may pose an issue on readability, so for very large files, the first method is the best one to use. There is a big benefit to using this method, and I used the word easiest, because it is easy. You are not going to maker together club mess with the makefile and add the MrLCD.c reference. You will only copy and paste the code pertaining to the LCD into a ".h" file. We will call this file "MrLCD.h", just like before. After that, all you need to do is have an #include "MrLCD.h" at the top of your main file and add the InitializeMrLCD() in the main routine. Below is the main file and the MrLCD.h file and how they may appear:
The Main File (main.c):
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#include "MrLCD.h"
int main(void)
{
InitializeMrLCD();
char positionString[4];
while(1)
{
for(int y = 1; y<=4; y++)
{
for(int x = 1;x<=20;x++)
{
itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String("X = ");
Send_A_String(positionString);
itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String("Y = ");
Send_A_String(positionString);
GotoMrLCDsLocation(x, y);
Send_A_String("x");
_delay_ms(50);
GotoMrLCDsLocation(x, y);
Send_A_String(" ");
itoa(x, positionString, 10);
GotoMrLCDsLocation(12, 3);
Send_A_String(" ");
itoa(y, positionString, 10);
GotoMrLCDsLocation(12, 4);
Send_A_String(" ");
}
}
}
}
The Header file (MrLCD.h):
#ifndef MrLCD
#define MrLCD
#include <avr/io.h>
#include <util/delay.h>
#include <stdlib.h>
#define MrLCDsCrib PORTB
#define DataDir_MrLCDsCrib DDRB
#define MrLCDsControl PORTD
#define DataDir_MrLCDsControl DDRD
#define LightSwitch 5
#define ReadWrite 7
#define BiPolarMood 2
char firstColumnPositionsForMrLCD[4] = {0, 64, 20, 84};
void Check_IF_MrLCD_isBusy(void);
void Peek_A_Boo(void);
void Send_A_Command(unsigned char command);
void Send_A_Character(unsigned char character);
void Send_A_String(char *StringOfCharacters);
void GotoMrLCDsLocation(uint8_t x, uint8_t y);
void InitializeMrLCD(void);
void Check_IF_MrLCD_isBusy()
{
DataDir_MrLCDsCrib = 0;
MrLCDsControl |= 1<<ReadWrite;
MrLCDsControl &= ~1<<BiPolarMood;
while (MrLCDsCrib >= 0x80)
{
Peek_A_Boo();
}
DataDir_MrLCDsCrib = 0xFF; //0xFF means 0b11111111
}
void Peek_A_Boo()
{
MrLCDsControl |= 1<<LightSwitch;
asm volatile ("nop");
asm volatile ("nop");
MrLCDsControl &= ~1<<LightSwitch;
}
void Send_A_Command(unsigned char command)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = command;
MrLCDsControl &= ~ ((1<<ReadWrite)|(1<<BiPolarMood));
Peek_A_Boo();
MrLCDsCrib = 0;
}
void Send_A_Character(unsigned char character)
{
Check_IF_MrLCD_isBusy();
MrLCDsCrib = character;
MrLCDsControl &= ~ (1<<ReadWrite);
MrLCDsControl |= 1<<BiPolarMood;
Peek_A_Boo();
MrLCDsCrib = 0;
}
void Send_A_String(char *StringOfCharacters)
{
while(*StringOfCharacters > 0)
{
Send_A_Character(*StringOfCharacters++);
}
}
void GotoMrLCDsLocation(uint8_t x, uint8_t y)
{
Send_A_Command(0x80 + firstColumnPositionsForMrLCD[y-1] + (x-1));
}
void InitializeMrLCD()
{
DataDir_MrLCDsControl |= 1<<LightSwitch | 1<<ReadWrite | 1<<BiPolarMood;
_delay_ms(15);
Send_A_Command(0x01); //Clear Screen 0x01 = 00000001
_delay_ms(2);
Send_A_Command(0x38);
_delay_us(50);
Send_A_Command(0b00001110);
_delay_us(50);
}
#endif
Part 18 - Using Alternative Power Sources
There are three main DC voltage sources available to supply power for our microcontroller projects: Batteries, wall adapters or the USB port of a computer. Generally, the power level requirement is dictated by the requirements of the devices that you use to build the circuit. These snaileye devices consist of the actual microcontroller and any peripheral hardware connected to it in some way. Peripherals may be sensors or other integrated circuit devices (ICs). So then, what do I mean by "dictated by the requirements of the devices?" To answer that question, let's start with a discussion of the power requirements of actual microcontroller.
Most microcontrollers can only accept voltage supplied within a specified range, and often this voltage is the determining factor for the operating speed of the system clock. In the case of the Atmega32 the manual states that it can receive voltage in the range of 4.5-5.5 volts. In terms of the voltage requirements for this device, that's not a large range. Therefore it might be somewhat difficult to add other peripherals that use different power requirements. Generally speaking though, the Atmega32 was made to receive 5 volts with leeway of 0.5 volts to either side, so that's what you have to deal with in order to use this device in a circuit. Fortunately its replacement (the Atmega324) can accept a greater voltage range. For example, the Atmega324A can accept a voltage in the range of 1.8-5.5 volts, with the lower portion of that range available for use in low-power circuits. The Atmega324p has a range of 2.7-5.5 volts, which still allows the use of standard peripherals that require either 3.3 volts or 5.5 volts.
As noted when selecting a microcontroller, you must also be conscious of the other components that will be used in the circuit. Try to find components that will match the voltage range of your microcontroller, so that you will not need to provide a voltage source at two levels. For example, if you have the older Atmega32 and you want to use an accelerometer that only accepts 3.3 volts, you will need to provide two voltage levels; one (4.5-5.5v) for the microcontroller, and another (3.3v) for the accelerometer.
Power Sources
So where do we get the power for our circuits? Fortunately, there are a few options available to us. We can either use batteries, a wall adapter, or the USB port from the computer. Keep in mind that if you are using a wall adapter, then its output voltage must be in direct current. Lets investigate each potential power source a bit further.
Batteries
There are many types and sizes of batteries, each with their own voltage rating. But to complicate matters a bit, they also have variable periods of operation in terms of the time that they can supply a circuit at their rated output. The amp-hour rating of a battery is a rating that indicates the amount of energy a battery can supply over a given period of time. When considering the small capacity batteries typically used in microcontroller circuits, we can also express this rating in terms of milliamp-hours (mAh), which is essentially how long (in hours) the battery will last if its current is being drawn from a load of 1 milliamps. If a battery is rated for 1000 mAh, it can theoretically supply a circuit requiring 100 milliamps for 10 hours, or a circuit requiring 50 milliamps for 20 hours. However since small batteries usually won't last very long when powering circuits containing a microcontroller in continuous operation, they should probably only be used for in situations where a power source cannot be furnished by other means. Rechargeable batteries may be a good option in such installations however, especially when they are used in a way that allows them to be recharged from a renewable source.
So the next question we need to consider then, since different battery types supply different voltages, is how to get the voltage we want?. This is relatively straightforward actually. Often all you need to do is to connect batteries together end-to-end (in "series"), until you reach the output voltage level you need. For example, if you are using batteries that give an output voltage of 1.5 volts each (a standard voltage for batteries like the common household AA, AAA, C and D-cell units), you can connect them together in series and just add the voltages from each battery. For example if two AA batteries are used in series, then you can expect a supply of about 3 volts; although you will probably see slightly more than that if the batteries are new. But you may be wondering--how then can we get a 5 volt supply? That's a bit of a problem actually, as there is no good way to get 5 volts from a battery power supply without using fib focused ion beam milling other components to regulate the voltage (as discussed below). If you were to add another battery to the 3 volts from the first two, then you will get 4.5 volts--which is too little. However if you then add another 1.5 volt battery, you get a whopping 6 volts. Since that is outside the rated operating range of the microcontroller, we might very well destroy it unless we can somehow "regulate" that voltage to be in the desired range.
To briefly review, two batteries are connected together in series by connecting the positive (+) pole of one battery to the negative (-) pole of the next. That's it!
Wall Adapter
These adapters are also often called "wall-warts," for obvious reasons. They are big and bulky, and stick out like a wart! If you are using a wall adapter, then you need to select one that has an output voltage matching the needs of the devices in your circuit. If you will be using a voltage regulator (discussed below), then you will need an adapter with an output voltage above that needed by your components.
All of the wall adapters that I have found or salvaged from my disposed electronics, have the input and output information specified (printed) someplace on the case. The input voltage for each adapter should of course match the power type in your country. For example, here in the US the power from a wall socket is in the range of 110-130 volts alternating current (AC). This is generally in the form of a sine wave, where the flow of current rapidly alternates is first in one direction, and then in the other. One other thing to look for on the wall adapter case is the output voltage, which will also be listed. For the purpose of our needs in circuits containing microcontrollers, the output voltage must be in the form of direct current (DC).
USB
This is an easy way to get a relatively smooth form of 5 volts of direct current. If your components can accept 5 volts, then this is usually a good power source. It is quite easy to tap into this voltage--just get out one of the many USB connectors that you probably have laying around your house, and strip the cable until you see four wires. You want the two wires that are either red or black, as the other wires in the cable are data lines that will not be needed for our purposes. Of the two wires that we're interested in, the black is the ground wire (0 volts) and the red wire is the 5 volt supply line. All you might need to do is to add a capacitor or two to smooth out the voltage, and you'll be good to go!
Voltage Regulators
As mentioned above, you may need a voltage regulator if you are using batteries, or if you can't find a wall adapter with the correct output voltage. While there are many regulators out there on the market, I generally use one or two of the following models: 7085, Max 603, or Max 604.
7805
This is a very popular "high-dropout" voltage regulator that is probably being used in many of the electronic gadgets in your home. This regulator outputs 5 volts, as long as the input voltage supplied to it is at least 2 volts higher than the 5 volts expected output. This 2-volt excess is called the dropout voltage. Although the regulator can accept 7 volts input to produce 5 volts of regulated output, it may be safer to provide a minimum of 8 volts input, in case there is a "ripple" (inconsistency) in the input voltage. You can also use input higher voltages, but you should not go higher than 30 volts--as the regulator will breakdown above this level.
Max 603/604
This voltage regulator also outputs 5 volts for the 603 (3 volts for the 604), but has a much lower dropout voltage. This IC device is intended for use with batteries and the 6 volts from four "AA" batteries will allow 5 volts of output (using the Max 603). Don't use an input voltage of more than 11.5 volts though, as the magic blue smoke will escape with anything above that! In other words, you'll destroy the voltage regulator. These regulators can even provide many other output voltages, if properly configured to do so using various resistors as will be discussed in the video for this tutorial.
Capacitor considerations:
You will notice in the video that I use a few capacitors. Since I've gotten a few requests to explain this, I have made another video on the subject. In short, the capacitors are used to smooth the "not so perfect" (pulsating) DC voltage. As you can see in the other video, there is a capacitor used before and after the regulator.
Chapter 1 | Chapter 2 | Chapter 3 | Chapter 4 | Chapter 5 | Chapter 6 | Chapter 7 | Chapter 8 | Chapter 9
|