Author Topic: Sending through serial port  (Read 14448 times)

Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Sending through serial port
« on: May 13, 2009, 06:29:11 PM »
Hi Mark,
    When a block of data is sent through a serial port, how can I tell when it has been sent? I am communicating with a GSM module, and have to know when all the data has been sent, as certain procedures has to be carried out before another send.

Neil

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: Sending through serial port
« Reply #1 on: May 13, 2009, 10:16:09 PM »
Hi Neil

When you have used fnWrite() to send a block of data to the serial interface, the data is first stored in the UART's output buffer. How long it takes for the complete buffer content to be sent depends on the transmission rate.

If you are using interrupt mode of transmission it is possible to see how much data is still in the output buffer by calling fnWrite(serial_handle, 0, 0). It will return the remaining space (so subtracting this from the output buffer size gives you the bytes remaining). In DMA mode this will not return the instantaneous space but will still give you an indication of whether transmission is still in progress or the buffer is completely free - in future versions the DMA should also be able to give the instantaneous value (being worked on).

Another method is to use the TX_FREE event. To use this the option WAKE_BLOCKED_TX should be active and it must be primed when sending, using
fnDriver( serial_handle, MODIFY_WAKEUP, (MODIFY_TX | OWN_TASK) ); (See example in debug.c) When the output buffer reduces below the low water mark it will send the TX_FREE event to the entered task. However this uses the receiver's low water mark and may not always be suitable (if flow control requires the level to be set quite high).

In the next service pack there will be an additional setting allowing a call back when the final character in a block has been transmitted. You may like to add this and use it since it gives an accurate timing in the final byte in the buffer (not when the transmission has completed but when the last byte has been sent - see exact timings for the Coldfire and other processors in the appendix to the MODBUS document: http://www.utasker.com/docs/MODBUS/uTasker_MODBUS.PDF

This can be added as follows:

1) in Tty_drv.c, send_next_byte()

        if (!ptTTYQue->tty_queue.chars) {                                // are there more to send?
            ptTTYQue->ucState &= ~TX_ACTIVE;                             // transmission of a block has terminated
            fnClearTxInt(channel);                                       // clear interrupt
#if defined (WAKE_BLOCKED_TX) && defined (SERIAL_SUPPORT_DMA)            // {2},{3}
            fnWakeBlockedTx(ptTTYQue, 0);
#endif
#ifdef UART_BREAK_SUPPORT
            if (ptTTYQue->opn_mode & BREAK_AFTER_TX) {
                fnStartBreak(channel);
            }
#endif
#ifdef UART_FRAME_COMPLETE                                               // {16}
            if (ptTTYQue->opn_mode & INFORM_ON_FRAME_TRANSMISSION) {
                fnUARTFrameTermination(channel);
            }
#endif
        }


Add fnUARTFrameTermination(channel); without the new operation mode flag to get it to call back.

2) Add extern void fnUARTFrameTermination(QUEUE_HANDLE channel); to hardware.h

3) Add this routine in your code and use it to activate any processing required. Note that this is called from within an interrupt so you may want to use it to send an event to your application task so that it can do the real work there. If using more that one UART you will get a call back for each but you can easily filter the correct one by checking the channel (UART number).

Regards

Mark



Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Re: Sending through serial port
« Reply #2 on: May 14, 2009, 09:14:00 AM »
Hi Mark,
   Thanks, that worked.
I simply commented out the following:
//if (ptTTYQue->opn_mode & INFORM_ON_FRAME_TRANSMISSION)

so the  fnUARTFrameTermination(channel); always gets called.

Neil

Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Re: Sending through serial port
« Reply #3 on: August 07, 2009, 03:23:05 PM »
Hi Mark,
  I have an output that that to be set low for the whole serial transmission. Here is what I have to do:

1. Set output pin low.
2. Generate break on line.
3. Send Serial data.
4. Set output pin high again.

The output pin gets set high within the fnUARTFrameTermination(QUEUE_HANDLE channel) routine when the channel matches my transmition channel. So should only go high when tranmission is complete..

With the attached snap shot you can see what happens. The top line is the output pin, and bottom the TX pin.
The output pin goes high before the last byte has been sent.

Does the fnUARTFrameTermination() get called when the last byte is placed in the Tx transmit  buffer, or when transmit is complete? I am looking for when the transmit is complete, and all has been sent out.

Thanks
Neil

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: Sending through serial port
« Reply #4 on: August 07, 2009, 09:35:43 PM »
Hi Neil

The M522XX interrupt timing is as follows - referenced to a 19'200Baud tx signal and the RTS line (being effectively controlled as a port)

•   M522xx Interrupt 19’200 Baud (Bit Period 52us). RTS activated about 17us before first bit. Last character interrupt about 10.9 bit times before end (0.57ms).
•   M522xx DMA 19’200 Baud (Bit Period 52us). RTS activated about 17us before first bit. Last character (DMA complete) interrupt about 22 bit times before end (1.144ms).


As you can see, the last character interrupt occurs before the data has been completely sent. It is also dependent on mode (interrupt driven or DMA).
There is in fact a further flag, TXEMP (with interrupt) which signals when the data has been fully transmitted.
I did once look at using this to aid in detecting the end of frame but decided against in in preference of using a timer interrupt (this is then more flexible too since it can be set to add extra delays beyond the exact end of the frame).

The MODBUS RS485 control uses this technique, where the fnUARTFrameTermination() is used to trigger a timer interrupt. The following is an example using a DMA timer:

    DMA_TIMER_SETUP timer_setup_RTS_negate;
    timer_setup_RTS_negate.int_type = DMA_TIMER_INTERRUPT;                         // configure the timer struct once for efficiency
    timer_setup_RTS_negate.channel = MODBUS0_DMA_TIMER_CHANNEL;          // timer channel is the same as RTU timer (never required at the same time)
    timer_setup_RTS_negate.int_priority = MODBUS0_DMA_TIMER_INTERRUPT_PRIORITY;
    timer_setup_RTS_negate.int_handler = fnTimer_RTS_0;                // enter timer call-back
    timer_setup_RTS_negate.mode = (DMA_TIMER_INTERNAL_CLOCK | DMA_TIMER_SINGLE_SHOT_INTERRUPT);
    timer_setup_RTS_negate.count_delay = DMA_TIMER_US_DELAY(1,1,_DELAY_TIME);


_DELAY_TIME is the exact delay in us required to negate the output after the final tx interrupt, which needs to be defined to match the speed (or taken from a list of entries for each possible baud rate).



extern void fnUARTFrameTermination(QUEUE_HANDLE Channel)
{
    fnConfigureInterrupt((void *)ptrSetupRTS); // start last delay to negation
}


When the last frame interrupt occurs the timer is started and the interrupt call-back fnTimer_RTS_0() is used to set the output high.

As long as you have a free hardware timer this technique should be suitable for you too.

Regards

Mark


Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Re: Sending through serial port
« Reply #5 on: August 08, 2009, 03:36:14 PM »
Hi Mark,
 As I can communicate with many devices, and perhaps at different baud rates, I think the TXEMP  interupt (interupt when sent) would be better for me, do you have an example of this?

Many Thanks
Neil

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: Sending through serial port
« Reply #6 on: August 08, 2009, 03:54:44 PM »
Hi Neil

I am sorry but I never tried implementing this.
However the MODBUS module is also using the timer method with various Baud rates. Depending on the Baud rate to be configured the timer delay value is entered.

Here is the code, which uses a table with Baud entries for interrupt and DMA operation - 7 and 8 bit characters:

These are the measured bit delay times for the modes for the Coldfire UART:

        #define RTS_BIT_DELAY_8BIT_INT (float)10.9                       
        #define RTS_BIT_DELAY_7BIT_INT (float)9.9
        #define RTS_BIT_DELAY_8BIT_DMA (float)21.7
        #define RTS_BIT_DELAY_7BIT_DMA (float)20.7


This is the table containing each setting:

static const unsigned short usRTSTimes[SERIAL_BAUD_115200 - SERIAL_BAUD_600][RTS_TIMES] = { // this table holds the required delay from the last interrupt in us for each speed/character length/int/dma combination
    {                                                                    // 1200 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 1200)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 1200)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 1200)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 1200))
    #endif
    },
    {                                                                    // 2400 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 2400)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 2400)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 2400)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 2400))
    #endif
    },
    {                                                                    // 4800 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 4800)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 4800)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 4800)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 4800))
    #endif
    },
    {                                                                    // 9600 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 9600)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 9600)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 9600)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 9600))
    #endif
    },
    {                                                                    // 14400 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 14400)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 14400)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 14400)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 14400))
    #endif
    },
    {                                                                    // 19200 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 19200)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 19200)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 19200)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 19200))
    #endif
    },
    {                                                                    // 38400 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 38400)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 38400)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 38400)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 38400))
    #endif
    },
    {                                                                    // 57600 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 57600)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 57600)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 57600)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 57600))
    #endif
    },
    {                                                                    // 115200 Baud time
    (unsigned short)((RTS_BIT_DELAY_8BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 115200)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_INT * REFERENCE_BIT_TIME) * (float)(115200 / 115200)),
    #ifdef SERIAL_SUPPORT_DMA
    (unsigned short)((RTS_BIT_DELAY_8BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 115200)),
    (unsigned short)((RTS_BIT_DELAY_7BIT_DMA * REFERENCE_BIT_TIME) * (float)(115200 / 115200))
    #endif
    },
};


When the timer delay value is configured, the value is taken from the const table as follows:

timer_setup_RTS_negate.count_delay = DMA_TIMER_US_DELAY(1,1,usRTSTimes[ucSpeed][ucType]);

ucSpeed is the Baudrate (eg. SERIAL_BAUD_19200 as defined in driver.h)
ucType is  0 for 8 bit interrupt mode, 1 is for 7 bit interrupt mode, 2 is for 8 bit DMA mode, 3 is for 8 bit DMA mode
Additional entries can of course be added or the table can be reduced if less modes are required.

Note that I saw that this method is also explained in the UART document:
http://www.utasker.com/docs/uTasker/uTaskerUART.PDF

You may thus find that the technique above is suitable for your requirements. If not we will need to study the additional interrupt capability - note however that the above method is processor independent (the delay defines for the table may be processor conditional though), whereas the UART flag interrupt method will be processor specific - probably not an issue for you, but moving the code between processors may then not always be possible.

Regards

Mark

Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Re: Sending through serial port
« Reply #7 on: August 10, 2009, 04:48:26 PM »
Hi Mark,
  The problem I have is that I am communicating with a SDI-12 device which is a single wire data line. The data line is only driven during sending, then the line has to be given up. The tx line goes to a buffer which is enabled with a OE pin, so when I drive this low, the tx line is in control of the data path. Straight after this, the device responses within 10ms, so I have to swith off the buffer right after the last bit is sent. I just cant seem to get it right, there is one command which works fine, as it doesnt resond that quick. So I am thining that I will have to get an interupt right after the last byte is actually sent, is there an easy way for this?

Regards
Neil

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: Sending through serial port
« Reply #8 on: August 10, 2009, 07:33:11 PM »
Hi Neil

Using the timer you should be able to get accuracy in the us region - typically disabling during the final stop bit is the goal since the stop bit will be detected whether the line is driven or disabled.

For a quick test I would try using the fnUARTFrameTermination() as it is and then add a loop there polling the UART_TEMP bit in the USR_UCSR_X (where X is the channel) register.
Something like
while (!(USR_UCSR_X & UART_TEMP)) {}

As soon as the bit is set you can disable the transmitter and  leave the fnUARTFrameTermination().
Of course this is not necessarily the most efficient method but it should allow you easily to prove the timing and verify that the base problems are not elsewhere.

Regards

Mark

Offline neil

  • Sr. Member
  • ****
  • Posts: 438
    • View Profile
Re: Sending through serial port
« Reply #9 on: August 10, 2009, 08:37:20 PM »
Hi Mark,
  Sorry, for some reason I never used your code for timing, I had been switching between projects and got mixed up.

I tried copying in the table below, but got errors , undefined identifiers RTS_TIMES etc..

I havent used the hardware timer yet, I have copied the below,and marked the ones not sure about, can you let me know what to set :

void fnTimer_0(void);

DMA_TIMER_SETUP  OE_negate;
negate.int_type = DMA_TIMER_INTERRUPT;                       
negate.channel =    //not sure here..         
negate.int_priority = //not sure here..
negate.int_handler = fnTimer_0;                // enter timer call-back
negate.mode = (DMA_TIMER_INTERNAL_CLOCK | DMA_TIMER_SINGLE_SHOT_INTERRUPT);
negate.count_delay = DMA_TIMER_US_DELAY(1,1,DMA_TIMER_US_DELAY(1,1,usRTSTimes[SERIAL_BAUD_1200][1]););//1200baud 7 bit

void fnTimer_0(void)
{
  set pin high....
}

extern void fnUARTFrameTermination(QUEUE_HANDLE Channel)
{
    fnConfigureInterrupt((void *)OE_negate); // start last delay to negation
}

Many Thanks
Neil

Offline mark

  • Global Moderator
  • Hero Member
  • *****
  • Posts: 3236
    • View Profile
    • uTasker
Re: Sending through serial port
« Reply #10 on: August 10, 2009, 10:42:31 PM »
Hi Neil

negate.channel = 0,1,2, or 3 (the channel of the DMA timer to use). This assumes that all 4 have not yet been allocated in the project of course.
negate.int_priority = DMA_TIMER1_INTERRUPT_PRIORITY; // this assumes DMA Timer 1 is used and the priority for it is defined in app_hw_m5223x.h (you should find the fine already in this file in the demo project - if not choose any free priority level and add one.

I found the following defines in my reference:
#ifdef SERIAL_SUPPORT_DMA
    #define RTS_TIMES 4                                                  // 8 bit int, 7 bit int, 8 bit DMA, 7 bit DMA
#else
    #define RTS_TIMES 2                                                  // 8 bit int, 7 bit int
#endif


This means that the missing defines are the setting possibility count in the time delay table. It was simply removing two unnecessary entries when no DMA support is used.

Good luck

Regards

Mark