使用avr定时器、快速pwm和中断驱动ws2812b

问题描述 投票:0回答:1

在有人对我大喊大叫之前,我知道 WS2812B LED 灯带需要非常精确的计时,并且使用汇编代码更容易完成(并且已经存在出色的库)。

不过,我想知道:真的不可能使用定时器和中断来驱动条带吗?毕竟,atmega328p 文档谈到计时器时说它“允许准确的程序执行定时(事件管理)和波形生成”。

我使用的是 arduino nano (atmega328p),CPU 频率为 16MHz(所以使用快速 PWM,我们得到一个持续时间为 6.25 ns 的周期,对吗?)

这就是我想要做的:我想使用 Timer0 生成位触发信号来驱动 LED 灯条。我查看了 light_ws2812 lib 的(工作)时序,我的数学给出了以下结果:

  • 短周期(T0H 或 T1L)为 350 ns,因此大约有 5 个定时器步长;
  • 长周期(T1H或T0L)为900 ns,因此大约有14个定时器步长;

因此,我根据 Atmega328p 数据表将定时器 0 设置为模式 7:快速 PWM 模式,OCR0A 定义的 TOP 限制为 14+5=19,并在 OCR0B 中设置比较匹配值 5 或 14,具体取决于要发送的位值。 OCR0B 中值的更改是通过计数器溢出标志中断来完成的

第一次尝试是点亮一个红色的 LED 灯;我编写了以下代码来实现这一切:

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>


#define T0H_t 5
#define T1H_t 14
#define TOTAL_t (T0H_t+T1H_t)

#define GET_TIME(B) (B & 1? T1H_t : T0H_t)

#define MAX_LEDS 10
#define BUFFER_SIZE (3*MAX_LEDS)


/**
 * @brief Buffer for GRB values to be sent via bit-banging.
 * 
 * Bytes are sent in MSBF. When all bytes are sent, buffer should get reset.
 */
struct sending_buffer 
{
    uint8_t buffer [BUFFER_SIZE];   // stores RGB bytes to be sent

    uint8_t byte_index;             // index to byte being sent
    uint8_t bit_index;              // index to next bit to be sent
    uint8_t last_byte;              // index of last byte + 1
};

struct sending_buffer colors_to_send;


struct sending_buffer * init_buffer(struct sending_buffer * sb)
{
    sb->byte_index = 0;
    sb->bit_index = 7;
    sb->last_byte = 0;

    return sb;
}

// returns the bit value of next bit to be sent from sb
uint8_t get_next_bit(struct sending_buffer * sb)
{
    uint8_t bit = (sb->buffer[sb->byte_index] >> sb->bit_index--) & 1;

    sb->bit_index = sb->bit_index > 7 ? 7 : sb->bit_index ; // Bytes are sent in MSBF
    if(sb->bit_index == 7) 
    {
        sb->byte_index++;
        sb->byte_index %= BUFFER_SIZE;
    }

    return bit;
}

uint8_t put_byte(struct sending_buffer * sb, uint8_t byte)
{
    sb->buffer[sb->last_byte++] = byte;
}

uint8_t send_color(uint8_t r, uint8_t g, uint8_t b)
{
    put_byte(&colors_to_send, g);
    put_byte(&colors_to_send, r);
    put_byte(&colors_to_send, b);
}

void init_send()
{
    if(colors_to_send.byte_index < colors_to_send.last_byte)
    {
        uint8_t next_bit = get_next_bit(&colors_to_send);
        OCR0B = GET_TIME(next_bit);
    }
    sei();
}

void setup_interrupt()
{
    /**
     * @brief Setting up Fast PWM mode with TOP value defined by OCR0A ; clearing OC0B on c-m between OCR0B and TCNT0.
     * 
     */

    // Set Fast PWM mode with TOP value defined by OCR0A
    TCCR0A |= (1 << WGM00) | (1 << WGM01);
    TCCR0B |= (1 << WGM02);

    // Set COM0B1 to clear OC0B on compare match
    TCCR0A |= (1 << COM0B1);

    // Set timer without prescaler (CS00)
    TCCR0B |= (1 << CS00);

    // Set TOP value
    OCR0A = TOTAL_t; // Assuming TOTAL_t is correctly calculated elsewhere

    // Set initial value of OCR0B to 0 (clear OC0B)
    OCR0B = 0;

    // Set pin 5 (OC0B) as output
    DDRD |= (1 << PD5);

    // Enable interrups
    TIMSK0 |= (1 << TOIE0);
}


ISR(TIMER0_OVF_vect)
{
    if(colors_to_send.byte_index < colors_to_send.last_byte)
    {
        uint8_t next_bit = get_next_bit(&colors_to_send);
        OCR0B = GET_TIME(next_bit);
    }
}

int main()
{
    _delay_ms(10);
    init_buffer(&colors_to_send);

    send_color(255,0,0);

    setup_interrupt();
    init_send();
    while(colors_to_send.byte_index < colors_to_send.last_byte)
    ;
    cli();
    OCR0B = 0;
    _delay_ms(10);

    while(1);

    return 0;
}

现在显然我做错了什么,因为这不起作用。所以我的问题是:

我的代码是否在某个地方出错了(如果是的话,在哪里?),或者以这种方式驱动 WS2812B 真的无法撤消吗?

c timer embedded interrupt avr
1个回答
0
投票

您有 1.25 微秒的时间来发送一位。那是 20 个时钟周期。由此,必须减去激活中断(

CALL
指令)的4个周期和从中断返回(
RETI
指令)的4个周期。您还剩下 12 个机器周期。您认为您创建的中断处理程序代码只有 12 条指令吗? 在我看来,即使在汇编程序中也无法做到这一点。在C中没办法。我同意这些评论。自己测量一下。没有什么比这更简单的了。如果你想在边缘做这样的事情,你还必须有合适的设备(逻辑分析仪或示波器)。

© www.soinside.com 2019 - 2024. All rights reserved.