cancel
Showing results for 
Search instead for 
Did you mean: 

DMA only sending one byte and not whole buffer

JustMe
Associate II
Posted on October 21, 2015 at 14:43

STM32f30x

The aim is to trigger the DMA from a timer. The DMA should then send one row of data from my buffer to the SPI. The DMA should then trigger an interrupt which points DMA to the next row in the buffer. It should now wait until it is triggered again from the timer before sending. Currently this all works but for one thing. It only sends 1 byte to the SPI and then waits for the timer trigger before sending the next. On a scope I can see that if I change the timer speed then the inter byte gap coming out of SPI changes.

#include ''stm32f30x_dma.h''
#include ''stm32f30x_gpio.h''
#include ''stm32f30x_rcc.h''
#include ''stm32f30x_spi.h''
#include ''stm32f30x_tim.h''
#include ''stm32f30x_misc.h''
#include ''stm32f30x_syscfg.h''
#define X_OFFSET 20
#define ROW 200
#define COL 32
#define START1 24
#define END1 312
uint8_t MemBuf[ROW][COL];
uint8_t RowCount = 0;
GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
SPI_InitTypeDef SPI_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
//-----------------------------------------------------------------
void Init(void)
{
uint16_t PrescalerValue = 0;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);//GPIOB clock enable
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); //TIM4 clock enable
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);//DMA1 clock enable
RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);//SPI2 clock enable
//------------------
//GPIO Setup - output for MOSI
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15 ; // we want to configure AF pins on port B
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF; // we want the pins to be alternate functions
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; // this sets the GPIO modules clock speed
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; // this sets the pin type to push / pull (as opposed to open drain)
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; // this sets the pullup / pulldown resistors
GPIO_Init(GPIOB, &GPIO_InitStruct);
//------------------
//TIM4 setup
PrescalerValue = (uint16_t) (SystemCoreClock / 2000000) - 1;//Compute the prescaler value
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);// Time Base configuration
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = X_OFFSET + 80;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
//setup the output compare
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Timing;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;
TIM_OCInitStructure.TIM_Pulse = X_OFFSET;
TIM_OC1Init(TIM4, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM4, TIM_OCPreload_Enable);//??
TIM_ARRPreloadConfig(TIM4, ENABLE); //??
//Master mode - trigger DMA
TIM_SelectOutputTrigger(TIM4, TIM_TRGOSource_OC1Ref);
//------------------
TIM_DMACmd(TIM4,TIM_DMA_CC1, ENABLE ); // Enable TIM1_CC1 DMA Requests
//------------------
//Interrupt setup
//Enable DMA1 channel IRQ Channel
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//------------------
//DMA1 setup
//Transfer Data to SPI2 from RAM buffer
//Trigger from TIM4 OC1
DMA_Cmd(DMA1_Channel1, DISABLE);
DMA_DeInit(DMA1_Channel1);//reset DMA1 channe1 to default values;
DMA_StructInit(&DMA_InitStructure);
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//channel will be used for memory to SPI transfer
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//setting normal mode (non circular)
DMA_InitStructure.DMA_Priority = DMA_Priority_High;//medium priority
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//source and destination data size half word=16bit
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//automatic memory increment enable.
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//Location assigned to peripheral register will be destination
DMA_InitStructure.DMA_BufferSize = COL;// / 2;//amount of data to be transfered - divide by 2 because we using 16bit
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&SPI2->DR;//source and destination start addresses
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)&MemBuf;
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//send values to DMA registers
DMA_ITConfig(DMA1_Channel1, DMA_IT_TC, ENABLE);// Enable DMA1 Channel Transfer Complete interrupt
//------------------
//SPI setup
SPI_InitStructure.SPI_Direction = SPI_Direction_1Line_Tx;
SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
SPI_InitStructure.SPI_CPOL = SPI_CPOL_High;
SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
SPI_InitStructure.SPI_NSS = SPI_NSS_Soft ;
SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_128;
SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
SPI_InitStructure.SPI_CRCPolynomial = 7;
SPI_Init(SPI2, &SPI_InitStructure);
SPI_I2S_DMACmd (SPI2,SPI_I2S_DMAReq_Tx,ENABLE);
//------------------
//Connect output to alternate function - AF2
GPIO_PinAFConfig(GPIOB, GPIO_PinSource15, GPIO_AF_5); //MOSI on SPI2
//------------------
SPI_Cmd(SPI2, ENABLE);
TIM_Cmd(TIM4, ENABLE);
DMA_Cmd(DMA1_Channel1, ENABLE);
}
//-----------------------------------------------------------------
void DMA1_Channel1_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_IT_TC1))
{
DMA_ClearITPendingBit(DMA1_IT_TC1 | DMA1_IT_GL1);
DMA_Cmd(DMA1_Channel1, DISABLE);
RowCount++;
if(RowCount >= ROW)
{
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)MemBuf;//end of buffer reached - start at beginning
RowCount = 0;
}
else
{
DMA_InitStructure.DMA_MemoryBaseAddr += COL;//move to next row in buffer
}
DMA_Init(DMA1_Channel1, &DMA_InitStructure);//send values to DMA registers
DMA_Cmd(DMA1_Channel1, ENABLE);
}
else
{
DMA_ClearITPendingBit(DMA1_IT_GL1);
}
}
//-----------------------------------------------------------------
int main(void)
{
Init();
for (uint8_t var = 0; var < 20; ++var)
{
MemBuf[0][var] = 0xaa;//put some stuff in one row of the buffer
}
while(1)
{
//put your code here
}
}

9 REPLIES 9
Posted on October 21, 2015 at 15:07

Does the DMA unit indicate an error state?

Consider if SPI->DR needs to be written as a 16 or 32-bit word, rather than a byte? You can still read the memory as a byte.

Tips, Buy me a coffee, or three.. PayPal Venmo
Up vote any posts that you find helpful, it shows what's working..
JustMe
Associate II
Posted on October 21, 2015 at 17:02

DMA is not showing an error status. I tried with sending 16 bits which is the max that the SPI can take and it is still the same.

Posted on October 21, 2015 at 22:35

There is ONE DMA transfer per request, and generally there is ONE source of requests per DMA channel (in the 'F30x's DMA requests are ORed, but it would make probably more harm than good if you would try to (ab)use this fact).

Thus, if you want to transmit more bytes from SPI, you want to make more DMA transfers into SPI->DR, thus you need to generate more requests - the timer should fire repeatedly at the the rate the SPI transmits the bytes. You can chain timers to achieve bursts of pulses.

> 

SPI_I2S_DMACmd (SPI2,SPI_I2S_DMAReq_Tx,ENABLE);

Note, that SPI_TX2 leads to a different DMA channel. You can enable it too, but, if it works in the same way as in 'F4 (I don't use 'F3), it would start to transfer as soon as you enable it. JW
JustMe
Associate II
Posted on October 22, 2015 at 09:52

Should the one DMA request not send the number of bytes as per NDT in the 'number of data' register. The datasheet says it can be up to 65535 bytes.

The datasheet says that if the TXDMAEN flag is set in the the SPI CR2 register then a DMA request will be sent whenever the SPI TXE flag is set. I would imagine that these request should continue until the DMA has sent the number of bytes listed in the NDT register

The problem I am having is that it only sends one byte at a time. It is most likely something I am doing wrong but I cant figure out what it is and this is not something that can be easily debugged.

Posted on October 22, 2015 at 10:54

> Should the one DMA request not send the number of bytes as per NDT in the 'number of data' register.

No. How would DMA know at what pace should it transfer the data? The DMA knows nothing on the character of source and target, it's just two addresses for it. 

> The datasheet says it can be up to 65535 bytes.

That's the number of transfers before the DMA stops reacting on further requests (in ''normal'' mode), or reloads the initial source/destination addresses and transfer numbers (in ''circular'' mode).

> The datasheet says that if the TXDMAEN flag is set in the the SPI CR2 register then a DMA request will be sent whenever the SPI TXE flag is set. I would imagine that these request should continue until the DMA has sent the number of bytes listed in the NDT register

Yes, but your imagination has to have a solid basis in the RM. Go to the DMA chapter and check, to which DMA channel does the SPI2_TX request couple.

>It is most likely something I am doing wrong but I cant figure out what it is and this is not something that can be easily debugged.

You are completely right. Your reading the RM is what you are doing wrong, and indeed, it's not something that can be easily debugged... 😉

JW

JustMe
Associate II
Posted on October 22, 2015 at 11:30

Okay I figured it out.

Thanks for your help jan. It was thought provoking.

The DMA does work like I thought it should....

Timer makes initial request, DMA setup to tranfer 40 bytes from ram to SPI.. SPI makes request after each byte is sent.

But as usual there is a mapping problem. I was using DMA1 channel 1 because TIM4 ch1 can trigger it. But I noticed that SPI2 which I am using cannot trigger it, which means the timer does initial trigger but SPI cannot do trigger after it sends a byte.

To solve the problem I changed to TIM4 compare reg3 which can trigger DMA1 chan 5. Channel 5 can also be triggered by SPI2 TX.

So now it works 🙂

Posted on October 22, 2015 at 12:12

So you now connect two DMA request sources to one DMA channel? That's what is recommended against in the RM, and I would not recommend it either.

What happens if you *remove* the timer's DMA request connection?

JW

JustMe
Associate II
Posted on October 22, 2015 at 12:40

Okay I might have posted too soon. Even though it seems to be working I have stopped the timer and it still works. So it looks like it is only under control of SPI at this moment.

I need to add some code to stop SPI reuest at the end of the row.

The DMA registers allow for source and destination setup and a request / trigger input. It does not say that the request has to come from the same peripheral as what is set in the destination / source register. I see it does mention that they are or'ed  and only one channel should be mapped at a time.... mmm okay I might have to change my strategy a bit. The chip registers have a rather clunky system of working :(

Posted on October 22, 2015 at 13:08

> I did not see any thing stating you cannot use two requests.

You probably can, but it may not work as you expect, and the RM recommends against. More precisely, it forbids it.

To quote from RM0316:

''The hardware requests from the peripherals ([...]) are simply logically ORed before entering the DMA1. This means that on one channel, only one request must be enabled at a time.''

> The whole DMA setup is a bit strange on this chip....

No, it's just that you misunderstand it. Please, read the DMA chapter in RM thoroughly.

> The DMA registers allow for source and destination setup which seems to indicate that it can get the initial trigger from elsewhere.

There is no ''initial trigger''. There are requests, each causing one transfer.

> If  it can only respond to the one request then why do they need you to say what the source / detsination and direction is? 

How else would the controller know, from where to read the datum and where to write it?

> If it was only one they could just allow you to setup source or destination and direction then wait until it gets a request and make that the destination / source.??

Yes, they could. It would be more complex, as the DMA would need to have a hardcoded list of sources/destinations, and much less flexible too.

There are microcontrollers out there, where DMA units are part of the peripherals; in fact this is the case in STM32 with ETH, OTG HS, LCD-TFT and Chrom-ART(DMA2D) too; their setup is then different - maybe simpler in some way; but their DMA can't be used for other than a very particular hardwired task.

JW