在有人对我大喊大叫之前,我知道 WS2812B LED 灯带需要非常精确的计时,并且使用汇编代码更容易完成(并且已经存在出色的库)。
不过,我想知道:真的不可能使用定时器和中断来驱动条带吗?毕竟,atmega328p 文档谈到计时器时说它“允许准确的程序执行定时(事件管理)和波形生成”。
我使用的是 arduino nano (atmega328p),CPU 频率为 16MHz(所以使用快速 PWM,我们得到一个持续时间为 6.25 ns 的周期,对吗?)
这就是我想要做的:我想使用 Timer0 生成位触发信号来驱动 LED 灯条。我查看了 light_ws2812 lib 的(工作)时序,我的数学给出了以下结果:
因此,我根据 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 真的无法撤消吗?
您有 1.25 微秒的时间来发送一位。那是 20 个时钟周期。由此,必须减去激活中断(
CALL
指令)的4个周期和从中断返回(RETI
指令)的4个周期。您还剩下 12 个机器周期。您认为您创建的中断处理程序代码只有 12 条指令吗?
在我看来,即使在汇编程序中也无法做到这一点。在C中没办法。我同意这些评论。自己测量一下。没有什么比这更简单的了。如果你想在边缘做这样的事情,你还必须有合适的设备(逻辑分析仪或示波器)。