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********************************/ #includebit 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******************************/ #includesbit 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.
Previous article:Programming of Single Chip Calculator
Next article:MCU I2C addressing mode
Recommended ReadingLatest update time:2024-11-16 13:38
- Popular Resources
- Popular amplifiers
- Wireless Sensor Network Technology and Applications (Edited by Mou Si, Yin Hong, and Su Xing)
- Modern Electronic Technology Training Course (Edited by Yao Youfeng)
- Modern arc welding power supply and its control
- Small AC Servo Motor Control Circuit Design (by Masaru Ishijima; translated by Xue Liang and Zhu Jianjun, by Masaru Ishijima, Xue Liang, and Zhu Jianjun)
Professor at Beihang University, dedicated to promoting microcontrollers and embedded systems for over 20 years.
- Innolux's intelligent steer-by-wire solution makes cars smarter and safer
- 8051 MCU - Parity Check
- How to efficiently balance the sensitivity of tactile sensing interfaces
- What should I do if the servo motor shakes? What causes the servo motor to shake quickly?
- 【Brushless Motor】Analysis of three-phase BLDC motor and sharing of two popular development boards
- Midea Industrial Technology's subsidiaries Clou Electronics and Hekang New Energy jointly appeared at the Munich Battery Energy Storage Exhibition and Solar Energy Exhibition
- Guoxin Sichen | Application of ferroelectric memory PB85RS2MC in power battery management, with a capacity of 2M
- Analysis of common faults of frequency converter
- In a head-on competition with Qualcomm, what kind of cockpit products has Intel come up with?
- Dalian Rongke's all-vanadium liquid flow battery energy storage equipment industrialization project has entered the sprint stage before production
- Allegro MicroSystems Introduces Advanced Magnetic and Inductive Position Sensing Solutions at Electronica 2024
- Car key in the left hand, liveness detection radar in the right hand, UWB is imperative for cars!
- After a decade of rapid development, domestic CIS has entered the market
- Aegis Dagger Battery + Thor EM-i Super Hybrid, Geely New Energy has thrown out two "king bombs"
- A brief discussion on functional safety - fault, error, and failure
- In the smart car 2.0 cycle, these core industry chains are facing major opportunities!
- The United States and Japan are developing new batteries. CATL faces challenges? How should China's new energy battery industry respond?
- Murata launches high-precision 6-axis inertial sensor for automobiles
- Ford patents pre-charge alarm to help save costs and respond to emergencies
- New real-time microcontroller system from Texas Instruments enables smarter processing in automotive and industrial applications
- Problems with dsp2812 downloading using C2Prog
- EEWORLD University Hall----Computer Vision (Lu Peng, Beijing University of Posts and Telecommunications)
- Why do the 2D lines in the DXF exported from PADS become very thick after opening it in CAD?
- New ways to play with VL53Lxx time-of-flight sensors
- ISM330DHCX finite state machine learning materials
- Huawei’s boss accepted another visit today. Is 5G ready for sale?
- MSP430F249 digital tube display
- [Atria AT32WB415 Series Bluetooth BLE 5.0 MCU] Part 1: Environment Construction and Burning Method
- Quickly determine what functional circuit the operational amplifier is
- Several filtering methods for single chip microcomputer to resist interference by software