Relé activado de cruce por cero

13

¿Cómo podría programar un interruptor (basado en un relé de estado sólido o un triac) que se dispara con potencia de cruce por cero?

Para aquellos que no están familiarizados con el tema: encienda la alimentación de 230 V cuando la onda sinusoidal de la línea de alimentación cruza el cero; el resultado es minimizar las perturbaciones electromagnéticas resultantes de un pico rápido en la corriente.

Específicamente, preferiría moverme tanto al software como sea posible. El circuito de detección que consiste en un pequeño transformador, un diodo y un par de resistencias para mantener controlados los niveles y las corrientes proporciona "1" cuando la potencia de entrada de CA está en la mitad positiva, "0" en negativo, conectado a un pin GPIO de entrada. La salida consta de unos pocos relés de estado sólido y elementos esenciales para mantenerlos en funcionamiento (pull-ups, etc.), conectados a los pines GPIO de salida.

El problema es el tiempo: con 50 Hz de CA obtenemos 100 cruces por cero en un segundo, un medio ciclo es de 10 ms. Para estar a una distancia razonable del cruce por cero para mantener dicho EMI bajo, no debemos activar la salida más del 10% después (o antes) del evento de cruce por cero, eso significa tolerancia + -1ms. Eso no significa un tiempo de reacción de 1 ms: podemos esperar razonablemente que el próximo cruce por cero ocurra exactamente 10 ms después del primero, o el cuarto - 40 ms. Se trata de granularidad: si permitimos 20 ms para la reacción, debe ser entre 19 y 21 ms, no 18 o 22.

¿Cómo puedo implementar dicho temporizador? GPIO de salida de disparo, ya sea dentro de 1 ms desde que la entrada detecta un borde, o dentro de un múltiplo fijo de 10 ms desde entonces, preferiblemente con margen para algunos sesgos negativos (por ejemplo, el transformador y el relé introducen un retraso de 1.6 ms; así que quiero que el disparador salga 8.4+ (n * 10) ms desde el pulso de entrada, de esa manera el sesgo contrarresta el retraso introducido por el circuito.) - por supuesto "a pedido del usuario", digamos, el usuario escribe "1 "a un archivo / sys / class / ... y en la oportunidad más cercana (más o menos) la salida se" enciende ". El usuario escribe "0", y cuando llega el cruce por cero, el relé específico se desactiva.

Creo que esto requeriría escribir o piratear un módulo del núcleo. ¿Podría indicarme qué maneja los pines GPIO de Raspberry Pi en el núcleo, y qué tipo de temporizadores podría adjuntarle (a menos que ya existan algunos) para obtener este tipo de funcionalidad?

SF.
fuente
proyecto muy interesante del que hablas! Solo para dar una estimación aproximada: primero conectaría la onda sinusoidal de la señal original de 50Hz a través de alguna lógica de disparo schmitt a GPIO. A partir de ahí, genere una interrupción en el borde ascendente o descendente de la señal. Ahora está bloqueado en 50Hz AC y podría 'predecir' cuándo ocurrirá el próximo cruce por cero. Ciertamente, esto implica algo de programación del controlador del núcleo. Google es tu amigo :-)
sparkie
Eche un vistazo al módulo de atenuación de CA de Inmojo . He clonado con éxito esto para un proyecto usando su documentación de código abierto. También hay algunos ejemplos de bacalao Arduino que pueden ayudarlo a través de la lógica de su código.
Mantequillas
@Butters: Me temo que la brecha entre la programación de Arduino y la programación del módulo Kernel de Linux haría que el lado del software fuera inútil para mí, pero los esquemas de hardware cubren el 100% de lo que necesito, gracias, ahorraron un poco de mi trabajo. La diferencia entre los dos proyectos es solo en el software (el atenuador debe cambiar el triac cíclicamente mientras que el interruptor solo lo enciende / apaga una vez por alternar el interruptor).
SF.
... 100% del lado del hardware, por supuesto.
SF.

Respuestas:

6

No necesitas hackear el kernel. Solo necesita mover el proceso fuera de la cola del planificador.

    #include<sched.h>

    struct sched_param param;               
    param.sched_priority = sched_get_priority_max(SCHED_FIFO);
    if( sched_setscheduler( 0, SCHED_FIFO, &param ) == -1 )
    {
            perror("sched_setscheduler");
            return -1;
    }

De ahora en adelante, nuestro proceso recibe cat /proc/sys/kernel/sched_rt_runtime_usmilisegundos de cada cat /proc/sys/kernel/sched_rt_period_ussegmento de tiempo de milisegundos, de ejecución ininterrumpida sin riesgo de ser adelantado durante ese tiempo (en la práctica, por defecto en BerryBoot: 0.95s por segundo). Si necesita más, lío con estos valores, pero no necesito más para mi propósito aquí.

Estoy usando una función de temporizador en milisegundos (es decir, la precisión que necesito) en función clock_gettime()de registrar mis retrasos.

Las llamadas se timer(1)restablecen, las llamadas timer(0)devuelven el tiempo desde el reinicio.

    #include<time.h>
    typedef unsigned long long ulong64;

    ulong64 timer(unsigned char reset)
    {
            struct timespec t;
            static struct timespec lt={0,0};
            clock_gettime(CLOCK_REALTIME, &t);
            if(reset)
            {
                    lt.tv_sec = t.tv_sec;
                    lt.tv_nsec = t.tv_nsec;
            }

            int r = ((ulong64)(t.tv_sec - lt.tv_sec))*1000 + (t.tv_nsec - lt.tv_nsec)/1000000;

            return r;
    }

Debe realizar un enlace contra la rtbiblioteca para que esto se compile: agregue -lrta su comando gcc.

Ahora, para el bucle principal. Estoy usando una entrada de interruptor para "solicitud del usuario" pero puede usar la red, el temporizador o lo que sea. Todo lo que necesitas es obtener el valor booleano in.

    while(1)
    {
            //when idle, return a lot of CPU time back to the system. 
            //A call every 100ms is perfectly sufficient for responsive reaction.
            usleep(100000); 

            in  = bcm2835_gpio_lev(SWITCH_PIN);
            out = bcm2835_gpio_lev(TRIAC_PIN);

            if(in==out) continue;   //nothing to do; wait user input, return control to system.

            //The output needs to be changed.
            //First, let's wait for zero-crossing event.
            timer(TIMER_RESET);
            zx = bcm2835_gpio_lev(ZEROXING_PIN);

            //We don't want to freeze the system if the zero-xing input is broken.
            //If we don't get the event within reasonable time, 
            // (like three half-sines of the power; ZEROXING_TIMEOUT = 70)
            // we're going to bail.
            while(timer(TIMER_READ) < ZEROXING_TIMEOUT)
            {
                    if(zx != bcm2835_gpio_lev(ZEROXING_PIN))
                    {
                            //Event detected.                  
                            timer(TIMER_RESET);
                            break;
                    }
            }
            if(timer(TIMER_READ) >= ZEROXING_TIMEOUT) continue;     //Zero-crossing detection is broken, try again soon.

            //Now we are mere milliseconds after zero-crossing event arrived
            // (but it could have taken some time to arrive) so let's wait for the next one, making adjustments for the system delay.
            // This is to be worked out using an oscilloscope and trial and error.
            // In my case BIASED_DELAY = 19.

            while(timer(TIMER_READ)<BIASED_DELAY) ;

            //We can reasonably expect if we perform this right now:
            bcm2835_gpio_set_pud(TRIAC_PIN, in);
            //the signal will reach the output right on time.

            // The 100ms delay on return to start of the loop should be enough 
            // for the signals to stabilize, so no need for extra debouncing.
    }
SF.
fuente
¿Funcionaría esto para implementar un atenuador controlado por pi para el aire acondicionado principal? Me imagino que tendría que 1) cambiar la resolución a algo mucho más pequeño (en lugar de cada 100 ms) y 2) en lugar de simplemente establecer el TRIAC_PINa in, tendría que establecer el TRIAC_PIN1, esperar una cantidad determinada de tiempo (en proporción a nivel de atenuación deseado) y luego TRIAC_PINvuelva a establecer 0. ¿Funcionaría?
rinogo
Supongo que en el bucle principal, también me quiero cambiar la línea if(in==out) continue;a if(out==0) continue;, ¿verdad? En realidad, soy totalmente nuevo en la programación para pi, así que tal vez eso no sea necesario. Supongo que todo esto está sucediendo sincrónicamente (es decir, no tenemos que preocuparnos de que se llame al bucle principal mientras los bucles anidados todavía se están ejecutando)
rinogo
(Todo esto está utilizando el módulo de atenuación Inmojo mencionado anteriormente, por supuesto: inmojo.com/store/inmojo-market/item/… )
rinogo
2
Hay un problema con eso. Para una actividad estable del sistema, DEBE ceder el control al sistema periódicamente y realmente dudo que lo restablezca dentro de poco tiempo (menos de) 20 ms. Entonces, estos rendimientos resultarán en pulsos perdidos y, como resultado, la bombilla parpadeará. Me pregunté a una pregunta acerca de eso, pero no obtuvo respuesta. Puede establecer tanto sched_rt_runtime_us como sched_rt_period_us en -1 para deshabilitar por completo la prevención del sistema, pero si no sched_yield () o usleep (), eso creará problemas.
SF.
2
Es decir: con SCHED_FIFO una vez que comienza un intervalo de tiempo, dura ininterrumpidamente hasta que cede voluntariamente (o sched_rt_runtime_us ha transcurrido) pero el sistema no garantiza cuándo obtiene ese intervalo de tiempo. En mi caso, noté que en el funcionamiento normal, el tiempo entre llamadas (dando intervalos de tiempo a la tarea) puede extenderse hasta 0.1s con la carga máxima de la CPU. Tal vez ese período se puede ajustar y forzar más corto, pero no sé cómo.
SF.