SOftUart - Interrup Driven - Page 1
Forum section: electronics / general

author: Ata
author_rank: Contributor
timestamp: June 23, 2016, 07:47:46 pm
content: Softuarts are very useful on cheap pics without hardware uart or when it is configured as SPI or I2C while at the same time you need to interface to multiple uart-TTL devices like USB, GSM, GPS, etc.The problem is that most existing softuart-routines are written in such a way that the pic is busy during all the bit-bang process sending or receiving the entire byte, so all other pending task must be stopped.The idea is to allow the pic to do other tasks while sending/receiving multiple softuart TX/RX data simultaneously.The attached softuart-routines were written for small-cheap pics like 12F1571, 16F18323, 16F1827, etc. Enhanced type running at 32MHz or 16MHz.They were coded as many small state-machine routines pointed by its state-var on behalf of a jump-table.The interrupt is driven by a TMR2 int with PR2 set to 3x of the fastest baudrate used.All RX/TX data is read-from or written-to between the main-prog and ISRs through circular buffers.All this are more explained on a readme.txt file attached.On the readme file there is table showing the PIC %time shared with these softuarts routines.Two examples: 3 running RX/TX softuarts at 9600bps on a 32MHz pic, will take 24%-32% of the pic-cyc on every TMR2 int.1x RX/TX softuart at 57600bps on a 32MHz pic, will take 57%-74%.These softuarts routines were written in ASM because I could not find a way to accomplish it in C.Although these softuarts can be easily used in C as In-Line-Assembler functions handled through its correspondingglobally declared buffers. I wonder if there are better and faster routines, hope for someone with more programmingskills can share a better solution or ideas.. Any opinions/suggestions (constructive or destructive) are welcome.

author: westfw
author_rank: Super Contributor
timestamp: June 23, 2016, 09:40:15 pm
content: Looks nice - well documented. Requires that all the ports run at the same speed, right?You should attach an explicit license so that people know how they're allowed to use. (I like the BSD or MIT license...)I'm not enough of a PIC assembly language programmer to suggest any micro-optimizations, but it seems to me that it might be possible to save a lot of cycles by running the transmitters in parallel and byte-synchronized, but it might be too complicated to be worthwhile...Code:;; Send start bit of all *active* tx ports;; send bit 1 of all ports ("mark" for inactive ports.);; :;; send bit 8 of all ports ("mark" for inactive ports.);; send stop bits on all tx portsI'm thinking that the complex decisions happen when you decide whether you're going to send a start bit, which is BETWEEN characters, so the timing is relaxed. Up to one character time "delay" between the user-level "send" command and the actual start of output, but that's not bad...

author: dannyf
author_rank: Super Contributor
timestamp: June 23, 2016, 09:49:49 pm
content: QuoteThat seems a lot.A lesser but potentially more useful approach is to write a TX only version -> many times we just want to get information out of the mcu. No need to run it at 3x of the baud rate.I would also suggest that you look into using the mssp / spi hardware as uart.Also, I typically write my uart transmission routines as uart_write(unsigned char *str); and the transmission would start in the background and ends by itself. No user interaction is needed - you will need to have a flag so not to over-run an unfinished transmission.Hope it helps.

author: Ata
author_rank: Contributor
timestamp: June 23, 2016, 10:54:46 pm
content: Westfw:Not all port run at the same speed... I mean that every softuart (3 in this case) runs independently.. Maybe at different baudrates... Or receiving and/or transmitting asynchronously between them...The way you suggested implies that the main-prog needs to transmit the 3 TX-Softuart when that is not necessarily true always..These softuarts works emulating real hardware uarts. That's the idea.Dannyf:The 3xBaudrate is the timing used by TMR2 int to sample 3 times every serial bit at the baudrate used... That not means we are running at 3x the baudrate.3 Softuarts TX/RX running 9600simultaneously and only take 30% of the PIC-cyc... seems a lot to you???... Try just 1x traditionally softuart at 4800 usually found on libs that takes ALL the cpu time.. Because they must run on the main-prog just one RX or TX not both.Your example "uart_write(unsigned char *str)" assumes a hardware uart... That's ok... But what if you don't have a hardware uart or the mssp of the pic is configured for SPI or I2C... And you need 2 uarts at the same time?... How to do it without losing all the pic-cyc available... That's the problem...

author: westfw
author_rank: Super Contributor
timestamp: June 24, 2016, 12:09:04 am
content: QuoteAh. I misread the "timer operates at 3xthe fastest bitrate" My other comment wouldn't apply without them all running at the same speed.30% of a PIC fo 3*9600bps seems pretty good to me. Even traditional HW uarts can be a lot of load when interrupting at once per character, and this has 10x that interrupt rate.QuoteNo it doesn't.

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 12:14:43 am
content: software uart, on a 12f675.It is entirely done over the tmr1 interrupt, through a state machine - supports 1 start bit, 8 data bits, 1/2 stop bitsCode://softuart transmission state machineswitch (_utx_status) {case -1://start bit has been sent. now send the dataIO_CLR(UTX_PORT, UTX);//send the start bit_utx_status = 1;//lsb firstbreak;case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8://send all 8 data bitsif (_utx_mask & *_utx_ptr) IO_SET(UTX_PORT, UTX);//send 1 bitelse IO_CLR(UTX_PORT, UTX);//send 0 bit_utx_status +=1;break;case 9://send the stop bitIO_SET(UTX_PORT, UTX);//stop bit sent_utx_status = 11;//10: send two stop bits. 11: send 1 stop bitbreak;case 10://send the 2nd stop bitIO_SET(UTX_PORT, UTX);//not necessary_utx_status = 11;break;case 11://transmission has ended. check for end of stringIO_SET(UTX_PORT, UTX);//utx return to idle state / highif (*++_utx_ptr) {//if the next char is not '\0', continue transmission_utx_status = -1;//send the stop condition next} else {//all chars have been sent -> stop transmission_utx_status = 0;//indicating module readyTMR1ON = 0;//turn off tmr1}The main loop checks for uart transmission status while flipping a pin:Code:while (1) {IO_FLP(LED_PORT, LED);//flip ledif (utx_ready()) {//if uart is availablestrcpy(uRAM, str0);//form the stringutx_puts(uRAM);//send message}}The same main loop ports over pretty much any mcu I work with, from avr, 8051, pic, stm8, to 32-bit ARMs, without any modification.

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 12:27:54 am
content: My routine doesn't yet support multiple uarts, but with some quick changes I can make it support as many uarts as the processing power allows.the MIPS drain is roughly the same as yours.

author: Ata
author_rank: Contributor
timestamp: June 24, 2016, 11:32:23 am
content: Hi DannyfGood C softuart code... But only TX...Can you please show on your Proteus simulation a timing diagram of the TX output to see if there is jitter?In my own C testing doing almost the same like your code... I couldn't avoid TX jitter .. I mean, using an oscilloscope I saw small differences on the TX-bit widths and jitter increases while going from bit0 to bit7... (timing differences accumulated from bit to bit).And what happens in your code if you need TX and RX simultaneously?? I mean, While transmitting, let's say' the 4th TX-bit, a start bit arrives at your RX-input port-bit?? In my C experience, it was very difficult for me to keep track of every timed action (input and output) on all cases independently and timely correct (aprox).... And it gets worse if you have 2 TX/RX Softuarts working at the same time.In a switch-case statements, the C compiler takes different execution times depending on which case is executed and gets more convoluted if two state-machines work together asynchronously.. That was my main problem in C... And that's why I had to do it in ASM... Maybe my lack of experience in C is the problem...WestfwYou are right,, uart_write(unsigned char *str) can also be used on software defined uarts too..I tried a little test in XC8 with this (very similar) function and it works well, but seems to me that the PIC stays there until all the string is transmitted... So, all other main-prog task are stop... Perhaps I'm wrong agian...

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 12:30:54 pm
content: Quoteit will keep transmitting but not receiving, as it is not designed to receive.If I were to design for full duplex, I would use an external int or pcint on the RX pin to start a timer and sample the RX pin based on the intervals determined by the timer. At the end of that reception, the received char will be dumped to a buffer and a flag will be raised for the processing unit. ie, it will be practically identical in concept to the tx state machine, mostly due to the limited timer capabilities on this chip.The RX timer will be different from the TX timer as the transmission / reception can be asynchronous.

author: Ata
author_rank: Contributor
timestamp: June 24, 2016, 01:17:45 pm
content: Good Idea.... But, what happens if the if the INT on the RX input pin occurs during the transmission of, let's say, the 5th TX-bitIt will stop transmitting while the start bit is received on the INT... That's a problem...How to keep both TX/RX working flawless asynchronously at the same time???In the routines attached in my first post, there is a sample main-prog that uses 3 Softuarts RX/TX simultaneously.A loop data is made as follows:PCcommTX(3) wired to RX0-->TX1 wired to RX2-->TX0 wired to RX1(3)-->TX2(17) wired to PCcommRX(2).And using any serial terminal program like putty (or hyperterminal) set at 9600 NO-ECHO.. You can send and receive data on the PCcontinuously through all the 3 softuarts smoothly, no jitter, no char-loss, taking only 30% of the pic-cyc.

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 01:24:26 pm
content: Quotesince you don't have prioritization and vectoring of interrupts on the pic, the current transmission bit will condition until the execution exits the isr.Right after that, it will jump right back to the isr (since the eint or pcint flag is set) and runs that portion of the isr.There is no way around it.The same with any pic code: at any given time, it can only execute one piece of code.

author: Ata
author_rank: Contributor
timestamp: June 24, 2016, 01:30:08 pm
content: I don't agree..Yes, there is no int prioritization... That's why I have to process all TX/RX softuarts at once on the same TMR2 INT, not Bit-port INT..It is the only way i found to solve the problem of simultaneously running many RX/TX channels at different states.

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 01:49:14 pm
content: You have to start the RX portion independent of the TX portion. Otherwise, you will not receive anything unless you are transmitting.Once the RX portion starts, you can sample the RX line in the TX timer isr -> unless you want to have the ability to TX/RX on different baud rates.In my code, I just need to turn off the uart timer when both TX/RX is done.

author: Ata
author_rank: Contributor
timestamp: June 24, 2016, 01:55:43 pm
content: Sorry... But maybe I don't understand your idea very well...As far as I can see in your small parts of code you posted, I can't see how you will receive anything while in the middle of a byte transmission...? Or How you will initiate a transmission in the middle of a byte reception without lossing a bit or introducing time jitter in your transmission??.If you could attach a sample prog so I can see and test it would be great... I really want to explore better ideas than mine.

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 03:05:06 pm
content: if you want to combine it, you will need to speed up the timer (so you sample the RX line in the middle).Something like this would work:Code://uart duplex / timer1 isrvoid interrupt isr(void) {static uint8_t rx_sample=0; //rx sample flaguint8_t rx_tmp = RX_PORT; //read rx line into rx_tmpif (TMR1IF) {TMR1IF = 0;//load offsetrx_sample ++; //increment rx sampleif (rx_sample & 0x01) { //old numbers for RX//run the rx state machine} else { //even numbers for TX//run the tx state machine}}//process rx starting bitif (INTF) { //rx start bit received -> set to trigger on the falling edgeINTE = 0;//disable rx external interrupt//clear the flag_rx_status = -1; //initialize rx status for the rx state machine_rx_databuf=0; //reset the receiving bufferif (_tx_status==0) {//no tx transmission in place//initialize the timer and load the offset}}}hope it helps.

author: Ata
author_rank: Contributor
timestamp: June 24, 2016, 08:58:01 pm
content: As far as I can understand.. Your code is not a feasible solution to run RX/TX Softuarts simultaneously and asynchronously.Your ISR will initiate when TMR1 or INTF interrupts arrives.If TMR1IF=1, depending of the ODD/EVEN value of rx_sample++, you will execute the RX-state-mach or the TX-state-mach.This means that you will have to reload TMR1 for the next int, at least with a 6x Baudrate-used, in order to have , at least 3x samples of every RX input bit or TX output bit....If that not the case and you simply reload TMR1 to 1x Baudrate-used, then you will execute either of RX or TX machine once every TMR1 INT..That's Ok while your state-mach are so fast or distributed in small steps executed every TMR1 int... Depends on how you will code your RX and TX states-machs (not shown)If INTIF=1, that means a falling edge of a start-bit was received... Set rx_status=-1 to flag the execution of the RX-state-mach without knowing whats is going on in your TX-state-mach already started on the last TMR1IF in case rx_sample++ was even... So, you check if tx-status=0 in ordet to know it... But if tx-status=1... Just ends the ISR waiting for the next TMR1F to continue with the TX state-mach already initiated... I can not see how could you synchronize them?Apart from the fact that it may probably be losing sync receiving bits once your RX state-mach has been initiated on INTIF=1Hope I'm wrong analyzing your short code description..

author: dannyf
author_rank: Super Contributor
timestamp: June 24, 2016, 09:25:58 pm
content: you are not understanding it.The reason to run it at 2x of the speed is so you can sample the RX line in the middle.The RX side and the TX side are completely separate because you don't know when the RX may start. So you have two state machines, one for the rx side and one for the tx side. they are unconnected by necessity.Hope it helps.

author: dannyf
author_rank: Super Contributor
timestamp: June 25, 2016, 12:53:49 am
content: Full duplex, TX/RX loop-around. Also works individually: TX only, or RX only.RX state machine:Code:switch (_urx_status) {case -1://start bit has been received._urx_status = 1;//waiting for the data bit to come_urx_data = 0;//reset the receiving data registerbreak;case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8:if (URX_GET()) _urx_data |= _utx_mask;//else _urx_data |= 0;_urx_status += 1;//increment the status registerbreak;case 9://first stop bit//perform exception checkif (URX_GET()) _urx_status = 11;//10: receive two stop bits. 11: receive 1 stop bitelse {//bit high -> no stop big -> invalid data_urx_status = 0;_urx_data = 0;//reset urx data registerINTF = 0;//clear the flagINTE = 1;//start listen for falling edge / start bit}break;case 10://first / 2nd stop bit//perform exception checkif (URX_GET()) _urx_status = 11;else {//bit high -> no stop bit -> invalid data_urx_status = 0;_urx_data = 0;//reset urx data registerINTF = 0;//clear the flagINTE = 1;//start listen for falling edge / start bit}break;case 11://end of reception_urx_buf = _urx_data;//save the data into buffer_urx_status = 0;_urx_datardy= 1;//new data has arrivedTMR1ON = (_utx_status==0)?0:1;//stop the timer only if tx transmission has endedINTF = 0;//clear the flagINTE = 1;//start listening for falling edge / start bitbreak;//case 0://do nothing}Looks remarkably similar to the TX state machine - I modified it based on the TX state machine,The code used to test is below:Code://RX: look for letter 't' and then flip a pinif (URX_DATARDY() && (URX_GETBUF()=='t')) {IO_SET(LED_PORT, LED);//flip led of if 't' is receivedIO_CLR(LED_PORT, LED);URX_CLRRDY();//_urx_datardy = 0;//has read the data}//TX: keep sending str0if (utx_ready()) {//uart is availablestrcpy(uRAM, str0);//form the string: "test uart. \n\r"utx_puts(uRAM);//send message}Just shy of 350 bytes under XC8 1.12.

author: westfw
author_rank: Super Contributor
timestamp: June 25, 2016, 08:37:04 am
content: I dunno; I see "the Dannyf algorithm" and "the Ata algorithm" petty much converging to "very similar" by the time they both implement the same number of full-duplex ports. Deciding to sample at 2x bitrate ("so you can find the middle") vs 3x bitrate (for more accurate detection of "start") seems pretty academic. (Ata runs the timer all the time and can sample all N ports in the same ISR, while Dannyf detects actual edge of the startbit (but needs a timer (or 2?) per port plus the edge-detect ISR.) I think?

author: Ata
author_rank: Contributor
timestamp: June 25, 2016, 08:38:45 pm
content: What I like about Dannyf's algorithm is its implementation in C... Is well done a very clever. Once you understand it... I learned a lot analyzing it.In my experience (I tried it) sampling the RX-ports at 2x bit-rate is not very accurate because you have 1/2 bit uncertainty that accumulates toward the 8th bit... But he solves this by starting on the falling edge of the start-bit.. very clever. But then you can not freely use any port-bit for RX-bit input, only those which has INTE capabilities.... And for his TX state machine... I wonder if there is any TX-Bit-output jitter due to C-compiler unpredictable real-time behavior... I mean, it is very difficult to know on which microsecond will any bit be set or clear at a given baudrate.... Although I have not tested his routine (I will do as soon as I can) I'm pretty sure it will work very well..My algorithm uses as lower resources as possible, just 1 timer. And its main goal is to emulate real "hardware UARTs" as much as possible, making them working simultaneously and independently on a multitasking environment main-prog.Very soon I will post other "background" interrupt driven routines that allows you to have 1x LCD+ 5x Key-inputs, 1x Wiegand reader working together with 2x RX/TX softuarts at the same time, just taking less than 40-50% of the pic-cyc time between Interrupts... running on small 16F enhanced pics.Any how, the idea was to share some techniques, concepts and solutions to common problems.

author: dannyf
author_rank: Super Contributor
timestamp: June 25, 2016, 11:10:47 pm
content: "But then you can not freely use any port-bit for RX-bit input, only those which has INTE capabilities"The use of external interupt for detection of the falling edge is by convenience, not by necessity. External interrupt in falling edge is the same as IOC or pcint and then current state of the pin is logic low. Fairly easy to switch between the two.

author: dannyf
author_rank: Super Contributor
timestamp: June 25, 2016, 11:12:42 pm
content: End if the day, you will find software uart to be a gimmick. Most mcus today have hardware uart and aren't that much more expensive than those without. So there is little point in implementing uart via software.

author: Ata
author_rank: Contributor
timestamp: June 26, 2016, 11:54:50 am
content: Yes... most cheap pics have one hardware MSSP that can be configured as a hardware uart... But what happens if you need to configure the only one MSSP as an I2C and at the same time need an uart...??That's when softuarts becomes very handy... that's not gimmick... I use them a lot on PICs 16F18323 and 16F1827 where I usually need to handle 2x serial peripherals, 1x I2C device, 1x Wiegand reader, 1x LCD-20X2 Display sharing its D7-D4+RS with 5 key inputs and drive H-bridge mosfets at the same time... with just 1 small-cheap pic. This is when you need real (and reliable working) background peripherals on a multitaskig-main-prog environment.

author: hans
author_rank: Super Contributor
timestamp: June 26, 2016, 02:22:06 pm
content: If such a situation comes along, then choose the easier protocol to bitbang. UART is probably one of the worst choices you can make in that case. SPI and I2C masters are very easy to bitbang.SPI and I2C slaves not so much either, rather use a peripheral for that.If a particular chip does not contain said combination of peripherals, get a different chip. It doesn't tick all of the boxes.I've once also gone down the road of a software UART on an ATTINY45. Although I was able to get a working RX statemachine within an hour, it was not a very nice solution. The high-rate and CPU intensive process makes it hard to design around with, especially when other time-critical stuff takes place in the system.

author: dannyf
author_rank: Super Contributor
timestamp: July 12, 2016, 11:22:25 am
content: "End if the day, you will find software uart to be a gimmick. "In case you are interested, there are cases where bit banging is desirable, even on commercial designs where hardware functionality is available. Jlink v8 (on stm32f) and v10 (on lpc43xx) for example. V10 uses sgpio for that.