cancel
Showing results for 
Search instead for 
Did you mean: 

ADC+DMA interfering with SPI (non-DMA) transfers?

domen2
Associate III
Posted on June 22, 2009 at 15:51

ADC+DMA interfering with SPI (non-DMA) transfers?

4 REPLIES 4
domen2
Associate III
Posted on May 17, 2011 at 13:15

Hello!

This problem is puzzling me, and I'm running out of ideas.

Setup:

1. Accelerometer on ADC1, channels 1, 2, 3, transferred with DMA, sampled at 800 Hz (TIM3 irq handler calls ADC_SoftwareStartConvCmd)

2. SD card with FAT (fatfs) on SPI2, no dma.

Alone either 1 or 2 alone works perfectly.

1 and 2 together produce invalid CRCs on data transferred from SD card.

1 and 2 together, but with ADC DMA disabled produce no such errors.

So it looks like DMA for transfers from ADC does something to interfere with SPI or its buffer? I can't see how can this happen.

Now, I could just not use DMA for ADC, and setup 3 single channel transfers, but:

a) that's a workaround;

b) I'm worried this ''problem'' might bite me later.

Any ideas are very much welcome.

Relevant code:

static vu16 adc_values[3];

#define ACCEL_ADC ADC1

#define ACCEL_CH_Z ADC_Channel_1

#define ACCEL_CH_Y ADC_Channel_2

#define ACCEL_CH_X ADC_Channel_3

nvic.NVIC_IRQChannel = ADC1_2_IRQChannel;

nvic.NVIC_IRQChannelPreemptionPriority = 3;

nvic.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&nvic);

DMA_DeInit(DMA1_Channel1);

dma.DMA_PeripheralBaseAddr = (u32)&ACCEL_ADC->DR;

dma.DMA_MemoryBaseAddr = (u32)adc_values;

dma.DMA_DIR = DMA_DIR_PeripheralSRC;

dma.DMA_BufferSize = 3;

dma.DMA_PeripheralInc = DMA_PeripheralInc_Disable;

dma.DMA_MemoryInc = DMA_MemoryInc_Enable;

dma.DMA_PeripheralDataSize = DMA_PeripheralDataSize_HalfWord;

dma.DMA_MemoryDataSize = DMA_MemoryDataSize_HalfWord;

dma.DMA_Mode = DMA_Mode_Circular;

dma.DMA_Priority = DMA_Priority_Low;

//dma.DMA_Priority = DMA_Priority_High;

dma.DMA_M2M = DMA_M2M_Disable;

DMA_Init(DMA1_Channel1, &dma);

DMA_Cmd(DMA1_Channel1, ENABLE);

adc.ADC_Mode = ADC_Mode_Independent;

adc.ADC_ScanConvMode = ENABLE;

adc.ADC_ContinuousConvMode = DISABLE;

adc.ADC_ExternalTrigConv = ADC_ExternalTrigConv_None;

adc.ADC_DataAlign = ADC_DataAlign_Right;

adc.ADC_NbrOfChannel = 3;

ADC_Init(ACCEL_ADC, &adc);

/* You want to run the slowest rate possible. Run the A/D at about 5 to 10x

* the signal bandwidth (how fast the signal can change).

* http://www.st.com/mcu/forums-cat-6431-23.html */

ADC_RegularChannelConfig(ACCEL_ADC, ACCEL_CH_X, 1, ADC_SampleTime_239Cycles5);

ADC_RegularChannelConfig(ACCEL_ADC, ACCEL_CH_Y, 2, ADC_SampleTime_239Cycles5);

ADC_RegularChannelConfig(ACCEL_ADC, ACCEL_CH_Z, 3, ADC_SampleTime_239Cycles5);

ADC_Cmd(ACCEL_ADC, ENABLE);

ADC_StartCalibration(ACCEL_ADC);

while (ADC_GetCalibrationStatus(ACCEL_ADC))

;

ADC_ITConfig(ACCEL_ADC, ADC_IT_EOC, ENABLE);

ADC_DMACmd(ACCEL_ADC, ENABLE); /****************** if I comment out this line, there are no ''SPI corruptions'' ******************/

// interrupt handlers:

static void adc_interrupt();

void __attribute__((interrupt)) ADC1_2_IRQHandler()

{

irq_adc++;

ADC_ClearITPendingBit(ACCEL_ADC, ADC_IT_EOC); /* needed on !dma */

/* a bit of wtf here, can't check ADC_IT_EOC flag, because it's

* cleared when reading ->DR (which DMA does) */

if (ADC_GetFlagStatus(ACCEL_ADC, ADC_FLAG_STRT)) {

ADC_ClearFlag(ACCEL_ADC, ADC_FLAG_STRT);

adc_interrupt();

}

}

static void tim_adc_handler();

void __attribute__((interrupt)) TIM3_IRQHandler()

{

irq_tim++;

if (TIM_GetITStatus(TIM_ADC, TIM_IT_Update)) {

TIM_ClearITPendingBit(TIM_ADC, TIM_IT_Update);

tim_adc_handler();

}

}

static void tim_adc_init()

{

NVIC_InitTypeDef nvic;

TIM_TimeBaseInitTypeDef tim_tb = {

.TIM_Period = 10, /* 800 Hz */

.TIM_Prescaler = CONFIG_HZ/8000-1, /* 8000 Hz resolution */

.TIM_ClockDivision = TIM_CKD_DIV1, /* no difference setting this? */

.TIM_CounterMode = TIM_CounterMode_Up,

};

RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);

TIM_InternalClockConfig(TIM_ADC);

TIM_TimeBaseInit(TIM_ADC, &tim_tb);

TIM_UpdateRequestConfig(TIM_ADC, TIM_UpdateSource_Regular);

TIM_UpdateDisableConfig(TIM_ADC, DISABLE);

TIM_ITConfig(TIM_ADC, TIM_IT_Update, ENABLE);

nvic.NVIC_IRQChannel = TIM3_IRQChannel;

nvic.NVIC_IRQChannelPreemptionPriority = 3;

nvic.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&nvic);

}

static void tim_adc_handler()

{

ADC_SoftwareStartConvCmd(ACCEL_ADC, ENABLE);

}

static void adc_interrupt()

{

// use adc_values... which are correct for ADC-DMA, so it looks like that works

}

// spi configuration (for SD card, no dma)

/*** spi functions ***/

void spi_set_speed(enum sd_speed speed);

void spi_init(void)

{

GPIO_InitTypeDef gpio;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);

gpio.GPIO_Pin = GPIO_Pin_13 | GPIO_Pin_14 | GPIO_Pin_15;

gpio.GPIO_Speed = GPIO_Speed_50MHz;

gpio.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOB, &gpio);

gpio.GPIO_Pin = GPIO_Pin_12;

gpio.GPIO_Speed = GPIO_Speed_50MHz;

gpio.GPIO_Mode = GPIO_Mode_Out_PP;

GPIO_Init(GPIOB, &gpio);

spi_set_speed(SD_SPEED_400KHZ);

}

void spi_set_speed(enum sd_speed speed)

{

SPI_InitTypeDef spi;

int prescaler = SPI_BaudRatePrescaler_128;

if (speed == SD_SPEED_400KHZ)

prescaler = SPI_BaudRatePrescaler_128;

else if (speed == SD_SPEED_25MHZ)

prescaler = SPI_BaudRatePrescaler_2;

/* ^ with /2 APB1 this will be 15mhz/234k at 60mhz

* 18/281 at 72. which is ok, 100<x

SPI_Cmd(SPI2, DISABLE);

spi.SPI_Direction = SPI_Direction_2Lines_FullDuplex;

spi.SPI_Mode = SPI_Mode_Master;

spi.SPI_DataSize = SPI_DataSize_8b;

spi.SPI_CPOL = SPI_CPOL_Low;

spi.SPI_CPHA = SPI_CPHA_1Edge;

spi.SPI_NSS = SPI_NSS_Soft;

spi.SPI_BaudRatePrescaler = prescaler;

spi.SPI_FirstBit = SPI_FirstBit_MSB;

spi.SPI_CRCPolynomial = 7;

SPI_Init(SPI2, &spi);

SPI_Cmd(SPI2, ENABLE);

}

#define spi_cs_low() do { GPIOB->BRR = GPIO_Pin_12; } while (0)

#define spi_cs_high() do { GPIOB->BSRR = GPIO_Pin_12; } while (0)

u8 spi_txrx(u8 data)

{

SPI2->DR = data;

while ((SPI2->SR & SPI_I2S_FLAG_TXE) == 0)

;

return SPI2->DR;

}

And sorry for the long post!

Edit: CODE tags don't work, so I used HTML pre

[ This message was edited by: domen on 19-06-2009 14:02 ]

domen2
Associate III
Posted on May 17, 2011 at 13:15

OK, I have a few more hints, crc fails because it doesn't read quite the same data.

diff of OK vs. corrupted (crc failed) data transfered via SPI:

--- raw_mbr 2009-06-19 14:40:52.000000000 +0200

+++ gdb_mbr 2009-06-19 14:41:10.000000000 +0200

@@ -11,7 +11,7 @@

56 bb 07 00 b4 0e cd 10

5e eb f0 eb fe bf 05 00

bb 00 7c b8 01 02 57 cd

-13 5f 73 0c 33 c0 cd 13

+13 5f 73 73 0c c0 cd 13

4f 75 ed be a3 06 eb d3

be c2 06 bf fe 7d 81 3d

55 aa 75 c7 8b f5 ea 00

--- raw_43192 2009-06-19 14:52:38.000000000 +0200

+++ gdb_43192 2009-06-19 14:53:42.000000000 +0200

@@ -5,7 +5,7 @@

31 39 37 30 30 34 32 37

57 53 30 20 10 00 98 1b

21 ec 00 00 00 00 d2 1b

-21 ec 81 1a f6 bb 02 00

+21 21 ec 1a f6 bb 02 00

31 39 37 30 30 34 32 38

57 53 30 20 10 00 d2 1b

21 ec 00 00 00 00 0c 1c

In both cases the first corrupted byte is copy from the last byte and the next one is what the previous should be. So the two bytes are delayed?

Any explanations why this could happen with DMA enabled on ADC?

Code that reads this is just a simple

Code:

<BR> for (i=0; i<len; i++) <BR> buf[i] = spi_txrx(0xff); <BR>

(Oh, but in this case code tag works? Interesting.)

[ This message was edited by: domen on 19-06-2009 15:03 ]

domen2
Associate III
Posted on May 17, 2011 at 13:15

Heh, and SOLVED

Code:

u8 spi_txrx(u8 data)

{

SPI2->DR = data;

- while ((SPI2->SR & SPI_I2S_FLAG_TXE) == 0)

+ while ((SPI2->SR & SPI_I2S_FLAG_RXNE) == 0)

;

return SPI2->DR;

}

Silly bug.

jj
Associate II
Posted on May 17, 2011 at 13:15

@domen,

Thank you - reviewed the code and could not see the error. Posts such as yours are instructive. (even to ''market leaders'')