Using ADC in PIC16F877a EN/ES
6 comments
After having studied the Discrete Input Handling and Discrete Output Handling on a microcontroller we should be able to create our first automated electronic system, however, although in a process there are many discrete sensors, analog sensors also abound and there are almost always variables of this type that need to be monitored and controlled.
That is why before venturing into an automatic system we are going to study how to handle the reading of data from an analog sensor using the ADC microcontroller, show the "raw value" on a 16X2 LCD display and generate a couple of alarms if certain values are exceeded. Basically we will be creating a monitoring and alarm system for an analog signal.
Luego de haber estudiado el Manejo de entradas discretas y Manejo de salidas discretas en un microcontrolador deberíamos ser capaces de poder crear nuestro primer sistema electrónico automatizado, sin embargo, aunque en un proceso existen muchas sensores discretos, los sensores analógicos también abundan y casi siempre existen variables de este tipo que necesitan ser monitoreadas y controladas.
Es por eso que antes de aventurarnos a un sistema automático vamos a estudiar cómo manejar la lectura de datos desde un sensor analógico usando el ADC de microcontrolador, mostraremos el "valor crudo" en un display LCD 16X2 y generaremos un par de alarmas si se sobrepasan determinados valores. Básicamente estaramos creando un sistema de monitoreo y alarma para una señal analógica.
Technical details |
---|
Before starting any application we must consider the limitations of the components we are using to avoid damaging them or even receiving physical damage ourselves and ensure that our designs will work optimally, that said let's remember some important points:
Our microcontroller only handles inputs and outputs with maximums of 5V/20mA, introducing a value higher than this may cause damage to the microcontroller.
The analog physical variables that can be measured have different magnitudes, some of them have electrical nature such as resistance, current, voltage, electromagnetic force, among others ... however there are some that are not directly related to electricity such as temperature, flow, pressure, level, speed, among many others.
An electronic sensor that can measure any variable even if it is not electrical in nature will always have among its elements two that are very important, the primary element capable of being in contact with the physical variable and produce changes proportional to those of the variable and an element called transducer capable of converting these physical variations in electrical variations proportionally, these transducers are responsible for delivering an electrical variable that changes proportionally to how it does the physical variable.
The transducers will not always deliver values in a comfortable electrical range to connect to a microcontroller (0V-5V), in some cases the delivery is from 0 to 10V or even instead of voltage delivers current 4-20mA or resistance, when it is the case, it is the duty of the designer to create circuits that adapt these signals to a magnitude and range that can be used by the microcontroller (0-5V), do not connect this type of sensors directly to the inputs of a microcontroller, it is always advisable to review the datasheet of each component to avoid damaging them by mishandling.
Antes de iniciar cualquier aplicación debemos considerar las limitaciones de los componentes que estamos utilizando para evitar dañarlos o incluso recibir daños físicos nosotros mismos y garantizar que nuestros diseños funcionaran de forma optima, dicho esto pasemos a recordar algunos puntos importantes:
Nuestro microcontrolador solo maneja entradas y salidas con máximos de 5V/20mA, introducir un valor superior a este puede producir daños en el microcontrolador.
Las variables físicas del tipo analógico que pueden ser medidas presentan diferentes magnitudes, algunas de ellas poseen naturalezas eléctricas como resistencia, corriente, voltaje, fuerza electromagnética, entre otras... sin embargo existen algunas que no tienen relación directa con la electricidad como lo son, temperatura, flujo, presión, nivel, velocidad, entre muchas otras.
Un sensor electrónico que puede medir cualquier variable incluso si no es de naturaleza eléctrica siempre tendrá entre sus elementos dos que son muy importantes, el elemento primario capaz de estar en contacto con la variable física y producir cambios proporcinales a los de dicha variable y un elemento llamado transductor capáz de convertir estas variaciones físicas en variaciones eléctricas en forma proporcional, son estos transductores los encargados de entregar una variable eléctrica que cambia de forma proporcional a como lo hace la variable física.
Los transductores no siempre entregarán valores en un rango electrico cómodo para conectarlo a un microcontrolador (0V-5V), en algunos casos la entrega es de 0 a 10V o incluso en lugar de voltaje entrega corriente 4-20mA o resistencia, cuando es el caso, es deber del diseñador crear circuitos que adapten estas señales a una magnitud y rango que puedan ser usados por el microcontrolador (0-5V), no conectar este tipo de sensores directamente a las entradas de un microcontrolador, siempre es recomendable revisar el datasheet de cada componente para evitar dañarlos por mala manipulación.
ADC in PIC16F877a |
---|
A microcontroller has pins that can take different functions as needed and as the microcontroller allows, we have already seen that the same pin can be configured as input or output. But in addition to this there are pins with specific functions that can be used when needed, among these are the analog input pins that are used for the ADC process, these pins are identified as ANn where "n" is a number that distinguishes between them.
Un microcontrolador posee pines que pueden tomar diferentes funciones según sea necesario y el microcontrolador lo permita, ya vimos que un mismo pin puede ser configurado como entrada o como salida. Pero además de esto existen pines con funciones específicas que pueden ser usadas cuando se necesite, entre estos están los pines de entradas analógicas que son usados para el proceso de ADC, estos pines están identificados como ANn donde "n" es un número que los distingue entre ellos.
For a PIC16F877A microcontroller we have a total of 8 analog inputs distributed 5 on Port A pins 2, 3, 4, 5 and 7 and 3 on Port E pins 8, 9 and 10. This means that with a single microcontroller of this model we can handle a maximum of 8 analog signals, for processes that need to control more than 8 our microcontroller is no longer useful and it is necessary to integrate external ADCs, replace it with a more powerful microcontroller or use more than one microcontroller of this model (the latter case is possible but makes no sense since an external ADC would be cheaper than a second microcontroller).
Each analog input can have a resolution of 8 or 10 bits, the higher the resolution the more accurate the measurement so it makes no sense to measure with 8bits when we can use 10bits. What a 10bit resolution implies is that the microcontroller will be able to take a number of samples of the original signal and represent them in 10bits, that is, a numerical quantity that can be represented with this number of bits and goes from 0 to 1023 (1024 values in total).
This value read in the range from 0 to 1023 I have baptized it as "raw value" because this reading will only indicate if the variable is in a minimum, maximum or intermediate values but it will not indicate the exact value that the variable has, let's understand that if in the input 5V are present the reading will be 1023, someone who is reading a reading of 1023 on a screen has no way of knowing what type of variable is being measured and much less what are the values represented in the physical magnitude of that variable, so I have called it "raw value" because it must be processed with mathematical formulas to obtain the value we want to read in relation to the measured variable.
To know the margin of error in the conversion is necessary to divide the maximum value that we cosideramos will have our variable between the maximum number of values that can be represented with 10bits (1024) and this will give us a quantity that will be multiplot of each bit, for example if our variable is voltage and the maximum is 5V we divide 5/1024 = 4. 8mV, this means that each measurement will be a multiple of 4.8mV, for example if we have 7mV input this value will not be shown, instead we will use an approximation close to a multiple of 4.8mV which in this case would be 9.6mV, as we notice here there is a small error that if we can not afford in a demanding process is necessary to use an ADC of higher resolution.
Para un microcontrolador PIC16F877A tenemos un total de 8 entradas analógicas distribuídas 5 en el Puerto A pines 2, 3, 4, 5 y 7 y 3 en el Puerto E pines 8, 9 y 10. Esto quiere decir que con un solo microcontrolador de este modelo podemos manejar un máximo de 8 señales analógicas, para procesos que necesiten controlar más de 8 nuestro microcontrolador deja de ser útil y es necesario integrarle ADCs externos, reemplazar por un microcontrolador más potente o usar más de un microcontrolador de este modelo (este último caso es posible pero carece de sentido ya que un ADC externo sería más económico que un segundo microcontrolador).
Cada entrada analógica puede tener una resolución de 8 o 10 bits, mientras mayor sea la resolución más precisa será la medición así que no tiene sentido medir con 8bits cuando podemos usar 10bits. Lo que implica una resolución de 10bits es que el microcontrolador podrá tomar un número de muestras de la señal original y los representará en 10bits esto es, una cantidad númerica que puda ser representado con este número de bits y va desde 0 a 1023 (1024 valores en total).
Este valor leído en el rango de 0 a 1023 lo he bautizado como "valor crudo" porque esta lectura solo indicará si la variable está en un mínimo, máximo o valores intermedios pero no indicará el valor exacto que posee la variable, comprendamos que si en la entrada están presente 5V la lectura será 1023, alguien que está leyendo una lectura de 1023 en una pantalla no tiene como saber que tipo de variable se está midiendo y mucho menos cuales son los valores representados en la magnitud física de dicha variable, por eso lo he llamado "valor crudo" porque debe procesarse con fórmulas matemáticas para obtener el valor que deseamos leer en relación a la variable medida.
Para conocer el margen de error en la conversión es necesario dividir el valor máximo que cosideramos tendrá nuestra variable entre la máxima cantidad de valores que pueden ser representados con 10bits (1024) y esto nos arrojará una cantidad que será multiplo de cada bit, por ejemplo si nuestra variable es voltaje y el máximo es 5V dividimos 5/1024 = 4.8mV, esto significa que cada medición será múltiplo de 4.8mV, por ejemplo si tenemos 7mV de entrada este valor no se mostrará, en su lugar se usará una aproximación cercana a un múltiplo de 4.8mV que en este caso sería 9.6mV, como notamos aquí existe un pequeño error que si no nos podemos permitir en un proceso exigente es necesario usar un ADC de mayor resolución.
ADC Instructions |
---|
Now that we know the physical connections of our analog signals and some of their characteristics let's proceed with the instructions that we will use in our compiler to correctly handle the ADC of our microcontroller:
- setup_adc_ports(); It is used to configure which pin or pins will be used as analog inputs, inside the parenthesis you can write the name of the pin with its reference ANn, if you want to enable several you can indicate them separating them with the symbol "_" example: AN0_AN7, you can also enable the Vref or Vss inputs with this instruction, in the following table are presented all the options available for this instruction.
Ahora que ya conocemos las conexiónes físicas de nuestras señales analógicas y algunas de sus características vamos a proceder con las instrucciones que usaremos en nuestro compilador para manejar correctamente el ADC de nuestro microcontrolador:
- setup_adc_ports(); Se utiliza para configurar cual o cuales pines serán usados como entradas analógicas, dentro del parentesis se puede escribir el nombre del pin con su referencia ANn, si se desean habilitar varias se indican separandolas con el simbolo "_" ejemplo: AN0_AN7, también se puede habilitar las entradas Vref o Vss con esta instrucción, en la tabla siguiente se presentan todas las opciones disponibles para esta instrucción.
@electronico
- setup_adc(); It is used to configure the type of frequency with which the ADC will work, generally the internal clock of the microcontroller is used (adc_clock_internal) but it is not the only option available, it is also possible to use different frequency dividers, I will quote the table of general options but in our applications we will always use internal clock.
- setup_adc(); Se usa para configurar el tipo de frecuencia con la que trabajará el ADC, generalmente se usa el reloj interno del microcontrolador (adc_clock_internal) pero no es la única opción disponible, también es posible usar distintos divisores de frecuencia, citaré la tabla de opciones generales pero en nuestras aplicaciones siempre usaremos reloj interno.
@electronico
setup_vref(); used to enable the Vref pins in some cases when the maximum of the analog signal is less than 5V, then the maximum expected is fed by the Vref pins.
set_adc_channel(); Allows to select the channel on which the reading will be performed, here we place the channel in numerical form, as there are 8 channels we can choose from 0 to 7.
read_adc(); We will use this instruction to read the ADC value (raw value).
setup_vref(); se usa para habilitar los pines Vref en algunos casos cuando el máximo de la señal analógica es menor a 5V, entonces se alimenta por los pines Vref el máximo esperado.
set_adc_channel(); Permite seleccionar el canal en el que se realizará la lectura, aquí colocamos el canal en forma númerica, como son 8 canales podemos elegír del 0 al 7.
read_adc(); Usaremos esta instrucción para leer el valor del ADC (valor crudo).
Programming...
|
---|
After all this information we only need to create an example application that can illustrate what has been said and polish the knowledge acquired. To do this we will use a potentiometer that allows us to vary an input voltage from 0 to 5V which will be read by an analog input AN0, in a LCD16X2 display will show the raw value of the reading (0-1023) and set two alarms, if the value is greater than 800 will turn on a yellow LED and display a warning message "WARNING! !!", if the value exceeds 1000 we will change the message to "DANGER!!!" and turn on a red led, in both cases of alarm both the display and the shift led will flash to attract the operator's attention.
Luego de toda esa información solo nos falta crear una aplicación de ejemplo que pueda ilustrar lo dicho y pulir los conocimientos adquiridos. Para ello vamos a usar un potenciometro que nos permita variar un voltaje de entrada de 0 a 5V el cual estará siendo leído por una entrada analógica AN0, en un display LCD16X2 mostraremos el valor crudo de la lectura (0-1023) y estableceremos dos alarmas, si el valor es mayor a 800 encenderemos un led amarillo y mostraremos un mensaje de advertencia "WARNING!!!", si el valor pasa de 1000 cambiaremos el mensaje a "DANGER!!!" y encenderemos un led rojo, en ambos casos de alarma tanto la pantalla como el led de turno se haran intermitentes para atraer la atención del operador.
We will write our header as usual but we will add the line ADC = 10 which defines that we will use a resolution of 10 for the ADC. Since we will be using LCD we must also add the LCD connection settings in the microcontroller and the LCD_16x2.c library for it to work correctly.
Escribiremos nuestro encabezado como de costumbre pero añadiremos la línea ADC = 10 la cual define que usaremos una resolución de 10 para el ADC. Como usaremos LCD debemos también añadir las configuraciones de conexión del LCD en el microcontrolador y la librería LCD_16x2.c para que funcione correctamente.
#include <16f877a.h>
#device ADC = 10
#fuses HS,NOWDT,NOPROTECT,NOPUT,NOLVP,BROWNOUT
#use delay(clock=20M)
#use standard_io(D)
#define LCD_DB4 PIN_D4
#define LCD_DB5 PIN_D5
#define LCD_DB6 PIN_D6
#define LCD_DB7 PIN_D7
#define LCD_RS PIN_D2
#define LCD_E PIN_D3
#include <LCD_16X2.c
Next we are going to create the variable raw_value of type long because in this variable we will copy the reading of our analog input to use it later at will, then in the main program we initialize the LCD, we enable the analog input AN0 in which we will read our analog data and we define the internal clock as working frequency.
A continuación vamos a crear la variable raw_value de tipo long porque en esta variable copiaremos la lectura de nuestra entrada analógica para posteriormente usarla a gusto, luego en el programa principal inicializamos el LCD, habilitamos la entrada analógica AN0 en la cual leeremos nuestro dato analógico y definimos el reloj interno como frecuencia de trabajo.
void main()
{
lcd_init();
setup_adc_ports(AN0);
setup_adc(adc_clock_internal);
Now we create the while loop in infinite repeat mode and inside it we write the code we want to keep repeating, we start by setting the read on channel 0 and set a small delay of 2us between each read, then we set our raw_value variable to the value of the analog read (read_adc()) for ease of use in later instructions. We then clear our screen and begin to steadily print the value of raw_value.
Ahora creamos el bucle while en modo repetición infinita y dentro de él escribimos el código que queremos se mantenga en repetición, iniciamos configurando la lectura en el canal 0 y establecemos un pequeño retardo de 2us entre cada lectura, luego igualamos nuestra variable raw_value al valor de la lectura analógica (read_adc()) para mejor facilidad de uso en las instrucciones posteriores. Luego limpiamos nuestra pantalla y comenzamos a imprimir de forma constante el valor de raw_value.
while(true)
{
set_adc_channel(0);
delay_us(2);
raw_value = read_adc();
lcd_clear();
lcd_gotoxy(1,2);
printf(lcd_putc, "ADC: %Lu", raw_value);
delay_ms(100);
Now we will create two if conditions from the value of raw_value, one for the WARNING warning when the value is greater than 800 and less than 1000 and another for the DANGER when the value is greater than or equal to 1000, we will write in those conditions the events that we established in the initial statement to this application.
Ahora crearemos dos condiciones if a partir del valor de raw_value, una para la advertencia de WARNING cuando el valor sea mayor que 800 y menor que 1000 y otra para el DANGER cuando el valor sea mayor o igual que 1000, escribiremos en esas condiciones los eventos que establecimos en el enunciado incial a esta aplicación.
if(raw_value > 800 && raw_value < 1000)
{
lcd_clear();
lcd_putc("WARNIN!!!");
lcd_gotoxy(1,2);
printf(lcd_putc, "ADC: %Lu", raw_value);
output_high(PIN_D0);
delay_ms(300);
lcd_clear();
output_low(PIN_D0);
delay_ms(50);
}
else if(raw_value >= 1000)
{
lcd_clear();
lcd_putc("DANGER!!!");
lcd_gotoxy(1,2);
printf(lcd_putc, "ADC: %Lu", raw_value);
output_high(PIN_D1);
delay_ms(300);
lcd_clear();
output_low(PIN_D1);
delay_ms(50);
}
}
}
Finally, after a very long but necessary day of content, we will proceed to simulate our program in Proteus to check the results.
Finalmente, luego de una muy larga pero necesaria jornada de contenido, vamos a proceder a simular nuestro programa en Proteus para comprobar los resultados.
Comments