MCU serial communication principle and control program

Publisher:breakthrough2Latest update time:2017-11-16 Source: eefocusKeywords:MCU Reading articles on mobile phones Scan QR code
Read articles on your mobile phone anytime, anywhere

When we learned serial communication, we focused on the operation process of the serial port's underlying timing, so the routines were all simple sending and receiving of characters or strings. In actual applications, the serial port often needs to interact with the host computer software on the computer to realize the functions of the computer software sending different instructions and the microcontroller correspondingly executing different operations. This requires us to organize a more reasonable communication mechanism and logical relationship to achieve the desired results.

The function of the program provided in this section is to send three different commands through the computer serial port debugging assistant. The first command: buzz on can make the buzzer sound; the second command: buzz off can make the buzzer silent; the third command: showstr, after the space of this command, you can add any string to make the string behind it displayed on the 1602 LCD. At the same time, no matter what command is sent, the microcontroller will send the command intact to the computer through the serial port after receiving it, to indicate "I received it...you can check if it is correct". Doesn't this feel more like a small project?

For the serial communication part, it is easy for the MCU to send a string to the computer. We can send as many bytes as the array size. But how many bytes should the MCU receive to form a complete frame of data? Where is the start and end of the data receiving? We have no way of knowing these before receiving the data. So what should we do?

Our programming idea is based on such a common fact: when a frame (multiple bytes) of data needs to be sent, these data are sent continuously, that is, after sending one byte, the next byte will be sent immediately, with no interval or a very short interval. When this frame of data is sent, there will be a long interval (relative to the interval when sending continuously) when no data is sent, that is, the communication bus will be idle for a long time. So we established such a program mechanism: set a software bus idle timer, this timer is cleared when there is data transmission (from the perspective of the microcontroller receiving, it is when data is received), and accumulates when the bus is idle (that is, when no data is received). When it accumulates to a certain time (30 ms in the routine), we can determine that a complete frame of data has been transmitted, so tell other programs that they can process the data. After the data is processed, it will be restored to the initial state and prepare for the next reception. So how much idle time is appropriate for determining the end of a frame? It depends on multiple conditions and there is no fixed value. Here we introduce several principles that need to be considered: First, this time must be greater than the baud rate cycle. Obviously, our microcontroller reception interrupt is generated after a byte is received, that is, a moment, and our program has no way of knowing its reception process. Therefore, you must not think that the idle time has been reached within at least one baud rate cycle. Second, the sender's system delay must be considered, because not all senders can send data strictly without intervals, because software response, interrupt shutdown, system critical area and other operations will cause delays, so you have to add a few to ten ms. The 30 ms we selected is a compromise experience value, which can adapt to most baud rates (greater than 1200) and most system delays (PC or other microcontroller systems).

I will first post the program in the most important UART.c file of this program and analyze it for you bit by bit. This is a common usage in actual project development, and everyone must understand it carefully.

/*********************************Uart.c file program source code********************************/
#include 

bit flagFrame = 0; //Frame reception completion flag, i.e. a new frame of data is received
bit flagTxd = 0; // Single-byte transmission completion flag, used to replace the TXD interrupt flag
unsigned char cntRxd = 0; //Receive byte counter
unsigned char pdata bufRxd[64]; //Receive byte buffer

extern void UartAction(unsigned char *buf, unsigned char len);

/* Serial port configuration function, baud-communication baud rate */
void ConfigUART(unsigned int baud){
    SCON = 0x50; //Configure the serial port to mode 1
    TMOD &= 0x0F; // Clear the control bit of T1
    TMOD |= 0x20; //Configure T1 to mode 2
    TH1 = 256 - (11059200/12/32)/baud; //Calculate T1 reload value
    TL1 = TH1; //initial value equals reload value
    ET1 = 0; //Disable T1 interrupt
    ES = 1; // Enable serial port interrupt
    TR1 = 1; //Start T1
}
/* Serial port data writing, i.e. serial port sending function, buf-pointer of data to be sent, len-specified sending length */
void UartWrite(unsigned char *buf, unsigned char len){
    while (len--){ //Loop to send all bytes
        flagTxd = 0; // clear the send flag
        SBUF = *buf++; //Send one byte of data
        while (!flagTxd); //Wait for the byte to be sent
    }
}
/* Serial port data reading function, buf-receiving pointer, len-specified reading length, return value-actual read length */
unsigned char UartRead(unsigned char *buf, unsigned char len){
    unsigned char i;
    //When the specified read length is greater than the actual received data length,
    //The read length is set to the actual received data length
    if (len > cntRxd){
        len = cntRxd;
    }
    for (i=0; i 0){ //When the receive counter is greater than zero, monitor the bus idle time
        if (cntbkp != cntRxd){ //Receive counter changes, that is, when data is just received, clear the idle timer
            cntbkp = cntRxd;
            idletmr = 0;
        }else{ //The receive counter has not changed, that is, when the bus is idle, the accumulated idle time
            if (idletmr < 30){ //When the idle timer is less than 30ms, continue to accumulate
                idletmr += ms;
                if (idletmr >= 30){ //When the idle time reaches 30ms, it is determined that a frame has been received
                    flagFrame = 1; //Set the frame reception completion flag
                }
            }
        }
    }else{
        cntbkp = 0;
    }
}
/* Serial port driver function, monitoring the reception of data frames, scheduling function, needs to be called in the main loop */
void UartDriver(){
    unsigned char len;
    unsigned char pdata buf[40];

    if (flagFrame){ //When a command arrives, read and process the command
        flagFrame = 0;
        len = UartRead(buf, sizeof(buf)); //Read the received command into the buffer
        UartAction(buf, len); //Transfer data frame and call action execution function
    }
}
/* Serial port interrupt service function */
void InterruptUART() interrupt 4{
    if (RI){ //Receive new byte
        RI = 0; // Clear the receive interrupt flag
        //When the receive buffer is not exhausted, save the received bytes and increment the counter
        if (cntRxd < sizeof(bufRxd)){{
            bufRxd[cntRxd++] = SBUF;
        }
    }
    if (TI){ //Bytes sent
        TI = 0; // Clear the transmit interrupt flag
        flagTxd = 1; //Set the byte sending completion flag
    }
}

You can analyze the Uart.c file by referring to the comments and the previous explanation. Here are two key points that I hope you will pay more attention to.

1. Processing of received data. In the serial port interrupt, all received bytes are stored in the buffer bufRxd. At the same time, another timer interrupt is used to call UartRxMonitor at intervals to monitor whether a frame of data has been received. The principle of judgment is the idle time we introduced earlier. When a frame of data is judged to be completed, the flagFrame flag is set. The main loop can detect the flag by calling UartDriver and process the received data. When processing the received data, first read the data in the receiving buffer bufRxd through the serial port reading function UartRead, and then judge and process the read data. Maybe you will say, since the data has been received in bufRxd, why can't I just use it here, why do I have to copy it to another place? We designed this double buffer mechanism mainly to improve the efficiency of receiving and responding to the serial port: first, if you process data in bufRxd, you can no longer receive any data at this time, because the newly received data will destroy the original data, causing it to be incomplete and chaotic; second, this processing process may take a long time, for example, the host computer now sends you a delayed display command, then during this delay you cannot receive new commands, and the host computer sees that you have temporarily lost response. The use of this double buffer mechanism can greatly improve this problem, because the time required for data copying is quite short, and as long as the data is copied out, bufRxd can immediately prepare to receive new data.

2. The serial port data writing function UartWrite sends the data block pointed to by the data pointer buf continuously from the serial port. Although our serial port program has enabled interrupts, the sending function here is not completed in the interrupt, but still relies on querying the sending interrupt flag flagTxd (because TI must be cleared in the interrupt function, otherwise the interrupt will enter the execution repeatedly, so another flagTxd is set to replace TI). Of course, you can also copy the sending data to a buffer first, and then send the buffer data in the interrupt, but this will consume extra memory and make the program more complicated. Here I still want to tell you that problems that can be solved by simple methods should not be made more complicated.

/********************************main.c file program source code******************************/
#include 
sbit BUZZ = P1^6; //Buzzer control pin
bit flagBuzzOn = 0; //Buzzer start flag
unsigned char T0RH = 0; //T0 high byte of reload value
unsigned char T0RL = 0; //Low byte of T0 reload value

void ConfigTimer0(unsigned int ms);
extern void UartDriver();
extern void ConfigUART(unsigned int baud);
extern void UartRxMonitor(unsigned char ms);
extern void UartWrite(unsigned char *buf, unsigned char len);
extern void InitLcd1602();
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
extern void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len);

void main(){
    EA = 1; // Enable general interrupt
    ConfigTimer0(1); //Configure T0 timing 1ms
    ConfigUART(9600); //Configure the baud rate to 9600
    InitLcd1602(); //Initialize LCD

    while (1){
        UartDriver(); //Call the serial port driver
    }
}
/* Memory comparison function, compares whether the memory data pointed to by two pointers are the same.
ptr1-pointer 1 to be compared, ptr2-pointer 2 to be compared, len-length to be compared
Return value - if the two memory segments have the same data, it returns 1, if they are different, it returns 0 */
bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len){
    while (len--){
        if (*ptr1++ != *ptr2++){ //Return 0 immediately when encountering unequal data
            return 0;
        }
    }
    return 1; //If all lengths are equal, return 1
}
/* Serial port action function, executes the response action according to the received command frame
buf-received command frame pointer, len-command frame length */
void UartAction(unsigned char *buf, unsigned char len){
    unsigned char i;
    unsigned char code cmd0[] = "buzz on"; //Buzzer on command
    unsigned char code cmd1[] = "buzz off"; //Buzzer off command
    unsigned char code cmd2[] = "showstr "; // string display command
    unsigned char code cmdLen[] = { //Command length summary table
        sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,
    };

    unsigned char code *cmdPtr[] = { //Command pointer summary table
        &cmd0[0], &cmd1[0], &cmd2[0],
    };

    for (i=0; i= cmdLen[i]){ //First, the length of the received data must be no less than the command length
            if (CmpMemory(buf, cmdPtr[i], cmdLen[i])){ // Exit the loop when the comparison is the same
                break;
            }
        }
    }
    switch (i){ //When the loop exits, the value of i is the index value of the current command
        case 0:
            flagBuzzOn = 1; //Turn on the buzzer
            break;
        case 1:
            flagBuzzOn = 0; //Turn off the buzzer
            break;
        case 2:
            buf[len] = '\0'; //Add a terminator to the received string
            LcdShowStr(0, 0, buf+cmdLen[2]); //Show the string after the command
            i = len - cmdLen[2]; //Calculate the number of valid characters
            if (i < 16){ //When the valid characters are less than 16, clear the subsequent characters on the LCD
                LcdAreaClear(i, 0, 16-i);
            }
            break;
        default: //When no matching command is found, a "wrong command" prompt is sent to the host
            UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
            return;
    }
    buf[len++] = '\r'; //After the effective command is executed, add it after the original command frame
    buf[len++] = '\n'; //The carriage return and line feed characters are returned to the host computer, indicating that the execution has been completed.
    UartWrite(buf, len);
}
/* Configure and start T0, ms-T0 timing time */
void ConfigTimer0(unsigned int ms){
    unsigned long tmp; //temporary variable
    tmp = 11059200 / 12; //Timer counting frequency
    tmp = (tmp * ms) / 1000; //Calculate the required count value
    tmp = 65536 - tmp; //Calculate the timer reload value
    tmp = tmp + 33; //Compensate for the error caused by interrupt response delay
    T0RH = (unsigned char)(tmp>>8); //Timer reload value is split into high and low bytes
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0; // Clear the control bit of T0
    TMOD |= 0x01; //Configure T0 to mode 1
    TH0 = T0RH; //Load T0 reload value
    TL0 = T0RL;
    ET0 = 1; // Enable T0 interrupt
    TR0 = 1; //Start T0
}
/* T0 interrupt service function, execute serial port receiving monitoring and buzzer driving */
void InterruptTimer0() interrupt 1{
    TH0 = T0RH; //Reload the reload value
    TL0 = T0RL;
    if (flagBuzzOn){ //Execute the buzzer to sound or turn it off
        BUZZ = ~BUZZ;
    }else{
        BUZZ = 1;
    }
    UartRxMonitor(1); //Serial port receiving monitoring
}

We have already done a lot of work on the structure of the main function and the main loop, so we will not go into details. Here we will focus on analyzing the specific parsing method of receiving data through the serial port. This usage is very universal. Mastering and applying it flexibly can make your future development work more efficient.

First, let's look at the CmpMemory function. This function is very simple. It compares two memory data, usually data in an array. The function receives pointers to two data segments and then compares them byte by byte - if (ptr1++ != ptr2++). This line of code not only compares the data pointed to by the two pointers, but also adds 1 to both pointers after the comparison. From this, we can also appreciate the simplicity and efficiency of the C language. The purpose of this function is to compare the received data with the command string placed in the program in advance to find the matching command.

Next is the analysis and processing method of the received data by the UartAction function. First, the received data is compared with the supported command strings one by one. In this comparison, we must first ensure that the received length is greater than the length of the command string. Then, we use the above CmpMemory function to compare byte by byte. If the comparison is the same, we will exit the loop immediately. If they are different, we will continue to compare the next command. When a matching command string is found, the final value of i is the index position of the command in its list. When no matching command is found after traversing the command list, the final value of i will be equal to the total number of commands. Then, we use the switch statement to perform specific actions according to the value of i. This does not need to be explained in detail.

/***************************Lcd1602.c file program source code********************************/
#include 
#define LCD1602_DB P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E = P1^5;

/* Wait for LCD to be ready */
void LcdWaitReady(){
    unsigned char sta;
    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do {
        LCD1602_E = 1;
        sta = LCD1602_DB; //Read status word
        LCD1602_E = 0;
    } while (sta & 0x80); //bit7 is equal to 1, indicating that the LCD is busy. Repeat the test until it is equal to 0.
}
/* Write a one-byte command to the LCD1602, cmd-the command value to be written */
void LcdWriteCmd(unsigned char cmd){
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* Write one byte of data to LCD1602, dat-data value to be written */
void LcdWriteDat(unsigned char dat){
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E = 1;
    LCD1602_E = 0;
}
/* Set the display RAM start address, that is, the cursor position, (x, y) - corresponding to the character coordinates on the screen */
void LcdSetCursor(unsigned char x, unsigned char y){
    unsigned char addr;
    if (y == 0){ //Calculate the display RAM address based on the input screen coordinates
        addr = 0x00 + x; //The first line of characters starts at 0x00
    }else{
        addr = 0x40 + x; //The second line of characters starts at 0x40
    }
    LcdWriteCmd(addr | 0x80); //Set RAM address
}
/* Display the string on the LCD, (x,y)-corresponds to the starting coordinates on the screen, str-string pointer */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str){
    LcdSetCursor(x, y); //Set the starting address
    while (*str != '\0'){ //Continuously write string data until the end character is detected
        LcdWriteDat(*str++);
    }
}
/* Clear the area, clear len characters starting from the (x,y) coordinate */
void LcdAreaClear(unsigned char x, unsigned char y, unsigned char len){
    LcdSetCursor(x, y); //Set the starting address
    while (len--){ //Continuously write spaces
        LcdWriteDat(' ');
    }
}
/* Initialize 1602 LCD */
void InitLcd1602(){
    LcdWriteCmd(0x38); //16*2 display, 5*7 dot matrix, 8-bit data interface
    LcdWriteCmd(0x0C); //Display on, cursor off
    LcdWriteCmd(0x06); //The text remains unchanged, the address is automatically increased by 1
    LcdWriteCmd(0x01); //Clear screen
}

The LCD file is basically the same as the LCD file of the previous example. The only difference is that a full-screen clearing function that is not used in this example has been deleted. In fact, it doesn’t matter if this function is kept. Keil will just prompt a warning to tell you that there is an uncalled function. You can ignore it.

After practicing these multi-file projects, have you discovered that after adopting multi-file modular programming, not only certain functions, but even the entire C file, if necessary, we can directly copy it to other new projects for use, which is very convenient for porting functional programs. In this way, as you accumulate more practice, you will find that your work efficiency becomes higher and higher.


Keywords:MCU Reference address:MCU serial communication principle and control program

Previous article:Programming of Single Chip Calculator
Next article:MCU I2C addressing mode

Recommended ReadingLatest update time:2024-11-16 13:38

Small resistance tester based on single chip microcomputer design
1. Introduction In the process of circuit testing, it is often encountered that there is a large error between the experimental data and the theoretical value due to the neglect of the influence of some small resistances, thus affecting the test effect. For example, copper resistance often exists in inducto
[Test Measurement]
Small resistance tester based on single chip microcomputer design
Design of MPPT controller for photovoltaic cells using microcontroller C8051F310
1 Introduction Energy is an important material basis for the existence and development of human society. With the development of society, energy is decreasing day by day, and as environmental problems become increasingly prominent, more and more countries are turning their attention to renewable energy. As one of the
[Microcontroller]
Design of MPPT controller for photovoltaic cells using microcontroller C8051F310
Design of fat scale based on 4-bit single chip microcomputer
   Fat scales, also known as health scales, can measure the proportion of fat and water in the human body at different times, thereby reflecting the health status of the human body at different times. Since the human body and the test electrode need to be in direct contact when testing the human body resistance, the f
[Microcontroller]
Design of fat scale based on 4-bit single chip microcomputer
Principle of 51 MCU decoding infrared remote control
The TV remote control uses a dedicated integrated transmitter chip to transmit the remote control code, such as Toshiba TC9012, Philips SAA3010T, etc. Usually, the transmission of the color TV remote control signal is to modulate the control command and system code (a sequence composed of 0 and 1) corresponding to a ce
[Microcontroller]
AT89C51 single-chip microcomputer water lamp C language program and detailed explanation (literacy tutorial)
AT89C51 microcontroller is a microcontroller that we must learn when we study microcontrollers. It is also a textbook for microcontrollers. The following is a method for writing a running light that is suitable for beginners. First, draw a simulation diagram in proteus to facilitate the simulation program Let's wr
[Microcontroller]
AT89C51 single-chip microcomputer water lamp C language program and detailed explanation (literacy tutorial)
Design solution based on single chip microcomputer and PCI interface
8-bit microcontrollers are widely used in embedded systems, but it has inherent defects to let them deal directly with PCI bus devices. 8-bit microcontrollers only have 16-bit address lines and 8-bit data ports. In the PCI bus 2.0 specification, in addition to 32-bit address data multiplexing AD , there are also imp
[Microcontroller]
Design solution based on single chip microcomputer and PCI interface
Design of automobile lighting system based on single chip microcomputer and LED
LED lighting has many advantages over traditional halogen low-voltage lighting: (1) The light source is relatively concentrated, and the brightness obtained by 1 W lighting is equivalent to the brightness of a 10-watt halogen lamp, so it is very energy-saving; (2) The life of LED lamps is longer than that of halogen
[Microcontroller]
Design of automobile lighting system based on single chip microcomputer and LED
Single chip microcomputer timer T0 counts seconds C51 program + circuit
Schematic diagram: The c51 microcontroller program is as follows: #include reg51.h #define UCHAR unsigned char #define UINT  unsigned int   UCHAR table = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; FLY counter; FLY timer;   void inittime(void) { timer
[Microcontroller]
Single chip microcomputer timer T0 counts seconds C51 program + circuit
Latest Microcontroller Articles
  • Download from the Internet--ARM Getting Started Notes
    A brief introduction: From today on, the ARM notebook of the rookie is open, and it can be regarded as a place to store these notes. Why publish it? Maybe you are interested in it. In fact, the reason for these notes is ...
  • Learn ARM development(22)
    Turning off and on interrupts Interrupts are an efficient dialogue mechanism, but sometimes you don't want to interrupt the program while it is running. For example, when you are printing something, the program suddenly interrupts and another ...
  • Learn ARM development(21)
    First, declare the task pointer, because it will be used later. Task pointer volatile TASK_TCB* volatile g_pCurrentTask = NULL;volatile TASK_TCB* vol ...
  • Learn ARM development(20)
    With the previous Tick interrupt, the basic task switching conditions are ready. However, this "easterly" is also difficult to understand. Only through continuous practice can we understand it. ...
  • Learn ARM development(19)
    After many days of hard work, I finally got the interrupt working. But in order to allow RTOS to use timer interrupts, what kind of interrupts can be implemented in S3C44B0? There are two methods in S3C44B0. ...
  • Learn ARM development(14)
  • Learn ARM development(15)
  • Learn ARM development(16)
  • Learn ARM development(17)
Change More Related Popular Components

EEWorld
subscription
account

EEWorld
service
account

Automotive
development
circle

About Us Customer Service Contact Information Datasheet Sitemap LatestNews


Room 1530, 15th Floor, Building B, No.18 Zhongguancun Street, Haidian District, Beijing, Postal Code: 100190 China Telephone: 008610 8235 0740

Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved 京ICP证060456号 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号