TODOPIC
Microcontroladores PIC => Lenguaje C para microcontroladores PIC => Mensaje iniciado por: jumu en 28 de Octubre de 2014, 11:31:51
-
Buenos Dias a Todos.
Hace mucho tiempo no pasaba por aqui, bueno tengo un proyecto personal, bueno mas que personal es para aprender algo nuevo, quiero controlar 3 servomotores con el PIC16F887 o con el 18F45K22 cualquiera de los dos, yo se que para controlar los servomotores a una frecuencia baja se debe de utilizar temporizador, me encontre una pagina donde con las imagenes mas o menos entendi un poco de como funciona aqui el link:
XC8 12f675 İle 5 Kanal Yazılımsal PWM (http://ramazansubasi.net/xc8-12f675-ile-5-kanal-yazilimsal-pwm/)
En esa pagina hacen el codigo con el PIC12F675 en XC8, yo quiero hacer lo mismo nada mas que con los PIC's antes mencionados, el problema es que no se como configurar la frecuencia, yo se que son 20ms el periodo pero no se como configurar el Timer para eso.
alguien con mas conocimientos que yo que por cierto son pocos :huh: que me ayude en esto.
Les agradesco de ante mano.
-
La difrencia es que el PIC16F887 tiene un modulo CCP, el cual te lo va a hacer mas simple, aunque funciona igual con el TMR2
En el datasheet del integrado pagina 128, aparecen las funciones y todos los registros involucrados, asi tambien los pasos que debes seguir para iniciarlo, pagina 131. Y tambien encontre esto:
Utilidad del XC8 para generar el codigo:
http://microchip.wikidot.com/8bit:mccpwm
Codigo ejemplo en XC8 para un 16F886 (Misma familia que el 16F887)
http://waihung.net/microchip-pic16-pic18-pwm-mode/
Usando esta pagina podes obtener los valores de los registros:
http://www.micro-examples.com/public/microex-navig/doc/097-pwm-calculator.html
Aunque observando para 50Hz vas a tener que disminuir mucho tu frecuencia del CPU, a aproximadamente 500khz,
Esto lleva a que uses el Oscilador interno y dividirlo en el postscaler (siempre y cuando no hagas otras cosas que requieran velocidad de ejecucion).
O si queres mantener la velocidad con un cristal de 20Mhz ( o con el oscilador interno a 8Mhz ) debes usar timers. El timer cuenta suma 1 cada 4/fIN ( donde fIN es o 20Mhz o 8 Mhz en nuestro ejemplo, supongamos 8Mhz), es decir que va a contar 1 cada 500nS (*2 debido al prescaler que es el minimo si usas el TMR0 te daria 1uS), podes usar el prescaler del TMR0 para que vaya mucho mas lento, dando como maximo 128uS con (1:256).
En fin depende con que oscilador lo estes usando.
Podes tambien usar el MISMO codigo, solamente cambiando los valores de TMR0 y Intcount asi corresponde a tu frecuencia ( sin mencionar los registros y fuses )
-
Gracias por responder KILLERJC.
he estado un poco alejado de esto pero ya volvi.
ahora estoy haciendo el codigo pero no logro conseguir una frecuencia de 50hz. me pueden ayudar?
aqui esta el codigo:
/*
* File: Test_PIC16F84A.c
* Author: Jose Luis
*
* Created on 01 de diciembre de 2014, 10:20 AM
*/
#include <xc.h>
// CONFIG
#pragma config FOSC = HS // Oscillator Selection bits (HS oscillator)
#pragma config WDTE = OFF // Watchdog Timer (WDT disabled)
#pragma config PWRTE = ON // Power-up Timer Enable bit (Power-up Timer is enabled)
#pragma config CP = OFF // Code Protection bit (Code protection disabled)
#define _XTAL_FREQ 10000000 // 10MHz
#define PWM_Pin RA0
unsigned char DutyPWM, i;
void InitPWM(void) {
TRISAbits.TRISA0 = 0;
PORTAbits.RA0 = 0;
T0IE = 1; //Enable Timer0 Interrupt
T0IF = 0; //Clear Timer0 Flag
GIE = 1; //Enable Global Interrupt
OPTION_REGbits.T0CS = 0; //Internal instruction cycle clock
OPTION_REGbits.PSA = 0; //Prescaler is assigned to the Timer0
OPTION_REGbits.PS2 = 1; //PS2:PS1 Prescaler 1:256
OPTION_REGbits.PS1 = 1;
OPTION_REGbits.PS0 = 1;
}
void interrupt PWM(void) {
if (T0IF) {
if (PWM_Pin) {
PWM_Pin = 0;
TMR0 = DutyPWM;
} else {
PWM_Pin = 1;
TMR0 = 255 - DutyPWM;
}
T0IF = 0;
}
}
void main() {
TRISA = 0x00; // PORTA
TRISB = 0x00; // PORTB
PORTA = 0x00; // PORTA
PORTB = 0x00; // PORTB
InitPWM();
while (1) {
for (i = 0; i < 255; i++) {
DutyPWM = i;
__delay_ms(100);
}
if (i == 254) {
i = 0;
}
}
}
alguien que me ayude, por lo menos ya tengo un pwm :D :D
otro dato, cambie el pic ahora estoy usando un pic16f84a para un proyecto pequeño que tengo en mente pero necesito pwm a 50hz para controlar servomotores, el pic puede correr a 10mhz o a 20mhz.
-
aca (http://www.todopic.com.ar/foros/index.php?topic=43789.0) hice el programa para controlar un servo SG90 (500 uS - 2400 uS), con el 16f887, corre a 8MHz, igual y te sirve como base para controlar los 3.
-
Empecemos con que el pic16f84a NO posee modulo CCP ( por lo tanto no tenes PWM en hardware ) pero si podes hacerlo en software.
ESTO POR SOFTWARE - Sin interrupciones
Como tu software tiene que controlar el periodo, jamas podes tener un __delay_ms(100); , el cual los 100ms son mas grandes que el periodo mismo de la señal. Que para 50hz son 20ms.
Me imagino el programa asi:
while(1) {
Encender led
Esperar X tiempo
apagar led
Esperar Periodo - X tiempo
}
Siempre y cuando X sea menor que tiempo que el periodo.
Eso te daria algo aproximado. Pero tendrias muchos problemas para agregar si quisieras hacer mas cosas, ya que deberias contar el tiempo que necesitas para hacer lo demas para restarselo.
Con interrupciones
Entonces si quisieras hacer algo mas aparte de eso y quisieras algo mas exacto deberias usar interrupciones. Tal como lo hiciste.
Lo que podrias hacer es dividir la cantidad de tiempo en la resolucion que le queres dar. Por ejemplo. si queres 8 bits de resolucion, 256 posibilidades. Haces tu disparo cada (periodo / 256) para el TMR0.
Entonces vos con una variable controlas cuando activa o no, si queres cambiar el duty, cambias DutyPWM
void interrupt PWM(void) {
T0IE = 0;
T0IF = 0;
if (cuentaints < DutyPWM) {
PWM_Pin = 1;
} else {
PWM_Pin = 0;
}
cuentaints ++;
if (cuentaints >= 256) {cuentaints = 0}
T0IE = 1;
}
otro dato, cambie el pic ahora estoy usando un pic16f84a para un proyecto pequeño que tengo en mente pero necesito pwm a 50hz para controlar servomotores, el pic puede correr a 10mhz o a 20mhz.
El pic16f84a puede usar un cristal de 20Mhz segun el datasheet pero recorda que la velocidad maxima esta dividida por 4. No significa que tengas una salida de 20mhz. Ejecuta instrucciones cada 4/20Mhz
-
gracias por ayudarme.
tengo una duda, veo que el codigo que uds me dan y el que tengo son diferentes, y ya me confundi.
que valor tengo que poner en TMR0 para que la señal sea de 50hz?
gracias d ante mano
-
En si el codigo que te pase es el mismo codigo que te paso Carl47D. Tienen el mismo enfoque.
Es decir no usar el TMR para contar las interrupciones, en el caso de Carl47D para que le de un valor exacto de tiempo, el ultimo recorrido no era FULL. Entonces tuvo que implementar unas banderas para determinar si era la ultima cuenta y asignarle un valor distinto al TMR0.
En mi codigo:
Yo defino un valor muy inferior , por ejemplo lo hago con el prescaler en 1:2.
Por ejemplo si quisiera unas 200 posiciones entre 0 y 100. Me quedaria 20ms/200 = 100us
Para 100us, con el prescaler en 1:2 serian 100us / 0.8us = 125
El valor que le cargaria al TMR siempre es de 131 ( 255 - 125 + 1 ).
Y contaria las interrupciones, si quiero un duty de 50% contaria 100 interrupciones en 1 y lo demas en 0. Si quisiera de 25% contaria solo 50 con la salida en 1 y lo demas en 0.
En tu caso
utilizas el TMR para cambiar de estados. En ambos casos hay que calcular el temporizador.
a esa frecuencia 10Mhz , tenes que el TMR0 sin prescaler cuenta cada 4/10Mhz = 0.4us
Con prescaler 1:256 -> 0.4us*256 = 102.4us
Con lo cual para tu periodo de 20ms ( 50 Hz ) tendria que contar : 20ms/102.4us = 195.3125
Ese es el TOTAL que va a definir tu periodo. Redondeemos a 195
Obviamente tu duty va a estar entre 0 < duty < 195
Y el valor minimo a cargar es de 255 - 195 + 1 = 61 en el TMR0 (complemento a 2 )
El tiempo en 1 va a estar dado por el duty ( recorda que es ascendente por lo tanto es 255 - duty + 1 ).
Y el tiempo en 0 por el (periodo - duty), es decir lo que le falta para llegar al periodo, y tenes que cargar el complemento a 2 de eso tambien xD. Es decir:
255 - (periodo - duty) + 1 = 255 - 195 + 1 + duty = 61 + duty
void interrupt PWM(void) {
// Aqui desabilitar interrupciones del TMR0
if (T0IF) {
if (PWM_Pin) {
PWM_Pin = 0;
TMR0 = 61 + DutyPWM; //Tiempo en 0
} else {
PWM_Pin = 1;
TMR0 = 255 - DutyPWM + 1; // Tiempo en 1
}
T0IF = 0;
// Aca habilitar las interrupciones nuevamente
}
}
En fin, vos activas tus salidas de acuerdo a lo que lleva contado el TMR0, yo uso el TMR0 para definir un tiempo exacto y cuento las interrupciones, dependiendo de la cantidad de interrupciones va a ser mi numero a contar.
Y yo puse el ejemplo de hacerlo con 200, pero lo podes hacer con 256 ( asi es de 8 bits ).
-
haa ya entendi, hay dos formas de hacer pwm con el timer, ahorita buscando por el universo del internet encontre una pagina donde hacen 5 pwm, aunque la pagina esta en no se que idioma el codigo se entiende un poco, y por lo que me explicaste este codigo lleva la idea que tu me diste. aqui pongo la pagina y el codigo.
http://ramazansubasi.net/xc8-12f675-ile-5-kanal-yazilimsal-pwm/
/*
* File: main.c
* Author: Ramazan Subasi (subram)
*
* Created on 22 Ocak 2014 Carsambai, 17:00
*/
#include <xc.h>
#pragma config FOSC = INTRCIO // Oscillator Selection bits (INTOSC oscillator: I/O function on GP4/OSC2/CLKOUT pin, I/O function on GP5/OSC1/CLKIN)
#pragma config WDTE = OFF // Watchdog Timer Enable bit (WDT disabled)
#pragma config PWRTE = OFF // Power-Up Timer Enable bit (PWRT disabled)
#pragma config MCLRE = OFF // GP3/MCLR pin function select (GP3/MCLR pin function is digital I/O, MCLR internally tied to VDD)
#pragma config BOREN = OFF // Brown-out Detect Enable bit (BOD disabled)
#pragma config CP = OFF // Code Protection bit (Program Memory code protection is disabled)
#pragma config CPD = OFF // Data Code Protection bit (Data memory code protection is disabled)
#define _XTAL_FREQ 4000000
#define ap GPIO0
#define bp GPIO1
#define cp GPIO2
#define dp GPIO4
#define ep GPIO5
int a, b, c, d, e, g;
int Intcount;
void interrupt t0() {
if (Intcount < 255) {
if (Intcount == a) {
ap = 0;
}
if (Intcount == b) {
bp = 0;
}
if (Intcount == c) {
cp = 0;
}
if (Intcount == d) {
dp = 0;
}
if (Intcount == e) {
ep = 0;
}
} else if (Intcount == 255) {
Intcount = 0;
ap = 1;
bp = 1;
cp = 1;
dp = 1;
ep = 1;
}
Intcount++;
TMR0 = 255;
T0IF = 0;
}
int main() {
CMCON = 0x07;
ANSEL = 0x00;
TRISIO = 0x00;
OPTION_REG = 0b00000000;
INTCON = 0b10100000;
a = 40;
b = 80;
c = 120;
d = 160;
e = 200;
Intcount = 0;
while (1) {
}
}
Modificado:
Una pregunta, si quiero usar adc para controlar el servomotor los valores serian solo entre 0 y 195, tendria que bajar la resolucion al ADC?
-
Modificado:
Una pregunta, si quiero usar adc para controlar el servomotor los valores serian solo entre 0 y 195, tendria que bajar la resolucion al ADC?
Si para el caso de tu codigo si. Si es de 8 bits podes "normalizarlo" lo multiplicas por 195/256 aunque no se cuanto ciclos de reloj lleve esto. creo que demasiados xD. Sino buscas alguna otra solucion
Si lo haces como esta el codigo que pasaste, buscas de que se cumplan los que el tiempo del timer sea lo mas cercano a periodo / 2^bits de resolucion del ADC o multiplo, asi para 10 bits, seria periodo /1024 , y luego segun el valor del ADC es tu duty, es lo mas simple.
Pero no entiendo como queres usar un ADC cuando el PIC16F84A NO posee un ADC.
-
Tal vez te sirva esto , esta con CCS pero seria muy facil pasarlo a XC8.
http://www.todopic.com.ar/foros/index.php?topic=30117.0;
-
Buenas tardes.
KILLERJC yo se que el PIC16F84A no posee modulo ADC pero yo lo digo por si algun dia lo implementara en otro pic, gracias por toda la ayuda.
AKENAFAB gracias por la ayuda.
gracias a los que respondieron, de verdad ya no me siento tan perdido como estaba al principio :D