ATtiny13A-无法使用CTC模式生成软件PWM


8

我正在尝试使用ATtiny13A制作遥控RGB LED灯。

我知道ATtiny85更适合于此目的,并且我知道我最终可能无法适应整个代码,但是现在我主要关心的是使用CTC模式下的中断生成软件PWM。

我不能在任何其它模式中操作(除了用快速PWM OCR0A作为TOP这基本上是相同的东西),因为我使用的IR接收器的代码需要它产生使用四氯化碳和38 kHz的频率OCR0A=122

因此,我试图(并且我已经在互联网上看到人们提到了这一点)使用Output Compare AOutput Compare B中断来生成软件PWM。

OCR0A,IR代码也使用,它确定频率,我不在乎。并且OCR0B,确定将用于更改LED颜色的PWM的占空比。

我期待能够通过改变来获得具有0-100%占空比的PWM OCR0B从价值0OCR0A。这是我对应该发生的事情的理解:

波形

但是实际上是这样的(这是来自Proteus ISIS仿真):

正如您在下面看到的那样,我能够获得大约25%-75%的占空比,但是对于〜0-25%和〜75-100%,波形只是被卡住了并且不会改变。

黄线:硬件PWM

红线:具有固定占空比的软件PWM

绿线:占空比变化的软件PWM

示波器结果

这是我的代码:

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

请问为什么不能使用硬件PWM?您给出的原因没有任何意义。不使用硬件的唯一原因是是否需要SPI接口或外部中断。
Maple

@Maple我正在尝试控制RGB LED,因此我需要3个PWM信号,每种颜色一个。OCR0A是IR代码使用的,所以我只有OCR0B。我正在尝试使用它在3个非PWM引脚上生成软件PWM。
Pouria P

38kHz软件PWM无法正常工作。对于MCU而言,这太快了。
JimmyB

1
您可以(并且已经这样做)在38kHz下运行ISR。但是对于除50%以外的任何占空比,您将需要更高的频率。示例:对于25%@ 38kHz,您需要能够在38kHz / 25%= 152kHz时间范围内处理两个连续的中断。ISR仅剩下约63个CPU时钟周期(9600kHz / 152kHz)。在占空比为10%的情况下,您需要为ISR留出25个CPU时钟。
JimmyB

3
您未指定所需的PWM频率。对于亮度控制,您不需要在38kHz附近。100Hz可能就足够了。我建议您将38kHz(IR)频率用作软件PWM的最低占空比,并将PWM实现为该频率的某个倍数,例如256,以便最低占空比为1/256(一个38kHz时钟周期),并且最高(低于100%)为(255/256),等于255 38kHz时钟周期。这为您提供了(38000/256)〜148Hz的8位PWM。
JimmyB

Answers:


8

最小的软件PWM可能如下所示:

volatile uint16_t dutyCycle;


uint8_t currentPwmCount;

ISR(TIM0_COMPA_vect){
  const uint8_t cnt = currentPwmCount + 1; // will overflow from 255 to 0
  currentPwmCount = cnt;
  if ( cnt <= dutyCyle ) {
    // Output 0 to pin
  } else {
    // Output 1 to pin
  }
}

您的程序将设置dutyCycle为所需的值,ISR将输出相应的PWM信号。dutyCycle是一个uint16_t允许0到256之间(含0和256)之间的值;256大于任何可能的值,currentPwmCount因此提供了完整的100%占空比。

如果您不需要0%(或100%),则可以通过使用a来缩短某些周期,以uint8_t使0占空比为1/256且占空比255为100%或0为0%且255占空比为255 / 256。

在38kHz ISR中,您仍然没有太多时间。使用一个小的内联汇编程序,您可以将ISR的周期数减少1/3到1/2。替代方法:仅每隔一个定时器溢出运行一次PWM代码,从而使PWM频率减半。

如果您有多个PWM通道,并且要进行PMW输入的引脚都在同一位置PORT,则还可以将所有引脚的状态收集到一个变量中,最后一步将它们输出到端口,然后只需要读取以下内容即可:端口,带掩码或新状态的端口写入一次,而不是每个引脚/通道一次

例:

volatile uint8_t dutyCycleRed;
volatile uint8_t dutyCycleGreen;
volatile uint8_t dutyCycleBlue;

#define PIN_RED (0) // Example: Red on Pin 0
#define PIN_GREEN (4) // Green on pin 4
#define PIN_BLUE (7) // Blue on pin 7

#define BIT_RED (1<<PIN_RED)
#define BIT_GREEN (1<<PIN_GREEN)
#define BIT_BLUE (1<<PIN_BLUE)

#define RGB_PORT_MASK ((uint8_t)(~(BIT_RED | BIT_GREEN | BIT_BLUE)))

uint8_t currentPwmCount;

ISR(TIM0_COMPA_vect){
  uint8_t cnt = currentPwmCount + 1;
  if ( cnt > 254 ) {
    /* Let the counter overflow from 254 -> 0, so that 255 is never reached
       -> duty cycle 255 = 100% */
    cnt = 0;
  }
  currentPwmCount = cnt;
  uint8_t output = 0;
  if ( cnt < dutyCycleRed ) {
    output |= BIT_RED;
  }
  if ( cnt < dutyCycleGreen ) {
    output |= BIT_GREEN;
  }
  if ( cnt < dutyCycleBlue ) {
    output |= BIT_BLUE;
  }

  PORTx = (PORTx & RGB_PORT_MASK) | output;
}

该代码将占空比映射到1引脚上的逻辑输出。如果您的LED具有“负逻辑”(当引脚为低电平时LED点亮),则可以通过简单地更改if (cnt < dutyCycle...)为来反转PWM信号的极性if (cnt >= dutyCycle...)


哇,真棒。我想知道我对您告诉我做的事情的理解是否正确,现在有了示例和全部内容,此信息非常丰富。再次感谢。
Pouria P

再说一件事,我是否正确理解了这一点:如果我每隔一个定时器溢出都要执行一次PWM,我将if在中断例程中放入一个,仅每隔两次执行一次PWM代码。通过这样做,如果我的PWM代码花费的时间太长,并且错过了下一个溢出中断,那么我的程序就可以了,因为下一个中断无论如何都不会做任何事情。这是你的意思吗?
Pouria P

是的,这就是我的意思,很抱歉。ISR应该足够快,不要一开始就错过任何中断,但是即使是这样,在一个ISR中花费90%的CPU时间也不是一件好事,因此您可以跳过'复杂的逻辑每隔一个中断就会为其他任务留出更多时间。
JimmyB

2

正如@JimmyB所说,PWM频率太高。

似乎这些中断的总等待时间为PWM周期的四分之一。

当重叠时,占空比由总等待时间决定,因为第二个中断在第一个中断退出后被排队并执行。

最小PWM占空比由PWM周期中的总中断等待时间百分比给出。相同的逻辑适用于最大PWM占空比。

查看这些图,最小占空比约为25%,然后总等待时间必须为〜/(38000 * 4)= 6.7 µs。

因此,最小PWM周期为256 * 6.7 µs = 1715 µs,最大频率为583 Hz。

有关高频补丁的更多解释:

当无法执行任何操作时,该中断具有两个盲窗口;进入保存状态并在上下文被保存和恢复时退出中断。由于您的代码非常简单,我怀疑这会占用很大一部分延迟。

跳过低值的解决方案至少在退出中断并进入下一个中断时仍会有等待时间,因此最小占空比将不符合预期。

只要不小于PWM步进,PWM占空比将以较高的值开始。仅比您现在拥有的略有改进。

我看到您已经在中断中使用了25%的处理器时间,那么为什么不使用50%或更多的时间,就留下第二个中断,只为比较标志池。如果仅使用最多128个值,则只有50%的占空比,但是有两条指令的延迟,可以在汇编器中对其进行优化。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.