Relais activé par passage à zéro

13

Comment pourrais-je procéder à la programmation d'un commutateur (basé sur un relais à semi-conducteurs ou un triac) qui se déclenche sur une puissance de passage à zéro?

Pour ceux qui ne connaissent pas le sujet: Mettez le 230V sous tension, lorsque l'onde sinusoïdale de la ligne électrique passe à zéro - le résultat est de minimiser les perturbations électromagnétiques résultant d'un pic rapide de courant.

Plus précisément, je préférerais passer autant de logiciels que possible. Le circuit de détection composé d'un petit transformateur, d'une diode et de quelques résistances pour contrôler les niveaux et les courants fournit "1" lorsque la puissance d'entrée CA est dans la moitié positive, "0" dans le négatif, attaché à une broche GPIO d'entrée. La sortie se compose de quelques relais statiques et de l'essentiel pour les faire fonctionner (pull-ups, etc.), attachés aux broches de sortie GPIO.

The problem is timing: with 50Hz AC we get 100 zero-crossings in a second, one half-cycle is 10ms. To get within reasonable distance from zero-crossing to keep said EMI low we shouldn't activate the output more than 10% past (or before) the event of zero-crossing, that means +-1ms tolerance. That doesn't mean 1ms reaction time - we can reasonably expect the next zero-crossing to occur precisely 10ms after the first one, or the fourth - 40ms. It's about granularity - if we allow 20ms for reaction, it must be between 19 and 21ms, not 18 or 22.

Comment puis-je implémenter un tel temporisateur - déclencher la sortie GPIO soit dans 1 ms car l'entrée détecte un front, soit dans un multiple fixe de 10 ms depuis - de préférence en tenant compte d'une polarisation négative (par exemple, le transformateur et le relais introduisent un retard de 1,6 ms; donc je veux que le déclencheur se déclenche 8,4+ (n * 10) ms depuis l'impulsion d'entrée, de cette façon la polarisation contrecarre le retard introduit par le circuit.) - tout bien sûr "à la demande de l'utilisateur", par exemple, l'utilisateur écrit "1 msgstr "vers un fichier / sys / class / ... et à l 'occasion (grossièrement) la plus proche, la sortie passe" on ". L'utilisateur écrit «0» et lorsque le passage à zéro arrive, le relais spécifique se désengage.

Je crois que cela nécessiterait d'écrire ou de pirater un module du noyau. Pourriez-vous m'indiquer ce qui gère les broches GPIO de Raspberry Pi dans le noyau, et quel type de minuteries pourrais-je y attacher (à moins qu'il y en ait déjà en place) pour obtenir ce type de fonctionnalité?

SF.
la source
projet assez intéressant dont vous parlez! Juste pour donner une estimation approximative: je commencerais par interfacer l'onde sinusoïdale du signal d'origine à 50 Hz via une logique de déclenchement schmitt à GPIO. À partir de là, générez une interruption sur le front montant ou descendant du signal.Vous êtes maintenant verrouillé sur 50Hz AC et pouvez `` prédire '' quand le prochain passage à zéro se produira. Cela implique certainement une programmation du pilote du noyau. Google est votre ami :-)
sparkie
Jetez un coup d'œil au module de gradateur AC d'Inmojo . J'ai réussi à cloner ceci pour un projet en utilisant leur documentation open source. Il existe également des exemples de codage Arduino qui peuvent vous aider dans la logique de votre code.
Butters
@Butters: Je crains que l'écart entre la programmation d'Arduino et la programmation du module du noyau Linux rende le côté logiciel inutile pour moi, mais les schémas matériels couvrent 100% de ce dont j'ai besoin - merci, vous avez sauvé une bonne partie de mon travail - le la différence entre les deux projets ne concerne que le logiciel (le variateur doit commuter le triac de façon cyclique tandis que le commutateur ne le met sous / hors tension qu'une fois par basculement du commutateur.)
SF.
... 100% du côté matériel bien sûr.
SF.

Réponses:

6

Vous n'avez pas besoin de pirater le noyau. Vous avez juste besoin de déplacer le processus hors de la file d'attente du planificateur.

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

Désormais, notre processus reçoit des cat /proc/sys/kernel/sched_rt_runtime_usmillisecondes sur chaque cat /proc/sys/kernel/sched_rt_period_ussegment de temps en millisecondes, d'une exécution ininterrompue sans risque d'être anticipé pendant ce temps (en pratique, par défaut sur BerryBoot: 0,95s sur chaque seconde.) Si vous en avez besoin de plus, mess avec ces valeurs, mais je n'en ai pas besoin de plus pour mon objectif ici.

J'utilise une fonction de minuterie en millisecondes (c'est à peu près la précision dont j'ai besoin) basée sur l' clock_gettime()horloge de mes retards.

L'appel le timer(1)réinitialise, l'appel timer(0)renvoie le temps écoulé depuis la réinitialisation.

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

Vous devez rtcréer un lien avec la bibliothèque pour que cela se compile - ajoutez-le -lrtà votre commande gcc.

Maintenant, pour la boucle principale. J'utilise une entrée de commutateur pour la "demande d'utilisateur", mais vous pouvez utiliser le réseau, la minuterie ou autre. Tout ce dont vous avez besoin est d'obtenir la valeur booléenne 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.
la source
Est-ce que cela fonctionnerait pour la mise en œuvre d'un gradateur commandé par pi pour la climatisation du secteur? J'imagine que je devrais 1) changer la résolution en quelque chose de beaucoup plus petit (au lieu de toutes les 100 ms) et 2) au lieu de simplement régler le TRIAC_PINà in, je devrais régler le TRIAC_PINà 1, attendre un laps de temps déterminé (proportionnellement à gradateur souhaité), puis réglez à TRIAC_PINnouveau sur 0. Est-ce que cela fonctionnerait?
rinogo
Je suppose que dans la boucle principale, je veux aussi changer la ligne if(in==out) continue;à if(out==0) continue;, droite? En fait, je suis totalement nouveau dans la programmation de pi, donc ce n'est peut-être pas nécessaire - je suppose que tout se passe de manière synchrone (c'est-à-dire que nous n'avons pas à nous soucier de l'appel de la boucle principale pendant que les boucles imbriquées sont toujours en cours d'exécution)
rinogo
(Tout cela utilise le module de gradateur Inmojo susmentionné, bien sûr: inmojo.com/store/inmojo-market/item/… )
rinogo
2
Il y a un problème avec ça. Pour une activité stable du système, vous DEVEZ céder le contrôle du système périodiquement et je doute vraiment que vous le restauriez dans un délai aussi court que (moins de) 20 ms. Ainsi, ces rendements entraîneront des impulsions manquées et, par conséquent, l'ampoule clignotera. J'ai posé une question à ce sujet mais je n'ai obtenu aucune réponse. Vous pouvez définir à la fois sched_rt_runtime_us et sched_rt_period_us sur -1 pour désactiver complètement la préemption du système, mais si vous ne planifiez pas du tout sched_yield () ou usleep (), cela est susceptible de créer des problèmes.
SF.
2
C'est-à-dire: avec SCHED_FIFO une fois que vous démarrez une tranche de temps, cela dure sans interruption jusqu'à ce que vous cédiez volontairement (ou que sched_rt_runtime_us soit écoulé) mais le système ne garantit pas quand vous obtenez cette tranche de temps. Dans mon cas, j'ai remarqué qu'en fonctionnement normal, le temps entre les appels (donnant des tranches de temps à la tâche) peut s'étendre jusqu'à 0,1 s avec une charge CPU maximale. Peut-être que cette période peut être affinée et forcée plus courte, mais je ne sais pas comment.
SF.