cancel
Showing results for 
Search instead for 
Did you mean: 

Trouble getting 800kHz from PWM timer and DMA

yoma1993
Associate II
Posted on June 01, 2015 at 04:56

Hi guys,

I've been trying to get this code working for the past week now and i'm out of ideas. I'm trying to control the ws2812 LEDs with PWM. These LEDs have tight timings and require a single 800kHz data/clock line. I seems no matter what i do i cannot get anywhere close to 800kHz. In this image DMA is disabled and just the timer is generating a PWM signal. not sure why its not square and the period is 60 ms instead of the 1.25ns to get 800kHz.

http://i.imgur.com/zenqjt8.png

This next image is with DMA enabled. I have no idea why the period is 3ms.

http://i.imgur.com/iDjAgwS.png

Data sheet for LEDs:

https://cdn.sparkfun.com/datasheets/Components/LED/COM-128pdf

Code i based my code off:

https://github.com/devthrash/STM32F1-workarea/blob/master/Project/WS2812/main.c

Here is my code:

#include <
stm32f10x.h
>
//#include <
stdio.h
>
#define GPIO_PWM_PIN GPIO_Pin_2
#define GPIO_USART_PIN GPIO_Pin_9
#define PWM_HIGH_WIDTH 17
#define PWM_LOW_WIDTH 9
#define LED_COUNT 1
/* Buffer that holds one complete DMA transmission.
* 
* Ensure that this buffer is big enough to hold
* all data bytes that need to be sent.
* 
* The buffer size can be calculated as followas:
* number of LEDs * 24 bytes + 42 bytes.
* 
* This leaves us with a maximum string length of
* (2^16 bytes per DMA stream - 42 bytes)/24 bytes per LED = 2728 LEDs.
*/
uint16_t ledBuff[24*LED_COUNT+42];
char usartBuff[32];
uint8_t rgb[5][3] = {
{255, 0, 0},
{0, 255, 0},
{0, 0, 255},
{255, 255, 255},
{196, 227, 185}
};
//Prototypes
void RCC_Config(void);
void GPIO_Config(void);
void TIM_Config(void);
void USART_Config(void);
void DMA_Config(void);
void send_data(uint8_t (*color)[3], uint16_t len);
void Delay(__IO uint32_t nCount) {
while(nCount--) {
}
}
int main() {
int16_t i;
//char str[500] = {(char)ledBuff};//''TEST!!!!TEST\n\r'';
RCC_Config();
GPIO_Config();
USART_Config();
TIM_Config();
DMA_Config();
while (1){ 
send_data(&rgb[3], 1);
for(i = 0; i < 
sizeof
(usartBuff); i++) {
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET) {}
USART_SendData(USART1, usartBuff[i]);
}
Delay(5000L);
}
}
void RCC_Config(void) {
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1 , ENABLE); // Enable clock for GPIOA and USART1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // Enable clock for TIM2
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); // Enable clock for DMA1
}
void GPIO_Config(void) {
GPIO_InitTypeDef GPIO_InitStruct;
GPIO_InitStruct.GPIO_Pin
= 
GPIO_PWM_PIN
| GPIO_USART_PIN;
GPIO_InitStruct.GPIO_Mode
= 
GPIO_Mode_AF_PP
;
GPIO_InitStruct.GPIO_Speed
= 
GPIO_Speed_50MHz
;
GPIO_Init(GPIOA, &GPIO_InitStruct);
}
void USART_Config(void) {
USART_InitTypeDef USART_InitStruct;
USART_InitStruct.USART_BaudRate
= 
115200
;
USART_InitStruct.USART_WordLength
= 
USART_WordLength_8b
;
USART_InitStruct.USART_StopBits
= 
USART_StopBits_1
;
USART_InitStruct.USART_Parity
= 
USART_Parity_No
;
USART_InitStruct.USART_HardwareFlowControl
= 
USART_HardwareFlowControl_None
;
USART_InitStruct.USART_Mode
= 
USART_Mode_Tx
;
USART_Init(USART1, &USART_InitStruct);
USART_Cmd(USART1, ENABLE);
}
void TIM_Config(void) {
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct;
TIM_OCInitTypeDef TIM_OCInitStruct;
uint16_t PrescalerValue = (uint16_t)(72000000 / 24000000) - 1; // 
PrescalerValue
= 
2
, Clock is scaled down to 72MHz /(PrescalerValue + 1) = 762MHz/
3
= 
24MHz
/* Time base configuration */
TIM_TimeBaseStruct.TIM_Period
= 
29
; // 800kHz // Species the period value
TIM_TimeBaseStruct.TIM_Prescaler
= 
PrescalerValue
; // Specifies the prescaler value used to divide the TIM clock.
TIM_TimeBaseStruct.TIM_ClockDivision
= 
0
; // Specifies the clock division.
TIM_TimeBaseStruct.TIM_CounterMode
= 
TIM_CounterMode_Up
; //
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStruct);
/* PWM1 Mode configuration: Channel1 */
TIM_OCInitStruct.TIM_OCMode
= 
TIM_OCMode_PWM1
; // Specifies the TIM mode.
TIM_OCInitStruct.TIM_OutputState
= 
TIM_OutputState_Enable
; //
//
TIM_OCInitStruct.TIM_OutputNState
= 
TIM_OutputNState_Disable
;
TIM_OCInitStruct.TIM_Pulse
= 
15
;
TIM_OCInitStruct.TIM_OCPolarity
= 
TIM_OCPolarity_High
;
//
TIM_OCInitStructure.TIM_OCNPolarity
= 
TIM_OCNPolarity_High
;
//
TIM_OCInitStructure.TIM_OCNIdleState
= 
TIM_OCNIdleState_Set
;
TIM_OC3Init(TIM2, &TIM_OCInitStruct);
//TIM_Cmd(TIM2, ENABLE);
}
void DMA_Config(void) {
DMA_InitTypeDef DMA_InitStruct;
DMA_DeInit(DMA1_Channel1); // Deinitialize DAM1 Channel 1 to their default reset values.
DMA_InitStruct.DMA_PeripheralBaseAddr = (uint32_t)&TIM2->CCR3; // Specifies Physical address of the peripheral in this case Timer 2 CCR1
DMA_InitStruct.DMA_MemoryBaseAddr = (uint32_t)&ledBuff; // Specifies the buffer memory address
DMA_InitStruct.DMA_DIR = DMA_DIR_PeripheralDST; // Data transfered from memory to peripheral
DMA_InitStruct.DMA_BufferSize = 42; // Specifies the buffer size
DMA_InitStruct.DMA_PeripheralInc = DMA_PeripheralInc_Disable; // Do not incrament the peripheral address
DMA_InitStruct.DMA_MemoryInc = DMA_MemoryInc_Enable; // Incrament the buffer index
DMA_InitStruct.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord; // Specifies the peripheral data width
DMA_InitStruct.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord; // Specifies the memory data width
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal; // Specifies the operation mode. Normal or Circular
DMA_InitStruct.DMA_Priority = DMA_Priority_High; // Specifies the software priority
DMA_InitStruct.DMA_M2M = DMA_M2M_Disable; //
DMA_Init(DMA1_Channel1, &DMA_InitStruct); // Initialize DAM1 Channel 1 to values specified in the DMA_InitStruct structure.
TIM_DMACmd(TIM2, TIM_DMA_CC3, ENABLE); // Enables TIM2's DMA request. TIM_DMA_CC1 : TIM Capture Compare 1 DMA source 
}
void send_data(uint8_t (*color)[3], uint16_t len) {
uint8_t i;
uint8_t led;
uint16_t memaddr;
uint16_t buffersize;
buffersize = (len*24)+42; // number of bytes needed is #LEDs * 24 bytes + 42 trailing bytes
memaddr = 0; // reset buffer memory index
led = 0;
while(len)
{ 
for (i = 0; i < 8; i++) // RED data
{
if ((color[led][0]<<i) & 0x80) // data sent MSB first, j = 0 is MSB j = 7 is LSB
{
ledBuff[memaddr] = PWM_HIGH_WIDTH; // compare value for logical 1
usartBuff[memaddr] = '1';
}
else
{
ledBuff[memaddr] = PWM_LOW_WIDTH; // compare value for logical 0
usartBuff[memaddr] = '0';
}
memaddr++;
}
usartBuff[memaddr+1] = '-';
for (i = 0; i < 8; i++) // GREEN data
{
if ((color[led][1]<<i) & 0x80) // data sent MSB first, j = 0 is MSB j = 7 is LSB
{
ledBuff[memaddr] = PWM_HIGH_WIDTH; // compare value for logical 1
usartBuff[memaddr+2] = '1';
}
else
{
ledBuff[memaddr] = PWM_LOW_WIDTH; // compare value for logical 0
usartBuff[memaddr+2] = '0';
}
memaddr++;
}
usartBuff[memaddr+3] = '-';
for (i = 0; i < 8; i++) // BLUE data
{
if ((color[led][2]<<i) & 0x80) // data sent MSB first, j = 0 is MSB j = 7 is LSB
{
ledBuff[memaddr] = PWM_HIGH_WIDTH; // compare value for logical 1
usartBuff[memaddr+4] = '1';
}
else
{
ledBuff[memaddr] = PWM_LOW_WIDTH; // compare value for logical 0
usartBuff[memaddr+4] = '0';
}
memaddr++;
}
usartBuff[memaddr+5] = '\n';
usartBuff[memaddr+5] = '\r';
led++;
len--;
}
// add needed delay at end of byte cycle, pulsewidth = 0
while(memaddr < buffersize)
{
ledBuff[memaddr] = 0;
memaddr++;
}
DMA_SetCurrDataCounter(DMA1_Channel1, buffersize); // load number of bytes to be transferred
DMA_Cmd(DMA1_Channel1, ENABLE); // enable DMA channel 1
TIM_Cmd(TIM2, ENABLE); // enable Timer 2
while(!DMA_GetFlagStatus(DMA1_FLAG_TC1)); // wait until transfer complete
TIM_Cmd(TIM2, DISABLE); // disable Timer 2
DMA_Cmd(DMA1_Channel1, DISABLE); // disable DMA channel 1
DMA_ClearFlag(DMA1_FLAG_TC1); // clear DMA1 Channel 1 transfer complete flag
}

4 REPLIES 4
Posted on June 01, 2015 at 07:25

I'd suggest a couple of things,

Configure the DMA once, in a circular mode, with twice the buffer size, fill the two halves with the desired pattern data, and test that. If that works use the DMA TC/HT interrupts to rebuild alternating halves of the pattern buffer.

Don't output via the USART, it's going to be slow with respect to the rate you are outputting data to the LEDs

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
yoma1993
Associate II
Posted on June 01, 2015 at 19:23

Hi clive,

the USART output was just for debugging. My plan is to eventually move to a circular buffer, but i haven't read up on them yet, and im not sure how to implement them with DMA.

Since im not getting the 800kHz shouldn't we be looking at the timer when its disabled and get that to 800kHz first or am i missing something?

Posted on June 01, 2015 at 20:22

You could craft a small example that posts 800 KHz to the pin, if you think that's where the problem is. I've posted an assortment of TIMx PWM examples, and the libraries come with other examples.

Check the pin and timer associations in the data sheet.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
yoma1993
Associate II
Posted on June 03, 2015 at 03:10

Hi clive,

Turns out my code has been working all this time. The LED wasn't light up cause it was burnt out, and the myDAQ scope i was using didnt have a high enough sample rate. The max frequency it could handle was 400kHz. I borrowed a proper scope from my school and thing are starting to come together and make sense. Next up is figuring out why my LEDs are flickering and implementing a circular DMA buffer! 

Thanks for your help! I'm sure i'll be back soon! ^_^