Simulación de un encoder incremental con arduino (Atmega328p) con Atmel Studio

Hola. Estoy preparando el trabajo de Fin de Grado que consiste en simular el funcionamiento de un encoder incremental por medio de un microcontrolador. El que me dio mi tutor es el Atmega 328p que es el que trae el arduino uno.

Bueno, puesto en faena de programarlo, el IDE de arduino me daba mas dolores de cabeza que soluciones, Tiempos muy alto de ejecucion de instrucciones como AnalogRead y Digitalwrite, extraños funcionamientos del Timer 1… Asi que cansado de pelear con el decidi, animado por algunos foros, instalar el Atmel Studio y su extension para programar la placa Arduino.

Hasta el momento todo bien, pero me surge un problema cuando intento usar _delay_ms con una variable, mi duda es si _delay_ms solo admite constantes y tengo que hacer una funcion expecifica que me varíe mediante un contador y los delays el tiempo a nivel bajo y alto de la señal que he de obtener.

Muchas gracias de antemano.

Si alguien tiene dudas subiré el codigo. De todas formas cuando concluya el proyecto lo subiré.
Saludos

Sería bueno que fueras mas específico con tu apreciación sobre tu afirmación

iempos muy alto de ejecucion de instrucciones como AnalogRead y Digitalwrite, extraños funcionamientos del Timer 1... Asi que cansado de pelear con el decidi, animado por algunos foros, instalar el Atmel Studio y su extension para programar la placa Arduino.

Hasta el momento todo bien, pero me surge un problema cuando intento usar _delay_ms

tiempos muy altos de ejecución?
funcionamientos extraños del Timer1?

Yo he trabajado con Atmel Studio y te cuento que termina compilando del mismo modo que el IDE usando averdude de modo que no puedes tener cosas distintas a menos que lo programes diferente.

Luego veo incoherente que hables de timer1 y _delay_ms. A mi no se me ocurriría usar ambos por ejemplo.

Sería bueno que presentes tu código y mejor aún que ya que hablas de un trabajo final de fin de grado expliques mejor toda la idea.
Sabemos que quieres simular un enconder. Bien hasta ahi.
Como lo planteas?
Será modificable o no?

Solo requieres comandar dos salidas con un defasaje entre ellas, invertir ese defasaje si simularas movimiento contrario, y cambiar frecuencia de los pulsos de salida para simular la velocidad de rotación.
Puedes usar TimerOne y timerThree, una librería muy versátil que con un poco de ensallo verás que resuelve tu problema uses IDE o Atmel Studio.

COMENTARIO sobre tu post:
Para terminar te comento que este es casi un [doble o triple post](http://Programar los Timer del Atmega de arduino) porque este tema lo habías abierto no hace mucho y ahora vas de nuevo.
Te digo esto porque mas allá que nadie te respondió, tal vez sea porque nadie puede hacerlo, pero no deberías abrir un post nuevo con el mismo objetivo.

A tiempos altos de ejecución me refiero a que el AnalogRead tarda en ejecutarse unos 100 us y no quería que la señal se parase mientras se le esta cambiando el valor de consigna. Yo necesito generar señales de 6,51 us.

En principio quería hacerlo con la función delay, o delayMicroseconds. Despues, ya que es un proyecto y se supone que es para aprendizaje principalmente, me decante por usar el Timer/counter1 de 16 bits, conseguí programarlo para generar una señal, pero no me daba los tiempos que yo le indicaba, ademas de no poder llegar a ver el flag de overflow, ni de consegir sobreescribir el registro en el que se encuentra, como puse en el post al que te refieres. No tengo pensado usar Timer y delay, solo uno de los dos.

Leyendo muchos hilos, decidi programarlo desde la IDE del Atmel Studio (en un hilo en el que comentabas tú por cierto) ya que ademas de servir para el Arduino, me servirá para aprender a programar cualquier micro de atmel. La verdad es que la IDE me parece mucho mas completa ya que te da la opcion de simular e ir paso a paso, cosa que en la IDE de Arduino no fui capaz de conseguir.

Principalmente porqué cambie de IDE he abierto este post, para que alguien que tenga la misma IDE y el día de mañana le pase algo similar, sea capaz de encontrarlo facilmente. Si he incurrido en alguna falta a las normas del Foro pido disculpas de antemano.

Bueno, contestando a tu Post aclarare un poco mejor lo que es el proyecto en si. El proyecto es hacer como bien dices, la simulación de las salidas del encoder, que como bien sabras son 3 canales, un Canal Z que da un pulso por cada revolución del Motor y otros dos canales desfasados 90º entre si dependiendo del sentido de giro del motor, que dan 1024 pulsos (para el encoder en que me baso en particular) por cada giro del motor.

El tema es que este proyecto es para que el Tutor realice unas practicas para su clase en laboratorio y asi ahorrarse el comprar, pongamos unos 20, encoders que rondan los 400 euros. Tambien se ahorra los motores y sus correspondientes variadores. De modo que, la idea es que desde las salidas analogicas de un automata,se genere una señal de 0 a 5 Voltios de forma que esa será la consigna para la velocidad del motor virtual, siendo 0V 0 rpm y 5V 9000rpm, que será introducida por la entrada A0 del arduino, que corresponde a la ADC0 del micro Atmega.

Asimismo mediante una señal digital del automata y por una entrada del arduino, se le indicara su sentido de giro para desfasar o adelantar la señal correspondiente.

El proyecto en si es esto, luego siempre van surgiendo cosillas.

El caso es que en el Atmel Studio, cojo la señal de ADC0, la convierto en digital con el ADC, la meto en una variable, hago las cuentas necesarias para calcular el periodo de la Señal Z, ademas del tiempo que tiene que estar a nivel alto (el cual coincide con la mitad del periodo de las señales A y B) y el tiempo que debe estar a nivel Bajo. Calculo el periodo de las señales A y B, lo divido de 2 y lo meto en una variable, y esa variable es la que no me deja meter en _delay_us.

Por eso preguntaba si es posible meter _delay_us(variable) o deberia crear una funcion que mediante un for hicera los _delay_us necesarios para completar el periodo.

Por cierto, gracias por tomarte tiempo en leer el post y contestar.

Escribi una larga respuesta que acaba de borrar al darme cuenta que no sirve la libreria que te comente antes.
No sirve porque mas alla de su flexibilidad no veo como puedas generar defasaje salvo que en la rutina ISR crearas tus senales defasadas del modo que corresponda.
Entonces, la libreria es TimerOne o TimerThree

Mira a ver que opinas si usando la libreria como una especia de generador tu terminas en la rutina ISR creando las dos salidas defasadas que buscas.

Intentare usar las librería que me comentas. El caso es que si en _delay_us pudiese meter variables en lugar de constantes el desfase lo podría realizar facilmente con ifs delays y cosas por el estilo. Como creo que solo ademite constantes creo que tendre que armarme unas subrutinas para generar el periodo de las señales y tendre que hacer otra para generar el desfase entre ambas. Esta noche le doy un empujon a ver si avanzo algo y mañana posteo el programa por si alguien siente curiosidad.

Como se postean los programas?

Entonce intenta con tu idea. Yo solo te daba una alternativa, usando la librería timer generas la frecuencia deseada, y en la ISR mueves los pines de salida a tu gusto.
Cuando decides cambiar de frecuencia modificas el periódo y sigues asi.

Pero intenta como creas y si hay problemas lo debatimos.

Muchisimas gracias surbyte. Agradezco mucho tu interes ya que empece este proyecto bastante perdido, hacia como 3 años que no programaba ningun micro, desde las clases de la universidad. Pero avanzando se van recordando cosas y surgen nuevas dudas, pero bueno, ese es el proceso de aprendizaje. Realmente me gusta este mundo y despues del proyecto seguiré haciendo proyectos para mi mismo.

Bueno, ayer le dimos un pequeño empujón, pero como yo creia _delay_us no acepta variables, solo constantes. _delay_loop_2 si acepta variables pero no tengo muy claro lo que hace esa función,de todas formas he visto que cuenta milisegundos con lo que no me vale, ya que yo tengo que contar microsegundos.

Posteo el codigo y comento los problemas que he encontrado.

#define F_CPU 16000000L //Define la Frecuencia del micro,para hacer los calculos para la frecuencia del ADC.

#include <avr/interrupt.h> //Contiene las macros necesarias para definir funciones, habilitar y deshabilitar las interrupciones globales.
#include <avr/io.h> //Llamada a la libreria que hace referencia a todas las palabras reservadas, para el control de los puertos de entrada y salida.
#include <util/delay.h> //Llamada a librería para manejar retardos de tiempo.
#include <stdio.h> //Llamada a la librería para hacer operaciones, estándar, de entrada y salida, así como la definición de tipos necesarias para dichas operaciones.
#include <stdint.h> //La definición de los tipos de datos enteros

#include <math.h> //Llamada a la libreria para el manejo de operaciones matemáticas.

#define PB5_OFF PORTB &= ~(1<<PB5) //para trabajar con el pin 13 del arduino solamente que es la PB5 en el Atmega328 , lo pone a 1, nivel bajo.
#define PB5_ON PORTB|=(1<<PB5) //para trabajar con el pin 13 del arduino solamente que es la PB5 en el Atmega328, lo pone a 1, nivel alto.

void ADCInit(); //definimos la funcion de inicializacion del ADC
int ADCRead(unsigned char canal);




unsigned int consigna; //Consigna es el nombre de la variable a la cual le doy al valor de entrada que controla la velocidad del motor virtual, que va de 0 a 5 voltios, con  una resolucion de 5/1023=0,00488 Voltios (4,88 mV).
float velocidad; //Velocidad es el nombre de la variable a la cual le doy el valor de la Velocidad de giro del motor virtual en RPM, va de 0 a 9000 rpm (maximas revoluciones que puede soportar el encoder)
float PERIODO; //PERIODO es el nombre de la variable a la cual le doy el valor del periodo de la señal que he de generar para simular el canal Z, que emite un puslo con cada revolución del motor.
float periodo1; //Periodo1 es el nombre de la variable a la cual le doy el valor del periodo de la señal cuadrada que he de generar para los canales A y B, que generar 1024 pulsos por cada revolución del motor.
float Vin; //Hago la conversion digital analogica para saber el valor de entrada y se lo asigno a Vin.
int senal1; //Señal1 es el nombre que lleva el tiempo que deben de estar a nivel alto y bajo las señales de los canales A y B para generar la señal cuadrada correspondiente.
float PERIODONIVELBAJO; //Es el tiempo que la señal del canal Z debe de estar a nivel Bajo.


void setup()
{

  DDRB=0XFF; //Para decir que son salidas, todo a uno. En este caso el puerto B.
  PORTB=0x00; //Ponemos el Puerto B a cero.
  PORTC=0X01; //Activamos la resistencia Pull-Up en la patilla A0.
  ADC_Init();
}

void loop()
{

consigna=ADCRead();

Vin= consigna*(5/((2^10)-1));


//Ahora que tenemos el valor de la entrada, realizamos las formulas necesarias para calcular los periodos de las señales.

PERIODO=((60*5)/(9000*Vin))*1000000; //Formula para calcular el periodo de la señal del canal Z
periodo1=PERIODO/1024; //Para sacar el periodo del canal A y B, divido el periodo del canal Z entre 1024 pulsos que dan por cada vuelta del motor, ya que el encoder tiene 1024 puntos.
senal1=periodo1/2;
PERIODONIVELBAJO=PERIODO-senal1;


  PB5_ON;
 _delay_ms(1000);
  PB5_OFF;
 _delay_ms(1000);
}


void ADC_Init(){
	
	ADMUX = _BV(REFS0); //Modificamos el registro ADMUX del ADC. Referencia a AVCC, NOSOTROS DEBERIAMOS USARLA CON REFERENCIA EXTERNA?
	//REFS1=0;REFS0=1; AVCC con capacitor externo en el Pin AVCC
	//ADLAR=0; Ajusta el resultado hacia la izquierda.
	//ADMUX4:0=0; Leer la señal de ADC0, que es la entrada A0 de la placa Arduino.
	
	ADCSRA = _BV(ADEN) |  _BV(ADPS2) |  _BV(ADPS1) |  _BV(ADPS0); //Modificamos el registro ADCSRA del ADC.
	//ADEN=1; Habilita el ADC
	//ADPS2:0=1; Prescaler a 128
	//Frecuencia de ADC = 16000000/128= 125 KHz
	//Frecuencia de muestreo = 1250000/13= 9,6 KHz
	//Periodo de muestreo = 1/9615 = 104 us.
	
}

int ADCRead() {
	
	
	ADCSRA |= _BV(ADSC);	//ADSC=1; se inicia la conversión.
	
	
	while(ADCSRA & _BV(ADSC));// Espera a que termina la conversión
	
	
	return ADC; //Regresamos el valor de la conversion.
}

[code]

El caso es que poniendo _delay_us(senal1) no me lo acepta, con _delay_loop_2(senal1) si acepta, pero no se modifica cuando vario la entrada AO mediante el potenciometro.

Querría saber si la conversion del ADC esta bien hecha, queria verlo por el serial monitor, pero no logro comunicar por el.

¿que opinas? 

Siento haber posteado el codigo así, pero no se hacerlo de otra forma. Saludos

Ya me comunica y ya comprobe todas las operaciones, salvo alguna pequeña depuración hace todo correctamente, sigo trabajando en el problema de los delays.

Voy a hacer una funcion con un contador para que me haga los delays con tiempos variables. A ver que sale.

casi te pusiste a programar en AVR.

prueba con

Syntax
delayMicroseconds(us)

Parameters
us: the number of microseconds to pause (unsigned int)

Si. La verdad es que me interesa mas aprender a programar el micro en si, mirando registros y todo eso. Ya lo habiamos hecho en clase con un micro algo mas simple, pero lo vimos todo. Entonces prefiero poder manejar el Atmega 328 para asi poder mirar mas adelante Micros mas potentes como los Phillips.

Con _delay_us tampoco acepta variables, tendre que probar con delayMicroseconds, si acepta Variables me ahorrara muchos calculos, porque ahora mismo mi idea es hacer un Switch case, y meter los tiempos en los delays uno por uno. Son 1023 asi que te puedes imaginar, no se si el Switch aceptara 1023 casos, y si los acepta no se si me cogera en la memoria del micro.

El switch case que tengo solo es para el valor de 5, lo hice con delay_ms para mirar si iba bien, parece que si.

switch(ADC)
	{
	case 1023:
		PB5_ON;
		PB3_ON;
	do
		{
		_delay_ms(1000);
		PB4_ON;
		_delay_ms(1000);
		PB5_OFF;
		PB3_OFF;
		_delay_ms(1000);
		PB4_OFF;
		_delay_ms(1000);
		PB5_ON;
		n=+1;
		}while(n=!10);
		n=0;
		break;
		default: 
		PB3_OFF;
		PB4_OFF; 
		PB5_OFF;
	}
}

[code]
Con esta idea tengo que hacer esto 1023 veces, y por supuesto calcular a mano todos los periodos de las señales para cada valor. Pero bueno, esta es la manera mas facil de hacerla.

De todas formas me surge un problemilla, ya que tengo que hacer _delay_ms de 1,63 us no son numeros enteros y no se si _delay_us acepta float, que ya me da que no, asi que al final creo que lo voy a tener que hacer con Timer1.

Gracias por tus consejos.

Bueno, ya está decidido que voy a recurrir al Timer1 para hacer las temporizaciones de los periodos de las señales.
El caso es que configurado sin prescaler cada cuenta equivaldria a 1/16000000=0.0625us, con lo que la maxima precision que tendria con el contador seria la de cargar un 65535 en el registro de cuenta y hacer cuentas de 0.0625 us, dado que no me queda muy bien para los calculos, podria cargar un 65536-8 y hacer cuentas de 0.5 us, es el minimo valor entero que consigo.

Mi idea era contar 0,01 us,ya que los valores de las señales tienen 2 decimales como 3,51 por ejemplo,pero por lo visto es imposible.

Voy a tener que hacer un redondeo, con lo cual la simulación de encoder quedará bastante restringida. De los 1023 valores que tenia pensado hacer creo que se van a tener que reducir (esto no se si es verdad, aun no lo he pensado bien, pero me temo que será asi).

Mi duda es si hay algún modo en el cual el Timer1 haga cuentas de menos de 0.5 enteras, es decir, si consigo contar 0,1 us ya seria buena cosa.

Obviamente con un cristal de frecuencia superior a 16 MHz si podria pero no tengo pensado montarle uno ya que viene en la placa arduino. Podría estudiarlo si en lugar de la placa, hacer en un futuro, el montaje de micro, y los componentes necesarios en una placa hecha por mi para este cometido, pero de momento me conformo con entregar lo que se pueda, maximizando las caaracteristicas propias de Arduino Uno.

Gracias de antemano.

Tienes 62.5 nseg posibles a la máxima frecuencia con prescaler 1 o sea a 16Mhz.

Quieres una resolución de 0.01 useg = 10 nseg Imposible.
Si la resolución máxima es de 62.5nS como vas a lograr 10nseg?

Tu resolución es 62.5nSeg y de ahí no puedes moverte.
0.1useg = 100nseg tampoco es generable tendrías que pensar en multiplos de 62.5nseg y de ahi partir.
Lo que si puedes usar es un chip externo que genere pulsos. Recuerdo haber participado de un debate donde alguien necesitaba 40Mhz para un DDS (Direct digital synthesizer) 0-40Mhz, Sine wave generator por $ 25

Si, lo de tener los 10 nanosegundos daba por hecho que seria imposible, a no ser como comentas tú, con un cristal externo de mas de 16 MHz.

Lo maximo que puedo conseguir con este Timer es de 0,5 us de resolución. Lo maximo que me sirva a mi claro, lo máximo son los 62,5 ns.

Yo preguntaba si sabias si usando otro Timer podria llegar a contar cada 0,2 us o cada 0,3.

Saludos

Tu quieres 0.2 us exactos?

Tu mejor resolución es 62.5nseg y arma todo con esos 62.5nseg que son mas que maravillosos.
Creo que incluso no podrás generar menos que 125nSeg porque la minima instrucción del ATMEL consume dos ciclos de reloj.

tus 200 nseg serán 62.5 x 3 = 187.5nseg o sea un error de 6.25%
tus 300 nseg seran 4.8 o sea puedes usar 5 ticks o 4
Con 5 tienes 4.16% de error
con 4 tienes 16.6% de error El peor de todos
No esta tan mal.

Ya calcule que el error minimo es de 0.02 us. Es una lastima, querría que fuese lo mas cercano posible al funcionamiento real, aunque la resolución del ADC ya me limita, con la resolución del Timer ya me han matado, pero son limitaciones de hardware así que hay que asumirlas. Tirare con lo que hay. De momento cuelgo lo que tengo hecho en la rutina de interrupcion del Timer, que se da cada 0.0625 us.

void loop()
{

consigna=ADCRead();

if(consigna1!=consigna)
{
consigna1=consigna;
Vin=(float)consigna*5/1023;

//Ahora que tenemos el valor de la entrada, realizamos las formulas necesarias para calcular los periodos de las señales.

PERIODO=((60*5)/(9000*Vin))*1000000; //Formula para calcular el periodo de la señal del canal Z
periodo1=PERIODO/1024; //Para sacar el periodo del canal A y B, divido el periodo del canal Z entre 1024 pulsos que dan por cada vuelta del motor, ya que el encoder tiene 1024 puntos.
senal1=periodo1/2;
PERIODONIVELBAJO=PERIODO-senal1;
MitadSenal=senal1/2;
cuenta=MitadSenal/0.0625;
}




TCNT1=65535; //Con F=16MHz y con preescaler=1 nos da una cuenta de 0.0625 us.
TCCR1B= _BV(CS10); //Arranca Timer.

SMCR= _BV(SE); //Registro de control para el modo Sleep. Con SE=1; entramos en modo IDLE.

//while(!(TIFR1&_BV(TOV1)));


}



ISR(TIMER1_OVF_vect)
{
	Serial.print("Valor de N: \t");
	Serial.print(n);

	if(m!=1023)
		{	
			if(m<(cuenta*2)) // Para hacer el primer ciclo de la señal, 1/4 periodo canal Z y A a nivel alto, 1/4 periodo canal A,B y Z a nivel alto.
			{
					if(n<=cuenta) 
					{
						PB5_ON;
						PB3_ON;
						n++;
						m++;
					}
				else //Segundo 1/4 del periodo de la señal, en este 1/4 A,B y Z a nivel alto.
					{
						PB5_ON;
						PB3_ON;
						PB4_ON;
						n++;
						m++;
					}
			}
			
		if(m==(cuenta*2)) // Cuando cambiamos al tercer 1/4 de la señal, en este momento A,B y Z aun a nivel alto.
		{
			n=0;
			m++;
		}
		
			
		if (m>(cuenta*2) && m<(cuenta*4)) // Tercer 1/4 de la señal, en este cuarto A,Z a nivel bajo, B a nivel alto.
			{
				if (n<cuenta)
				{
					PB5_OFF;
					PB3_OFF;
					PB4_ON;
					n++;
					m++;
				} 
				else //Cuarto 1/4 de la señal, A, B y Z a nivel bajo.
				{
					PB4_OFF;
					PB5_OFF;
					n++;
					m++;
				}
				
		if(m==(cuenta*4))
			{
			n=0;
			m++;	
			}
		if (m>(cuenta*4))
			{
				if (n<cuenta)
				{
					PB5_ON;
					PB4_OFF;
					n++;
					m++;
				} 
				else
				{
					PB4_ON;
					PB5_ON;
					n++;
					m++;
				}
			 
			}
		
			
		}
		else
		{
		n=0;
		m=0;	
		}

}
		}

[code]
Aun falta mucho por depurar, pero esta es la idea, contar un cuarto del periodo y con eso realizar el desfase entre las señales A y B. Tambien contar los 1024 pulsos que se dan cada giro del motor para realizar la señal Z que solo tiene un pulso que coincide con el primer pulso de la señal A.

Bueno, cuando lo tenga depurado lo subire. Saludos.

Porque me haces eso AmaterPrincipiante. Porque pones un código sin usar los tags?
Lee las normas por favor!!! Y edita eso.

Bueno. Ya lo tengo depurado pero aun así, haciendo pruebas con el Timer1 en modo normal no consigo que varie su tiempo de conteo, casi ni se entera.

He visto que usando el modo CTC se pueden generar dos señales desfasadas 180 grados por las patillas OC1A y OC1B, como se muestra en este video.

Con un poco de trabajo creo que se podría generar un desfase de solo 90 grados y variar el periodo, asi resultaria todo mas sencillo, asi solo habria que hacer un contador para contar los 1023 pulsos de la señal A y generar el pulso inicial del canal Z.

Creo que está es la forma mas facil y efectica, le estaba dando vueltas a otras formas que realmente son mas complicadas y dan mas problemas que soluciones.

¿Que opinas?

Pregunte al inicio del Hilo como se hacia para postear un codigo. Busque en normas del Foro y no vi como se hacia. Sorry.

Si me explicas lo haré de ahora en adelante.

SI tu me dices que no entiendes el punto 7, no se que decir.
Normas del foro

Yo no te las voy a explicar.

Ok. no es lo que habia leido. Ahora estoy dandole vueltas al CTC, ahora que acabe los edito. Una vez mas, lo siento y gracias por tu ayuda de nuevo.