| |
EL RINCÓN
DEL
CCS C
1 2
3 4
|
|
| |
Ideas,
Rutinas y
Técnicas
|
|
| |
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 (v.3.242) |
|
Í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.
|
|
|
-
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:
|
|
 |
Esta página se modificó el
27/12/2008
|