STM8 single chip microcomputer serial port recognizes both custom protocol and Modbus protocol

   in the development of single chip microcomputer, serial port is the most commonly used channel to exchange data with the outside world. To use serial port, the essential is communication protocol. Communication protocol is the language of communication between single chip microcomputer and the outside world. In order to communicate normally with other equipment, the language must be connected first.

  in the actual development process, due to various reasons, the single chip microcomputer is incompatible with other external equipment protocols, which is troublesome when used. For example, the single chip microcomputer needs to communicate with two devices, but the communication protocols of the two devices are different. When in use, the single chip microcomputer must use two serial ports to communicate with two devices respectively. If the two devices are used at the same time without wasting resources, if only one device is connected at a time, the other serial port cannot be used as other functions and must be reserved for standby. In this way, the resources of MCU are wasted. So think about whether you can support two protocols on a serial port and let the single chip microcomputer automatically identify which protocol is used for the received data.

   generally, when using communication protocol to receive data, it is necessary to determine when to start receiving data and when to stop receiving data by judging the data header and data tail. However, if you want to be compatible with multiple protocols, you can't use this method to receive data. You must first receive a group of data, and then analyze which protocol the data uses according to the characteristics of the data.

  the first thing to realize is how to judge whether a group of data has been received.

   the general idea of implementation is that the single chip microcomputer uses interrupt to receive data, records the length of the received data, and circularly reads the character length received by the serial port in the main function. If the length of the data received in the serial port does not change after a certain time, it indicates that a group of data has been received.

   for example, if the current serial port data length is not 0 read in the main function, it means that the serial port is receiving data at this time. At this time, record the current serial port data length, and delay for a period of time to read the length of the serial port received data. If the data length at this time is the same as the last data length, it means that the serial port received data is over, You can process the received data. If the data length at this time is different from that of the last time, it means that the serial port is receiving data, and the received data has not ended, so you can't process the data.

  the following is an engineering case to demonstrate the use of a serial port compatible with two protocols.

   the device uses the user-defined protocol by default. The protocol format is as follows:
[header 1] (0xa5) [header 2] (0x5a) [address] [command] [data high bit] [data low bit] [tail 1] (0x55) [tail 2] (0xAA)

  later, the equipment needs to be connected with other industrial equipment in the market, and most industrial equipment uses Modbus protocol. If the equipment protocol is directly modified to Modbus protocol, many old equipment and new equipment will be incompatible. In order to be compatible with the old equipment, it also needs to be connected with other industrial equipment, Then the device shall be compatible with Modbus Protocol on the basis of user-defined protocol. Modbus protocol format is as follows:

[address] [function code] [start address high] [start address low] [total registers high] [total registers low] [CRC low] [CRC high]

  the characteristics of these two protocols are analyzed below.

  first look at the user-defined protocol. The data length of the user-defined protocol is fixed, and the beginning and end of the data are identified by two bytes. Then it's easy to identify the user-defined protocol. Just judge the header and data directly. When the serial port receives a group of data, judge whether the received data starts with 0xA5 and 0X5A and ends with 0x55 and 0xAA. If so, use a custom protocol to parse the data.

   next, analyze the Modbus protocol. Since the devices are used separately and do not need to be cascaded, the address of the device is fixed 0x01. At the same time, because the function of the device is relatively simple, only the two functions of read holding register (0x03) and write holding register (0x06) are used when using Modubus protocol, Therefore, there are only two cases 0x01 0x03 and 0x01 0x06 at the beginning of Modbus data. If these two cases are at the beginning of data, use Modbus protocol to analyze data.

  through the analysis of the protocol, the idea has been very clear. Next, use code to implement it.

struct uart_info
{
    u8 cnt;
    u8 rec_buf[10];
};

struct uart_info uart1;

//Change Printf formatter to Large in Library Options
//Redirect the putchar function to support the printf function
int putchar( int ch )
{
    while( !( UART1_SR & 0X80 ) ); //Cycle sending until sending is completed
    UART1_DR = ( u8 ) ch;
    return ch;
}
static void uart_io_init( void )
{
    PD_DDR |= ( 1 << 5 ); //Output mode TXD
    PD_CR1 |= ( 1 << 5 ); //Push pull output
    PD_DDR &= ~( 1 << 6 ); //Input mode RXD
    PD_CR1 &= ~( 1 << 6 ); //Floating input
}
//The baud rate can be set to 38400 at most
void uart_init( unsigned int baudrate )
{
    unsigned int baud;

    uart_io_init();

    baud = 16000000 / baudrate;
    UART1_CR1 = 0;
    UART1_CR2 = 0;
    UART1_CR3 = 0;
    UART1_BRR2 = ( unsigned char )( ( baud & 0xf000 ) >> 8 ) | ( ( unsigned char )( baud & 0x000f ) );
    UART1_BRR1 = ( ( unsigned char )( ( baud & 0x0ff0 ) >> 4 ) );
    UART1_CR2_bit.REN = 1;        //Receive enable
    UART1_CR2_bit.TEN = 1;        //Send enable
    UART1_CR2_bit.RIEN = 1;       //Receive interrupt enable
}


//Receive interrupt function interrupt number 18
#Pragma vector = 20 / / for the interrupt number in IAR, add 2 to the interrupt number in STVD
__interrupt void UART1_Handle( void )
{
    unsigned char res = 0;
    UART1_SR &= ~( 1 << 5 );                    //RXNE reset

    res = UART1_DR;
    if( uart1.cnt < 9 )
        uart1.rec_buf[uart1.cnt++] = res;
}

   define a structure to store the received data length and data. Since the longest data of user-defined protocol and Modbus protocol is only 8 bytes, the array length here can be set to 10. Next, initialize the IO port used by the serial port and set the data bit and baud rate. Finally, the data is received in the interrupt and stored in the array.

  next, I'm writing a function to analyze the received data.

//Detect serial port data
void read_uart( void )
{
    static u8 recevie_buf[10];
    static u8 recevie_cnt = 0;
    u8 i = 0;

    delay_ms( 10 ); //Check the serial port data length every other period of time. If the data length does not change, it indicates that the serial port has completed receiving data

    if( ( uart1.cnt != recevie_cnt ) && ( uart1.cnt > 6 ) )
    {
        recevie_cnt = uart1.cnt;
        for( i = 0; i < recevie_cnt; i++ )            //Copy data
        {
            recevie_buf[i] = uart1.rec_buf[i];
        }
        //Distinguish the protocol according to the received data
        //Custom protocol
        if( ( recevie_buf[0] == 0xA5 ) && ( recevie_buf[1] == 0x5A ) )
        {
            self_define_protocol( recevie_buf, recevie_cnt );
        }
        //modbus protocol 
        if( ( recevie_buf[0] == 0x01 ) && ( ( recevie_buf[1] == 0x03 ) || ( recevie_buf[1] == 0x06 ) ) )
        {
            modbus_protocol( recevie_buf, recevie_cnt );
        }

        //Empty array
        for( i = 0; i < 10; i++ )
        {
            uart1.rec_buf[i] = 0;
            recevie_buf[i] = 0;
        }
        uart1.cnt = recevie_cnt  = 0;
    }
}

   since the data length of the two protocols in this device is relatively small, there is no need to judge the data length twice, and then judge whether the data reception is completed according to the data length. Only judge the data length after a delay of 10ms. When the received data length is greater than 6, it indicates that the data has been basically received. Since the maximum length of the data is 8, when the received data length is greater than 6, the basic data has been received, and the speed of receiving 8 data in the interrupt is still very fast. If the amount of data is large and the data is long, it is better to judge by the data length. Here, for the convenience of writing, it is simple to judge the time delay.

   when the data length received by the serial port is greater than 6, it indicates that a group of data has been received. At this time, it is necessary to copy the data received by the serial port for use. Why copy the data instead of directly using the serial port to receive the data in the buffer? This is to prevent the serial port from receiving new data in the process of data processing, so that the new data will overwrite the old data, which may lead to data exceptions. For data security, copy the data for use. Even if the data in the serial port buffer changes during data processing, the last received data will not be destroyed.

   after the data copy, judge which protocol is used for the currently received data according to the characteristics of the data. If the first two received data are 0xA5 and 0x5A, the user-defined protocol processing function will be called directly. If the previous data are 0x01 and 0x03 or 0x01 and 0x06, the Modbus protocol processing function will be called directly.

  next, you can write two separate functions to deal with this protocol respectively.

//Process custom protocols
// [header 1](0xA5) [header 2](0x5A) [address] [command] [data high order] [data low order] [tail 1](0x55) [tail 2](0xAA)
//A5 5A 00 01 01 90 55 AA
void self_define_protocol( u8 arr[], u8 size )
{

    if( ( arr[0] == 0xA5 ) && ( arr[1] == 0x5A ) && ( arr[6] == 0x55 ) && ( arr[7] == 0xAA ) )
    {
		//Perform different actions according to the command value
    }
}

//Handling modbus Protocol
//[address] [function code] [start address high] [start address low] [total number of registers high] [total number of registers low] [CRC low] [CRC high]
//01 03 00 00 00 01 84 0A
//01 06 00 00 03 E8 89 74
void modbus_protocol( u8 arr[], u8 size )
{
    //Call modbus related processing code
}

  after identifying the data protocol, you can process the data in the usual way of processing the protocol. Finally, call the data query function circularly in the main function to check whether the serial port receives data.

void main( void )
{
   
    __asm( "sim" );                             //Prohibit interrupt
    SysClkInit();
    delay_init( 16 );
    LED_GPIO_Init();
    uart_init( 9600 );
    __asm( "rim" );                             //Open interrupt
    while( 1 )
    {
        read_uart();
    }
}

  next, test the code with these two protocols respectively.

  when using the serial port assistant to send a custom protocol, the code will call the custom protocol processing function.

   when sending the Modbus protocol, the code will call the Modbus protocol processing function.

  in this way, according to the characteristics of the protocol, the type of the protocol can be automatically identified through the code, and the analysis of different protocols can be realized with one serial port. More protocols can be parsed in the same way. At present, the baud rate used in different protocols should be the same, otherwise the data parsed with different baud rates will be wrong, resulting in protocol parsing failure.

Tags: Single-Chip Microcomputer

Posted on Fri, 03 Dec 2021 17:07:50 -0500 by oneofayykind