Software serial?

All ESP8266 boards running MicroPython.
Official boards are the Adafruit Huzzah and Feather boards.
Target audience: MicroPython users with an ESP8266 board.
User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Software serial?

Post by deshipu » Wed Aug 31, 2016 8:03 pm

I tried to use this library last weekend https://github.com/plieningerweb/esp8266-software-uart together with MicroPython (https://github.com/deshipu/micropython/ ... 77d6529d39) but the reads always get only "\x00\x00" for every byte sent. I suspect this is because MicroPython is using the interrupt handler that this library also needs.

I think it would be better to write a simple low-speed software serial in Python. It should be possible using what MicroPython already exposes.

alien
Posts: 5
Joined: Sat Nov 19, 2016 4:46 pm

Re: Software serial?

Post by alien » Tue Nov 22, 2016 8:29 am

This will be implemented once the esp8266 ?

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Software serial?

Post by deshipu » Wed Nov 23, 2016 12:04 pm

We don't know.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Software serial?

Post by pythoncoder » Thu Nov 24, 2016 9:00 am

It got too few votes in the KS campaign to be accepted as a stretch goal, so I guess it's up to us. The timing requirements at any reasonable baud rate are quite challenging, especially if the driver has to work in the background like the Pyboard UART class.
Peter Hinch
Index to my micropython libraries.

cversek
Posts: 13
Joined: Sat Jan 07, 2017 4:45 am

Re: Software serial?

Post by cversek » Sat Jan 07, 2017 6:12 am

Hi All,
I'm new to micropython and the esp8266, but I've been a long time Python and Arduino user. On my first IoT data logger project I was finding micropython a joy to develop in and the software machine.I2C library quite easy to adapt for an AM2315 (http://www.adafruit.com/products/1293) humidity and temperature sensor. However, I hit a wall when I needed to integrate a UART communicating carbon dioxide sensor and found that the hardware UARTs were tied up and there was no built-in software serial module.

I actually found deshipu 's original suggestion to be quite helpful. After learning the basics of writing C extension modules (and a lot of trial-and-error hacking) I was able to successfully adapt a `softuart` C code library (http://github.com/plieningerweb/esp8266-software-uart) and wrap it in a micropython extension class `machine.SoftUART` that is based very closely on `machine.UART`.

I wanted to share this code to help others who may find this thread, so I put together a github micropython fork that makes the needed changes (https://github.com/p-v-o-s/micropython/ ... 8a89287a8f).
This firmware can be built according to Adafruit's helpful Feather HUZZAH guide (https://learn.adafruit.com/building-and ... d-firmware) if you substitute this repo instead of the one included on the vagrant VM.

In order initialize the SoftUART object, two Pin objects, tx & rx, are the only required arguments (similar to the I2C object's sda & scl pins). For example:
>>> import machine
>>> ser = machine.SoftUART(machine.Pin(12),machine.Pin(14), baudrate=9600, timeout=1000, timeout_char = 10)
>>> print(ser)
SoftUART(tx=12, rx=14, baudrate=9600, bits=8, parity=None, stop=1, timeout=1000, timeout_char=10)
>>> ser.
init flush read readline
readinto write
>>>

The baudrate and timeouts are configurable; however, the data bits, stop bits, and parity are fixed. The softuart.c library uses a statically allocated receive buffer with a default size of 64 bytes (https://github.com/p-v-o-s/micropython/ ... tuart.h#L4). The new `flush` method resets the receive buffer to empty state. One major shortcoming of the `machine.SoftUART` implementation is that only one instance of a SoftUART class can function in overlapping code since it uses a global Softuart struct instance `softuartDevice` - I was unable to get multiple instances working properly using dynamic allocation.

Please let me know if you find this useful or need help to get it working.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Software serial?

Post by pythoncoder » Sat Jan 07, 2017 7:50 am

That is a great feature, congratulations for porting it :D

I think it's worth pointing out the warning in the original code that loopback tests won't work owing to timing issues. I suspect that true full duplex also won't work for the same reason: a received start bit triggers an interrupt handler which only exits when the entire byte has been read. If this occurs while a transmission is in progress, and the receive interrupt has higher priority, the transmission will be disrupted. If rx has lower priority and data is received while a tx is in progress, then the rx interrupt won't get serviced until it's too late. Mind you, this is based on the warning and inspecting the code rather than on actual testing.

One comment on the code (which you inherited from the original). I think that timer overflow is handled incorrectly both in transmission and reception. Bear in mind that a microsecond timer logically and-ed with x7FFFFFFF will overflow every 35 minutes, so this isn't academic. The code

Code: Select all

     unsigned start_time = 0x7FFFFFFF & system_get_time();

      for(i = 0; i < 8; i ++ )
      {
        while ((0x7FFFFFFF & system_get_time()) < (start_time + (s->bit_time*(i+1))))
        {
          //If system timer overflow, escape from while loop
          if ((0x7FFFFFFF & system_get_time()) < start_time){break;}
        }
simply bails out if an overflow occurs, setting subsequent timing adrift. I think each iteration of the loop should compute an end_time by adding s->bit_time to the previous end_time, then and-ing it with x7FFFFFFF. Then check for overflow - something along these lines:

Code: Select all

      unsigned start_time = 0x7FFFFFFF & system_get_time();
      unsigned end_time = 0x7FFFFFFF & (system_get_time() + s->bit_time) ;
      for(i = 0; i < 8; i ++ )
      {
        if ((0x7FFFFFFF & system_get_time()) > end_time) // An overflow must occur first
          {
          while ((system_get_time() & 0x7FFFFFFF) > 1000000) ; // wait for overflow
          }
        while ((0x7FFFFFFF & system_get_time()) < end_time) ; // wait for bit time
        end_time = 0x7FFFFFFF & (system_get_time() + s->bit_time) ; // now process bit
I hope this is correct- my C is decidedly rusty so do check if you implement this ;)
[edit]Just to correct a } where there was a {
Last edited by pythoncoder on Sun Jan 08, 2017 5:59 am, edited 1 time in total.
Peter Hinch
Index to my micropython libraries.

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Software serial?

Post by dhylands » Sat Jan 07, 2017 8:16 am

I believe that code will fail if start_time + the bit time wraps.

For example, if start_time = 0x7ffffff0 and the duration being added == 0x20, then start_time + duration == 0x80000010 and 0x7FFFFFFF & system_get_time() will always be less that start_time + duration.

If you declare end_time and get_system_time() as signed 32-bit integers, then you can do:

Code: Select all

end_time = get_system_time() + duration;
It's possible that end_time will wrap and wind up being less than get_system_time(), if you do the comparison using:

Code: Select all

if (end_time - get_system_time() > duration)
and it will work even when wraparound occurs.

cversek
Posts: 13
Joined: Sat Jan 07, 2017 4:45 am

Re: Software serial?

Post by cversek » Sun Jan 08, 2017 12:56 am

Thanks for catching this timing glitch guys. I have a stupid question: if the whole point is to delay s->bit_time microseconds, then why not just use:

os_delay_us(s->bit_time);

?
It is already being used in the code for a similar purpose (https://github.com/p-v-o-s/micropython/ ... art.c#L291).

User avatar
dhylands
Posts: 3821
Joined: Mon Jan 06, 2014 6:08 pm
Location: Peachland, BC, Canada
Contact:

Re: Software serial?

Post by dhylands » Sun Jan 08, 2017 4:13 am

Using os_delay_us can lead to cumulative errors.

The calls to os_delay_us aren't exact, and you have overhead in the code before and after the call. So if you call os_delay_us(20) 100 times in a row, you will have delayed for quite a bit more than 2000 useconds.

If you have a timer and your comparison increments by 20 useconds each time, then you'll be very close to "on-time" at the end of 100 iterations.

It's a subtle difference but can make a substantial difference for things that are supposed to be scheduled on a regular time basis. It might be ok for UART stuff because you only have 9 or 10 bit times that are important, but for longer term things it makes a difference.

cversek
Posts: 13
Joined: Sat Jan 07, 2017 4:45 am

Re: Software serial?

Post by cversek » Sun Jan 08, 2017 5:49 am

Thanks again for that explanation...sometimes the subtleties escape me when I'm looking to "simplify". I'm so used to things like pyserial where it just works - and when you peer underneath that nice Pythonic interface you see a grab bag of arcane special case handling. The python (and micropython) community is lucky to have this kind of expertise on hand.

I've taken your comments into consideration and produced some changes that I think address the system_get_time() roll-over issue and avoid the perils of accumulated timing errors:
https://github.com/p-v-o-s/micropython/ ... 0415a7ed14

The code seems to pass my - admittedly - not very thorough testing. BTW most of my use cases are simple one-line serial commands and replies at low baudrate (9600) and this implementation seems up to that task. I would recommend that anyone who wants to integrate this code into their own projects should do more thorough testing for special use cases.

Post Reply