Ws2812 DMA library [RESOLVED]

It’s strange to see, that there is no discussions about DMA of stm32 in Spark Core.

I’m trying to make adaptation of library wich using DMA on stm32f4…
And there’s strange thing: it’s stuck on NVIC init. Here is my code of initialisation:

TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef nvic_init;

uint16_t PrescalerValue;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_7;        /// D0 on board
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

/* Compute the prescaler value */
PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1;
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = 29; // 800kHz 
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);

/* configure DMA */
/* DMA clock enable */
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);


DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)TIM3_CCR1_Address;	// physical address of Timer 3 CCR1
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) PWM_Buffer;		// this is the buffer memory 
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;						// data shifted from memory to peripheral
DMA_InitStructure.DMA_BufferSize = PWM_BUFFER_SIZE;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					// automatically increase buffer index
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;							// stop DMA feed after buffer size is reached
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

DMA_Init(DMA1_Channel6, &DMA_InitStructure);

/* TIM3 CC1 DMA Request enable */
TIM_DMACmd(TIM3, TIM_DMA_CC1, ENABLE);

DMA_ITConfig(DMA1_Channel6, DMA_IT_HT, ENABLE);
DMA_ITConfig(DMA1_Channel6, DMA_IT_TC, ENABLE);


Serial.println("start dma");

DMA_SetCurrDataCounter(DMA1_Channel6, PWM_BUFFER_SIZE); 	// load number of bytes to be transferred
DMA_Cmd(DMA1_Channel6, ENABLE); 			// enable DMA channel 6
TIM_Cmd(TIM3, ENABLE); 						// enable Timer 3

Serial.println("after start dma");


nvic_init.NVIC_IRQChannel = DMA1_Channel6_IRQn;
nvic_init.NVIC_IRQChannelPreemptionPriority = 4;
nvic_init.NVIC_IRQChannelSubPriority = 0;
nvic_init.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&nvic_init);

Serial.println("after nvic init");

After running this code on core it writes to Serial “after start dma” and enters the mode, where it accessible from cloud, but not running my code…

Is there any suggestions? I’m really stuck.

Cool! What exactly are you trying to achieve? And what are you using as reference so far?

You might want to take a look at :spark: Section 15 - General-purpose timers (TIM2 to TIM5) (p.350) in the STM32F Reference Manual (RM0008) (9.2MB)

I see some reference to PWM1 and TIM3 in your code… make sure these are all referring to the correct timer.

1 Like

Hi BDub, you will also be interested, as i think :slight_smile:
I’m trying to combine https://github.com/x893/stm-ledstrip/ and https://github.com/Torrentula/STM32F1-workarea/tree/master/Project/WS2812 with rewriting in c++
I’ve already reviewed my code and changed: pin to PB6(D1) timer to TIM4 channel 1.
Now it’s running, but not sending any of pwm to PB6, and also didn’t calling DMA1_Channel6_IRQHandler function…
Fund thata IRQ callbacks must be defined with extern “C”, still didn’t checked, because found a bug in my websockets port and trying to fix it :slight_smile:

Checked this working.
NVIC initializes ok without failings, MCU walks thru code ok, not reboots(it’s really connected to node,js and answers), but there is still no callback on:

  void DMA1_Channel6_IRQHandler(void)
{
	Serial.println("DMA1_Channel6_IRQHandler");
}

Aha… very nice. This seems like a decent way to get accurate timing for the NeoPixels… certainly a very efficient way using DMA.

You have quite a few issues to solve if you have already worked in websockets into the equation.

I would suggest hard coding your prescaler to 0, and bump up your period value to compensate for the change. This will give you more resolution in your PWM duty cycle, and ultimately more accuracy on the various timing of WS2812, WS2812B and WS2811, etc…

Definitely an involved setup, and I wish you luck! Please keep us updated on your progress :smile:

You might have to show all of your code on this one to get proper help. :smile:

That’s I tried to say in NeoPixel topic, but my message was totally ignored :stuck_out_tongue:

Prescaler and other feautures i will tune with oscilloscope, now i want just to send array to pin, but it doesn’t want to do this.

Did you even tried to use DMA on STM’s? I think that there is some mistake in my code, and hope someone can review it and correct me. This is last edition of code initiating TIM, PB6 and DMA:

TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
TIM_OCInitTypeDef  TIM_OCInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;



void ws2812::init_buffers(void)
{
	Serial.println("init buffers");
	for(int i = 0; i < PWM_BUFFER_SIZE; i++)
	{
		PWM_Buffer[i] = 0;
	}
	for(int i = 0; i < FRAMEBUFFER_SIZE; i++)
	{
		framebuffer[i].red = 0;
		framebuffer[i].green = 0;
		framebuffer[i].blue = 0;
	}
}

void ws2812::init(void) {
	Serial.println("init");
	init_buffers();


	uint16_t PrescalerValue;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	/* GPIOA Configuration: TIM3 Channel 1 as alternate function push-pull */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;        /// D0 on board
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	/* Compute the prescaler value */
	PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1;
	/* Time base configuration */
	TIM_TimeBaseStructure.TIM_Period = 29; // 800kHz 
	TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
	TIM_TimeBaseStructure.TIM_ClockDivision = 0;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

	/* PWM1 Mode configuration: Channel1 */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &TIM_OCInitStructure);

	/* configure DMA */
	/* DMA clock enable */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);

	/* DMA1 Channel6 Config */
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & TIM4->CCR1;	// physical address of Timer 3 CCR1
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) PWM_Buffer;		// this is the buffer memory 
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;						// data shifted from memory to peripheral
	DMA_InitStructure.DMA_BufferSize = PWM_BUFFER_SIZE;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					// automatically increase buffer index
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Circular;							// stop DMA feed after buffer size is reached
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

	DMA_Init(DMA1_Channel6, &DMA_InitStructure);
	DMA_ITConfig(DMA1_Channel6, DMA_IT_TC | DMA_IT_HT, ENABLE);

	/* TIM3 CC1 DMA Request enable */
	TIM_DMACmd(TIM4, TIM_DMA_CC1, ENABLE);

	


	Serial.println("start dma");
	DMA_SetCurrDataCounter(DMA1_Channel6, PWM_BUFFER_SIZE); 	// load number of bytes to be transferred
	DMA_Cmd(DMA1_Channel6, ENABLE); 			// enable DMA channel 6
	TIM_Cmd(TIM4, ENABLE); 						// enable Timer 3
	Serial.println("after start dma");


	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel6_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	Serial.println("after nvic init");

}

and in application.cpp

extern "C"
{
	void DMA1_Channel6_IRQHandler(void)
	{
		Serial.println("DMA1_Channel6_IRQHandler");
		// Half-Transfer completed
		if (DMA_GetITStatus(DMA1_IT_HT6))
		{
			DMA_ClearITPendingBit(DMA1_IT_HT6);
			ws2812.Update_Buffer(ws2812.PWM_Buffer);
		}

		// Transfer completed
		if (DMA_GetITStatus(DMA1_IT_TC6))
		{
			DMA_ClearITPendingBit(DMA1_IT_TC6);
			ws2812.Update_Buffer(ws2812.PWM_Buffer + (PWM_BUFFER_SIZE / 2));
		}
	}
}

You can see in the RM0008 Reference Manual, “13.3.7 DMA request mapping” (page 272) that the DMA requests from TIM4_CH1 will go to DMA Channel 1 instead of Channel 6., so you should also try changing DMA1_Channel6 to DMA1_Channel1.

2 Likes

Thank you! It helped!

Guys, i’ve spent a lot of time in another works. It’s deep update released, firmware changes applied. So now i’m faced problems:
My code worked on previous releases of firmware files for local IDE.
But on last release I’ve got red led blink and reboot(as i see - it’s a “panic mode”). I’ve tried to use previous files, but first of all they’ve dropped my wifi credentials after flashing old bin file on updated core. Then it won’t connect with Spark Core android software. So i’ve entered credentials thru USB and core still won’t connect to cloud(that old bin file contains compiled firmware, which can’t disable cloud connection).

Can somebody tell me, is there any changes on DMA usage from firmware?
This is my code of DMA initialization, which causes panic mode:

void ws2812::init(int size) {
	Serial.println("ws2812 init");
	init_buffers(size);
	Serial.println("after buffers init");

	uint16_t PrescalerValue;

	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
	/* GPIOA Configuration: TIM3 Channel 1 as alternate function push-pull */
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;        /// D1 on board
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
	/* Compute the prescaler value */
	PrescalerValue = (uint16_t) (SystemCoreClock / 24000000) - 1;
	/* Time base configuration */
	TIM_TimeBaseStructure.TIM_Period = 29; // 800kHz  
	TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue; 
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
	/* PWM1 Mode configuration: Channel1 */
	TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);
	TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
	TIM_OCInitStructure.TIM_Pulse = 0;
	TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
	TIM_OC1Init(TIM4, &TIM_OCInitStructure);
	Serial.println("TIM conf end");
	/* configure DMA */
	/* DMA clock enable */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
	Serial.println("pre DMA conf");
	/* DMA1 Channel6 Config */
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t) & TIM4->CCR1;	// physical address of Timer 3 CCR1
	DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t) PWM_Buffer;		// this is the buffer memory 
	DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;						// data shifted from memory to peripheral
	DMA_InitStructure.DMA_BufferSize = PWM_BUFFER_SIZE;
	DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
	DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;					// automatically increase buffer index
	DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;
	DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;
	DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							// stop DMA feed after buffer size is reached
	DMA_InitStructure.DMA_Priority = DMA_Priority_High;
	DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;

	DMA_Init(DMA1_Channel1, &DMA_InitStructure);
	DMA_ITConfig(DMA1_Channel1, DMA_IT_TC | DMA_IT_HT, ENABLE);

	/* TIM4 CC1 DMA Request enable */
	TIM_DMACmd(TIM4, TIM_DMA_CC1, ENABLE);
	Serial.println("DMA enabled");
	NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 4;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	Serial.println("after nvic init");


	Serial.println("ws2812 end init");
}

I’ve tried to remove NVIC enabling and initialization of buffers in function “init_buffers(size)” it’s doesn’t helped. Looks like using of DMA+TIM4 drive core into panic mode.

So, if anyone have local IDE (or maybe cloud IDE can get library files .cpp and .h) I can send them my full code of running ws2812 leds with DMA, that doesn’t works for me now(but worked before).

Hmm. I was wrong. There is no mistakes in DMA config, and it works fine. Problem in color conversion function. I’ve took this function from stm32f4 DMA ws2811 example… Tried to debug it… system doesn’t print even first “color2pwm”, but all previous records is ok.

Also if i comment “DO” structure, function passes ok,

inline void ws2812::color2pwm(uint16_t ** const dest, const uint8_t color) {
	Serial.println("color2pwm");
	delay(500);
	Serial.print("Color:");
	Serial.println(color);
	delay(200);
	uint8_t mask = 0x80;

	do {

		if (color & mask) {
			Serial.print("1");
			**dest = 17;
		} else {
			Serial.print("0");
			**dest = 7;
		}
		*dest += 1;
		mask >>= 1;

	} while (mask != 0);
	Serial.println("color2pwm end");
	delay(100);
}

The do {} while() loop should be executing 8 times with mask starting at 0x80, then 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01, masking off single bits of the color argument.

How are you calling it? The first argument is a pointer to a pointer to a uint16_t and therefore somewhat unusual in the Arduino world. It also does *dest += 1; implying it writes 8 locations with either 7 or 17.

1 Like

I’m calling it like in example:

		framebufferp = &framebuffer[frame_pos++];
		bufp =  buffer + (i * 24);

		color2pwm( &bufp, framebufferp->green); // green
		color2pwm( &bufp, framebufferp->red); // red
		color2pwm( &bufp, framebufferp->blue); // blue

where buffer is : uint16_t* buffer

So, it must fill buffer with values of 17 or 7 to send it thru DMA to timer…

So where is buffer assigned? There should be a call to malloc or a statically allocated bit of RAM that the pointer buffer points to. If it is just declared as uint16_t *buffer, that does not allocate any storage for the data.

The code above makes sense since you are writing memory with 8 locations for each of green, red, and blue, hence the (i * 24).

1 Like

ahaaa! that’s the error! There was static declaration of dynamic array;

uint16_t PWM_Buffer[];

Many thanks, @bko !

Hmm, but now i have a question:
Now - i’ve fixed it with:

#define FRAMEBUFFER_SIZE 10
#define PWM_BUFFER_SIZE FRAMEBUFFER_SIZE*24//+24
	uint16_t PWM_Buffer[PWM_BUFFER_SIZE];

But there is no dynamic moves. I want to call my class function ws2812.init() with parameter int size, to create needed buffers to send data to leds. So, how i can dynamically resize arrays or declare it dynamic?

Without knowing too much about it, I would just make it a class data member since there is only going to be one ws2812 global object, right? If you have trouble with the constructor, just make sure the size is truly compile-time constant. If you need to have global DMA code know where it is, have a method to return a pointer to it, I guess. This breaks OO principles but might be very practical.

You can call malloc on the Spark core, but doing so over and over will surely run you out of memory eventually since the heap management is fairly limited. I try to avoid it.

1 Like

I need to resize this arrays once at time, just on initialization of work.
My plan is:
Read SD card -> read file with animation -> read config from first line of animation file -> initialize ws2812 object (yes in will be only one) with needed parameters -> show animation from next file contents (prepared chars for putting in array :slight_smile: )

Thanks again /went to google for malloc, never used it/

I’m stuck again:

Here is a code:

int framebuffer_size;
int pwm_buffer_size;
led * framebuffer;

void ws2812::init_buffers(int size)
{
	int pwm_size;
	pwm_size = size*24+20;
	pwm_buffer_size = pwm_size;
	framebuffer_size = size;
	PWM_Buffer = (uint16_t*) malloc (pwm_buffer_size);
	framebuffer = (led*) malloc (framebuffer_size);

	for(int i = 0; i < pwm_buffer_size; i++)
	{
		PWM_Buffer[i] = 0;
	}

	for(int i = 0; i < size; i++)
	{
		framebuffer[i].red = 0;
		framebuffer[i].green = 0;
		framebuffer[i].blue = 0;
	}
}
void ws2812::color2pwm(int pos, const uint8_t color1_in, const uint8_t color2_in, const uint8_t color3_in) {
	uint8_t mask,color1,color2,color3;
	int j,pwm_pos = pos*24;
	mask = 0x80;
	color1 = color1_in;
	color2 = color2_in;
	color3 = color3_in;
	if(color1 > 255)
		color1 = color1%255;
	if(color2 > 255)
		color2 = color2%255;
	if(color3 > 255)
		color3 = color3%255;


	for (j = 0; j < 8; j++)				
	{
		if ( (color1<<j) & 0x80 )
		{
			PWM_Buffer[pwm_pos++] = 15; 	// compare value for logical 1
		}
		else
		{
			PWM_Buffer[pwm_pos++] = 6;	// compare value for logical 0
		}
	}


	for (j = 0; j < 8; j++)					
	{
		if ( (color2<<j) & 0x80 )	// data sent MSB first, j = 0 is MSB j = 7 is LSB
		{
			PWM_Buffer[pwm_pos++] = 15; 	// compare value for logical 1
		}
		else
		{
			PWM_Buffer[pwm_pos++] = 6;	// compare value for logical 0
		}
	}

	for (j = 0; j < 8; j++)					// GREEN data
	{
		if ( (color3<<j) & 0x80 )	// data sent MSB first, j = 0 is MSB j = 7 is LSB
		{
			PWM_Buffer[pwm_pos++] = 15; 	// compare value for logical 1
		}
		else
		{
			PWM_Buffer[pwm_pos++] = 6;	// compare value for logical 0
		}
	}
}

void ws2812::Update_Buffer()
{
	Serial.println("update buf");
	struct led *framebufferp;

	int i,k;
	//uint16_t * bufp;
	//uint16_t * buffer;
	//buffer = PWM_Buffer;
	//pwm_pos = 0;


	for(int k=0;k<framebuffer_size;k++)
	{
		Serial.print("red:");
		Serial.print(framebuffer[k].red);
		Serial.print("blue:");
		Serial.print(framebuffer[k].blue);
		Serial.print("green:");
		Serial.println(framebuffer[k].green);
	}



	for (int i = 0; i < framebuffer_size; i++) 
	{
		//framebufferp = &framebuffer[i];
		Serial.print("i:");
		Serial.print(i);
		Serial.print("framecolor1:");
		Serial.print(framebuffer[i].green);
		Serial.print(";framecolor2:");
		Serial.print(framebuffer[i].red);
		Serial.print(";framecolor3:");
		Serial.println(framebuffer[i].blue);

		color2pwm(i, framebuffer[i].green,framebuffer[i].red,framebuffer[i].blue); // green
	}

	for(int k=0;k<framebuffer_size;k++)
	{
		Serial.print("red:");
		Serial.print(framebuffer[k].red);
		Serial.print("blue:");
		Serial.print(framebuffer[k].blue);
		Serial.print("green:");
		Serial.println(framebuffer[k].green);
	}

	Serial.println("end update buf");
}

makes this output in Serial:

update buf
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
red:0blue:0green:0
i:0framecolor1:0;framecolor2:0;framecolor3:0
i:1framecolor1:0;framecolor2:0;framecolor3:0
i:2framecolor1:0;framecolor2:0;framecolor3:0
i:3framecolor1:0;framecolor2:0;framecolor3:0
i:4framecolor1:0;framecolor2:0;framecolor3:0
i:5framecolor1:0;framecolor2:0;framecolor3:0
i:6framecolor1:0;framecolor2:0;framecolor3:0
i:7framecolor1:0;framecolor2:6;framecolor3:6
i:8framecolor1:0;framecolor2:6;framecolor3:6
i:9framecolor1:0;framecolor2:6;framecolor3:6
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
red:6blue:6green:0
end update buf

So, there is a question : what happening between two cycles of printing… Why two of three colors are 6 instead of 0.