46
Template para el uso de la Interrupción RTCC mediante el TIMER0 La interrupción RTCC se produce cada vez que el contador TIMER0 pasa de FFh a 00h. El TIMER0 hace un cómputo completo de 00h a FFh cada 512 μS, sin embargo este tiempo puede ser cambiado mediante un preescaler o sea un divisor, ajustable. Los tiempos generados para cada configuración son (Ver Nota 1 ): :2 -> 512 μS al mínimo preescaler posible. :4 -> 1.0 mS :8 -> 2.0 mS :16 -> 4.0 mS :32 -> 8.1 mS :64 -> 16.3 mS :128 -> 33.3 mS :256 -> 66.6 mS al máximo preescaler posible. El Template que propongo usa un Preescaler de 128 para producir una interrupción RTCC cada 33.3 mS y así cada 30 veces que se produce cambio de estado la variable Flag, o sea 33.3 x 30 = 999 mS. Exactamente este Template es el utilizado en el experimento WinkIntc en el que hacemos parpadear un Led cada 0.25 segundos (aproximadamente). Template para el uso de la Interrupció n RTCC mediante el TIMER0 #include <16f628.h> // Selecciona el PIC #fuses XT,NOWDT,NOPROTECT,PUT,BROWNOUT // Opciones de configuración

Nuevo Material

Embed Size (px)

Citation preview

Page 1: Nuevo Material

Template para el uso de la Interrupción RTCC mediante el TIMER0

   La interrupción RTCC se produce cada vez que el contador TIMER0 pasa de FFh a 00h.

   El TIMER0 hace un cómputo completo de 00h a FFh cada 512 μS, sin embargo este tiempo puede ser cambiado mediante un preescaler o sea un divisor, ajustable. Los tiempos generados para cada configuración son (Ver Nota1):

:2   -> 512  μS al mínimo preescaler posible. :4   -> 1.0  mS :8   -> 2.0  mS :16  -> 4.0  mS :32  -> 8.1  mS :64  -> 16.3 mS :128 -> 33.3 mS :256 -> 66.6 mS  al máximo preescaler posible.

   El Template que propongo usa un Preescaler de 128 para producir una interrupción RTCC cada 33.3 mS y así cada 30 veces que se produce cambio de estado la variable Flag, o sea 33.3 x 30 = 999 mS.

   Exactamente este Template es el utilizado en el experimento WinkIntc en el que hacemos parpadear un Led cada 0.25 segundos (aproximadamente).

Template para el uso de la Interrupción RTCC mediante el TIMER0

#include <16f628.h>                    // Selecciona el PIC#fuses XT,NOWDT,NOPROTECT,PUT,BROWNOUT // Opciones de configuración#use delay(clock=4000000)              // Velocidad del Cristal : 4 Mhz

byte const NInts=30;                   // Numero de interrupciones para 1 Segundo

// VARIABLES GLOBALES

Page 2: Nuevo Material

char C_Ints=0;                         // Contador de Interrupciones ocurridaschar Flag=0;                           // Flag que cambia cada NInts interrupciones

#int_RTCC                              // Interrupción por desbordamientoRTCC_isr() {                           // del TIMER0 RTCC

  if(C_Ints > NInts){                  // Si las ints ocurridas > ints para 1 Seg.

    if(Flag==0){      Flag=1;    }    else{      Flag=0;    }    C_Ints=0;                          // Reinicializo Contador de Ints  }  ++C_Ints;                            // Incremento el número de interrupciones}                                      // Ocurridas

void main(void) {

  setup_counters(RTCC_INTERNAL,RTCC_DIV_128);// TIMER0: Clock Interno, Presescaler 128  setup_timer_1(T1_DISABLED);                // para una RTCC cada 33.3 milisegundos  setup_timer_2(T2_DISABLED,0,1);            // -> 1 Segundo = 30 RTCC  setup_comparator(NC_NC_NC_NC);  setup_vref(FALSE);  enable_interrupts(INT_RTCC);               // Habilito Interrupción RTCC  enable_interrupts(global);                 // Habilito Interrupciones

  do{ // Bucle infinito

    if(Flag==K){}    else    {                                        // si ha cambiado Flag ...

      // AQUI HAGO LO QUE DESEE CADA 1 SEGUNDO

      k=Flag;                                // Guardo estado

Page 3: Nuevo Material

anterior de Flag    }

  }While(TRUE);}

Descargar Template RTCC

   Haciendo Click sobre la imagen inferior puede verse un momento de la simulación realizada con el programa anterior haciendo parpadear un Led conectado a PORTB.0. (La simulación está realizada con el PIC Simulator IDE 5.22 de Oshon Soft)

 

 

Template para el uso de la Interrupción RDA de la USART

   La interrupción RDA se produce cada vez que en la USART hay disponible un carácter para ser leído.

   El buffer de recepción de la USART del PIC 16F628 dispone de solo 2 bytes por lo que es importantísimo el descargarlo tal como se van recibiendo los caracteres, de esta forma evitamos el que se vean tristemente perdidos en el limbo serie.   Este Template demuestra cómo recibir cada carácter que llega haciéndole eco desde el programa principal. Si necesitamos recoger muchos caracteres y nuestra rutina principal es larga y farragosa es interesante recibir mediante RDA los caracteres e irlos almacenando en un BUFFER más amplio para ser posteriormente tratados.

Template para el uso de la Interrupción RDA de la USART

Page 4: Nuevo Material

#include <16f628.h> // Selecciona el PIC#fuses XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT // Opciones de configuración#use delay(clock=4000000) // Velocidad del Cristal : 4 Mhz#use standard_io(b)#use rs232(baud=9600, xmit=PIN_B2, rcv=PIN_B1) // Definición del RS232

char Keypress=' ';

#int_rdavoid serial_isr() {

  Keypress=0x00;  if(kbhit()){    Keypress=getc();    if(Keypress!=0x00){      putchar(keypress);      keypress=0x00;    }  }}

void main() {

  enable_interrupts(global);  enable_interrupts(int_rda);

  printf("\r\n\Listen on RS232 (Int)\r\n");

  do {

  } while (TRUE);}

Descargar Template RDA

 

Una aplicación práctica de RTCC : Cronopic 1.0

   Este artículo nos presenta un método razonable para implementar un cronómetro en nuestro PIC. Hacemos uso en él del Template para el uso de la Interrupción RTCC mediante el TIMER0 descrito más arriba así como del Hardware descrito en Hardware de Experimentos : 4 x 7 Segmentos.

Page 5: Nuevo Material

   Para este ejemplo vamos a cambiar de micro y nos mudamos del 16F628 al 16F876A, aunque nuestro programa funcionaría exactamente igual en uno que en otro con solo cambiar el include correspondiente.

   No estaría de más que le dieses un vistazo al artículo Los Cristales y El Tiempo donde discutimos los cálculos que después vamos a utilizar en nuestro Cronopic 1.0.

   He intentado comentar suficientemente el código fuente, sin embargo debo explicar al menos que técnica he seguido para desarrollarlo. El asunto es como sigue:

   Cronopic 1.0 habilita la interrupción RTCC usando un Cristal de 4 Mhz y con un Preescaler de 1:256 por lo que se produce un desbordamiento cada 66.6 ms. Con 15 interrupciones de éstas tenemos 15 * 66.6 = 999 ms, o aproximadamente un segundo.

   Así que lo que vamos a implementar es un contador de segundos que solo se incrementa cada 15 RTCC's consecutivas. Para esto utilizamos la variable nRTCC que cuando es igual a la constante RTCCxS permite incrementar la variable segundo, que es nuestro contador de segundos transcurridos. Si segundo pasa de 59 incrementamos minuto, y si éste sobrepasa el valor de 59 volvemos a comenzar reiniciándolo a 0. Esto dentro de la rutina de tratamiento de la interrupción RTCC.

   En el bucle principal, y eterno, dentro de Main() habilitamos una variable ksegundo que si no es igual asegundo nos indica que el segundo actual ha cambiado. Al ocurrir esto disparamos la actualización de los valores a sacar por nuestros 7 segmentos. Para ello llamamos a time2bcd() que es la rutina que va a formatear segundo y minuto para que puedan ver visualizados. Inmediatamente hacemos ksegundo igual a segundo para que no volvamos a hacer esto mismo hasta que no cambie el segundo actual, que volveremos a detectar comparándolo con ksegundo.

   El formateo realizado en time2bcd() consiste en convertir segundo y minuto de sus actuales valores binarios a BCD que es el que acepta el driver de los displays. Esta conversión carga con sus nuevos valores las variables D1 y D2 que son los dígitos Low y Hight en que se convierte segundo y D3 y D4 que son los deminuto.

   Dentro del bucle principal de main() se llama constantemente a la rutina display_reloj() que es la encargada de poner los valores de D1, D2, D3 y D4 en el driver de los displays.Y eso esto, o casi todo ya que este Cronopic tiene un error de 1 milisegundo por cada segundo contado por lo que no debes tener una fe absoluta en él si tu vida depende de ello. No he querido complicarlo en esta primera versión pero no es difícil compensar este desfase usando el método que he bautizado como pic-

Page 6: Nuevo Material

bisisesto y que lo realizaremos para versiones posteriores de Cronopic.

Cronopic 1.0

#include <16f876a.h> // Selecciona el PIC#fuses XT,NOWDT,NOPROTECT,PUT,BROWNOUT // Opciones de configuración#use delay(clock=4000000) // Velocidad del Cristal : 4 Mhz#use standard_io(B) // PORTB en estandar IO digital#use fixed_io(b_outputs=PIN_B0,PIN_B1,PIN_B2,PIN_B3,PIN_B4,PIN_B5,PIN_B6,PIN_B7)

char const RTCCxS=15; // Número de RTCC's para 1 segundo con 4 Mhz / 1:256.

// VARIABLES GLOBALES

int nRTCC=0x00;           // Contador de interrupciones RTCC completasint segundo=0x0;          // Segundos del Relojint minuto=0x0;           // Minutos del Relojint D1=0x00;              // Contenido de los Displaysint D2=0x00;int D3=0x00;int D4=0x00;int l_digit, h_digit;     // Resultado de la conversión bin2bcdint i;                    // index general

void testdisplays(void);  // Función que testea los displaysvoid display_reloj(void); // Función que muestra el contenido del relojvoid time2bcd(void);      // Función que convierte minutos y segundos a 4 x BCDvoid bin2bcd(int valor);  // Función que convierte de Binario a BCD

#int_RTCC                 // Interrupción por desbordamientoRTCC_isr() {              // del TIMER0 RTCC

  if(++nRTCC==RTCCxS){    nRTCC=0x00;    if(++segundo>59){      segundo=0;      if(++minuto>59){        minuto=0;      }    }  }

}

void main(void) {

  int ksegundo=0x00;

Page 7: Nuevo Material

  setup_counters(RTCC_INTERNAL,RTCC_DIV_256); // TIMER0: Clock Interno y Preescaler  setup_timer_1(T1_DISABLED);  setup_timer_2(T2_DISABLED,0,1);  setup_comparator(NC_NC_NC_NC);  setup_vref(FALSE);

  enable_interrupts(INT_RTCC);// Habilito Interrupción RTCC  enable_interrupts(global);  // Habilito Interrupciones

  do{ // Bucle infinito

    if(segundo!=ksegundo){    // si cambia el segundo actualizo las      time2bcd();             // variables con lo que hay que mostrar      ksegundo=segundo;     }

    display_reloj();          // Muestra constantemente

  }While(TRUE);

}

void display_reloj(void){ // Función que muestra el contenido del reloj

  output_b(D1);           // Muestro 1er carácter de segundo  output_high(PIN_B4);  delay_us(30);  output_b(D2);           // Muestro 2do carácter de segundo  output_high(PIN_B5);  delay_us(30);  output_b(D3);           // Muestro 1er carácter de minuto  output_high(PIN_B6);  delay_us(30);  output_b(D4);           // Muestro 2do carácter de minuto  output_high(PIN_B7);  delay_us(30);

}

void time2bcd(void){     // Función convierte minutos y segundos a 4 x BCD

  bin2bcd(segundo);      // Paso de binario a BCD el segundo y actualizo  D1 = l_digit;          // contenido a mostrar  D2 = h_digit;  bin2bcd(minuto);       // Paso de binario a BCD el minuto y actualizo  D3 = l_digit;          // contenido a mostrar  D4 = h_digit;

}

Page 8: Nuevo Material

void bin2bcd(int valor){ // Función que convierte de Binario a BCD 

  h_digit=0;

  if (valor>=10){    do{      valor-=10;      h_digit++;    } while (valor>=10);  }  l_digit=valor;}

 

 

Bin2BCD

   Cuando queremos utilizar uno de esos drivers para Displays de 7 segmentos que solo aceptan datos en BCD se impone una rutina que nos convierta nuestro byte en tan estrambótico Binary Code Decimal.

   Ahí os dejo una función para realizar la necesaria conversión:

Bin2BCD (1):

int l_digit, h_digit;     // resultado de la conversion bin2bcd

void bin2bcd(int valor){  // Funcion que convierte de Binario a BCD

  h_digit=0;

  if (valor>=10){    do{      valor-=10;      h_digit++;    }while (valor>=10);  }  l_digit=valor;}

Page 9: Nuevo Material

 

Una librería para manejar un LCD con los Pines que deseemos: flex_lcd.c

   Aqui os brindo una librería para manejar un LCD con 4 bits de datos, pudiendo establecer los pines que deseemos para ellos y para los de control E, R/W y RS. Sólo hay que modificar los #defines de los mismos.

  Para usarla solo debéis incluir el correpondiente #include "flex_lcd.c" en vuestro programa.

flex_lcd.c

// flex_lcd.c

// Change these pins to fit your own board.

#define LCD_DB4 PIN_B4#define LCD_DB5 PIN_B5#define LCD_DB6 PIN_B6#define LCD_DB7 PIN_B7

#define LCD_RS PIN_C0#define LCD_RW PIN_C1#define LCD_E  PIN_C2

// If you only want a 6-pin interface to your LCD, then// connect the R/W pin on the LCD to ground, and comment// out the following line.

#define USE_LCD_RW 1

//========================================

#define lcd_type 2 // 0=5x7, 1=5x10, 2=2 lines#define lcd_line_two 0x40 // LCD RAM address for the 2nd line

int8 const LCD_INIT_STRING[4] ={  0x20 | (lcd_type << 2), // Func set: 4-bit, 2 lines, 5x8

Page 10: Nuevo Material

dots  0xc, // Display on  1, // Clear display  6 // Increment cursor};

//-------------------------------------void lcd_send_nibble(int8 nibble){  // Note: !! converts an integer expression  // to a boolean (1 or 0).  output_bit(LCD_DB4, !!(nibble & 1));  output_bit(LCD_DB5, !!(nibble & 2));  output_bit(LCD_DB6, !!(nibble & 4));  output_bit(LCD_DB7, !!(nibble & 8));

  delay_cycles(1);  output_high(LCD_E);  delay_us(2);  output_low(LCD_E);}

//-----------------------------------// This sub-routine is only called by lcd_read_byte().// It's not a stand-alone routine. For example, the// R/W signal is set high by lcd_read_byte() before// this routine is called.

#ifdef USE_LCD_RWint8 lcd_read_nibble(void){  int8 retval;  // Create bit variables so that we can easily set  // individual bits in the retval variable.  #bit retval_0 = retval.0  #bit retval_1 = retval.1  #bit retval_2 = retval.2  #bit retval_3 = retval.3

  retval = 0;

  output_high(LCD_E);  delay_cycles(1);

  retval_0 = input(LCD_DB4);  retval_1 = input(LCD_DB5);  retval_2 = input(LCD_DB6);  retval_3 = input(LCD_DB7);

  output_low(LCD_E);

  return(retval);}#endif

Page 11: Nuevo Material

//---------------------------------------// Read a byte from the LCD and return it.

#ifdef USE_LCD_RWint8 lcd_read_byte(void){  int8 low;  int8 high;

  output_high(LCD_RW);  delay_cycles(1);

  high = lcd_read_nibble();

  low = lcd_read_nibble();

  return( (high<<4) | low);}#endif

//----------------------------------------// Send a byte to the LCD.void lcd_send_byte(int8 address, int8 n){  output_low(LCD_RS);

#ifdef USE_LCD_RWwhile(bit_test(lcd_read_byte(),7)) ;#elsedelay_us(60);#endif

  if(address)  output_high(LCD_RS);  else  output_low(LCD_RS);

  delay_cycles(1);

#ifdef USE_LCD_RWoutput_low(LCD_RW);delay_cycles(1);#endif

  output_low(LCD_E);

  lcd_send_nibble(n >> 4);  lcd_send_nibble(n & 0xf);}

//----------------------------void lcd_init(void){  int8 i;

Page 12: Nuevo Material

  output_low(LCD_RS);

#ifdef USE_LCD_RWoutput_low(LCD_RW);#endif

  output_low(LCD_E);

  delay_ms(15);

  for(i=0 ;i < 3; i++)  {    lcd_send_nibble(0x03);    delay_ms(5);  }

  lcd_send_nibble(0x02);

  for(i=0; i < sizeof(LCD_INIT_STRING); i++)  {    lcd_send_byte(0, LCD_INIT_STRING[i]);

    // If the R/W signal is not used, then    // the busy bit can't be polled. One of    // the init commands takes longer than    // the hard-coded delay of 60 us, so in    // that case, lets just do a 5 ms delay    // after all four of them.#ifndef USE_LCD_RWdelay_ms(5);#endif}

}

//----------------------------

void lcd_gotoxy(int8 x, int8 y){  int8 address;

  if(y != 1)  address = lcd_line_two;  else  address=0;

  address += x-1;  lcd_send_byte(0, 0x80 | address);}

//-----------------------------void lcd_putc(char c){  switch(c)

Page 13: Nuevo Material

  {    case '\f':      lcd_send_byte(0,1);      delay_ms(2);      break;

    case '\n':      lcd_gotoxy(1,2);      break;

    case '\b':      lcd_send_byte(0,0x10);      break;

    default:      lcd_send_byte(1,c);      break;  }}

//------------------------------#ifdef USE_LCD_RWchar lcd_getc(int8 x, int8 y){  char value;

  lcd_gotoxy(x,y);

  // Wait until busy flag is low.  while(bit_test(lcd_read_byte(),7));

  output_high(LCD_RS);  value = lcd_read_byte();  output_low(lcd_RS);

  return(value);}#endif

void lcd_setcursor_vb(short visible, short blink) {  lcd_send_byte(0, 0xC|(visible<<1)|blink);}

Descargar flex_lcd.c

 

 

Modificando el ancho de un pulso, generado con RTCC, mediante ordenes RS232:

Page 14: Nuevo Material

   Este ejemplo nos muestra cómo podemos realizar el ultra famoso led parpadeando, pero realizado con la interrupción RTCC del Timer0, y recibiendo ordenes vía RS232 para poder modificar el ancho del pulso, o la separación entre dos pulsos sucesivos.

Conceptos, ideas y técnicas utilizadas:

- Cada vez que salta la interrupción RTCC cambiamos de LOW a HIGH, ó de HIGH a LOW, el pin RB0. Para ello utilizamos la variable Led que nos indica cual fue el último cambio para poder hacer el complementario.

- Cada vez que realizamos dicho cambio de LOW a HIGH, o de HIGH a LOW, cargamos el contador del Timer0 con un valor variable que podemos ajustar externamente: OffsetL para el semiperiodo LOW y OffsetH para el semiperiodo HIGH

(Si OffsetL y OffsetH son igual a cero, como es el caso cuando acabamos de resetear el micro, tenemos una onda cuyo periodo es exactamente igual a 2 * RTCC: Una RTCC completa conde ponemos RB0 en HIGH y otra RTCC completa en la que ponemos RB0 en LOW)

- Recibimos distintas ordenes desde el RS232 con las cuales modificamos los valores de OffsetH y OffsetL. 

- Cualquier valor mayor que 0 en OffsetH o en OffsetL hace que al producirse la correspondiente interrupción RTCC, que como sabéis salta al pasar el Timer0 de FFh a 00h, se carge el Timer0 con dicho valor: Por lo que la siguiente interrupción será mas corta, y de forma directamente proporcional al valor cargado, ya que en vez de tener que esperar a que Timer0 recorra todo su

Page 15: Nuevo Material

contador de 00h a FFh lo va ha hacer solamente desde OffsetH, u OffsetL, hasta FFh.

* Cuanto mayor sea OffsetH menos tiempo va a estar RB0 en HIGH.* Cuanto mayor sea OffsetL menos tiempo va a estar RB0 en LOW

- Si OffsetH y OffsetL tienen el mismo valor la onda producida tendrá iguales ambos semiperiodos, con valores distintos será por ende distintos ambos y por ello nos variará la frecuencia. Para mantener la frecuencia constante, o el periodo constante, debemos ampliar o disminuir complementariamente ambos Offset. Lo que se incremente uno de ellos debemos disminuir el otro, o viceversa.

- Hemos incluido una variable Onda con la que podemos ordenarle a qué parte de nuestro pulso va a afectar la siguiente orden: "C" (de cuadrada para ambos semiperiodos) "H" para el semiperiodo Alto y "L" para el semiperiodo Bajo.

Este ejemplito es básicamente un PWM realizado por Software que puede ser fácilmente adaptado a cualquier micro PIC, por ejemplo para manejar un Servo.

Fuente pwm_rtcc_232.c

#include <16f876a.h>#fuses XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT#use delay(clock=4000000)#use standard_io(b)#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)

int Led=0x00;int Offseth=0x00;int Offsetl=0x00;char Onda='C';char Keypress=' ';

void eco_offset(void);

#int_rdavoid rda_isr() {

  Keypress=0x00;  if(kbhit()){    Keypress=getc();  }}

#int_RTCCRTCC_isr(){

Page 16: Nuevo Material

  // RB0  Switch(Led){  Case 0: output_high(PIN_B0);          set_timer0(Offseth);          break;  Case 1: output_low(PIN_B0);          set_timer0(Offsetl);          break;  }  if(++Led>1){    Led=0;  }}

void main() {

  setup_counters(RTCC_INTERNAL,RTCC_DIV_2); // TEST

  enable_interrupts(int_rda);  enable_interrupts(global);

  printf("\r\n\PWM (RTCC) OS\r\n");

  Onda='C';  Offseth=0x00;  Offsetl=0x00;

  enable_interrupts(INT_RTCC);

  do {

    if(Keypress!=0x00){

      switch(Keypress){        // Tipo de Onda        case 'c': Onda='C';                  break;        case 'h': Onda='H';                  break;        case 'l': Onda='L';                  break;        // Incrementando y decrementando periodos        case '+': if(Onda=='C'){ ++Offseth; ++Offsetl; }                  if(Onda=='H'){ ++Offseth; }                   if(Onda=='L'){ ++Offsetl; }                   break;        case '-': if(Onda=='C'){ --Offseth; --Offsetl; }                   if(Onda=='H'){ --Offseth; }                   if(Onda=='L'){ --Offsetl; }                   break;        // Periodos Prefijados

Page 17: Nuevo Material

        case '1': if(Onda=='C'){ Offseth=0; Offsetl=0; }                  if(Onda=='H'){ Offseth=0; }                   if(Onda=='L'){ Offsetl=0; }                   break;        case '2': if(Onda=='C'){ Offseth=128; Offsetl=128; }                  if(Onda=='H'){ Offseth=128; }                   if(Onda=='L'){ Offsetl=128; }                   break;        case '4': if(Onda=='C'){ Offseth=192; Offsetl=192; }                  if(Onda=='H'){ Offseth=192; }                   if(Onda=='L'){ Offsetl=192; }                   break;      }      eco_offset();      Keypress=0x00;    }  } while (TRUE);

}

void eco_offset(void){  printf("\r\nOnda %c h%u l%u\r\n",Onda,Offseth,Offsetl);}

Descargar pwm_rtcc_232.c

 

 

Una librería para rastrear un Teclado matricial 4x4

   Aquí tenéis una librería que permite leer un teclado matricial de 16 teclas (4 filas x 4 columnas). Solo tenéis que incluir con #include esta librería en vuestro programa principal y llamar a la función kbd_getc() donde lo necesitéis.

Nota: Esta librería esta derivada de la original incluida en  \drivers del CCS PIC C. En cursiva marco los cambios que he realizado para adaptarla a mi hardware particular.

kbd_lib.c

/////////////////////////////////////////////////////////////////////////////// kbd_lib.c by Redraven //////// //////// Derived from kbdd.c ////

Page 18: Nuevo Material

//// Generic keypad scan driver //////// //////// kbd_init() Must be called before any other function. //////// //////// c = kbd_getc(c) Will return a key value if pressed or /0 if not //////// This function should be called frequently so as //////// not to miss a key press. //////// /////////////////////////////////////////////////////////////////////////////////// (C) Copyright 1996,1997 Custom Computer Services //////// This source code may only be used by licensed users of the CCS C //////// compiler. This source code may only be distributed to other //////// licensed users of the CCS C compiler. No other use, reproduction //////// or distribution is permitted without written permission. //////// Derivative programs created using this software in object code //////// form are not restricted in any way. ////////////////////////////////////////////////////////////////////////////////

////////////////// The following defines the keypad layout on port D

// Un-comment the following define to use port B

#define use_portb_kbd TRUE

// Make sure the port used has pull-up resistors (or the LCD) on// the column pins

#if defined(__PCH__)#if defined use_portb_kbd#byte kbd = 0xF81 // This puts the entire structure#else#byte kbd = 0xF83 // This puts the entire structure#endif#else#if defined use_portb_kbd#byte kbd = 6 // on to port B (at address 6)#else#byte kbd = 8 // on to port D (at address 8)#endif#endif

#if defined use_portb_kbd#define set_tris_kbd(x) set_tris_b(x)#else#define set_tris_kbd(x) set_tris_d(x)#endif

//Keypad connection: (for example column 0 is B0)

#define COL0 (1 << 0) // PIN_B0#define COL1 (1 << 1) // PIN_B1

Page 19: Nuevo Material

#define COL2 (1 << 2) // PIN_B2#define COL3 (1 << 3) // PIN_B3

#define ROW0 (1 << 4) // PIN_B4#define ROW1 (1 << 5) // PIN_B5#define ROW2 (1 << 6) // PIN_B6#define ROW3 (1 << 7) // PIN_B7

#define ALL_ROWS (ROW0|ROW1|ROW2|ROW3)#define ALL_PINS (ALL_ROWS|COL0|COL1|COL2|COL3)

// Keypad layout:char const KEYS[4][4] = {{'1','2','3','A'},                         {'4','5','6','B'},                         {'7','8','9','C'},                         {'*','0','#','D'}};

#define KBD_DEBOUNCE_FACTOR 33 // Set this number to apx n/333 where// n is the number of times you expect// to call kbd_getc each second

void kbd_init() {}

  char kbd_getc( ) {  static byte kbd_call_count;  static short int kbd_down;  static char last_key;  static byte col;

  byte kchar;  byte row;

  kchar='\0';  if(++kbd_call_count>KBD_DEBOUNCE_FACTOR) {    switch (col) {      case 0 : set_tris_kbd(ALL_PINS&~COL0);               kbd=~COL0&ALL_PINS;               break;      case 1 : set_tris_kbd(ALL_PINS&~COL1);               kbd=~COL1&ALL_PINS;               break;      case 2 : set_tris_kbd(ALL_PINS&~COL2);               kbd=~COL2&ALL_PINS;               break;      case 3 : set_tris_kbd(ALL_PINS&~COL3);               kbd=~COL3&ALL_PINS;               break;    }    if(kbd_down) {      if((kbd & (ALL_ROWS))==(ALL_ROWS)) {         kbd_down=false;         kchar=last_key;         last_key='\0';

Page 20: Nuevo Material

      }    } else {      if((kbd & (ALL_ROWS))!=(ALL_ROWS)) {          if((kbd & ROW0)==0)                   row=0;          else if((kbd & ROW1)==0)                        row=1;            else if((kbd & ROW2)==0)                           row=2;              else if((kbd & ROW3)==0)                             row=3;          last_key =KEYS[row][col];          kbd_down = true;      } else {         ++col;         if(col==4)            col=0;      }    }    kbd_call_count=0;  }  set_tris_kbd(ALL_PINS);  return(kchar);}

Descargar kbd_lib.c

 

Procesador de comandos vía RS232 (con Buffer de recepción)

   Hasta ahora, en nuestros trabajos anteriores, utilizábamos comandos enviados vía RS232 consistentes en un único carácter, con el que hacíamos ejecutar alguna de las funciones que nuestro programa implementaba. Este método es de muy corto alcance por dos motivos fundamentales: Primero porque eran ejecutados inmediatamente tan pronto eran recibidos y segundo porque no podíamos enviarle datos (una cadena de caracteres) para ser procesados.

   En este nuevo artículo vamos a solucionar exactamente eso. He imaginado un programa que admite dos comandos de alto nivel: uno de lectura "\r" sin argumentos y otro de escritura "\w" que va seguido de un argumento tan largo como necesitemos.

   Para manejar el envío de ambos comandos así como el/los argumentos necesarios he implementado lo básico para manejar el buffer en el PIC, a base de teclas únicas,

Page 21: Nuevo Material

cada una con su función: [INTRO] 0x0Dpara indicar que hemos terminado de escribir el comando, [RETROCESO] 0x08 para borrar el último carácter enviado y [ESCAPE] 0x1B para borrar todo el contenido actual del buffer. (Como podéis imaginar este mini-programa puede complicarse hasta el infinito y solo tenéis que añadir comandos a vuestra entera discreción).

   El programa va entonces recibiendo caracteres, uno tras otro, y guardándolos en el Buffer. Si recibe el comando [INTRO] habilita un flag para que se procese el comando, y tras ser procesado borra el buffer y vuelve a empezar. Las otras dos teclas de [RETROCESO] y [ESCAPE] las usamos para editar el contenido del buffer antes de enviarle la orden de procesado.

   He intentado comentar profusamente el programa para que sea meridianamente claro su funcionamiento y hacer así innecesarias mas explicaciones:

command_232_buffered.c

#include <16f876a.h>       // Definiciones del PIC 16F876A#fuses XT,NOWDT,NOPROTECT,NOLVP,PUT,BROWNOUT // Los Fuses de siempre#use delay(clock=4000000) // Oscilador a 4 Mhz#use rs232(baud=9600, xmit=PIN_C6, rcv=PIN_C7)// RS232 Estándar

// CONSTANTES /////////////////////////////////////////////////////////////////

int const lenbuff=32; // Longitud de buffer, Ajustar                      // a lo que desees (o te sea posible)

// VARIABLES EN RAM ///////////////////////////////////////////////////////////

int xbuff=0x00;      // Índice: siguiente char en cbuffchar cbuff[lenbuff]; // Bufferchar rcvchar=0x00;   // último carácter recibidoint1 flagcommand=0;  // Flag para indicar comando disponible 

// Declaración de Funciones ///////////////////////////////////////////////////

void inicbuff(void);        // Borra buffer int addcbuff(char c);       // añade carácter recibido al buffervoid echos(char c);         // Eco selectivo sobre RS232void procesa_comando(void); // Procesa comando

// INTERRUPCIONES /////////////////////////////////////////////////////////////

#int_rdavoid serial_isr() {    // Interrupción recepción serie USART

Page 22: Nuevo Material

  rcvchar=0x00;        // Inicializo carácter recibido   if(kbhit()){         // Si hay algo pendiente de recibir ...    rcvchar=getc();    // lo descargo y ...    addcbuff(rcvchar); // lo añado al buffer y ...    echos(rcvchar);    // hago eco (si procede).  }}

// Desarrollo de Funciones ////////////////////////////////////////////////////

void echos(char c){ // Echo selectivo ----------------------

  switch(c){    case 0x0D: printf(" [Ent] "); // Si he pulsado la tecla [Intro]               break;    case 0x08: printf(" [Del] "); // Si he pulsado la tecla [Retroceso]               break;    case 0x1B: printf(" [Esc] "); // Si he pulsado la tecla [Escape]               break;    default:   putc(rcvchar);     // Echo de cualquier otro carácter  }}

void inicbuff(void){ // Inicia a \0 cbuff -------------------  int i;

  for(i=0;i<lenbuff;i++){   // Bucle que pone a 0 todos los    cbuff[i]=0x00;          // caracteres en el buffer  }  xbuff=0x00;               // Inicializo el índice de siguiente                            // carácter}

int addcbuff(char c){ // Añade a cbuff -----------------------

  switch(c){    case 0x0D:           // Enter -> Habilita Flag para procesar      flagcommand=1;     // Comando en Main      break;    case 0x08:           // Del -> Borra último carácter del Buffer      if(xbuff>0) cbuff[--xbuff]=0x00;      break;    case 0x01B:          // Esc -> Borra el Buffer completamente      inicbuff();      break;    default:      cbuff[xbuff++]=c; // Añade carácter recibido al Buffer  }}

// Programa Principal /////////////////////////////////////////////////////////

void main() {

Page 23: Nuevo Material

  inicbuff(); // Borra buffer al inicio

  printf("\r\n\** RS232 Buffered **\r\n\r\n"); // Presenta menú  printf("[Enter] Procesa comando\r\n");  printf("[Escape] Borra todo el buffer\r\n");  printf("[Delete] Borra último carácter del buffer\r\n");  printf("[\\w] Comando Escribe\r\n");  printf("[\\r] Comando Lee\r\n");  printf("\r\n");

  enable_interrupts(int_rda); // Habilita Interrupción RDA  enable_interrupts(global);  // Habilita interrupciones

  do {

    if(flagcommand) procesa_comando(); // Si hay comando pendiente                                       // de procesar ... lo procesa.

  } while (TRUE);

}

// Procesador de Comandos /////////////////////////////////////////////////////

void procesa_comando(void){

  int i;  char arg[lenbuff]; // Argumento de comando (si lo tiene)

  flagcommand=0; // Desactivo flag de comando pendiente.  printf("\r\nProcesando ... "); // Monitorizo procesando ...

  for(i=0;i<lenbuff;i++){ // Bucle que pone a 0 todos los    arg[i]=0x00; // caracteres en el argumento  }

  if(cbuff[0]=='\\'&&cbuff[1]=='r'){ // Comparo inicio del buffer con comando "\r"

    printf("Leyendo ... ");         // Aqui lo que deseemos hacer con comando "\r"

  }

  if(cbuff[0]=='\\'&&cbuff[1]=='w'){ // Comparo inicio del buffer con comando "\w"    i=2;    do{ // Extraemos argumento del buffer       arg[i-2]=cbuff[i]; // a partir del 3er byte y hasta \0.    }while(cbuff[++i]!=0x00);

    printf("Escribiendo %s ... ",arg);// Aqui lo que deseemos hacer con comando "\w"                                      // Monitorizamos el argumento.

Page 24: Nuevo Material

  }

  inicbuff(); // Borro buffer.

  printf("Procesado.\r\n\r\n"); // Monitorizo procesado.}

Descargar rs232_buffered.c

   Y por último solo me queda mostrar los resultados sobre mi monitor RS232 en el PC:

Page 25: Nuevo Material

Nota: Esta página Web esta repleta de imágenes, textos, logotipos y demás material extraídos de los

Page 26: Nuevo Material

Inicios con el 18F2550 y 18F4550 en C

<< < (2/19) > >>

ccencho:Hola chicos aca subo el video del ejemplo ADC...espero opiniones icon_biggrin icon_biggrin icon_biggrinhttp://www.youtube.com/watch?v=wz-_Ce497ys

ccencho:Hola amigos aca les traigo avances con el modulo USB en el  ejemplo 3 y bueno antes del ejemplo y compartir mis avances....bueno debo decirles que este es un primer avance del uso de las librerias del ccscompiler.....para lograr la comunicacion usb.

Bueno en esta oportunidad cabe decir que....hay 4 principales tipos de comunicacion o clases con el usb las cuales son:

CDC ( Communications Devices Class)HID (Dispositivo de Interfas Humana, mouses,keypad,etc)BULK TRANSFERS USB( comunicacion bidireccional masiva)MSD USB Yo escoji para empezar la comunicación CDC ya que según revisando es la mas facil de empezar ya que simula un puerto serial COM y lo comunica con el USB, es decir utiliza el driver del puerto serial COM que trae Windows y lo comunica con el conector USB... así que se podría decir que todo los avances con el puerto serial( comunicacion rs232,485 programas echos en visual basic ,etc) nos vas servir mucho.

Lo primero que debemos hacer es tener el driver de microchip a la mano para poder instalarlo cuando windows no lo pida al momento de enchufar nuestro usb.

Lo adjunto al final del post.

Ahora......vamos a digitar el siguiente codigo:

Citar

#include <18f2550.h>                                 // declarar pic18f2550#fuses hspll,nowdt,noprotect,nolvp,nodebug,usbdiv,pll5,cpudiv1,vregen     // fuses

Page 27: Nuevo Material

#use delay(clock=48M)                                // clock  a 48Mhz#include <usb_cdc.h>                                 // declaro librerias USB

//*********************** configuracion y declaraciones ****************

#define ledv  PIN_B6                                 // asignando etiquetas a el pinb6#define ledr  PIN_B7#define ledon output_high                            // asignando etiquetas a la funcion output  #define ledoff output_low

void main(){                                         // inicio del codigo

   ledoff(ledv);                                     // enciendo el led rojo yq aun no detecta el host   ledon(ledr);      usb_cdc_init();   usb_init();                                        // inicializamos el USB   usb_task();                                        //habilita periferico usb e interrupciones   usb_wait_for_enumeration();                        //esperamos hasta que el PicUSB sea configurado por el host      ledoff(ledr);                                       // encendemos el led verde   ledon(ledv);

while(true)   if(usb_enumerated())                               //retorna verdadero si el dispositivo esta enumerado   {printf(usb_cdc_putc,"Enumerado\n\r");}             // envia una la palabra enumerado   }

El cual lo que hace es encender el un diodo led rojo al conectar el usb, una vez que el usb sea reconocido por el pc , el led rojo se apagara y se encenderá el led verde y luego enviara la palabra enumerado..Para visualizar la palabra es necesario utilizar el Hypeterminal o algún programa hecho en visual basic o C++.

Page 28: Nuevo Material

Continuara..... %&

ccencho:Bueno amigos retomando de tiempo  tm este tema con nuevos avances jeeee, bueno primero quiero culminar el post ultimo subiendo las el circuito+ imágenes que funciona jaaa

CIRCUITO

ccencho:Hola amigos aca continuando con la saga del USB  (st1) jaaa..les traigo el ejemplo 4....el cual trata del Control de dispositivos mediante una interfase creada en visual basic .

Debo decir que el programa para el PIC fue realizado en CCS compiler 4.093 , fue simulado en Proteus 7.6 sp0, la interfas fue echa en visual 6.0( las proximas versiones seran hechas en visual basic 2008 ), esta vez se utilizo el pic 18F4550 ,asi como debo decir que esto es un demo academico para que todos aquellos que se inician en esto puedan empezarlo de forma facil.

El proyecto fue probado en real y funciona al 100%.....aunq debo decir que hay otros ejemplos que los probe en proteus y  (st18) no funcionan como deberia ser...pero eso es tema de otros post....

"Antes de empezar ya con el ejemplito..debo decir que es algo avanzadito yq aun me falta explicarles bien todo el tema de los comando que use en CCS asi como del visual basic,reconocimiento del HOST y enumeracion,asi como otros puntos mas que me salte jeee pero lo haré en el proximo post"  (st4) (st4)

Bueno empezamos con el proyectito.....

1. Aca ta el codigo del pic..jee icon_lolCitar

//****************************************************************************//************* ENCIENDE LEDS DESDE LA PC( PC A PIC.....)*********************

Page 29: Nuevo Material

//****************************************************************************#include <18f4550.h>#fuses hspll,nowdt,nomclr,noprotect,nolvp,nodebug,usbdiv,pll1,cpudiv1,vregen#use delay(clock=48M)                                // clock  a 48Mhz#define USB_CON_SENSE_PIN PIN_B2#include <usb_cdc.h>                                 // declaro librerias USB#include <usb_desc_cdc.h>                             // Descriptores del dispositivo USB.//*********************** configuracion y declaraciones ****************#define ledv  PIN_B6                                 // asignando etiquetas a el pinb6#define ledr  PIN_B7#define ledon output_high                            // asignando etiquetas a la funcion output  #define ledoff output_lowint8 data;void main(){                                         // inicio del codigo   set_tris_d(0);   set_tris_b(0);    output_d(0);//********* CONFIGURAR EL HOST Y VISUALIZAR MEDIANTE LEDS************//      ledoff(ledv);                                      // enciendo el led rojo yq aun no detecta el host   ledon(ledr);      usb_cdc_init();   usb_init();                                        // inicializamos el USB   usb_task();                                        //habilita periferico usb e interrupciones   usb_wait_for_enumeration();                        //esperamos hasta que el PicUSB sea configurado por el host   ledoff(ledr);                                       // encendemos el led verde   ledon(ledv);//************ TERMINA DE VISUALIZAR LOS LEDS ****************************/////////////////////////////////////////////////////////////////////////////// ************** INICIO DEL PROGRAMA *************************************

while(!usb_cdc_connected()){}                      // espera a detectar una transmisión de la PC (Set_Line_Coding).{}                         

Page 30: Nuevo Material

while(true){      usb_task();   if(usb_enumerated()){                               //retorna verdadero si el dispositivo esta enumerado                                                          if(usb_cdc_kbhit()){                               // en espera de un nuevo caracter en el buffer de recepcion                                                 data=usb_cdc_getc();        if(data=='1'){ // ¿ llegó el caracter 1?              output_toggle(PIN_D0);}  // Conmuta Led 1.                                                        if(data=='2'){ // ¿ llegó el caracter 2?              output_toggle(PIN_D1);} // Conmuta Led 2.                                            if(data=='3'){ // ¿ llegó  el caracter 3?              output_toggle(PIN_D2); } //Conmuta Led 3.                                             if(data=='4'){ // ¿ llegó el caracter 4?              output_toggle(PIN_D3);} // Conmuta Led 4.                                              if(data=='5'){ // ¿ llegó el caracter 5?              output_toggle(PIN_D4);} // Conmuta Led 5.                                             if(data=='6'){ // ¿ llegó el caracter 6?              output_toggle(PIN_D5);} //Conmuta Led 6.                                             if(data=='7'){ // ¿ llegó el caracter 7?              output_toggle(PIN_D6);} // Conmuta Led7.                                            if(data=='8'){ // ¿ llegó el caracter8?              output_toggle(PIN_D7);} // Conmuta Led 8.                          if(data=='9'){ // ¿ llegó el caracter 9?              output_d(0); }   // apaga todo                           if(data=='a'){ // ¿l llegó el caracter a?              output_d(255);  }  // prende todo                                                          usb_cdc_putc(data);                                                                               }                       

Page 31: Nuevo Material

      }   }}   

La interfas la realize en visual asiq aca unas capturas de imagen  icon_lol:

Y bueno aca el circuito que ya es clasico....

Aca un videito de su funcionamiento...

http://www.youtube.com/watch?v=LjjkDPi2ThE

Bueno para todos aquellos que quieran probar esta aplicacion aca adjunto todo (programa pic .hex + circuito en proteus + archivo del visual basic)

ccencho:Ahora explicare las funciones(o comandos) que se utilizaron en el codigo anterior y asi poder tener las cosas mas claras que el agua de Huacachina  icon_lol icon_lol icon_lol, bueno..aca le vamos.

FUNCIONES USB PARTE 1

usb_init()

Esta función inicializa el hardware del USB, se encuentra en un bucle infinito hasta que el conector usb es conectado al bus( puede estar enumerado o no por el HOST), Habilita y utiliza las interrupciones del USB

usb_task()

Inicializa el periférico USB o resetea el USB stack y el periférico

Esta funcion esta siempre al tanto del SENSE pin( Pin RB2 en el 18F4550) para saber si el cable usb esta conectado o no, si esta conectado inicializa el usb si es requerido, de lo contrario ( cable no conectado) desactiva el puerto usb del

Page 32: Nuevo Material

pic.Todo esto ocurre si el SENSE PIN esta declarado.Si el SENSE PIN no esta declarado el hadware usb es iniciado de forma automatica al conectar el cable usb al host.

usb_cdc_init()

Sencillamente esta función configura el USB para que pueda ser usado por el puerto COM virtual. Tiene que ir si o si al inicio.Para los que han usado el puerto COM via RS232, sabran que se tiene que configurar parametros de pariedad y todo ese rollo bueno ...el USB_CDC_INIT() hace lo mismo por eso digo que se tiene que usas si o si ;)

Sino cheuqen loq lleva esta función en su interior:

  usb_cdc_line_coding.dwDTERrate = 9600;   usb_cdc_line_coding.bCharFormat = 0;   usb_cdc_line_coding.bParityType = 0;   usb_cdc_line_coding.bDataBits = 8;   (int8)usb_cdc_carrier = 0;   usb_cdc_got_set_line_coding = FALSE;   usb_cdc_break = 0;   usb_cdc_put_buffer_nextin = 0;   usb_cdc_get_buffer_status.got = 0;

Bueno regreso...jee es hora de comer..... (st16) (st16) ..Listo regrese de comer ..ahora seguimos donde me quede aya ..haber siguiente funcion:

usb_enumerated()

Retorna con un 1 lógico si el host enumera el dispositivo, una vez esto el  pic puede enviar o recibir paquetes de datos por el puerto.

usb_wait_for_enumeration()

Se encierra en un bucle hasta que el PicUSB sea configurado por el host( para aplicaciones donde el pic hace otras cosas mas es recomendable usar el usb_enumerated())

usb_cdc_kbhit()

Page 33: Nuevo Material

Devuelve TRUE( 1 lógico)  si hay uno o más caracteres recibidos y esperando en el búfer de recepción

usb_cdc_connected()

Devuelve TRUE si hemos recibido una Set_Line_Coding. En la mayoría de los programas de terminal por puerto serie (como  HyperTerminal), se enviará un Set_Line_Coding Mensaje cuando se inicia el programa y se abre el virtual Puerto COM. Esta es una forma simple para determinar si el PC está dispuesta a mostrar los datos en un programa de terminal serie.

usb_cdc_getc()

Recibe datos de la PC en el buffer del PICUsb. Si usted no quiere a esperar en un bucle infinito, use usb_cdc_kbhit  () primero para comprobar si hay datos  antes de llamar a usb_cdc_getc ().

usb_cdc_putc()

Transmite datos del PicUsb a la Pc

Bueno creo que es suficiente por ahora....asiq espero con este pequeño resumen de las funciones que hemos usado para manejar el modulo USB las cosas esten mas claras...bueno....si quieren mas detalles  lee revisen las librerias del CCScompiler que son:

usb_cdc.h y  usb_desc_cdc.h ahi esta al inicio de ellas la definición de las funciones mencionadas, asi como otras y otras que están en otras librerías dentro de las mismas  ;)

visual basic

  EJERCICIO DE LA CALCULADORA

El botón Nuevo CálculoPrivate Sub Command5_Click()Text1 = ""

Page 34: Nuevo Material

Text2 = ""Text3 = ""Text1.SetFocusEnd Sub

El botón SumarPrivate Sub Command1_Click()Text3 = Val(Text1) + Val(Text2)End Sub

El botón RestarPrivate Sub Command2_Click()Text3 = Val(Text1) - Val(Text2)End Sub

El botón MultiplicarPrivate Sub Command3_Click()Text3 = Val(Text1) * Val(Text2)End Sub

El botón DividirPrivate Sub Command4_Click()Text3 = Val(Text1) / Val(Text2)End Sub

El botón SalirPrivate Sub Command6_Click()Unload MeEndEnd Sub 

                                                                  EJERCICIO CALCULADORA 2

El botón CalcularPrivate Sub Command1_Click()If Option1 = True ThenText3 = Val(Text1) / Val(Text2)ElseIf Option2 = True ThenText3 = Val(Text1) * Val(Text2)ElseIf Option3 = True ThenText3 = Val(Text1) - Val(Text2)ElseIf Option4 = True ThenText3 = Val(Text1) + Val(Text2)End IfEnd Sub

El botón Otra OperaciónPrivate Sub Command2_Click()

Page 35: Nuevo Material

Text1 = ""Text2 = ""Text3 = ""Text1.SetFocusEnd Sub

                                                                 EJERCICIO CONVERSIONES

El botón ConvertirPrivate Sub Command1_Click()If Option1 = True ThenText2 = Val(Text1) * 1000ElseIf Option2 = True ThenText2 = Val(Text1) * 100ElseIf Option3 = True ThenText2 = Val(Text1) * 10ElseIf Option4 = True ThenText2 = Val(Text1) / 1000End IfEnd Sub                                                                         EJERCICIO TABLAS

El botón Ver TablaPrivate Sub Command1_Click()Dim res As Long, retornoFor n = 1 To 10res = Text1 * nretorno = Chr(13) & Chr(10)Text2 = Text2 & Text1 & " x " & n & " = " & res & retornoNext nEnd Sub

El botón Salir

If MsgBox("Por Favor Confirma Si Ya Te Quieres Ir", vbYesNo, "¿Qué Ya Te Quieres Ir? = vbYes ThenEndElseCancel = 1End If

                                                                EJERCICIO TERMÓMETRO

Boton salir

Private Sub cmbSalir_Click()Beep

Page 36: Nuevo Material

EndEnd Sub

Private Sub mnuFileExit_Click()EndEnd Sub

Barra

Private Sub vsbTemp_Change()txtCent.Text = vsbTemp.ValuetxtFahr.Text = 32 + 1.8 * vsbTemp.ValueEnd Sub