J'essaie de créer une lumière LED RGB à télécommande à l'aide d'un ATtiny13A.
Je sais que l'Attiny85 est mieux adapté à cette fin, et je sais que je ne pourrai peut-être pas éventuellement adapter tout le code, mais pour l'instant ma principale préoccupation est de générer un logiciel PWM utilisant des interruptions en mode CTC.
Je ne peux pas fonctionner dans un autre mode (sauf pour PWM rapide avec OCR0A
as TOP
qui est fondamentalement la même chose) parce que le code du récepteur IR que j'utilise a besoin d'une fréquence de 38 kHz qu'il génère en utilisant CTC et OCR0A=122
.
J'essaie donc (et j'ai vu des gens le mentionner sur Internet) d'utiliser les interruptions Output Compare A
et Output Compare B
pour générer un logiciel PWM.
OCR0A
, qui est également utilisé par le code IR, détermine la fréquence, ce qui m'est égal. Et OCR0B
, détermine le rapport cyclique du PWM que je vais utiliser pour changer les couleurs des LED.
Je m'attends à pouvoir obtenir un PWM avec un rapport cyclique de 0 à 100% en changeant la OCR0B
valeur de 0
à OCR0A
. Voici ma compréhension de ce qui devrait arriver:
Mais ce qui se passe réellement est le suivant (ceci provient de la simulation Proteus ISIS):
Comme vous pouvez le voir ci-dessous, je peux obtenir un rapport cyclique d'environ 25% à 75% mais pour ~ 0-25% et ~ 75-100% la forme d'onde est juste bloquée et ne change pas.
Ligne JAUNE: Matériel PWM
Ligne ROUGE: Logiciel PWM avec rapport cyclique fixe
Ligne VERTE: logiciel PWM avec cycle d'utilisation variable
Et voici mon code:
#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
}
la source
OCR0A
est utilisé par le code IR donc je n'ai queOCR0B
. J'essaie de l'utiliser pour générer un logiciel PWM sur 3 broches non PWM.Réponses:
Un PWM logiciel minimal pourrait ressembler à ceci:
Votre programme est réglé
dutyCycle
sur la valeur souhaitée et l'ISR émet le signal PWM correspondant.dutyCycle
est unuint16_t
pour permettre des valeurs comprises entre 0 et 256 inclus; 256 est plus grand que toute valeur possiblecurrentPwmCount
et fournit ainsi un cycle de service complet de 100%.Si vous n'avez pas besoin de 0% (ou 100%), vous pouvez raser certains cycles en utilisant un
uint8_t
afin que soit0
un cycle d'utilisation de 1/256255
soit 100% soit0
0%255
soit un cycle d'utilisation de 255 / 256.Vous n'avez toujours pas beaucoup de temps dans un ISR à 38 kHz; en utilisant un petit assembleur en ligne, vous pouvez probablement réduire le nombre de cycles de l'ISR de 1/3 à 1/2. Alternative: exécutez votre code PWM uniquement tous les autres dépassements de minuterie, réduisant de moitié la fréquence PWM.
Si vous avez plusieurs canaux PWM et que les broches que vous utilisez PMW sont toutes identiques,
PORT
vous pouvez également collecter tous les états des broches dans une variable et enfin les sortir sur le port en une seule étape qui n'a alors besoin que de la lecture de- port, et-avec-masque, ou-avec-nouvel état, écriture sur port une fois au lieu d' une fois par broche / canal .Exemple:
Ce code mappe le rapport cyclique à une
1
sortie logique sur les broches; si vos LED ont une «logique négative» (LED allumée lorsque la broche est faible ), vous pouvez inverser la polarité du signal PWM en changeant simplementif (cnt < dutyCycle...)
enif (cnt >= dutyCycle...)
.la source
if
dans la routine d'interruption pour exécuter uniquement le code PWM une fois sur deux. En faisant cela si mon code PWM prend trop de temps et que la prochaine interruption de débordement est manquée, mon programme ira bien car la prochaine interruption n'allait rien faire de toute façon. C'est ce que vous vouliez dire?Comme l'a commenté @JimmyB, la fréquence PWM est trop élevée.
Il semble que les interruptions aient une latence totale d'un quart du cycle PWM.
En cas de chevauchement, le rapport cyclique est fixé par la latence totale, car la deuxième interruption est mise en file d'attente et exécutée après la sortie de la première.
Le rapport cyclique PWM minimum est donné par le pourcentage de latence d'interruption total dans la période PWM. La même logique s'applique au cycle de service PWM maximal.
En regardant les graphiques, le rapport cyclique minimum est d'environ 25%, puis la latence totale doit être ~ 1 / (38000 * 4) = 6,7 µs.
En conséquence, la période PWM minimale est de 256 * 6,7 µs = 1715 µs et 583 Hz de fréquence maximale.
Quelques explications supplémentaires sur les correctifs possibles à haute fréquence:
L'interruption a deux fenêtres aveugles lorsque rien ne peut être fait, entrant en fin de l'interruption lorsque le contexte est enregistré et récupéré. Puisque votre code est assez simple, je soupçonne que cela prend une bonne partie de la latence.
Une solution pour ignorer les valeurs faibles aura toujours une latence au moins aussi élevée que la sortie de l'interruption et l'entrée dans l'interruption suivante, de sorte que le rapport cyclique minimum ne sera pas comme prévu.
Tant qu'il ne s'agit pas d'une étape PWM, le rapport cyclique PWM commencera à une valeur plus élevée. Juste une légère amélioration par rapport à ce que vous avez maintenant.
Je vois que vous utilisez déjà 25% du temps processeur dans les interruptions, alors pourquoi n'en utilisez-vous pas 50% ou plus, laissez la deuxième interruption et regroupez-vous simplement pour l'indicateur de comparaison. Si vous n'utilisez que des valeurs jusqu'à 128, vous n'aurez que 50% de rapport cyclique, mais avec la latence de deux instructions qui pourraient être optimisées dans l'assembleur.
la source