Relé ativado com cruzamento zero

13

Como eu poderia programar um comutador (baseado em relé de estado sólido ou triac) que é acionado com potência de cruzamento zero?

Para aqueles que não estão familiarizados com o assunto: Ligue a alimentação de 230V, quando a onda senoidal da linha de energia ultrapassar zero - o resultado é minimizar as perturbações eletromagnéticas resultantes do rápido aumento da corrente.

Especificamente, eu preferiria mudar o máximo possível para o software. O circuito de detecção que consiste em um pequeno transformador, um diodo e alguns resistores para manter os níveis e correntes sob controle fornece "1" quando a energia de entrada CA está na metade positiva, "0" em negativo, conectado a um pino GPIO de entrada. A saída consiste em alguns relés de estado sólido e itens essenciais para mantê-los funcionando (pull-ups, etc.), conectados aos pinos GPIO de saída.

O problema é o tempo: com 50Hz CA, obtemos 100 cruzamentos de zero em um segundo, um meio ciclo é 10ms. Para ficar a uma distância razoável do cruzamento de zero para manter o EMI baixo, não devemos ativar a saída mais de 10% após (ou antes) do evento de cruzamento de zero, isso significa tolerância de + -1ms. Isso não significa tempo de reação de 1 ms - podemos razoavelmente esperar que o próximo cruzamento de zero ocorra precisamente 10 ms após o primeiro ou o quarto - 40 ms. Trata-se de granularidade - se permitirmos 20ms para reação, deve ser entre 19 e 21ms, não 18 ou 22.

Como implementar esse GPIO de saída de disparo por timer dentro de 1ms desde que a entrada detecta uma borda ou dentro de um múltiplo fixo de 10ms desde então - de preferência com tolerância a algum viés negativo (por exemplo, o transformador e o relé introduzem um atraso de 1,6ms; então, eu quero que o gatilho dispare 8,4+ (n * 10) ms desde o pulso de entrada, dessa forma o viés neutraliza o atraso introduzido pelo circuito.) - é claro que "sob demanda do usuário", digamos, o usuário escreve "1 "para um arquivo / sys / class / ... e na oportunidade mais próxima (aproximadamente) a saída continua" ligada ". O usuário grava "0" e, quando o cruzamento de zero chega, o relé específico é desativado.

Eu acredito que isso exigiria escrever ou invadir um módulo do kernel. Você poderia me indicar o que lida com os pinos GPIO do Raspberry Pi no kernel e que tipo de temporizador eu poderia anexar a ele (a menos que já existam alguns) para obter esse tipo de funcionalidade?

SF.
fonte
projeto bastante interessante sobre o qual você fala! Apenas para fazer uma estimativa aproximada: primeiro eu faria a interface da onda senoidal do sinal original de 50Hz por meio de alguma lógica de gatilho schmitt para o GPIO. A partir daí, gere uma interrupção na borda ascendente ou descendente do sinal. Agora você está bloqueado em 50Hz CA e pode 'prever' quando o próximo cruzamento de zero ocorrerá. Certamente, isso envolve alguma programação de driver do kernel. O Google é seu amigo :-)
sparkie
Dê uma olhada no módulo dimmer CA da Inmojo . Eu clonei isso com sucesso em um projeto usando sua documentação de código aberto. Há também alguns códigos de amostra do Arduino que podem ajudá-lo na lógica do seu código.
Butters
@ Butters: Receio que a diferença entre programar o Arduino e programar o módulo Linux Kernel tornaria o lado do software inútil para mim, mas os esquemas de hardware cobrem 100% do que eu preciso - obrigado, você salvou um pouco do meu trabalho - o A diferença entre os dois projetos é apenas em software (o dimmer deve alternar o triac ciclicamente enquanto o interruptor apenas liga / desliga uma vez por alternar o comutador.)
SF.
... 100% do lado do hardware, é claro.
SF.

Respostas:

6

Você não precisa hackear o kernel. Você só precisa mover o processo para fora da fila do planejador.

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

A partir de agora, nosso processo recebe cat /proc/sys/kernel/sched_rt_runtime_usmilissegundos de cada cat /proc/sys/kernel/sched_rt_period_ussegmento de tempo de milissegundos, de execução ininterrupta sem risco de ser antecipado durante esse período (na prática, por padrão, no BerryBoot: 0,95s de segundo). Se precisar de mais, mexa com esses valores, mas não preciso mais para o meu propósito aqui.

Estou usando uma função de timer em milissegundos (é sobre a precisão que preciso) com base no clock_gettime()relógio dos meus atrasos.

A chamada timer(1)redefine, a chamada timer(0)retorna o tempo desde a redefinição.

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

Você precisa vincular a rtbiblioteca para que isso seja compilado - adicione -lrtao seu comando gcc.

Agora, para o loop principal. Estou usando uma entrada de switch para "solicitação do usuário", mas você pode usar a rede, o timer ou o que for. Tudo o que você precisa é obter o 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.
fonte
Isso funcionaria para a implementação de um interruptor dimmer controlado por pi para a / c da rede elétrica? Eu imagino que eu teria que 1) alterar a resolução para algo muito menor (em vez de cada 100ms) e 2) em vez de apenas definir o TRIAC_PINcomo in, eu teria que definir o TRIAC_PINcomo 1, aguardar um período determinado (em proporção a dimmer desejado) e, em seguida, TRIAC_PINvolte a 0. Isso funcionaria?
Rinogo 21/08
Suponho que no loop principal, eu também gostaria de mudar a linha if(in==out) continue;para if(out==0) continue;, certo? Na verdade, sou totalmente novo em programação para pi, então talvez isso não seja necessário - acho que tudo isso está acontecendo de forma síncrona (ou seja, não precisamos nos preocupar com o loop principal que está sendo chamado enquanto os loops aninhados ainda estão em execução)
rinogo 21/08
(Isto é tudo utilizando o acima mencionado Inmojo modulo de resistência, é claro: inmojo.com/store/inmojo-market/item/... )
rinogo
2
Há um problema com isso. Para uma atividade estável do sistema, DEVE ceder controle ao sistema periodicamente e duvido muito que você o restaure dentro de um tempo tão curto quanto (menos de) 20ms. Portanto, esses rendimentos resultarão em pulsos perdidos e, como resultado, a lâmpada piscará. Eu pedi uma pergunta sobre isso, mas não tenho respostas. Você pode configurar o sched_rt_runtime_us e o sched_rt_period_us como -1 para desativar completamente a preempção do sistema, mas se você não tiver sched_yield () ou usleep (), isso certamente criará problemas.
SF.
2
Ou seja: com SCHED_FIFO, uma vez iniciado o intervalo de tempo, ele permanece ininterrupto até que você ceda voluntariamente (ou o sched_rt_runtime_us seja decorrido), mas o sistema não garante quando você obtém esse intervalo de tempo. No meu caso, notei que, em operação normal, o tempo entre as chamadas (dando intervalos de tempo à tarefa) pode se estender até 0,1s com carga máxima da CPU. Talvez esse período possa ser ajustado e forçado mais curto, mas não sei como.
SF.