STM32F1 USART DMA TX does not complete the second transfer - c

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.

Related

Mixed voltage reading from different AD7606 channels

Please help! I am using FSMC to connect a STM32F407 MCU with AD7606 to sample voltage value. MCU would send sampled values to PC using USB HS port after 1024 conversions. But when I inspect the values from PC, I found that readings from channel 0 occasionally contains data from other channels. For example, if connect channel 0 to 5v, connect channel 8 to 3.3v, connect other channels to ground. Then the printed value from channel 0 would contain 5v, 0v, 3.3v. The basic setup is as follows:
A 200KHZ PWM single is generated by TIM10 to act as CONVST signal for AD7606.
7606 will then issue a BUSY signal which I used as an external interrupt source.
In the Interrupt handler, An DMA request would be issued to read 8 16bit data
from FSMC address space to memory space. TIM10 PWM would be stopped if 1024
conversions has been done.
In the DMA XFER_CPLT call back, if 1024 conversions has been done, the converted
data would be sent out by USB HS port, and TIM10 PWM would be enabled again.
Some code blocks:
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == GPIO_PIN_7)
{
// DMA data from FSMC to memory
HAL_DMA_Start_IT(&hdma_memtomem_dma2_stream0, 0x6C000000, (uint32_t)(adc_data + adc_data_idx) , 8);
adc_data_idx += 8;
if (adc_data_idx >= ADC_DATA_SIZE)
HAL_TIM_PWM_Stop(&htim10, TIM_CHANNEL_1);
}
}
void dma_done(DMA_HandleTypeDef *_hdma)
{
int i;
int ret;
// adc_data[adc_data_idx] would always contain data from
// channel 1, led1 wouldn't light if every thing is fine.
if (adc_data[adc_data_idx] < 0x7f00 )
HAL_GPIO_WritePin(led1_GPIO_Port, led1_Pin, GPIO_PIN_SET);
if (adc_data_idx >= ADC_DATA_SIZE)
{
if(hUsbDeviceHS.dev_state == USBD_STATE_CONFIGURED)
{
// if I don't call CDC_Transmit_HS, everything is fine.
ret = CDC_Transmit_HS((uint8_t *)(adc_data), ADC_DATA_SIZE * 2 );
if (ret != USBD_OK)
{
HAL_GPIO_WritePin(led1_GPIO_Port, led2_Pin, GPIO_PIN_SET);
}
}
adc_data_idx = 0;
HAL_TIM_PWM_Start(&htim10, TIM_CHANNEL_1);
}
}
It seems that a single USB transaction would take longer than 5us(one conversion time), so I stopped PWM signal to stop conversion...
If I only send the second half of the data buffer, there is no data mixture. It's very strange.
According to your description, I think the processing is correct, and the problem is at the CDC_Transmit_HS(); I have met the problem on the CDC_Transmit_FS(), which can't transmit more than 64 bytes data for original code, and need to modify some code, otherwise the some error occurs. Did you check the number of received data is correct?
Reference:
I can't receive more than 64 bytes on custom USB CDC class based STM32 device
I'm not sure your ADC_DATA_SIZE size; if it's larger than 64 bytes, maybe you can modify to smaller than 64 bytes and try again and check whether or not the data is correct. I am not sure if it is affected by this problem, but I think you can give it a try.
On the other hand, it may also be necessary to GND the ADC IN pins not used by AD7606 to avoid interference between channels.
Or you can try other communication (I2C, SPI, UART...etc) to send the data.
If there is no problem with other communication methods, there is a high chance that it is a problem with CDC_Transmit_HS(). If there are problems with other transmission methods, you may have to check whether there is a conflict between the ADC conversion time or the transmission time.

Write SPI dummy bytes using DMA circular mode

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.

MSP430 UART TX Interrupt Enabling/Disabling

I have RX interrupts working just fine, but I wanted to add TX interrupts. I respond to long commands over the UART and don't want to waste cycles waiting for the TX to complete before sending along the next byte. I am trying to enable interrupts, transmit the data that needs to be transmitted, then disable the interrupts until the next TX packet comes along.
This works fine for the FIRST payload that I send out. I see it go out just fine. However as soon as I disable TX Interrupts once, I am never able to enter the ISR again. How on the MSP430 does one enable TX Interrupts on the UART and have it ever get into the ISR again?
Below where you see EUSCI_A_UART_enableInterrupt call, shouldn't this line trigger my ISR every time the interrupt gets enabled? If not, HOW DO I GET BACK INTO THE ISR AGAIN?
Here's the Transmit code:
void UartSendChar(uint8_t tx_char)
{
ring_buffer_put(_rbdTx, &tx_char);
EUSCI_A_UART_enableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_TRANSMIT_INTERRUPT); // Enable interrupt
}
Here's my ISR:
void EUSCI_A1_ISR(void)
{
int c=-1;
switch(__even_in_range(UCA1IV,USCI_UART_UCTXCPTIFG))
{
case USCI_NONE: break;
case USCI_UART_UCRXIFG:
...
case USCI_UART_UCTXIFG:
// If there's something in the Ring Buffer, transmit it
// If not, then disable TX interrupts until new data gets written.
if (ring_buffer_get(_rbdTx, &c) == 0)
{
EUSCI_A_UART_transmitData(EUSCI_A1_BASE, (uint8_t)c);
}
else
{
EUSCI_A_UART_disableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_TRANSMIT_INTERRUPT);
}
I figured out the problem. In this particular MSP430, when the interrupt vector is read and it shows a TX interrupt, the TXIFG bit automatically gets cleared by the micro. (How nice of it)
In order to get this ISR to fire again after re-enabling TX interrupts, the TXIFG bit must be manually set back to 1 again, which shows an interrupt pending. That way, when interrupts are enabled after data is shoved into the queue, the TXIFG bit is set, so the ISR executes and ships the data off.
Now my ISR looks like this:
void EUSCI_A1_ISR(void)
{
int c=-1;
switch(__even_in_range(UCA1IV,USCI_UART_UCTXCPTIFG))
{
case USCI_NONE: break;
case USCI_UART_UCRXIFG:
...
case USCI_UART_UCTXIFG:
// If there's something in the Ring Buffer, transmit it
// If not, then disable TX interrupts until new data gets written.
if (ring_buffer_get(_rbdTx, &c) == 0)
{
EUSCI_A_UART_transmitData(EUSCI_A1_BASE, (uint8_t)c);
}
else
{
EUSCI_A_UART_disableInterrupt(EUSCI_A1_BASE, EUSCI_A_UART_TRANSMIT_INTERRUPT);
// Set TXIFG manually back to 1 for next time.
HWREG16(EUSCI_A1_BASE + OFS_UCAxIFG) |= EUSCI_A_UART_TRANSMIT_INTERRUPT;
}

PIC16F1459 I2C Master Ack Issue with 24LC32

I'm facing a weird issue. I've always used bit bangin I2C functions on my PIC16F1459, but now I want to use the MSSP (SPI,I2C Master Slave Peripheral). So I've started writing the functions according to the datasheet, Start, Stop, etc. The problem I have is my PIC won't ACK the data I send to the I2C EEPROM. It clearly says in the datasheet that the ACK status can be found at SSPCON2.ACKSTAT. So my guess was to poll this bit until the slave responds to my data, but the program hangs in the while Loop.
void vReadACK (void)
{
while (SSPCON2.ACKSTAT != 0);
}
And here's my write function, my I2CCheck function and I2C Master Initialization function
void vI2CEcrireOctet (UC ucData, UC ucRW)
{
vI2CCheck();
switch (ucRW)
{
case READ:
SSPBUF = ucData + 1;
break;
case WRITE:
SSPBUF = ucData + 0;
break;
}
vReadACK();
}
void vI2CCheck (void)
{
while (SSPCON2.ACKEN); //ACKEN not cleared, wait
while (SSPCON2.RCEN); //RCEN not cleared, wait
while (SSPCON2.PEN); //STOP not cleared, wait
while (SSPCON2.SEN); //Start not cleared, wait
while (SSPCON2.RSEN); //Rep start not cleared, wait
while (SSP1STAT.R_NOT_W); //TX not done wait
}
void vInitI2CMaster (void)
{
TRISB4_bit = 1; //SDA IN
TRISB6_bit = 1; //SCL IN
SSP1STAT.SMP = 1; //No slew rate
SSP1STAT.CKE = 0; //Disable SMBus inputs
SSPADD = 0x27; //100 KHz
SSPCON1 = 0b00101000; //I2C Master mode
SSPCON3 = 0b00000000; //Rien de slave
}
Just so you know, 24LC32A WriteProtect tied to VSS, A2-A1-A0 tied to GND, so adress 0xA0. 4k7 pull-ups are on I2C line. PIC16F1459 at 16MHz INTOSC.
I'm completely stuck. I've went through the MSSP datasheet 5 to 6 times without finding any issue. Can you guys help?
And here's my logic analyzer preview (removing the while inside vReadAck() )
Well it looks like I've found the answer to my question. What I was doing was the exact way of doing this. The problem seemed to be the Bus Free Time delay required for the slave to respond. At 16Mhz, my I2C was probably too fast for the EEPROM memory. So I've added a small Delay function right after the stop operation, so the write sequences are delayed and BAM, worked.
Bus free time: Time the bus
must be free before a new
transmission can start.
Despite the fact you "totally know" know "PIC won't ACK the data I send to the I2C EEPROM" because it's not supposed to, you still seem to misunderstand how I2C acknowledgements are supposed to work. They're called acknowledgements because they can be both positively (ACK) and negatively (NAK) acknowledged. If you look at the the analyzer screen shot you posted you'll find that its quite clearly labelled each byte being sent as having been NAK'ed by the transmitter.
To properly check for I2C ACKs you should be polling the trailing edge of the ACKTIM bit, and then checking the ACKSTAT bit to find out whether the slave transmitted an ACK or a NAK bit. Something like this:
int
vReadACK() {
while(!SSPCON3.ACKTIM);
while(SSPCON3.ACKTIM);
return SSPCON2.ACKSTAT;
}
As for why your slaved device is apparently NAKing each byte it isn't clear from the code you've posted, but there's a couple of notable omissions from your code. You need to generate start and stop conditions but you've shown no code to do this.

Receiving SPI data via DMA on PIC32

I know that this topic (DMA & SPI) has already been talked about on numerous threads in the microchip forum, actually i've read all the 15 pages in result of the search with keyword "dma" and read all the topics about dma & spi.
And I am still stuck with my problem I hope someone can help me :)
Here is the problem.
My chip is a PIC32MX775F512H.
I am trying to receive (only receive) data using SPI via DMA.
Since you cannot "just" receive in SPI, and that the SPI core starts toggling the SPI clock only if you write into the SPIBUF (SPI1ABUF for me) I am trying to receive my data using 2 DMA channels.
DMA_CHANNEL1 for the transmitting part.
DMA_CHANNEL2 for the receiving part.
I copy pasted the code from http://www.microchip.com/forums/tm.aspx?tree=true&high=&m=562453&mpage=1#
And tried to make it work without any luck. It only receives several bytes (5 or 6).
I've set the Event Enable Flags to DMA_EV_BLOCK_DONE for both dma channels, no interrupt occurs.
Do you have any idea ?
Here is the code I am using :
int Spi_recv_via_DMA(SPI_simple_master_class* SPI_Port, int8u *in_bytes, int16u num_bytes2)
{
DmaChannel dmaTxChn=DMA_CHANNEL1;
DmaChannel dmaRxChn=DMA_CHANNEL2;
SpiChannel spiTxChn=SPI_Port->channel;
int8u dummy_input;
DmaChnOpen(dmaTxChn, DMA_CHN_PRI3, DMA_OPEN_DEFAULT);
DmaChnOpen(dmaRxChn, DMA_CHN_PRI3, DMA_OPEN_DEFAULT);
DmaChnSetEventControl(dmaTxChn, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1A_RX_IRQ));
DmaChnSetEventControl(dmaRxChn, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1A_RX_IRQ));
DmaChnClrEvFlags(dmaTxChn, DMA_EV_ALL_EVNTS);
DmaChnClrEvFlags(dmaRxChn, DMA_EV_ALL_EVNTS);
DmaChnSetEvEnableFlags(dmaRxChn, DMA_EV_BLOCK_DONE);
DmaChnSetEvEnableFlags(dmaTxChn, DMA_EV_BLOCK_DONE);
//SpiChnClrTxIntFlag(spiTxChn);
//SpiChnClrRxIntFlag(spiTxChn);
DmaChnSetTxfer(dmaTxChn, tx_dummy_buffer, (void *)&SPI1ABUF, num_bytes2, 1, 1);
DmaChnSetTxfer(dmaRxChn, (void *)&SPI1ABUF, in_bytes, 1, num_bytes2, 1);
while ( (SPI1ASTAT & SPIRBE) == 0)
dummy_input = SPI1ABUF;
SPI1ASTAT &= ~SPIROV;
DmaRxIntFlag = 0;
DmaChnEnable(dmaRxChn);
DmaChnStartTxfer(dmaTxChn, DMA_WAIT_NOT, 0);
while(!DmaRxIntFlag);
return 1;
}
with those two interrupt handlers :
// handler for the DMA channel 1 interrupt
void __ISR(_DMA1_VECTOR, ipl5) DmaHandler1(void)
{
int evFlags; // event flags when getting the interrupt
//LED_On(LED_CFG);
INTClearFlag(INT_SOURCE_DMA(DMA_CHANNEL1)); // acknowledge the INT controller, we're servicing int
evFlags=DmaChnGetEvFlags(DMA_CHANNEL1); // get the event flags
if(evFlags&DMA_EV_BLOCK_DONE)
{ // just a sanity check. we enabled just the DMA_EV_BLOCK_DONE transfer done interrupt
DmaTxIntFlag = 1;
DmaChnClrEvFlags(DMA_CHANNEL1, DMA_EV_BLOCK_DONE);
}
// LED_Off(LED_CFG);
}
void __ISR(_DMA2_VECTOR, ipl5) DmaHandler2(void)
{
int evFlags; // event flags when getting the interrupt
INTClearFlag(INT_SOURCE_DMA(DMA_CHANNEL2)); // acknowledge the INT controller, we're servicing int
evFlags=DmaChnGetEvFlags(DMA_CHANNEL2); // get the event flags
if(evFlags&DMA_EV_BLOCK_DONE)
{ // just a sanity check. we enabled just the DMA_EV_BLOCK_DONE transfer done interrupt
DmaRxIntFlag = 1;
DmaChnClrEvFlags(DMA_CHANNEL2, DMA_EV_BLOCK_DONE);
}
}
So I end up waiting forever at the line : while(!DmaRxIntFlag);
I have put breakpoints in the interrupt vectors, they are never called.
This is the state of several registers during the ever lasting wait :
DMACON 0x0000C800
DMASTAT 0x00000001
I am using SPI1A port, so SPI1ABUF and _SPI1A_RX_IRQ
DCH1SPTR 0x5
DCH1SSIZ 0x2B
DCH2DPTR 0x6
DCH2DSIZ 0x2B
DCH2CON 0x00008083
DCH2ECON 0x1B10
DCH2INT 0x00800C4
DCH2SSA 0x1F805820
DCH2DSA 0x00000620
Channel 1 is used to transmit
Channel 2 is used to receive
You are missing these:
INTEnable(INT_SOURCE_DMA(dmaTxChn), INT_ENABLED); // Tx
INTEnable(INT_SOURCE_DMA(dmaRxChn), INT_ENABLED); // Rx
rigth before
DmaRxIntFlag = 0;
DmaChnEnable(dmaRxChn);
DmaChnStartTxfer(dmaTxChn, DMA_WAIT_NOT, 0);
Good luck!
Are you using the SPI in slave mode? or you are on master mode, trying to read some response for a command?
Have you check the silicon errata for this chip? The dspic 33fj family had an issue where SPI slave mode simply didn't work.
Other than that, I don't think it is a good idea to busy wait for DmaRxIntFlag change. You should configure the DMA transfer and continue with your main loop. The DMA will trigger the interrupt handler.
Hope this helps.

Resources