ATtiny13A - Không thể tạo ra phần mềm PWM với chế độ CTC


8

Tôi đang cố gắng tạo ra đèn LED RGB điều khiển từ xa bằng ATtiny13A.

Tôi biết ATtiny85 phù hợp hơn cho mục đích này và tôi biết cuối cùng tôi có thể không phù hợp với toàn bộ mã, nhưng bây giờ mối quan tâm chính của tôi là tạo ra một phần mềm PWM sử dụng các ngắt trong chế độ CTC.

Tôi không thể hoạt động trong bất kỳ chế độ khác (trừ PWM nhanh với OCR0Anhư TOPđó là cơ bản điều tương tự) vì mã máy thu IR Tôi đang sử dụng cần có một tần số 38 kHz mà nó tạo ra sử dụng CTC và OCR0A=122.

Vì vậy, tôi đang cố gắng (và tôi đã thấy mọi người đề cập đến vấn đề này trên Internet) sử dụng Output Compare AOutput Compare Bngắt để tạo ra một phần mềm PWM.

OCR0A, cũng được sử dụng bởi mã IR, xác định tần suất mà tôi không quan tâm. Và OCR0B, xác định chu kỳ hoạt động của PWM mà tôi sẽ sử dụng để thay đổi màu LED.

Tôi hy vọng có thể có được một PWM với chu kỳ nhiệm vụ 0-100% bằng cách thay đổi OCR0Bgiá trị từ 0sang OCR0A. Đây là sự hiểu biết của tôi về những gì sẽ xảy ra:

Dạng sóng

Nhưng những gì thực sự đang xảy ra là điều này (đây là từ mô phỏng Proteus ISIS):

Như bạn có thể thấy bên dưới, tôi có thể nhận được khoảng 25% -75% chu kỳ thuế nhưng với ~ 0-25% và ~ 75-100% dạng sóng chỉ bị kẹt và không thay đổi.

Dòng VÀNG: Phần cứng PWM

Dòng RED: Phần mềm PWM với chu kỳ nhiệm vụ cố định

Dòng XANH: Phần mềm PWM với chu kỳ nhiệm vụ khác nhau

Kết quả dao động

Và đây là mã của tôi:

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

Tôi có thể hỏi tại sao bạn không thể sử dụng phần cứng PWM? Lý do bạn đưa ra không có ý nghĩa gì. Lý do duy nhất để không sử dụng phần cứng là nếu bạn cần giao diện SPI hoặc ngắt ngoài.
Maple

@Maple Tôi đang cố điều khiển đèn LED RGB nên tôi cần 3 tín hiệu PWM, mỗi tín hiệu cho mỗi màu. OCR0Ađược sử dụng bởi mã IR nên tôi chỉ có OCR0B. Tôi đang cố gắng sử dụng nó để tạo ra phần mềm PWM trên 3 chân không phải là PWM.
Pouria P

Phần mềm 38kHz PWM sẽ không hoạt động. Điều đó quá nhanh đối với MCU.
JimmyB

1
Bạn có thể (và đã làm như vậy) chạy ISR @ 38kHz. Nhưng đối với bất kỳ chu kỳ nhiệm vụ nào ngoài 50%, bạn sẽ cần tần suất cao hơn. Ví dụ: Đối với 25% @ 38kHz, bạn cần có khả năng xử lý hai ngắt liên tiếp trong khung thời gian 38kHz / 25% = 152kHz. Chỉ còn lại khoảng 63 chu kỳ xung nhịp CPU (9600kHz / 152kHz) cho ISR. Ở chu kỳ nhiệm vụ 10%, bạn còn lại 25 đồng hồ CPU cho ISR.
JimmyB

3
Bạn đã không chỉ định tần số PWM mong muốn. Để kiểm soát độ sáng, bạn sẽ không cần ở bất cứ đâu gần 38kHz. 100Hz có thể là đủ. Tôi khuyên bạn nên sử dụng tần số 38kHz (IR) làm chu kỳ nhiệm vụ thấp nhất cho phần mềm PWM của bạn và triển khai PWM như một số bội số đó, ví dụ 256, để chu kỳ nhiệm vụ thấp nhất là 1/256 (một chu kỳ xung nhịp 38kHz) và cao nhất (dưới 100%) là (255/256), bằng 255 chu kỳ xung nhịp 38kHz. Điều này cung cấp cho bạn một PWM 8 bit ở (38000/256) ~ 148Hz.
JimmyB

Câu trả lời:


8

Một phần mềm tối thiểu PWM có thể trông như thế này:

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

Chương trình của bạn đặt dutyCyclethành giá trị mong muốn và ISR xuất tín hiệu PWM tương ứng. dutyCycleuint16_tđể cho phép các giá trị nằm trong khoảng từ 0 đến 256 ; 256 lớn hơn bất kỳ giá trị có thể nào currentPwmCountvà do đó cung cấp chu kỳ thuế 100% đầy đủ.

Nếu bạn không cần 0% (hoặc 100%), bạn có thể loại bỏ một số chu kỳ bằng cách sử dụng uint8_tsao cho 0kết quả trong chu kỳ nhiệm vụ là 1/256 và 255là 100% hoặc 0là 0% và 255là chu kỳ thuế là 255 / 256.

Bạn vẫn không có nhiều thời gian trong ISR 38kHz; bằng cách sử dụng một trình biên dịch nội tuyến nhỏ, bạn có thể giảm số chu kỳ của ISR xuống còn 1/3 đến 1/2. Thay thế: Chỉ chạy mã PWM của bạn mỗi lần tràn bộ đếm thời gian khác, giảm một nửa tần số PWM.

Nếu bạn có nhiều kênh PWM và các chân bạn PMW-ing đều giống nhau, PORTbạn cũng có thể thu thập tất cả các trạng thái của chân trong một biến và cuối cùng xuất chúng ra cổng theo một bước mà sau đó chỉ cần đọc từ- cổng và-với-mặt nạ, hoặc-với-trạng thái mới, ghi vào cổng một lần thay vì một lần trên mỗi pin / kênh .

Thí dụ:

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

Mã này ánh xạ chu kỳ nhiệm vụ đến một 1đầu ra logic trên các chân; nếu đèn LED của bạn có 'lý tiêu cực' (LED trên khi pin là thấp ), bạn có thể đảo ngược phân cực của tín hiệu PWM bằng cách đơn giản thay đổi if (cnt < dutyCycle...)để if (cnt >= dutyCycle...).


Wow bạn thật tuyệt vời. Tôi đã tự hỏi nếu sự hiểu biết của tôi về những gì bạn bảo tôi làm là chính xác và bây giờ có câu trả lời nhiều thông tin này với các ví dụ và tất cả. Cảm ơn một lần nữa.
Pouria P

Chỉ một điều nữa, tôi đã hiểu điều này một cách chính xác: Nếu tôi thực hiện tràn bộ đếm thời gian khác, tôi sẽ đặt một ifthói quen ngắt để chỉ thực thi mã PWM mỗi lần. Bằng cách thực hiện điều này nếu mã PWM của tôi mất quá nhiều thời gian và ngắt tràn tiếp theo bị bỏ lỡ thì chương trình của tôi sẽ ổn vì lần gián đoạn tiếp theo sẽ không làm gì cả. Đó có phải ý của bạn?
Pouria P

Vâng, đây là những gì tôi muốn nói, xin lỗi vì quá ngắn gọn về nó. ISR phải đủ nhanh để không bỏ lỡ bất kỳ sự gián đoạn nào ngay từ đầu, nhưng ngay cả khi đó, việc dành 90% thời gian CPU cho một ISR cũng có thể không tốt, vì vậy bạn có thể cắt giảm gần một nửa bằng cách bỏ qua ' logic phức tạp mỗi lần gián đoạn khác để lại nhiều thời gian hơn cho các nhiệm vụ khác.
JimmyB

2

Như @JimmyB nhận xét tần số PWM quá cao.

Có vẻ như các ngắt có tổng độ trễ bằng một phần tư chu kỳ PWM.

Khi chồng lấp, chu kỳ nhiệm vụ được cố định theo tổng độ trễ, vì ngắt thứ hai được xếp hàng và được thực hiện sau khi thoát thứ nhất.

Chu kỳ nhiệm vụ tối thiểu của PWM được tính bằng tổng phần trăm độ trễ ngắt trong khoảng thời gian PWM. Logic tương tự áp dụng cho chu kỳ nhiệm vụ tối đa PWM.

Nhìn vào biểu đồ, chu kỳ nhiệm vụ tối thiểu là khoảng 25%, và sau đó tổng độ trễ phải là ~ 1 / (38000 * 4) = 6,7 khúc.

Do đó, thời gian tối thiểu của PWM là 256 * 6,7 Lời = 1715 Âm và tần số tối đa 583 Hz.

Một số giải thích thêm về các bản vá có thể có tần suất cao:

Ngắt có hai cửa sổ mù khi không có gì có thể được thực hiện, nhập vào cuối thoát ra khỏi ngắt khi bối cảnh được lưu và phục hồi. Vì mã của bạn khá đơn giản, tôi nghi ngờ rằng điều này chiếm một phần lớn độ trễ.

Một giải pháp để bỏ qua các giá trị thấp sẽ vẫn có độ trễ ít nhất là thoát khỏi ngắt và đi vào ngắt tiếp theo để chu kỳ nhiệm vụ tối thiểu sẽ không như mong đợi.

Miễn là điều này không ít hơn một bước PWM, chu kỳ nhiệm vụ của PWM sẽ bắt đầu ở giá trị cao hơn. Chỉ là một cải tiến nhỏ từ những gì bạn có bây giờ.

Tôi thấy bạn đã sử dụng 25% thời gian của bộ xử lý trong các ngắt, vậy tại sao bạn không sử dụng 50% trở lên, hãy để lại ngắt thứ hai và chỉ dùng nhóm cho cờ so sánh. Nếu bạn chỉ sử dụng các giá trị tối đa 128, bạn sẽ chỉ có tối đa 50% chu kỳ nhiệm vụ, nhưng với độ trễ của hai hướng dẫn có thể được tối ưu hóa trong trình biên dịch chương trình.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.