This is a RS485 class that uses the second UART and was tested on a Nucleo F030R8. A main demo program howto use the class is included. This class control the direction pin on the transceiver buffer automatically, and used transmit and receive interrupts. Ring buffers (256 bytes) are implemented on both transmission and reception. It assumes a ADM3485 'type' buffer where pins 2 ans 3 are connected and seen as direction. This test program could easily be adapted as base for other programs.

Dependencies:   mbed

dlms_comms.cpp

Committer:
creatron
Date:
2014-11-23
Revision:
11:ca1e0ca3a673
Parent:
10:e104a1b24165

File content as of revision 11:ca1e0ca3a673:

#include "mbed.h"

#include "RawSerial.h"
#include "dlms_comms.h"

//#define DEBUG_DLMS_COMMS 

// Instantiate the raw serial (2nd uart) Tx then Rx
   RawSerial  rs485(PA_9, PA_10);
  
// pin to switch the rs485 buffer direction
  DigitalOut dir_485 (PB_0);
  
/** ---------------------------------------------------------------------------
 * @brief  C O N S T R U C T O R
 */
dlms_comms::dlms_comms (void)
{
    // enable the rs485 buffer receiver, 
    dir_485 = 0;

    // clear the pointers and ring buffer
    rx_head_ptr = 0;
    rx_tail_ptr = 0;
    memset (rx_buffer, 0, sizeof(rx_buffer));

    // attach the receiver input to 'run' a method
    rs485.attach(NULL, Serial::TxIrq);
    
    // when characters received
    rs485.attach(this,
                 &dlms_comms::Rx_interrupt,
                 Serial::RxIrq);
    timer = NULL;
    debug_uart = NULL;
    tx_irq_count = 0l;
    rx_irq_count = 0l;
}
/** ---------------------------------------------------------------------------
 * @brief bq34z110::init_port_expander Put all ports in INPUT mode
 * @param parent_uart  The debug uart class pionter
 * @param parent_timer The main timer (system tick) class pointer
 */
void dlms_comms::init  (RawSerial * parent_uart,
                        Timer     * parent_timer)
{  
    // we use the parent timer and debug port
    debug_uart  = parent_uart; 
    timer       = parent_timer;
#ifdef DEBUG_DLMS_COMMS    
    debug_uart->printf ("   init_dlms_comms..\r\n");
#endif      
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::initialise This method initialise the rs485 comms port
 *
 * @param baudrate   The RS485 baudrate
 * @param bits       The RS485 number of bits (8)
 * @param parity     The parity required 'n','o', 'e'
 * @param stop_bits  The number of stop bits 1 or 2
 */
void dlms_comms::initialise (INT_16      baudrate,
                             UINT_8      bits,
                             UINT_8      parity,
                             UINT_8      stop_bits )
{
    // default comms parameters
    SerialBase::Parity my_parity = SerialBase::None;
    tx_irq_count = 0l;
    rx_irq_count = 0l;
    // set the class baudrate
    rs485.baud(baudrate);
    // enable the receiver on the tranceiver buffer chip
    dir_485 = 0;
    // clear the ring buffer
    rx_head_ptr = 0;
    rx_tail_ptr = 0;
    memset (rx_buffer, 0, sizeof(rx_buffer));
    // set the enum parity as required by class
    switch (parity)
    {
    default:
    case 'n' :
        my_parity = SerialBase::None;
        break;
    case 'o':
        my_parity = SerialBase::Odd;
        break;
    case 'e':
        my_parity = SerialBase::Even;
        break;
    }
    // lets doit .. config the rs485 now
    rs485.format((int) bits,
                 my_parity,
                 (int) stop_bits);
#  ifdef DEBUG_DLMS_COMMS    
     debug_uart->printf ("   init_dlms_comms..Baudrate (%d) %d bits Parity=%c\r\n",
                         baudrate, bits, parity);
     // dummy send packet to rs485 
     send_packet ("\r\nHello cruel world\r\n",12);       
#  endif                          
     if ( rs485.readable())
          rs485.getc();
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::get_dir485 . This method  returns the state of the rs485
 * direction
 */
bool dlms_comms::get_dir485 (void)
{
    return dir_485;
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::Tx_interrupt
 */
void dlms_comms::Tx_interrupt(void)
{
    tx_irq_count++;
    
    // disable the transmit interrupt
    rs485.attach(NULL, Serial::TxIrq);
    // enable the receiver, we are finito with the transmission of characters
    // this changes the direction on the transceiver buffer
    dir_485 = 0;       
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::Rx_interrupt . This method received all characters from
 * the uart and saved it in a ring buffer, 256 bytes long. The CPU only has 16
 * byte fifo, this makes it a 256 bit fifo
 */
void dlms_comms::Rx_interrupt(void)
{
    UINT_8 value;
    rx_irq_count++;
    // read the uart and place character in ring buffer
    value = rs485.getc();
    rx_buffer[rx_head_ptr++] = value;
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::available This method return if we have any characters
 * available (received on the rs485 port)
 * @return true if characters received on the rs485
 */
bool dlms_comms::char_available (void)
{
    if (rx_head_ptr == rx_tail_ptr)
        // nope
        return false;
    else
        return true;
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::get_char This method return the last character received
 * on the rs485 port
 * @return Unsigned byte
 * @note The user must make sure that characters are available before, else
 * this method shall wait for the next character, no timeouts yet
 */
UINT_8 dlms_comms::get_char (void)
{
    // we have to wait for at least on charcter in the ring buffer
    while (char_available() == false);
    // return the character
    return (rx_buffer[rx_tail_ptr++]);
}
/** ---------------------------------------------------------------------------
 * @brief dlms_comms::send_packet This method sends a serial packet data to the
 * rs485 bus, the directoion of the tranceiver chip is changed to transmit and
 * the packet is tranmitted.
 * @param ptr     Pointer address for start of the packet
 * @param length  Packet length
 * @note          The direction is changed with a interrupt when the transmitter
 *                is empty
 */
void dlms_comms::send_packet (const UINT_8  *ptr,
                              const UINT_8   length)
{
    // local stack variable
    UINT_8 * tmp_ptr =(UINT_8 *) ptr;

    // change the tranceiver direction to output
    dir_485 = 1;
    for (UINT_8 i= 0; i < length; i++)
    {
        rs485.putc (*tmp_ptr++);
    }
    rs485.attach(this,
                 &dlms_comms::Tx_interrupt,
                 Serial::TxIrq);
}
UINT_64 dlms_comms::ret_rx_irq_count (void)
{
    return rx_irq_count;
}
UINT_64 dlms_comms::ret_tx_irq_count (void)
{
    return tx_irq_count;
}
bool dlms_comms::ret_dir_485 (void)
{
    return dir_485;
}
//----[ the end ]--------------------------------------------------------------