• To cut to the chase:

    Question: What do I do wrong that Espruino Pico's

    Serial1.setup(300, {rx:B7, tx:B6, parity:"o"});
    

    gets nothing for me but framing and parity errors?

    To be more helpful, first some TL;DR:

    I'd like to reuse - and showcase the device - but being it in the context and connectivity of more recent peripherals.

    For exploration and possibly future interfacing, I picked Espruino Pico, because I have access to the device's TTL signal levels before it's fed into RS-232 drivers that do the typical -12V/+12V business or into FSK (Frequncy Shift Keying) communication over regular copper phone lines as of that time for remote operation. Commisurate with the means at that time is the speed: about 300 baud or 300 bps (bits per second) - Baud from French Baudot 5-bit code, invented 150+ years ago for telegraphy...

    Espruino and Pico are a good match, for easy coding and quick to apply, and has TTL / 5V signal compatibility. Espruino software supports Serial communication with configuration option for number of data, parity and stop bits.

    Unfortunately, for yet unknown reason, the code below

      Serial1.setup(300, {rx:B7, tx:B6, parity:"o", errors:true});
      Serial1.on('data', function (data) { print(data); });
      if (errs) {
        Serial1.on('parity',  ()=>console.log('P'));
        Serial1.on('framing', ()=>console.log('F'));
      }
    
    

    prints nothing but Fs for framing and Ps for parity errors for a block of 0x20 0xAA and another block of 0x20 0x55. Looking a the signal timings, it shows that a bit is around 0.003148 seconds, yielding a rate of 318 bps. 18 is about a 6% error, over the 10-1/2 sampling times this makes worst case 63% 'off-beat'. Center sampling can at best handle a 40%..45% off-beat situation, so no surprise it failed... But I'm in for a greater surprise: even with setup adjusted to 318 bps, the result is equally depressing: PF.

    Since the 40+ year old device is so slow, I just used Pico and a few lines of code to find the timings including baud rate AND visualization of the signal shape... The code looks like this:

    var baudFindWId = null   // baud find watch ID
      , ths, lst             // this and last bit's Espruino getTime()
      , SOD = B7             // USART1 B7 rx - watching device's Serial Out Data
      ;
    function findBaud() {
      unfindBaud();
      pinMode(SOD,"input");
      lst = getTime();
      baudFindWId = setWatch( function(s) {
          console.log(
              ( (s.state)
                   ? "|_"         // low signal for...
                   : "_|" )       // high signal for...
            , (ths=getTime())-lst // ...t sec(s) before switching to high, rsp low
            ); 
          lst=ths;
        }, SOD, {repeat:true, edge:"both", bounce:10} );
      log("made SOD watched");
    }
    function unfindBaud() {
      if (baudFindWId) { 
        baudFindWId = clearWatch(baudFindWId);
        log("made baud unfound");
      } else {
        log("baud is (already) unfound");
      }
    }
    

    The device was setup to transmit this sequence of 0x20 0x##s bytes (ignore var t=... js, it overcome render challenges of fixed font text blocks in forum... how to <pre> w/ line # but w/o coloring?):

    var t=`
    Byte# 0x##: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F 10 .. 1f
    Value 0x##: 00 01 02 04 08 10 20 40 80 00 00 00 FF FF 0F F0 00 .. 00
    CRC   0x##: FC
    ;`
    

    The generated out put in console looks like this:

    var t=`
    _| 9.82303428649 // <--- A) idle, since setup / before tx and
    |_ 0.02833652496            before start of 1st byte 0x00
    _| 0.00949668884 // <--- B) before start of 2nd byte 0x01
    |_ 0.00314712524
    _| 0.00315475463
    |_ 0.02518844604
    _| 0.00633430480 // <--- C) before start of 3rd byte 0x20
    |_ 0.00630855560
    _| 0.00314426422
    |_ 0.02202987670
    _| 0.00633335113 // <--- D) before start of 4th byte 0x40
    |_ 0.00944900512
    _| 0.00314807891
    |_ 0.01888561248
    _| 0.00635814666 // <--- E) before start of 5th byte 0x80
    |_ 0.01259136199
    _| 0.00315093994
    |_ 0.01572895050
    _| 0.00633239746 // <--- F) before start of 6th byte 0x10
    ....
    ....
    _| 0.00948810577
    |_ 0.02837276458
                      // <--- G) unknown idle time after transmission
    `; 
    

    Since in above 'graph' the signal level line show NOT to time, I manually extended them according the times measured. I commented start bit, data bits 0 thru 7, odd parity bit, stop bit and the idle or byte separation 'bit'. With the extension, the signal lines make it very obvious what is going on:

    var t=`
    Signal Level TTL Low 0
    |Signal Level TTL High 1
    || Bit type or value (start, value, stop, idle/'space'(:))
    || | Bit # 0 (LSB) thru 7 (MSB)
    || | |     Byte# - last is CRC - and Byte value 0x##
    || | |     |     seconds of equal signal level
    || | |     |     |              (n) number of average
    || | |     |     |              |   time units of one bit
    VV V V     V     V              V
     | :
    _| :          
    |  start   #00
    |  0 0
    |  0 1
    |  0 2
    |  0 3
    |  0 4
    |  0 5
    |  0 6
    |_ 0 7     00    0.02833652496 (9)
     | odd
     | stop
    _| :             0.00949668884 (3)
    |_ start   #01   0.00314712524 (1)
    _| 1 0           0.00315475463 (1)
    |  0 1
    |  0 2
    |  0 3
    |  0 4
    |  0 5
    |  0 6
    |  0 7     01
    |_ odd           0.02518844604 (8)
     | stop
    _| :             0.00633430480 (2)
    |  start   #02
    |_ 0 0           0.00630855560 (2)
    _| 1 1           0.00314426422 (1)
    |  0 2
    |  0 3
    |  0 4
    |  0 5
    |  0 6
    |  0 7     02 
    |_ odd           0.02202987670 (7)
     | stop
    _| :             0.00633335113 (2)
    |  start   #03 
    |  0 0
    |_ 0 1           0.00944900512 (3)
    _| 1 2           0.00314807891 (1)
    |  0 3
    |  0 4
    |  0 5
    |  0 6
    |  0 7     04
    |_ odd           0.01888561248 (6)
     | stop
    _| :             0.00635814666 (2)
    .....
    .....
    .....
    |_ 0.02839469909 (9) [#1D](https://forum.espruino.com/search/­?q=%231D)
    _| 0.00951004028 (3)
    |_ 0.02837276458 (9) [#1E](https://forum.espruino.com/search/­?q=%231E)
    _| 0.00948810577 (3)
    |_ 0.02837276458 (9) [#1F](https://forum.espruino.com/search/­?q=%231F)
     | odd
     | stop
    _| :             0.01068019866 (3+...) - prep CRC
    |  start  CRC
    |  0 0
    |_ 0 1           0.00946521759 (3)
     | 1 2
     | 1 3
     | 1 4
     | 1 5
     | 1 6
     | 1 7    FC    
     | odd
     | stop
     | :
     | :
    `;
    

    End of TL;DR and back to the question:

    What is wrong with my setup of Serial1?

    Bitbanging shows that the device produces what it states... with the minor adjustment of the baud rate (which btw does not really matter, since the partner device is of the same make in hard and software (but I consider changing the device's code to be as close as possible to the 300 - or configurable - bps... after all, it does bit banging too, but no unpredictability by garbage collect or interrupts...). Bitbanging will also get me reading the device, but 40+ and the 32 Bit ST ... what ever should have outgrown that need... after all.

    The device in today's money is about $1120... $290 then... Espruino Pico today cost about $25. Considering these numbers just from a point of money, would it be fair to conclude: You get what you pay for ? - The answer to that is an easy: no.

    What do I do wrong in my Serial1 setup and use?

  • I'm not a expert, but my first question would be can the Pico uart driver even go that slow? I know hardware drivers have to do clock dividers to get to a certain frequency and that might be lower then the ranges it can work in.

    But you could try and take a jumper from your Pico TX uart lines and connect it to your RX lines. Thus created a loop in your communication. It should be a quick test to see if the device can actually produce and receive that range. If it can't i would hook up a oscilloscope and see the timing. It might be slightly off.

  • @ser156811

    First, thank you for reading thru and sharing your thoughts.

    Regarding speed, I had a similar thought: can Espruino Pico - the serial appliance of the STM32F401CDU6 - go so low down to 300 bps, but did not think about the divider issue... I should though have taken that though serious, since I know how USARTs (chips of same age) have to be configured...

    I also had the thought of using Espruino firmware's software serial... but it may have the very same issue. After all, it uses the same basic hardware: some timer(s), relaying on the same counters...

    There are server options for a next step before more serious actions have to be considered:

    1. Change Serial1 to a software new Serial()... very easy to do
    2. The device has also an output only serial routine using similar bitbanging hardware and software, but with configurable baud rate, and it usually ran on 1200bps and can go up to 400bps. Disadvantages are: has no parity, no CRC (checksum), and disabling for full control of the device: it has output ONLY. The input is only for checking DTR.
    3. Making changes to the firmware of the device, posing several sub-options. The devices hardware is though too weak for a reasonable implementation of a configurable, bidirectional serial communication to implement serial with XON/XOFF flow control. Basically: there is no independent time or interrupt control used (and implemented in the devices monitor).

    Hooking on a scope is an option... which I did 40+ years ago when validating the devices capabilities and putting it to service. Time fluctuations according to Espruino Poco's measurments are at it's most extreme in the range of 0.003152 thru 0.003157 (time of 1 bit), which is less than 0.2%. With that, I can comfortably exclude accuracy issues, but for sure not speed range ones.

    New results will be available soon.

  • @ser156811

    Regarding hooking up scope: mentioned only the device. But hooking it to Espruino Pico USART1 and transmitting something with 300 (or 316) bauds would also provide new information, mostly so if it will go down that low...

    @Gordon mostlikely can answer that right away and would also know how accurate timing is across the bandwidth. I know that at very high frequencies differences between bit times are very course grain (see Espruino controlling LC resonance experiment in HAM Radio class, in particular, post #4).

  • Using software serial moved me ahead a bit and may even be the solution...

    Minimal change of the routine I had for hardware serial:

    function receiveSOD() {
      var errs = true
        , serial = Serial1
        ;
      serial = new Serial();
      serial.setup(316, {rx:SOD, tx:SID, parity:"o", errors:errs });
      serial.on('data', function (data) { print(data); });
      if (errs) {
        serial.on('parity',  ()=>console.log('P'));
        serial.on('framing', ()=>console.log('F'));
      }
    }
    

    Console output for 0x0E byte sequence of:

    Byte# 0x## 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E
    Value 0x##: 00 01 02 04 08 10 20 40 80 FF 00 0F F0 55 AA
    VALUE dec.: 0 1 2 4 8 16 32 64 128 255 0 15 240 85 170

    var t=`
    >
    
    
    
    
     
    @
    
    ÿ
    
    ð
    U
    ª
    ü
    >
    `;
    

    With ü as being the CRC / checksum.

    Creating a version that prints the data in decimal to show non-printable
    characters - changing print(data); to print(data.charCodeAt(0)); -
    shows the expected (values in) console:

    var t=`
    >
    
    0
    1
    2
    4
    8
    16
    32
    64
    128
    255
    0
    15
    240
    85
    170
    252
    >
    `;
    

    But as @Gordon pointed out in USART / UART / Serial Port for software serial, there is not much processing cycles left for other things while communication is going on, such as my attempt to print the received data in hex using my generic approach, which also can handle reception of more than one byte at a time. Here the changes and the versatile (hex) output creation (complete, runnable code to load):

    // wire connections (in code use ums view names)
    //  ums=pico view | ums view || ums              pico
    var SOD=B7   // I | SOD   O  || serial output on USART1 SERIAL1 RX (B7) I
      , SID=B6   // O | SID   I  || serial input  on USART1 SERIAL1 TX (B6) O
      ;
    
    function setPinModes() {
      //      ums - pico view
      pinMode(SID,"output");
      pinMode(SOD,"input");
    }
    
    function hex(v) { return ((v>15) ?
      hex(v>>4) : "")+"0123456789ABCDEF".charAt(v&15); } // hex...
    function hep(v,ds) { // hex, digits (w/ blank padding up to ds)
      return ("        "+hex(v)).substr(-ds); }
    function lep(v,ds) { // hex, digits (w/ 0 padded up to ds)
      return ("00000000"+hex(v)).substr(-ds); }
    
    function receiveSOD() {
      var errs = true
        , serial = Serial1
        , opt
        ;
      serial = new Serial();
      serial.setup(316, {rx:SOD, tx:SID, parity:"o", errors:errs });
      opt = // 0 // 0 or any: just print as received (as initialy)
            // 1 // most unasumptional, flexibel hex output
            // 2 // assuming 1 byte at a time reception hex output
               3 // assuming 1 byte at a time charCode (dec) output
               ;
      serial.on('data'
        , (opt==1) ? (function(data){ 
           for (i=0; i++; i<data.length)
             print(lep(data.charCodeAt(i),2)); } )
        : (opt==2) ? (function(data){ 
             print(lep(data.charCodeAt(0),2)); } )
        : (opt==3) ? (function(data){ 
             print(data.charCodeAt(0)); } )
        : (function(data){ print(data); }) );
      if (errs) {
        serial.on('parity',  ()=>console.log('P'));
        serial.on('framing', ()=>console.log('F'));
      }
    }
    function onInit() {
      setPinModes();
      receiveSOD();
    }
    
    setTimeout(onInit,999); // dev only; remove before upload for save()
    

    With output option 1, the console output was absent... (a simpler implementation for 2-digit hex output may have worked, but was not done and tried).

    For that reason, I built a buffering into/around the code with a routine for visualization of the buffer... shown in a next post, including the Espruino js Bitbanging solution (not using any appliances (except the timer/interrupts... as is used by setWatch, setTimeout, setInterval for that purpose).

  • Glad you got this working with software serial.

    I just checked the code for serial on STM32 and we call through to the STM32 API and that would appear to be fine.

    However, checking on a scope, just calling Serial1.setup and printing something, I can see that as soon as you get below about 330, the baud rate goes completely wrong - I believe the baud rate register is overflowing and ST's API never does any kind of range checking.

    So you have two options:

    • Just use a baud rate of 330 - as you're having to use 316 on software serial, it's possible using 330 may work fine.
    • You can actually lower the clock rate of the UART so that then it can work without overflowing its counter. Run E.setClock({M:8, N:336, P:4, Q:7, PCLK1:2, PCLK2:8}) before you set up the UART and I believe that'll fix it!
  • @Gordon,

    thanks for taking the time to read and respond... I'll give it a shot, last but not least because I'm not successful yet with 'playing back' the received data, which means that either Espruino is not producing the right bit stream or the modulation/demodulation in the original case added something that was compensated in the device's receiving software.

    Questions - answered them myself by reading the API doc... haha...:

    1. Is the E.setClock(...) also good for Serial2/USART2? - switched over to Serial2 from Serial1
    2. What is the impact of changing this for any other serial communication?

    Btw, I noticed that the protocol is two (2) stop bits... (explaining the 'gap' of one bit between the bytes received).

    I noticed that I never shared the code that does super-soft software serial in JS... Espruino JavaScript is so fast and efficient, that it can handle the 316 baud rate...

    Main line is:

    • have a single object for control and data to minimize scope for / optimize var look up (rather than have individual globals or a bunch of locals w/ multiple parameters passings - does it matter? @Gordon can debunk by thoughts...)
    • setup to wait for first start bit in wSB(), start flank
    • sample start bit sSB() 'half a bit later' (or a tad shorter - 1.47 instead of 1.57 ms... to not be/start late for sure... also trying to compensate not only for the setup of setWatch() but also for the setup of setInterval()... have though no clue if it and what value makes sense... I'm just thinking)
    • keep sampling remaining bits - mainly data bits - in sDB() in intervals of 'a bit later' until including all bits of a byte are done: data, parity and stop bits. Also handle / check parity and keep only 'good' bytes. Key concept used is the bit weight .w that controls everything... (state of 'state machine').
    • start over with waiting and sampling until done - stop stopRecSOD() is invoked from console (when device's transmit LED stops flickering), which sets a flag to not watch for stop bits anymore and clear eventual watch, but let eventual sampling finish.
    • do hex dump for validation of bytes receive after waiting at least for a byte

    Here the code (a bit formatted for legibility...):

    // SWSerialInEspruinoJS.js
    // 20231228ao
    //
    var u; // = undefined constant
    var lon = true; // false; // true; // logging is on
    function log() { if (lon) { console.log.apply(console,arguments); } }
    
    // wire connections (in code use ums view names)
    //  ums=pico view | ums view || ums              pico
    var SOD=A3   // I | SOD   O  || serial out on USART2/1 SERIAL2/1 RX (A3/B7) I
      , SID=A2   // O | SID   I  || serial in  on USART2/1 SERIAL2/1 TX (A2/B6) O
      ;
    
    function setPinModes() {
      //      ums - pico view
      pinMode(SID,"output");
      pinMode(SOD,"input");
    }
    
    function hex(v) { return ((v>15) ?
      hex(v>>4) : "")+"0123456789ABCDEF".charAt(v&15); } // hex...
    function hep(v,ds) { // hex, digits (w/ blank padding up to ds)
      return ("        "+hex(v)).substr(-ds); }
    function lep(v,ds) { // hex, digits (w/ 0 padded up to ds)
      return ("00000000"+hex(v)).substr(-ds); }
    
    var sod =
    { l: 2056 // length of d
    , d:   [] // bytes as u8int array
    , f:    0 // fill level of d
    , wId:  u // stop bit watch id
    , iId:  u // data / parity / stop bit interval id
    , byt:  0 // byte
    , w:    0 // bit weight
    , pC:   0 // parity calculated
    , pR:   0 // parity received
    , pEC:  0 // parity error count
    , fEC:  0 // framing error count
    , r:    0 // running (status)
    };
    sod.d = new Uint8Array(sod.l);
    
    function startRecSOD() { // uses:
      // wSB(): Watch SOD Start Bit
      // sSB(): Sample SOD Start Bit
      // sDB(): Sample SOD Data Bit and others too, incl parity handling
      // sPB(); Sample SOD Parity Bit - part of sDB() - no need, included in sDB
      // sTB(); Sample SOD sTop Bit - part of sDB() - no need, included in sDB
      if (sod.d.length !== sod.l) sod.d=new Uint8Array(sod.l);
      sod.f=0; sod.pEC=0; sod.fEC=0; sod.r=1;
      wSB(sod); // 314,157 ms full bit time / half bit time
    }
    
    function stopRecSOD(sAddr) {
      sod.r=0;
      if (sod.wId) sod.wId=clearWatch(sod.wId);
      setTimeout(function(){
          // console.log(new Uint8Array(sod.buffer,0,_.f));
          console.log("...stopRecSOD.");
          var arr=[], prtd=0, cols=16, addr = sAddr || 0;
          console.log( lep(addr,4) + "..."
                     + lep(addr+sod.f - 1 - 1,4) + " + "
                     + lep(sod.d[sod.f - 1],2) + " check byte: "
                     + sod.f + " bytes ok, "
                     + sod.pEC + " parity errs, "
                     + sod.fEC + " framing errs"
                     );
          while (prtd<sod.f-1) { arr=[]; cols=16;
            arr.push(lep(addr,4)); arr.push(":");
            while (--cols>=0 && prtd<sod.f-1) {
              arr.push(lep(sod.d[prtd++],2)); addr++;
            } console.log(arr.join(" "));
          }
        },11*3.14);
    }
    
    function wSB(_) {
      _.byt=0; _.w=1; _.pC=1;
      if (_.r) {
        _.wId=setWatch(()=>setTimeout(sSB,1.47,_­)
           ,SOD,{repeat:0,edge:"falling"}); } }
    
    function sSB(_) {
      _.iId=(!digitalRead(SOD))
              ? setInterval(sDB,3.14,_)
              : wSB(_); }
    
    function sDB(_) {
      var b=digitalRead(SOD);
      if (_.w<256) {
        if (b) {
          _.byt+=_.w;
          _.pC^=b;
        }
        _.w=_.w<<1;
      } else {
        _.w=_.w<<1;
        if (_.w>512) {
          clearInterval(_.iId);
          if (b) {
            if (_.pC===_.pR) _.d[_.f++]=_.byt; else _.pEC++;
          } else {
            _.fEC++;
          }
          wSB(_);
        } else {
          _.pR=b; } } }
      
    function onInit() {
      setPinModes();
      startRecSOD();
    }
    
    setTimeout(onInit,999); // dev only; remove before upload for save()
    

    Here the console output:

    var t=`
    |  __|___ ___ ___ _ _|_|___ ___
    |  __|_ -| . |  _| | | |   | . |
    |____|___|  _|_| |___|_|_|_|___|
             |_| espruino.com
     2v19 (c) 2021 G.Williams
    >
    startRecSOD...
    >stopRecSOD(0x800);
    =undefined
    ...stopRecSOD.
    0800...080E + FC check byte: 16 bytes ok, 0 parity errs, 0 framing errs
    0800 : 00 01 02 04 08 10 20 40 80 FF 0F F0 00 55 AA
    > 
    `;
    

    The code borrowed ideas from the device's implementation of reading serial input. Something next in line to simulate input for.

    'Formatted' code looks a bit bloated... here the origin:

    function wSB(_) { _.byt=0; _.w=1; _.pC=1; if (_.r)
      _.wId=setWatch(()=>setTimeout(sSB,1.47,_­)
        ,SOD,{repeat:0,edge:"falling"}); }
    function sSB(_) {
      _.iId=(!digitalRead(SOD))?setInterval(sD­B,3.14,_):wSB(_); }
    function sDB(_) { var b=digitalRead(SOD);
      if (_.w<256) { if (b)  { _.byt+=_.w; _.pC^=b; } _.w=_.w<<1;
      } else { _.w=_.w<<1; if (_.w>512) { clearInterval(_.iId);
        if (b) { if (_.pC===_.pR) _.d[_.f++]=_.byt; else _.pEC++; 
        } else { _.fEC++; } wSB(_); } else _.pR=b; } }
    

    Now working on reading the data back: sending the data back to device as received before and stored in a 'file'. Even Espruino Software Serial is not producing a serial data stream accepted by the device. Playing w/ the 330 baud rate and E.setClock(...) gives me some hope until I will take out the 'big guns', like DSO... (My pride to be frugal in material but wasteful in thinking and time gets in my way for moving speedy... but it is somewhat the goal: understanding what's going on and where 'it' leaves the 'happy path'... (I'm part of the 'it'... haha...)...)

    ...so much for (late) Happy New Espruino Year 2024

  • @Gordon,

    tried both... using 330 for baud rate failed no matter what - w/ and w/o E.setClock(). But the combination of lowering the clock rate of the UART using E.setClock({M:8, N:336, P:4, Q:7, PCLK1:2, PCLK2:8}); and 316bps did the job (on Serial1/USART1).

    What properties and values do I have to use in E.setClock(...) for using Serial2/USART2?

  • What properties and values do I have to use in E.setClock(...) for using Serial2/USART2?

    USART2 is on PCLK1 according to the reference manual, so you'd need to use PCLK1:4 to halve the clock rate of that.

    I can't be sure where USB gets its clock from, so there is some possibility that lowering the PCLK1 speed will break USB - I'm not sure

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Exploring the serial I/O of a 40+ year old uP / MPU / SBC / TTL device

Posted by Avatar for allObjects @allObjects

Actions