Search code examples
cserial-portembeddedmcu

How to transfer a string through serial communication and extract the number from the specified position to fill in the variable in C?


I am new in learning how to program on MCU in C. I am programing a MCU countdown timer. I hope it could receive time data from PC, my C# application sends data in string "Hou-min-sec", the hou,min,sec are variables. PC sends "Hou-min-sec" to the MCU, but it couldn't show data correctly, the dynamic digital tube always show 00-00-00. What should I do? I had asked Bing but still couldn't find the answer. Here is my code below. My English is not very good, thank you for your reading and help.

#include "reg52.h"
#include "string.h"
#include "stdlib.h"
#define uchar unsigned char
typedef unsigned int u16;     
typedef unsigned char u8;

sbit LSA=P2^2;
sbit LSB=P2^3;
sbit LSC=P2^4;
char rec_data[12] = "\0";
char str[12] = "\0";

u8 u[3];
u8 code smgduan[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
                    0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};

u8 sec,min,hou;
u8 DisplayData[8];
u8 count = 0;

void delay(u16 i)
{
    
    while(i--); 
}


void Timer0Init()
{
    TMOD|=0X01;
    TH0=(65536-50000)/256;  
    TL0=(65536-50000)%256;
    ET0=1;
    EA=1;
    TF0 = 1;
    TR0=1;  
}
void Timer0(void) interrupt 1
{   
    TH0=(65536-50000)/256;
    TL0=(65536-50000)%256;
    count++;
    if(count == 20)
    {
        count = 0;
        if (hou >= 0 || min >= 0 && sec != 0)
        {
                if(sec > 0)
                {
                    sec = sec - 1;
                }
                if (min != 0 && sec == 0)
                {
                    min = min - 1;
                    sec = 59;
                }
                if (hou != 0 && min == 0 && sec == 0)
                {
                    hou = hou - 1;
                    min = 59;
                    sec = 59;
                }
        }
        if (hou == 0 && min == 0 && sec == 0)
        {
            hou = 0;
        min = 0;
        sec = 0; 
        }
    
}

void DigDisplay()
{
    u8 i;
    for(i=0;i<8;i++)
    {
        switch(i)
        {
            case(0):
                LSA=1;LSB=1;LSC=1; break;
            case(1):
                LSA=0;LSB=1;LSC=1; break;
            case(2):
                LSA=1;LSB=0;LSC=1; break;
            case(3):
                LSA=0;LSB=0;LSC=1; break;
            case(4):
                LSA=1;LSB=1;LSC=0; break;
            case(5):
                LSA=0;LSB=1;LSC=0; break;
            case(6):
                LSA=1;LSB=0;LSC=0; break;
            case(7):
                LSA=0;LSB=0;LSC=0; break;   
        }
        P0=DisplayData[i];
        delay(100); 
        P0=0x00;
    }
}

void datapros()
{
    DisplayData[0]=smgduan[hou/10];
    DisplayData[1]=smgduan[hou%10];
    DisplayData[2]=0x40;    
    DisplayData[3]=smgduan[min/10];
    DisplayData[4]=smgduan[min%10];
    DisplayData[5]=0x40;
    DisplayData[6]=smgduan[sec/10];
    DisplayData[7]=smgduan[sec%10];
}

void UartInit(u8 baud)      
{
    TMOD|=0X20; 
    SCON=0X50;  
    PCON=0X80;  
    TH1=baud;   
    TL1=baud;
    TR1 = 1;        
    EA=1;
    ES=1;   
}

void uart() interrupt 4 
{   
    if(RI == 1)
    {
        rec_data[12]=SBUF;
        RI = 0;         
    }
    RI = 0;         
    SBUF=rec_data[12];  
    while(!TI);     
    TI=0;           
}

void Select()
{
        const char del[2] = "-"; 
        char *p; 

        p = strtok(rec_data, del); 
    hou = atoi(p); 
        p = strtok(NULL, del); 
        min = atoi(p);
        p = strtok(NULL, del); 
        sec = atoi(p); 
}

void main()
{   
    UartInit(0XFA);
    Timer0Init();
    Select();
    hou = u[0];
    min = u[1];
    sec = u[2];
                
        
    while(1)
    {
        datapros();
        DigDisplay();
    }
    
        
}

I tried strtok but it seems didn't work. I hope when MCU recieve data could transform string "hou-min-sec" to int data hour = hou, minute = min, second = sec. Like this:enter image description here


Solution

  • First of all nothing ever writes any data to rec_data. You only ever write to rec_data[12] which is out of bounds.

    Secondly your interrupt routine appears to be echoing back the received data, with a busy-loop polling TI - you should never put an indefinite loop in an ISR, and the ISR should certainly not busy-wait for the length of time taken to clock out UART data.

    You also need some sort of delimiter so that ther receiver can know wherer the time data starts and ends. So if say you have a format:

    hh-mm-ss<lf>
    

    i.e. with a line-feed as a delimiter, then something like:

    void uart() interrupt 4 
    {
        static int index = 0 ;
        static char rec_buffer[sizeof(rec_data)] ;
    
        if(RI == 1)
        {
            char ch = SBUF ;
            if( SBUF = '\n' )
            {
                memcpy( rec_data, rec_buffer, index ) ;
                rec_data[index] = 0 ;
                index = 0 ;
            }
            else if( index < sizeof(rec_data) )
            {
                rec_data[index] = ch ;
            }
    
            RI = 0;         
        }
    }
    

    Secondly you read rec_data in Select() on start-up without ever actually waiting for the data to arrive - given the above uart() then:

    void Select()
    {
        bool time_valid = false ;
        while( !time_valid )
        {
            // wait for received string
            while( rec_data[0] == '\0' )
            {
            }
            
            unsigned hh, mm, ss ;
            time_valid = sscanf( rec_data, "%02u-%02u-%02u", &hh, &mm, &ss ) == 3 ;
            if( time_valid )
            {
                 hou = (u8)hh ;
                 min = (u8)mm ;
                 sec = (u8)ss ;
            }
            else
            {
                // Wait for a new string
                rec_data[0] = '\0'
            }
        }
    }
    

    If for some reason you still need to echo back the input I suggest that you do it outside of the interrupt context for example:

    void uart_send_string( char* str )
    {
        int index = 0 ;
    
        TI = 0 ;
        while( str[index] != '\0' )
        {
            SBUF = str[index] ;  
            while(!TI) { } ;     
            TI = 0 ;
        }    
    }
    

    Then in Select():

        while( !time_valid )
        {
            ...
    
            uart_send_string( rec_data ) ;
        }
    

    You really should avoid all those unnecessary globals - see https://www.embedded.com/a-pox-on-globals/, and data shared with an interrupt context should be volatile. So:

    char rec_data[12] = "" ;
    

    Note the \0 is not needed in the initialiser, that is implicit in "". In fact zero initialisation for static data is provided, so the initialiser is unnecessary in any case - but I would not discourage it.

    I would be surprised if these are the only issues, and you should be aware that all the above is untested since I don't have your platform. Regard it as illustrative. The take home here is that:

    • You were not reading the data into rec_data
    • You were not waiting for the data to arrive before attempting to process it.

    Also be aware that the above solution is not necessarily the manner in which I would recommend implementing this, that would require starting again. This is rather a fix to your existing design.