??? 07/04/04 01:27 Read: times |
#73589 - RE: Polling -- long Responding to: ???'s previous message |
Hi Joseph,
I've read and deliberated your posting. Thank you for taking the time to do so. ...I do not understand a couple of points....
On the one hand you have polling wherein you must use at least one instruction to decide whether to service the serial port buffer (e.g.,If RI == 1;). This compares to a hardware interrupt which requires zero instructions to decide that you need to service the serial port buffer. Yes, but the hardware still has to perform an LCALL to get to the ISR (i.e., start execution at 0x0023), then even though the hardware determined (at no cost) that interrupt service was necessary, the ISR must still determine which of RI or TI generated the interrupt, since they both vector to the same location. The LCALL must be matched by an RETI, adding more overhead. Also, unless the entire ISR fits between one vector and the next, a jump must be performed to get to the longer ISR -- again, more overhead. Let's compare a contrived example of polling versus interrupts that is so simple that it ignores errors, timeouts, etc. First, the interrupt-driven approach, which will receive a 16-byte packet and then transmit a 16-byte packet using register bank 1 and avoids using ACC, all to keep the size of the processor context that must be saved down to a minimum ("#~" indicates the number of instruction cycles): ;*************************************************************** ;* ;* Serial Port Interrupt Vector ;* ;*************************************************************** CSEG AT 0023H ; 2~ for H/W's LCALL LJMP SIO_ISR ; 2~ CSEG AT 002BH LJMP TBD_ISR ; ... ;*************************************************************** ;* ;* Serial Port Interrupt Service Routine ;* ;*************************************************************** SIO_ISR: ; 4~ to get here PUSH PSW ; 2~ Save context MOV PSW,#00001000B ; 2~ Select register bank 1 JBC RI,RX_HDLR ; 2~ If not RI, assume TI. ;10~ For ISR entry and Rx/Tx dispatch ; TI_HDLR ---------------------- 10~ to get here CLR TI ; 1~ Relieve the interrupt CJNE R1,#0,TX_EXIT ; 2~ If done transmitting, ; ..TI clear prevents more interrupts. MOV SBUF,@R0 ; 2~ Transmit a byte INC R0 ; 1~ Advance buffer pointer DEC R1 ; 1~ Count off one byte TX_EXIT: POP PSW ; 2~ Restore context (and register bank) RETI ; 2~ *** 21 cycles/byte RX_HDLR: ;---------------------- 10~ to get here MOV @R0,SBUF ; 2~ Buffer received byte INC R0 ; 1~ Advance buffer pointer DJNZ R1,RX_EXIT ; 2~ If done receiving, CLR REN ; ..REN clear prevents more interrupts. RX_EXIT: POP PSW ; 2~ Restore context RETI ; 2~ *** 19 cycles/byte ;*************************************************************** ;* ;* Concurrent "Work" and I/O ;* ;*************************************************************** ; Receive a 16-byte packet. USING 1 MOV AR0,#RX_BUFFER ; Point to the receive buffer MOV AR1,#16 ; Number of bytes to receive CLR RI ; Enable the receiver, allowing the SETB REN ; ..ISR to buffer the received bytes. RX_WAIT: LCALL DO_SOMETHING ; Do something useful MOV A,AR1 ; ..until the ISR decrements the JNZ RX_WAIT ; ..byte counter down to zero. ; Process the received packet. ; ... ; ... ; ... ; Transmit a 16-byte packet. MOV AR0,#TX_BUFFER ; Point to the transmit buffer MOV AR1,#16 ; Number of bytes to transmit SETB TI ; Enable the transmitter, allowing the ; ..ISR to transmit bytes from the buffer. TX_WAIT: LCALL DO_SOMETHING ; Do something useful MOV A,AR1 ; ..until the ISR decrements the JNZ TX_WAIT ; ..byte counter down to zero. We've got 21 cycles and 19 cycles per byte for interrupt-driven transmit and receive, respectively. Now let's look at the polled version with the same scenario of receiving a 16-byte packet and then transmitting a 16-byte packet. ; Receive a 16-byte packet. MOV R0,#RX_BUFFER ; Point to the receive buffer MOV R1,#16 ; Number of bytes to receive CLR RI ; Enable the receiver SETB REN RX_LOOP: JNB RI,$ ; 2~ Wait for a byte to shift in CLR RI ; 1~ MOV @R0,SBUF ; 2~ Buffer received byte INC R0 ; 1~ Advance buffer pointer DJNZ R1,RX_LOOP ; 2~ If not done receiving, *** 8 cycles/byte ; ..loop for next byte. ; NOTE: Polling doesn't need REN cleared. ; Process the received packet. ; ... ; ... ; ... ; Transmit a 16-byte packet. MOV R0,#TX_BUFFER ; Point to the transmit buffer MOV R1,#16 ; Number of bytes to transmit TX_LOOP: JNB TI,$ ; 2~ Wait for a byte (if any) to shift out CLR TI ; 1~ MOV SBUF,@R0 ; 2~ Transmit a byte from the buffer INC R0 ; 1~ Advance buffer pointer DJNZ R1,TX_LOOP ; 2~ If done transmitting, *** 8 cycles/byte ; ..loop for next byte. So we've got 8~/8~ for polled versus 21~/19~ for interrupt-driven. Now, in the extreme case, one could "unroll" the receive loop to achieve 5 cycles per byte, for example: JNB RI,$ ; 2~ CLR RI ; 1~ MOV A,SBUF ; 1~ MOV #RX_BUFFER+0,A ; 1~ *** 5 cycles/byte ; . ; . ; . ; 14 more instances of the code sequence ; . ; . ; . JNB RI,$ CLR RI MOV A,SBUF MOV #RX_BUFFER+15,A It seems to me that as the data throughput increases, the need for interrupts increases. But this is the opposite of your experience. I hope my examples have cleared that up. So, this is my interpretation/guess concerning your experience. Since your program did nothing else but service the serial port buffer ("And by the way, the system had absolutely nothing else to do until it received a packet."), your entire program was, in effect, the SPI routine. In this case my program was a cellular radio control "server" to a much more powerful "client" processor. The radio control processor "served" the requests made by the client for things like controlling complex transmit and receive GSM signal processor chips (100's of registers each), programmable transmit and receive synthesizers, octal DACs, temperature sensors, EEPROM, etc. There was nothing to do until the client requests some kind of radio control, then control is performed and the results transmitted back -- a closed-loop, "interlocked" system. No need for interrupt-driven serial I/O and the data rate was fast. So it seems that this is a good example of the circumstance I was asking about. If the main program is a dedicated serial port service routine, then there is no need for interrupt vectoring. Or more clearly, if the main program is dedicated to awaiting something via the serial port, then does something with that something (i.e., has two [or more] mutually exclusive states), then there is no need for interrupt-driven serial I/O. Moreover, using the hardware vectoring becomes a waste of cycles thereby making polling the preferable approach. Stated simpler, if the whole program is a serial port routine, using hardware interrupts is a waste. A generalization, but yes. But, would you say that if the program is not dedicated exclusively to servicing the serial port, it is probably better to use the interrupt? Generally, yes. I would say that the majority of my designs over 26 years have used interrupt-driven I/O. Now I'd say that it's more like 80/20 or 90/10 rather than saying polling is the <5% exception. Or, is it possible that there are circumstances wherein polling within a larger context (program) might still be preferable to using interrupts? I hesitate to say it, but yes to that too. You see, it's all a matter of designing to fit requirements -- all types of requirements -- functional, performance, ease of future enhancements, portability, you name it. And how would one quantify it? Study all your requirements. If you don't have a clear pick which way to go, you haven't studied hard enough. Or, just go with interrupt-driven and deal with design changes later, if required. The top two benefits for polling (my list) are 1) performance and 2) simplicity (oh, bugger -- yes I know, interrupts aren't hard, yadda, yadda, yadda). Just to give you another example, and this one only because I'm in the thick of it at the moment, is U-Boot. This is not an 8051 thing. It is arguably the most popular non-x86 (e.g., PowerPC, ARM, etc.) bootloader for 32-bit OS's, Linux included. It supports serial ports, ethernet, oodles of different flash devices, etc. on a whole bunch of different COTS boards and is tailored to being customized by the likes of me for custom boards. Does it use interrupts? Nope! Too much extra baggage portability-wise -- now there's a requirement. The bottom line: As you can see, I predominantly use interrupt driven I/O because of the overall flexibility it offers in complex systems, but I am often called upon to squeeze performance out of systems that would otherwise be unachievable if I didn't use polling in certain areas. |