Estou tentando criar uma luz LED RGB de controle remoto usando um ATtiny13A.
Sei que o ATtiny85 é mais adequado para essa finalidade e sei que talvez não consiga ajustar todo o código, mas, por enquanto, minha principal preocupação é gerar um PWM de software usando interrupções no modo CTC.
Não posso operar em nenhum outro modo (exceto no PWM rápido com OCR0A
o TOP
qual é basicamente a mesma coisa) porque o código do receptor de IR que estou usando precisa de uma frequência de 38 kHz que gera usando CTC e OCR0A=122
.
Então, estou tentando (e vi pessoas mencionarem isso na Internet) usar o Output Compare A
e Output Compare B
interrompe para gerar um PWM de software.
OCR0A
, que também é usado pelo código IR, determina a frequência com a qual não me importo. E OCR0B
determina o ciclo de trabalho do PWM que utilizarei para alterar as cores dos LEDs.
Espero conseguir um PWM com ciclo de trabalho de 0 a 100%, alterando o OCR0B
valor de 0
para OCR0A
. Esta é a minha compreensão do que deve acontecer:
Mas o que realmente está acontecendo é isso (isto é, da simulação Proteus ISIS):
Como você pode ver abaixo, sou capaz de obter cerca de 25% a 75% do ciclo de trabalho, mas para ~ 0-25% e ~ 75-100% a forma de onda está presa e não muda.
Linha AMARELA: Hardware PWM
Linha RED: Software PWM com ciclo de serviço fixo
Linha VERDE: Software PWM com ciclo de trabalho variável
E aqui está o meu código:
#ifndef F_CPU
#define F_CPU (9600000UL) // 9.6 MHz
#endif
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
int main(void)
{
cli();
TCCR0A = 0x00; // Init to zero
TCCR0B = 0x00;
TCCR0A |= (1<<WGM01); // CTC mode
TCCR0A |= (1<<COM0A0); // Toggle OC0A on compare match (50% PWM on PINB0)
// => YELLOW line on oscilloscope
TIMSK0 |= (1<<OCIE0A) | (1<<OCIE0B); // Compare match A and compare match B interrupt enabled
TCCR0B |= (1<<CS00); // Prescalar 1
sei();
DDRB = 0xFF; // All ports output
while (1)
{
OCR0A = 122; // This is the value I'll be using in my main program
for(int i=0; i<OCR0A; i++)
{
OCR0B = i; // Should change the duty cycle
_delay_ms(2);
}
}
}
ISR(TIM0_COMPA_vect){
PORTB ^= (1<<PINB3); // Toggle PINB3 on compare match (50% <SOFTWARE> PWM on PINB3)
// =>RED line on oscilloscope
PORTB &= ~(1<<PINB4); // PINB4 LOW
// =>GREEN line on oscilloscope
}
ISR(TIM0_COMPB_vect){
PORTB |= (1<<PINB4); // PINB4 HIGH
}
fonte
OCR0A
é usado pelo código IR, então eu só tenhoOCR0B
. Estou tentando usá-lo para gerar software PWM em 3 pinos não-PWM.Respostas:
Um PWM mínimo de software poderia ter esta aparência:
Seu programa define
dutyCycle
o valor desejado e o ISR emite o sinal PWM correspondente.dutyCycle
é umuint16_t
para permitir valores entre 0 e 256 inclusive; 256 é maior que qualquer valor possívelcurrentPwmCount
e, portanto, fornece um ciclo de trabalho 100% completo.Se você não precisar de 0% (ou 100%), poderá cortar alguns ciclos usando um
uint8_t
para que0
resulte em um ciclo de trabalho de 1/256 e255
seja 100% ou0
seja 0% e255
seja um ciclo de trabalho de 255 / 256Você ainda não tem muito tempo em um ISR de 38kHz; usando um pequeno montador em linha, você provavelmente pode reduzir a contagem de ciclos do ISR em 1/3 a 1/2. Alternativa: execute seu código PWM apenas em todos os outros temporizadores, reduzindo pela metade a frequência PWM.
Se você possui vários canais PWM e os pinos que você usa como PMW estão todos iguais,
PORT
você também pode coletar todos os estados dos pinos em uma variável e finalmente enviá-los para a porta em uma etapa que só precisa da leitura de porta, com máscara, ou com novo estado, gravação na porta uma vez em vez de uma vez por pino / canal .Exemplo:
Esse código mapeia o ciclo de trabalho para uma
1
saída lógica nos pinos; se seus LEDs tiverem 'lógica negativa' (LED aceso quando o pino estiver baixo ), você poderá inverter a polaridade do sinal PWM simplesmente mudandoif (cnt < dutyCycle...)
paraif (cnt >= dutyCycle...)
.fonte
if
rotina de interrupção para executar apenas o código PWM em todas as outras vezes. Ao fazer isso, se meu código PWM demorar muito e a próxima interrupção de transbordamento for perdida, meu programa ficará bom porque a próxima interrupção não faria nada de qualquer maneira. É isso que você quis dizer?Como o @JimmyB comentou, a frequência do PWM é muito alta.
Parece que as interrupções têm uma latência total de um quarto do ciclo do PWM.
Ao se sobrepor, o ciclo de serviço é fixo, dado pela latência total, pois a segunda interrupção é enfileirada e executada após a saída da primeira.
O ciclo de trabalho mínimo do PWM é fornecido pela porcentagem total de latência de interrupção no período do PWM. A mesma lógica se aplica ao ciclo de trabalho PWM máximo.
Observando os gráficos, o ciclo de trabalho mínimo é de cerca de 25% e a latência total deve ser ~ 1 / (38000 * 4) = 6,7 µs.
Como conseqüência, o período mínimo de PWM é de 256 * 6,7 µs = 1715 µs e frequência máxima de 583 Hz.
Mais algumas explicações sobre possíveis correções em alta frequência:
A interrupção possui duas janelas cegas quando nada pode ser feito, entrando e saindo da interrupção quando o contexto é salvo e recuperado. Como seu código é bastante simples, suspeito que isso considere boa parte da latência.
Uma solução para ignorar os valores baixos ainda terá uma latência, pelo menos, ao sair da interrupção e entrar na próxima interrupção, para que o ciclo de trabalho mínimo não seja o esperado.
Desde que isso não seja menor que uma etapa do PWM, o ciclo de trabalho do PWM começará com um valor mais alto. Apenas uma ligeira melhoria do que você tem agora.
Vejo que você já usa 25% do tempo do processador em interrupções; então, por que você não usa 50% ou mais dele, deixa a segunda interrupção e apenas agrupa o sinalizador de comparação. Se você usar valores apenas até 128, terá apenas até 50% de ciclo de serviço, mas com a latência de duas instruções que poderiam ser otimizadas no assembler.
fonte