correct way to change Next Counter Register at run time

Discussion about SAM7 Series and ARM7TDMI based products.

Moderator: nferre

osyan
Posts: 7
Joined: Wed Oct 08, 2014 11:22 am

correct way to change Next Counter Register at run time

Fri Jul 10, 2015 4:25 pm

I am using USART,DMA on at91sam7x256 and i need to use chained buffer to receive data like this:
  • [1] Receive 4-byte on usart0 store in bufferA.(ENDRX interrupt routine fired)
  • [2] Based on 4th byte i should change the Receive Next Counter Register
  • [3] THEN enable the RXBUFF interrupt so i get data with specified lenght and put them in the next buffer: bufferB 
example:
       bufferA      ________________________________    bufferB
0x01 0x23 0x11 0x05  ---so i need to get 5 bytes->   ?  ?  ?  ?  ?
0x01 0x07 0x42 0x03  ---so i need to get 3 bytes->   ?  ?  ?

In the ISR i have :

Code: Select all

// we have a end-of-receive interrupt (ENDRX)
/// pUsart0->US_RCR = 4; // i commented this line so it don't restore the receive count - so ENDRX flag not cleared

 pUsart0->US_RNCR = (unsigned int)bufferA[3] ; // set the next counter register based on 4th byte of received data

/// disable the end-of-receive interrupt, enable the RXBUFF (both receive buffer full) interrupt
 pUsart0->US_IER =  AT91C_US_RXBUFF; // enable usart0 RXBUFF interrupt
 pUsart0->US_IDR = ~AT91C_US_RXBUFF; // disable all interrupts except RXBUFF 

 // enable receive DMA transfers
 pUsart0->US_PTCR = AT91C_PDC_RXTEN | // enable  receive transfer,
                 AT91C_PDC_TXTDIS; // disable transmit transfer

Result: i get the first part of data- ENDRX interrupt  fired. but RXBUFF don't fired after receiving data.
Whats my problem? any suggestion? 
blue_z
Location: USA
Posts: 1560
Joined: Thu Apr 19, 2007 10:15 pm

Re: correct way to change Next Counter Register at run time

Sat Jul 11, 2015 1:13 am

osyan wrote:I am using USART,DMA on at91sam7x256 and i need to use chained buffer to receive data like this:
...
Based on 4th byte i should change the Receive Next Counter Register
...
Whats my problem?
(1) You didn't read the datasheet carefully.
(2) Your code doesn't seem to write anything to the Next Pointer Register.
(3) Writing to the Next Counter Register after the ENDRX interrupt is too late! The transfer has probably already been terminated! Proper DMA chaining does not require the transfer to be (re)enabled for each buffer.

Regards
osyan
Posts: 7
Joined: Wed Oct 08, 2014 11:22 am

Re: correct way to change Next Counter Register at run time

Sat Jul 11, 2015 9:33 am

blue_z wrote: (1) You didn't read the datasheet carefully.
(2) Your code doesn't seem to write anything to the Next Pointer Register.
(3) Writing to the Next Counter Register after the ENDRX interrupt is too late! The transfer has probably already been terminated! Proper DMA chaining does not require the transfer to be (re)enabled for each buffer.

Regards
Thanks for your answer.
The code above is just the USART_ISR.
I have initialized the next pointer register Once at the start of main routine as below:

Code: Select all

pUSART0->US_RPR = (unsigned int)BufferA;   // address of DMA input buffer
pUSART0->US_RCR = 4;				    // we'll read in 4 chars via DMA
pUSART0->US_RNPR = (unsigned int)BufferB; // next DMA receive buffer address
pUSART0->US_RNCR = (unsigned int)0;	    // next DMA receive counter
//here i set the US_RNCR to zero because at first i don't know the length of data for BufferB
So in the USART_ISR I just change the next counter register to get the new length of data that i need.

Code: Select all

char BufferA[4];                              // holds 4-bytes of received characters 
unsigned long nCharsA = 0;             // counts number of received chars
char *pBufferA = &BufferA[0];          // pointer into BufferA
    
char BufferB[128];                           // holds n-bytes(0-128) of received characters 
unsigned long nCharsB = 0;             // counts number of received chars
char *pBufferB = &BufferB[0];          // pointer into BufferB

The first buffer exactly get 4-byte each time.
but the next buffer needs to get different size of data.
(3) Writing to the Next Counter Register after the ENDRX interrupt is too late! The transfer has probably already been terminated!
but what can be the solution? 
blue_z
Location: USA
Posts: 1560
Joined: Thu Apr 19, 2007 10:15 pm

Re: correct way to change Next Counter Register at run time

Mon Jul 13, 2015 11:44 pm

osyan wrote:but what can be the solution?
To what?
Seems like you have two problems, and IMO the solutions may be mutually exclusive.

DMA chaining

Granted the Atmel datasheet could describe DMA chaining a lot better, since it's tersely described in one sentence with related details scattered in other paragraphs.
Some of it is ambiguous or misleading:
Programming the Next Counter/Pointer registers chains the buffers. The counters are decremented after each data transfer as stated above ...
Presumably "counters are decremented" refers only to the active PERIPH_RCR or PERIPH_TCR register, and not the PERIPH_RNCR and PERIPH_TNCR registers.

The key point is:
... but when the transfer counter reaches zero, the values of the Next Counter/Pointer are loaded into the Counter/Pointer registers in order to re-enable the triggers.
So when the PERIPH_RCR (the current count) decrements to zero,
  • (1) the nonzero value in PERIPH_RNCR (the backup count) is copied into PERIPH_RCR (the current count),
    (2) the PERIPH_RNPR (the backup pointer) is copied into PERIPH_RPR (the current pointer),
    (3) the data transfer continues with these new values in PERIPH_RCR & PERIPH_RPR,
    (4) the PERIPH_RNCR (the backup count) is set to zero to inhibit another chaining operation unless PERIPH_RNCR is written again
    (5) the ENDRX flag is set because the PERIPH_RCR register reached zero.
But if you haven't written anything to the PERIPH_RNCR (the backup count) before the current operation completes, then none of the above happens, since:
When the counter reaches zero, the transfer is complete and the PDC stops transferring data
To perform dynamic DMA chaining, you need to schedule a transfer to provide the time to calculate the next backup transfer parameters and load them into the DMA controller. That is, the "current" DMA transfer must span a time interval long enough to derive/calculate and then write the count and pointer for the "backup" DMA transfer. The "backup" transfer must be setup before the "current" transfer completes.

If you're extracting a value from data received in transfer #1, then that data is not (safely) available until after transfer #1 is complete. If DMA chaining is utilized, then that data is available while chained-transfer #2 is in progress. You could use that extracted value to setup a chained-transfer #3, but you must have setup an intermediate chained-transfer #2 to buy time to perform the necessary extraction & processing.


message framing

Your code requires the USART "device driver" to be cognizant of the message protocol (in order to extract a length value), and has the "device driver" receive intact message frames (as opposed to a stream of bytes).

Trying to detect and assemble the message frame in the device driver is typically a bad idea, and even worse in the ISR. That overloads the functionality of the device driver, and reduces modularity and abstraction layers.
Your ISR code may work with idealized input in a controlled environment for a homework assignment, but out in the real world, such simple code would not be robust enough to handle message-frame issues such as a loss of message byte alignment. Simplistic message-framing code can lead to occasional mysterious link failures that require manual intervention. Attempts to enhance the robustness of the framing code will add bulk to the ISR, which is not optimal for performance.

For example, in your ISR before bufferA[3] is written to PERIPH_RNCR, is there a check to verify that bufferA[0] is actually an ASCII SOH character indicating the (possible) start of a message? What recovery action does the ISR perform if bufferA[0] is not the expected SOH character (and by implication, bufferA[3] should not be used as a byte count)?

The USART device driver should only receive the data and store it in a buffer without trying to analyze or process it. Perform the scan for the message frame on this buffered data in a tasklet or a higher-level protocol layer.

Regards
jonavarque
Posts: 29
Joined: Fri Jul 10, 2015 6:35 pm

Re: correct way to change Next Counter Register at run time

Sat Aug 01, 2015 3:24 am

blue_z



.. Thanks you for the detailed reply. I am trying to get a double buffer scheme for an I2SC0 implementation on a SAMG55. I think you've answered my main question and that was when to write to the next buffer/counter. If I am reading this correctly there is no message that the next buffer pointer has been transferred. So the only solution is a scheduled write that has to occur when the lead buffer is being written out?
blue_z
Location: USA
Posts: 1560
Joined: Thu Apr 19, 2007 10:15 pm

Re: correct way to change Next Counter Register at run time

Tue Aug 04, 2015 7:05 pm

jonavarque wrote:If I am reading this correctly there is no message that the next buffer pointer has been transferred.
"Message"???
The "next" registers are used as backup registers, and not directly involved in DMA transfers.
jonavarque wrote:So the only solution is a scheduled write that has to occur when the lead buffer is being written out?
"Lead buffer"???
I don't understand your question.

Regards
jonavarque
Posts: 29
Joined: Fri Jul 10, 2015 6:35 pm

Re: correct way to change Next Counter Register at run time

Tue Aug 04, 2015 11:58 pm

Thanks blue_z. Apologies for my bad terminology. I was referring to the TPR/TCR pointer/counter as 'main', not the NEXT buffer. I understand them a bit more now that I sort of have it working. As far as the Next counter and pointer being transferred to the main buffer pointer and counter.

The problem I am having is that I had intended to use an interrupt somehow to reload the Next params. I have it all working well in a polling loop but I wasn't able to get an interrupt to work as the handler was swamped with interrupts, even though I had only enabled TXEMPTY or only ENDTX. It seems interrupts were not cleared upon writing new values to the Next registers.
blue_z
Location: USA
Posts: 1560
Joined: Thu Apr 19, 2007 10:15 pm

Re: correct way to change Next Counter Register at run time

Wed Aug 05, 2015 3:17 am

jonavarque wrote:I was referring to the TPR/TCR pointer/counter as 'main', not the NEXT buffer. ...
As far as the Next counter and pointer being transferred to the main buffer pointer and counter.
"Main" and "main buffer" might work for you, but the more common descriptor is "current". "Current" is not used in the Atmel HW datasheet, but is used in the Atmel Linux driver and is also what I'm used to using.
"Current" has a temporal attribute that "main" lacks.
jonavarque wrote:It seems interrupts were not cleared upon writing new values to the Next registers.
You should be able to check that by reading the status register before and after updating the next/backup count register.
Note that writing to the pointer registers will not reset the interrupt condition.

Regards
jonavarque
Posts: 29
Joined: Fri Jul 10, 2015 6:35 pm

Re: correct way to change Next Counter Register at run time

Wed Aug 05, 2015 4:10 pm

Main, Current... pretty much the same thing here... but.. ok, that wasn't my question though. I have had to take over someone else's design and I have very little time to get this wrapped up. All I want to know is how to accomplish interrupt driver buffer management for the I2S block. I find the manual woefully inadequate and I am under a massive deadline. I had it partially working but I was unable to control the interrupts

I'm back at this problem this morning so I will try to hack through it.


The manual says that the I2SC_SSR register is used to reset and interrupt but there are only 4 parameters in that register, RXOR, TXUR, RXORCH, TXURCH. I don't see how to reset from a TXBUFE, TXEN, ENDTX, etc. That's where I am confused.
blue_z
Location: USA
Posts: 1560
Joined: Thu Apr 19, 2007 10:15 pm

Re: correct way to change Next Counter Register at run time

Wed Aug 05, 2015 7:55 pm

jonavarque wrote:The manual says that the I2SC_SSR register is used to reset and interrupt but there are only 4 parameters in that register, RXOR, TXUR, RXORCH, TXURCH.
A status condition set through the I2SC_SSR register should be cleared by writing to the I2SC_SCR register.
jonavarque wrote:I don't see how to reset from a TXBUFE, TXEN, ENDTX, etc.
"TXEN"???
For the PDC interrupt conditions, review my previous post and this thread.

Regards
jonavarque
Posts: 29
Joined: Fri Jul 10, 2015 6:35 pm

Re: correct way to change Next Counter Register at run time

Wed Aug 05, 2015 9:01 pm

I think I have it working using 3 buffers.
I start I2SC0 when I have BufA ready. (lowest latency)
Then load BufB and set the NEXT values with BufB'a addr/length


The interrupt handler:

Code: Select all

i2s_callback_t TXReadyCB()
{
	*((uint32_t *)0x40000218 ) = GetNextWaveBuf(); // gets the addr of the current ready buf
	*((uint32_t *)0x4000021C ) = GetNextWaveBufLen(); // Gets the length

	NVIC_ClearPendingIRQ(I2SC0_IRQn);//using this to clear the interrupt
	ReadNext();// get the next data into the dirty buffer. (only one of 3 not in use for sure)
}
And the buffer routines, called from the TXReady interrupt handler:

Code: Select all

void ReadNext()
{
	if(++curBuffer >= MAX_WAVE_BUFFERS)
		curBuffer = 0;
	ReadPage(pkgAudioBaseAddr + waveAddr+waveIndex,WAVE_BUF_SIZE,theBuffers[curBuffer]);
	waveIndex += WAVE_BUF_SIZE;
	
	if(waveIndex >= waveLen)
	{
		printf("\r\nIRQ PLAY DONE  XX");
		StopPlayback();
		return;
	}
}

uint32_t GetNextWaveBuf()
{
	return theBuffers[curBuffer];
}

This seems to work well. Sounds great anyway. Muddling through it.
Yes.. not TXEN, TXREADY, apologies again... massive multitasking going on here..

Return to “SAM7 ARM7TDMI MCU”

Who is online

Users browsing this forum: Google [Bot] and 1 guest