PicManía by RedRaven
 

Búsqueda personalizada

El Rincón del CCS C - Parte

 

  El lenguaje C es el que uso por defecto para el 90% de mis programas. Aquí os muestro algunos ejemplos de cómo hacer cosas con este idioma. Todos ejemplos que se muestran en esta sección se han realizado usando el PCW PICC de CCS (Veriones 3.242 a 4.096 según depende)

Más CCS C : 1 2 3 4
Índice General:
 
  • BIN2BCD
    para enviar nuestros bytes a los Displays de 7 segmentos.
  • Flex LCD
    una completa librería para manejar un módulo LCD con la posibilidad de configurar los pines a usar.
 
Serie Técnicas en C:
 
 
Nota1:
  • Los Micros usados son el 16F628, el 16F876A y el 18F4550 pero puede ser fácilmente adaptado a otros modelos de PIC. Ocasionalmente se usan también los 18F1320 y 18F2550.
  • Todos los tiempos están calculados para cristales de 4 Mhz y 20 Mhz, salvo en los 18F4550 y 18F2550 en funciones de USB que "corren" a 48 Mhz mediante el PLL interno.

 

 

 


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

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


#int_RTCC                              // Interrupción por desbordamiento
RTCC_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 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  
 
#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_rda
void 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.
 
   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 a segundo 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 de minuto.
 
   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-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 completas
int segundo=0x0;          // Segundos del Reloj
int minuto=0x0;           // Minutos del Reloj
int D1=0x00;              // Contenido de los Displays
int D2=0x00;
int D3=0x00;
int D4=0x00;
int l_digit, h_digit;     // Resultado de la conversión bin2bcd
int i;                    // index general

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

#int_RTCC                 // Interrupción por desbordamiento
RTCC_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;

  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;

}

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;
}

 

 
 

 

 

 


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 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_RW
int8 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

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

#ifdef USE_LCD_RW
int8 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_RW
while(bit_test(lcd_read_byte(),7)) ;
#else
delay_us(60);
#endif

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

  delay_cycles(1);

#ifdef USE_LCD_RW
output_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;

  output_low(LCD_RS);

#ifdef USE_LCD_RW
output_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_RW
delay_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)
  {
    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_RW
char 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:
 
   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 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_rda
void rda_isr() {

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

#int_RTCC
RTCC_isr(){

  // 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
        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 ////
//// 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
#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';
      }
    } 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, cada una con su función: [INTRO] 0x0D para 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 cbuff
char cbuff[lenbuff]; // Buffer
char rcvchar=0x00;   // último carácter recibido
int1 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 buffer
void echos(char c);         // Eco selectivo sobre RS232
void procesa_comando(void); // Procesa comando

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

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

  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() {

  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.
  }

  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:

 



Nota: Esta página Web esta repleta de imágenes, textos, logotipos y demás material extraídos de los mas variados medios de los que no soy ni autor ni depositario de los correspondientes derechos de autor, uso y/o reproducción. Si Ud. es depositario de dichos derechos y desea que el material correspondiente sea eliminado de esta Web no dude en ponerse en contacto conmigo mediante e-mail y será inmediatamente retirado. Gracias.
 
Visitas
Totales : 260543 Hoy: 2 Activas: 1 Vistas: 260543

Esta página fue modificada el 07-08-2010 22:42:21

           
 DmSoft WAMP Escribir Unreal