Serial port in the product application is very common, but the default MCU with serial port is often less, sometimes there will be insufficient serial port, so I think can use ordinary IO port analog serial port to achieve the function of serial port.
To simulate a serial port, we must first understand the principle of serial port data transmission.
Commonly used serial port formats are 1 start bit, 8 data bit, no parity bit, and 1 end bit. The start bit is low and the end bit is high. Data 0 is low level, and data 1 is high level.
So the simplest serial port transmits a byte with a total of 10 level variations, the width of each level determined by baud rate.
For detailed analysis of serial port data, please refer to this article: STM8 Learning Notes – Analyzing serial port data by oscilloscope.
Below is a look at how the level width of each bit is calculated by baud rate.
Send one byte, using the calculation of 9600bit/s baud rate in STM8 as an example (9600 bits per second). It can be calculated that the time required to transmit 1 bit T1 = 1/9600 is about 104us.
By calculation, if baud rate is 9600, the level width of a bit should be 104us.
The figure above shows the complete waveform of one byte. The start bit is low, the end bit is high, the middle 8 bits are data bits, and there is no check bit. Data bits are low before high after. So the starting bit is the lowest bit, and the ending bit is the highest bit. From this waveform you can analyze that the data sent is 0x00.
Let’s look at the waveform of 0X01.
The start bit is low level, and the end bit is high level. There is one high level in the middle data bit, and the rest are low levels. According to the rule that the low level comes first and the high level comes later, the data bit is 0000 0001 is exactly 0x01 in hexadecimal.
Know the format of serial data, the following start to write code, first write serial send code.
Void Send_1(void) {SIM_TXD = 1; } // output 0 void Send_0(void) {SIM_TXD = 0; } // Send a byte void WriteByte(unsigned char sdata) // Baud rate 9600 {// If the bit error rate is high, you can change the delay_us delay time unsigned char I; unsigned char value = 0; // Send the starting bit Send_0(); delay_us( 100 ); For (I = 0; i < 8; i++ ) { value = ( sdata & 0x01 ); If (value) {Send_1(); } else { Send_0(); } delay_us( 100 ); Sdata = sdata >> 1; sdata = sdata >> 1; } // Stop bit Send_1(); delay_us( 100 ); }Copy the code
First send the start bit, the IO port voltage level low, delay 104us, down to send 8 data bits, low in the front, high in the back, each send a delay 104us. Finally send end bit, set IO port level high, in delay 104us. So a byte is sent.
Because the delay program is realized by software, the delay accuracy is not very high. When the delay parameter is set to 100, it is just 104US to see one by oscilloscope. Because of different compilers, different microcontroller, the delay time calculated by software is different, so the delay time here is best measured by oscilloscope, the accuracy of the delay time determines the bit error rate of communication. Delay time accurate communication bit error rate is low, delay error is relatively large, communication bit error rate is high.
Now let’s look at the receiver
Void ReadByte(void) {unsigned char I, value = 0; if( ! SIM_RXD) //RXD_IN starts receiving when RXD equals 0 {// Equal over start bit Start bit is low level delay_us(100); For (I = 0; i < 8; i++ ) { value >>= 1; if( SIM_RXD ) //RXD_IN { value |= 0x80; } delay_us( 100 ); } // The end bit is high //delay_us(100); RecFlag = 1; RecFlag = 1; RecFlag = 1; RecBuf = value; return; }}Copy the code
Receive data is better understood, read the level of IO port, if low level appears, start to receive data, then read the level of 8 data bits, and wait for the end bit to end. One byte of data is received.
So how to determine when to receive serial port data?
There are two ways to achieve this. One is to judge in an infinite loop by querying, reading the level of IO all the time. If low level occurs, it is considered that the serial port has data sent in.
The implementation code is as follows:
Void ReadString(void) {unsigned int CNT = 0, I = 0, j = 0; unsigned char recstr[100] = {0}; _Bool send_F = 0; while( 1 ) { if( ! RecFlag) // Scan when no data is received {ReadByte(); // scan data cnt++; Else if(RecFlag) else if(RecFlag) {CNT = 0; RecFlag = 0; recstr[i++] = RecBuf; // Store the received data RecBuf = 0; send_F = 1; } if((CNT >= 100) && (send_F == 1)) // Scan times more than 100 and send data {CNT = 0; WriteString( recstr ); // Send received data send_F = 0; // Clear the send flag I = 0; // Clear the subscript for(j = 0; j < 100; Recstr [j] = 0; } return; Else if(CNT > 500) {CNT = 0; return; }}}Copy the code
This approach is relatively simple to implement, but it is more troublesome to write programs, because you have to always monitor the IO port, so the program is doing other things, it is very likely to miss the data received. You can use the second way, THE IO port interrupt to determine when to start receiving data, set the IO port to the falling edge interrupt, when there is a falling edge, indicating that the serial port data in, and then to read serial port data. When no interrupt occurs, the program can do something else.
The implementation code is as follows:
RXD #pragma vector = 7 __interrupt void RXDInterrupt(void) {pc_cr2&= ~(1 << 3); // Disallow external interrupts ReadByte(); If (recEnd = = 0 x01) {if (x0a RecBuf = = 0) / / receiving end 0 x0a tag data from the {recEnd | = 0 x02; recCNT = 0; } } if( recEnd ! = 0x03 ) { if( RecBuf ! 0x0d 0x0a {recBUFF[recCNT++] = RecBuf; RecBuf = 0; RecBuf = 0; } else if (RecBuf x0d = = 0) / / received 0 x0d end tag to {recEnd | = 0 x01; } } PC_CR2 |= ( 1 << 3 ); // Enable external interrupts}Copy the code
When the falling edge after entering the interrupt program, this time to close the external interrupt, start reading IO port level state. If you do not close the interrupt, in the process of reading IO level interrupt will continue to enter, so it will affect the accuracy of reading data. So entering the interrupt will first close the interrupt, after receiving one byte, after opening the interrupt, receive the next byte. Until a carriage return newline character is received (0x0D 0x0A), the data is considered to have been sent. Exit the receiving process, and the main program can process the received data.
In this way, the serial port sending and receiving through IO level mode can be realized.
Let’s see what happens
Some reference codes are as follows:
Analog send and receive code:
#include "myuart.h" unsigned char recBUFF[100] = {0}; Unsigned char recCNT = 0; Unsigned char recEnd = 0; // Unsigned char RecBuf; // Receive buffer _Bool RecFlag = 0; / / received data sign bit / / analog serial port initialization PC3 RXD PC4 TXD void MyUart_Init (void) {PC_DDR | = (1 < < 4); / / PC4 output TXD PC_CR1 | = (1 < < 4); / / PC4 push-pull output PC_CR2 | = (1 < < 4); PC_DDR &= ~( 1 << 3 ); //PC3 enter RXD pc_cr1&= ~(1 << 3); //PC3 PC_CR2 |= ( 1 << 3 ); / / can make external interrupt EXTI_CR1 | = (1 < < 5); 1 void Send_1(void) {SIM_TXD = 1; } // output 0 void Send_0(void) {SIM_TXD = 0; } // Send a byte // Take the calculation of 9600bit/s baud rate in STM8 as an example (9600 bits per second). T1 = 1/9600 = 104us Void WriteByte(unsigned char sdata) // Baud rate 9600 {// If the bit error rate is high, you can change the delay_us delay time unsigned char I. unsigned char value = 0; // Send the starting bit Send_0(); delay_us( 100 ); For (I = 0; i < 8; i++ ) { value = ( sdata & 0x01 ); If (value) {Send_1(); } else { Send_0(); } delay_us( 100 ); Sdata = sdata >> 1; sdata = sdata >> 1; } // Stop bit Send_1(); delay_us( 100 ); } void WriteString(unsigned char *s) {while(*s! = 0 ) { WriteByte( *s ); s++; Void ReadByte(void) {unsigned char I, value = 0; if( ! SIM_RXD) //RXD_IN starts receiving when RXD equals 0 {// Equal over start bit Start bit is low level delay_us(100); For (I = 0; i < 8; i++ ) { value >>= 1; if( SIM_RXD ) //RXD_IN { value |= 0x80; } delay_us( 100 ); } // The end bit is high //delay_us(100); RecFlag = 1; RecFlag = 1; RecFlag = 1; RecBuf = value; return; RXD #pragma vector = 7 __interrupt void RXDInterrupt(void) {pc_cr2&= ~(1 << 3); // Disallow external interrupts ReadByte(); If (recEnd = = 0 x01) {if (x0a RecBuf = = 0) / / receiving end 0 x0a tag data from the {recEnd | = 0 x02; recCNT = 0; } } if( recEnd ! = 0x03 ) { if( RecBuf ! 0x0d 0x0a {recBUFF[recCNT++] = RecBuf; RecBuf = 0; RecBuf = 0; } else if (RecBuf x0d = = 0) / / received 0 x0d end tag to {recEnd | = 0 x01; } } PC_CR2 |= ( 1 << 3 ); // Enable external interrupts}Copy the code
Delay code:
#include "delay.h" volatile u8 fac_us = 0; Void delay_init(u8 CLK) {if(CLK > 16) {if(CLK > 16) {if(CLK > 16) {if(CLK > 16) { fac_us = ( 16 - 4 ) / 4; Else if(CLK > 4) {fac_us = (clk-4) / 4; } else { fac_us = 1; }} / / delay of nus / / delay time = (fac_us * 4 + 4) * nus * (T) / / which, T to the reciprocal of CPU running frequency (Mhz), unit for us. / / accuracy: Void delay_us(u16nus) {void delay_us(u16nus) {/* / DELAY_XUS: LD A,_fac_us //1T,fac_us loaded into accumulator A DELAY_US_1: NOP //1T, NOP delay DEC A //1T,A-- JRNE DELAY_US_1 // not equal to 0, jump (2T) to DELAY_US_1 continue, if equal to 0, do not jump (1T). NOP //1T, NOP delay DECW X //1T,x-- JRNE DELAY_XUS // if not equal to 0, jump (2T) to DELAY_XUS, if equal to 0, do not jump (1T). POP A //1T, stack #endasm */ / "PUSH A \n" //1T, stack "DELAY_XUS: \n" "LD A,fac_us \n" //1T,fac_us load to accumulator A "DELAY_US_1: The NOP \ n \ n "" "/ / 1 t, NOP delay "DEC A \ n" / / 1 t, A, "JRNE DELAY_US_1 \ n" / / is not equal to zero, then jump (2 t) to DELAY_US_1 continue, if it is equal to zero, do not jump (1 t). "the NOP \ n" "POP A \n" //1T,nop delay "DECW X \n" //1T, X -- "JRNE DELAY_XUS \n" // not equal to 0, then jump (2T) to DELAY_XUS, if equal to 0, do not jump (1T). }Copy the code
The main program
#include "iostm8s103F3.h" #include "main.h" #include "led.h" #include "exti.h" #include "delay.h" #include "myuart.h" extern unsigned char recEnd; extern unsigned char recBUFF[100]; void SysClkInit( void ) { CLK_SWR = 0xe1; CLK_CKDIVR = 0x00; } void main(void) {__asm("sim"); // disable SysClkInit(); delay_init( 16 ); LED_GPIO_Init(); MyUart_Init(); __asm( "rim" ); WriteString("Virtual Serial port test!! \r\n"); while( 1 ) { //WriteString("0123456789 abcdefghijklmnopqrstuvwxyz ABCDEFGHIJKLMNOPQRSTUVWXYZ "); // Send test if(recEnd==0x03) {WriteString(recBUFF); recEnd=0x00; } LED = ! LED; delay_ms(500); }}Copy the code
Stm8 MCU simulation serial port function