I am trying to read a fixed number of bytes (27 bytes in total) from an SPI slave device using DMA. I'm running an STM32F4 chip.
"In order to read from the SPI bus, a clock needs to be generated. So you need to write in order to read." I set up my DMA controller to write a dummy byte (0xFF) in circular mode.
uint8_t tx_buffer[] = {0xFF};
DMA_InitStruct.DMA_Mode = DMA_Mode_Circular;
DMA_InitStruct.DMA_BufferSize = 1;
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)tx_buffer;
and my Rx dma stream is setup using double buffer mode:
uint8_t rx_buffer0[27];
uint8_t rx_buffer1[27];
DMA_InitStruct.DMA_Mode = DMA_Mode_Normal;
DMA_InitStruct.DMA_BufferSize = 27;
DMA_InitStruct.DMA_Memory0BaseAddr = (uint32_t)rx_buffer0;
DMA_DoubleBufferModeConfig(DMA2_Stream0, (uint32_t)rx_buffer1, DMA_Memory_0);
DMA_DoubleBufferModeCmd(DMA2_Stream0, ENABLE);
(I have omitted other irrelevant initialisations.)
After the 27 bytes are received a Transfer Complete interrupt is triggered.
void DMA2_Stream0_IRQHandler(void)
{
if(DMA_GetITStatus(DMA2_Stream0, DMA_IT_TCIF0) == SET)
{
DMA_ClearITPendingBit(DMA2_Stream0, DMA_IT_TCIF0);
DMA_Cmd(DMA2_Stream1, DISABLE); // disable Tx circular writes
}
}
The problem is as follows: I see correct data in the first buffer. But while the interrupt triggers the Tx circular DMA continues, filling the first few bytes of the second buffer until it is disabled.
I want to avoid reserving 27 bytes for dummy data, so is there any way to stop a circular DMA stream after a fixed amount of cycles?
Enable DMA in normal mode and set number of elements to 27.
Later, disable memory increase for memory and for peripheral and your approach will work.
Related
I'm trying to set communication between esp32 (master) and stm32 (slave) over SPI. esp32 is running under micropython and sends four bytes, for example
spi.write_readinto(b'\x31\x32\x33\x34', buf)
stm32' code is here (instead of this i use SPI_InitDef.SPI_NSS = SPI_NSS_Soft;)
void SPI_Init(void) {
...
// initialize SPI slave
// for slave, no need to define SPI_BaudRatePrescaler
SPI_InitDef.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
SPI_InitDef.SPI_Mode = SPI_Mode_Slave;
SPI_InitDef.SPI_DataSize = SPI_DataSize_8b; // 8-bit transactions
SPI_InitDef.SPI_FirstBit = SPI_FirstBit_MSB; // MSB first
SPI_InitDef.SPI_CPOL = SPI_CPOL_Low; // CPOL = 0, clock idle low
SPI_InitDef.SPI_CPHA = SPI_CPHA_2Edge; // CPHA = 1
SPI_InitDef.SPI_NSS = SPI_NSS_Hard; // use hardware SS
SPI_InitDef.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_64; // APB2 72/64 = 1.125 MHz
SPI_InitDef.SPI_CRCPolynomial = 7;
SPI_Init(SPI1, &SPI_InitDef);
SPI_Cmd(SPI1, ENABLE);
NVIC_EnableIRQ(SPI1_IRQn);
//Тут мы разрешаем прерывание по приему
SPI_I2S_ITConfig(SPI1, SPI_I2S_IT_RXNE, ENABLE);
}
void main() {
/* Setup SysTick Timer for 10ms interrupts */
if (SysTick_Config(SystemCoreClock / 100))
{
/* Capture error */
while (1);
}
/* Configure the SysTick handler priority */
NVIC_SetPriority(SysTick_IRQn, 0x0);
SPI_Init();
while(1) {
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
for (u8 i=0; i<4; i++) {
printf("0x%02x ", SPI_I2S_ReceiveData(SPI1));
}
printf("\r\n");
}
}
But when I send four bytes 0x31 0x32 0x33 0x34 (analyzer confirms bytes were sent) and my stm gets only 0x31 0x32 0x31 0x32
UPD
I use std periph library and SPI_I2S_ReceiveData is a native method to read byte from SPI.
uint16_t SPI_I2S_ReceiveData ( SPI_TypeDef * SPIx )
Returns the most recent received data by the SPIx/I2Sx peripheral.
Parameters:
SPIx,: To select the SPIx/I2Sx peripheral, where x can be: 1, 2 or 3 in SPI mode or 2 or 3 in I2S mode or I2Sxext for I2S full duplex mode.
Return values:
The value of the received data.
uint16_t SPI_I2S_ReceiveData ( SPI_TypeDef * SPIx )
Returns the most recent received data by the SPIx/I2Sx peripheral.
Parameters:
SPIx,: To select the SPIx/I2Sx peripheral, where x can be: 1, 2 or 3 in SPI mode or 2 or 3 in I2S mode or I2Sxext for I2S full duplex mode.
Return values:
The value of the received data.
But maybe I exit out from IRQ before all data are read. I found to run the while loop until the transmission of the last byte is complete
I think the following code is not correct (but I don't know what the function SPI_I2S_ReceiveData is doing):
while(!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE));
for (u8 i=0; i<4; i++) {
printf("0x%02x ", SPI_I2S_ReceiveData(SPI1));
}
You exit from the while as soon as one byte is ready to be read. I assume SPI_I2S_ReceiveData is only reading the SPI FIFO. in that case you try to read 4 bytes when possibly only one or two has been received.
You didn't precise the kind of STM32 you're using so I am describing the SPI of STM32H7 (as far as I know it should be pretty similar in other STM32).
To setup a reception in slave mode you should define in particular these 3 parameters:
the length of the socalled "frame" (number of bytes to be read/written at once). This is the field SPI_DataSize` in the HAL data structure, here 8 bits.
the number of transfer (TSIZE) which specifies when the End Of Transmission event is generated. It is expressed in number of "frames". This parameter must be set through register SPI.CR2 before each reception (provided you know the number of bytes to be received of course).
the "FIFO threshold". It specifies at which frequency an event RXP or TXP is generated. You can change this parameter to decrease the workload on the software but to receive only 4 bytes it has no impact.
In your case I think you should setup a transfer size of 4 (4 bytes) and wait for EOT flag to be set. When it is set you only have to read 4 bytes from SPI Receive Register (you can read all 4 bytes at once by the way).
I suggest you do not use the HAL but write your own SPI reception / transmission routines by reading / writing registers. It is not a very complex peripheral (so it will not cost you a lot of time) and you will understand precisely how it works (instead of digging into the HAL).
Hopefully someone will throw some light on my incompetence.
I have setup the USART for DMA transfers using DMA interrupts.
void DMA1_Channel7_IRQHandler(void)
{
// USART2 TX handler
/* Test on DMA Stream Transfer Complete interrupt */
if (DMA_GetITStatus(DMA1_IT_TC7))
{
/* Clear DMA Stream Transfer Complete interrupt pending bit */
DMA_ClearITPendingBit(DMA1_IT_GL7 | DMA1_IT_TC7);
}
DMA_Cmd(DMA1_Channel7, DISABLE);
}
I am trying to send data to the TX buffer with a command such as:
SCICommsStringIntoTxQ("\n\rTEST_CODE V0.01: \r\n");
where the command is
void SCICommsStringIntoTxQ(int8_t * str_out_string)
{
memset(TxBuffer, '\0', sizeof(TxBuffer));
strcpy(TxBuffer, str_out_string);
USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE);
DMA_Cmd ( DMA1_Channel7, ENABLE );
}
My DMA Init is:-
/* Configures the DMA1 Channel7 for UART2_CHAN Transmission. */
DMA_DeInit(DMA_Channel_USART2_TX);
DMA7_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR);
DMA7_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA7_InitStructure.DMA_BufferSize = (uint16_t)sizeof(TxBuffer) -1;
DMA7_InitStructure.DMA_MemoryBaseAddr = (uint32_t)TxBuffer;
DMA7_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA7_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;
DMA7_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA7_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA7_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA7_InitStructure.DMA_Priority = DMA_Priority_High;
DMA7_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA_Channel_USART2_TX, &DMA7_InitStructure);
USART_DMACmd(USART2, USART_DMAReq_Tx, ENABLE); // Enable USART2 DMA TX
DMA_ITConfig(DMA_Channel_USART2_TX, DMA_IT_TC, ENABLE);
The first send works fine, however as soon as I try and send the next string it corrupts out my first. Can anyone advise what I have done wrong?
Thanks for taking time to respond. Im not checking or waiting for the USART to finish. I assumed the DMA took care of it. Should I be looking at the UART XE (transmit data empty flag ) before sending the next string to the DMA?
I am not sure from your code if you are waiting the USART finishes sending your first message before sending the second string.
If not, and if TxBuffer points to the same memory location for both strings, the second time you call the memset you are changing the memory portion that the DMA is serving to the USART while the latter is still sending out the previous string.
EDIT:
Answering to your question, Wikipedia reports:
With DMA, the CPU first initiates the transfer, then it does other operations while the transfer is in progress, and it finally receives an interrupt from the DMA controller when the operation is done.
This means that when you start a transmission over the USART bus, the DMA controller gets data from RAM and serves this data to the USART controller while the CPU can do other operations. This means that your CPU can potentially change on the fly the portion of RAM the DMA controller is accessing to while serving the USART controller.
I do not know the specific details of your implementation and hardware configuration but I think you should check when the USART or the DMA controller ends the transmission before starting a second transfer. How to do this depends on your current configuration. Checking XE might work or maybe you can rely on an interrupt that raises when the DMA ends its transmission.
For instance maybe you can set a your internal flag able_to_transmit inside this block:
/* Test on DMA Stream Transfer Complete interrupt */
if (DMA_GetITStatus(DMA1_IT_TC7)) {
able_to_transmit = 1;
}
And do something like this before transmitting:
while(!able_to_transmit)
; //wait or do something else
SCICommsStringIntoTxQ("\n\rTEST_CODE V0.01: \r\n");
able_to_transmit = 0;
I hope this helps.
I'm using an NXP LH79525, ARM7TDMI based processor. There is an EEPROM connected via SPI bus to the SSP port.
The objective is to read the EEPROM into SRAM for faster accessing.
The present working code sends a read command to the EEPROM, the reads the data byte per byte, which takes a long time.
I want to use the DMA to read EEPROM on the SPI bus directly, without intervention from the CPU.
Here is my code snippet:
// Initialize the DMA for use with SPI bus.
// Source is the EEPROM on the SPI bus.
// Destination is "buffer".
p_dma_stream_2->source_low = 0U;
p_dma_stream_2->source_high = 0U;
const uint32_t dest_addr = (uint32_t) buffer;
p_dma_stream_2->dest_low = (dest_addr & 0xFFFFU);
p_dma_stream_2->dest_high = (dest_addr >> 16U);
p_dma_stream_2->max_count = bytesToRead;
*p_dma_intr_mask = 0U; // Disable all dma interrupts.
*p_dma_intr_clear = 0xFF; // Clear the interrupts.
SSP->dmacr = 0x01; // Enable reading with DMA
p_dma_stream_2->control = 0x06U; // + 0x01 to enable transfer.
// 0x400 - status of stream 2. 1 == stream is active.
uint32_t status = *p_dma_status;
while ((status & 0x400U) != 0U)
{
OSTimeDelay(10U); // 10 milliseconds
status = *p_dma_status;
}
I'm reading incorrect values from the EEPROM when using the above example.
The DMA registers are counting correctly.
The SSP is already initialized before this code fragment, for reading bytes.
I'm looking for a working code example snippet, but haven't found any on the web.
According to this User's Guide table 5-1 it seems as SSPRX is assigned to Stream 0 and supports only half-word source data width (table 5-15).
Your code seems to use Stream 2 and byte adressing.
I'm trying to recreate a project of writing to an SD card (using FatFS) for a dsPIC33FJ128GP802 microcontroller.
Currently to collect the date from the SPI I have a do/while that loops 512 times and writes a dummy value to the SPI buffer, wait for the SPI flag, then read the SPI value, like so:
int c = 512;
do {
SPI1BUF = 0xFF;
while (!_SPIRBF);
*p++ = SPI1BUF;
} while (--c);
I'm trying to recreate this using the DMA intterupts but it's not working like I had hoped. I'm using one DMA channel, SPI is in 8 bit mode for the time being, so DMA is in byte mode, it's also in 'null write' mode, and continuous without ping pong. My buffers are only one member arrays and the DMA is matched.
DMA2CONbits.CHEN = 0; //Disable DMA
DMA2CONbits.SIZE = 1; //Receive bytes (8 bits)
DMA2CONbits.DIR = 0; //Receive from SPI to DMA
DMA2CONbits.HALF = 0; //Receive full blocks
DMA2CONbits.NULLW = 1; //null write mode on
DMA2CONbits.AMODE = 0; //Register indirect with post-increment
DMA2CONbits.MODE = 0; //continuous mode without ping-pong
DMA2REQbits.IRQSEL = 10; //Transfer done (SPI)
DMA2STA = __builtin_dmaoffset(SPIBuffA); //receive buffer
DMA2PAD = (volatile unsigned int) &SPI1BUF;
DMA2CNT = 0; //transfer count = 1
IFS1bits.DMA2IF = 0; //Clear DMA interrupt
IEC1bits.DMA2IE = 1; //Enable DMA interrupt
From what I understand from the null write mode, the DMA will write a null value every time a read is performed. However, the DMA wont start until an initial write is performed by the CPU, so I've used the manual/force method to start the DMA.
DMA1CONbits.CHEN = 1; //Enable DMA
DMA1REQbits.FORCE = 1; //Manual write
The interrupt will now start, and runs without error. However the code later shows that the collection was incorrect.
My interrupt is simple in that all I'm doing is placing the collected data (which I assume is placed in my DMAs buffer as allocated above) into a pointer which is used throughout my program.
void __attribute__((interrupt, no_auto_psv)) _DMA2Interrupt(void) {
if (RxDmaBuffer == 513) {
DMA2CONbits.CHEN = 0;
rxFlag = 1;
} else {
buffer[RxDmaBuffer] = SPI1BUF;
RxDmaBuffer++;
}
IFS1bits.DMA2IF = 0; // Clear the DMA0 Interrupt Flag
}
When the interrupt has run 512 times, I stop the DMA and throw a flag.
What am I missing? How is this not the same as the non-DMA method? Is it perhaps the lack of the while loop which waits for the completion of the SPI transmission (while (!_SPIRBF);). Unfortunately with the null write mode automatically sending and receiving the SPI data I can't manually put any sort of wait in.
I've also tried using two DMA channels, one to write and one to read, but this also didn't work (plus I need that channel later for when I come to proper writing to the SD card).
Any help would be great!
The below IRQ handler drains the USART3 of incoming 32 byes of data. The first IRQ TC event reads the first 6 bytes, then reprograms the DMA engine to read in the last 24 bytes. The second TC repograms it to read the header again This method will allow for variable length messages using DMA. However I cannot seem to be able the change the DMA start address. I'd like to be able to store each message in a seperate buffer, however it just over writes the first buffer on upon recieving each message. What am I doing wrong? The microcontroller is a STM32F103ze (Cortex-M3).
void DMA1_Channel3_IRQHandler(void)
{
uint32_t tmpreg = 0;
/* Test on DMA1 Channel3 Transfer Complete interrupt */
if(DMA_GetITStatus(DMA1_IT_TC3))
{
if (rx3_dma_state == RECIEVE_HEADER )
{
DMA_Cmd(DMA1_Channel3, DISABLE);/* Disable DMA1_Channel2 transfer*/
// Now that have received the header. Configure the DMA engine to receive
// the data portion of the message
DMA_SetCurrDataCounter(DMA1_Channel3,26);
DMA_Cmd(DMA1_Channel3, ENABLE);
} else if ( rx3_dma_state == RECIEVE_MSG )
{
//CurrDataCounterEnd3 = DMA_GetCurrDataCounter(DMA1_Channel3);
DMA_Cmd(DMA1_Channel3, DISABLE);
/* Get Current Data Counter value after complete transfer */
USART3_Rx_DMA_Channel_Struct->CMAR = (uint32_t) RxBuffer3LookUp[rx_buffer_index];
DMA_SetCurrDataCounter(DMA1_Channel3, 6);
DMA_Cmd(DMA1_Channel3,ENABLE);
// Set up DMA to write into the next buffer slot (1 of 8)
++rx_buffer_index;
rx_buffer_index %= 8;
}
/* Clear DMA1 Channel6 Half Transfer, Transfer Complete and Global interrupt pending bits */
DMA_ClearITPendingBit(DMA1_IT_GL3);
}
// Update the state to read fake header of 6 bytes
// or to read the fake data section of the message 26 bytes
++rx3_dma_state;
rx3_dma_state %= 2;
isr_sem_send(dma_usart3_rx_sem);
}
You say:
...reprograms the DMA engine to read in the last 24 bytes.
but your code says:
DMA_SetCurrDataCounter(DMA1_Channel3,26);
Is that right? Is it 24 or 26?