Close
0%
0%

The Internet of Nixie Clocks

Wherein the tubes of Nix do bridge betwix the worlds of form and ether.
I see no reason one should bridge, but that won't stop me, either.

Similar projects worth following
A Nixie clock is given Internet connectivity.
Or, rationalization # 2452 as to why I am not going to tidy up the garage today.

This project was intended to be a quicky with stuff I had on-hand.  As such, you might prefer to do things differently.

The goal was to see if I could do something 'interesting' with the Nixie clock I have, which also has a serial port.  Just hooking it up to the computer isn't particularly interesting, but somehow putting it on the network does seem more interesting.  Maybe just because I can futz with it when I want to, perhaps via the web, without having to pull out adapters and cables and serial tty programs.

The goal was also to waste this holiday on a project that could ostensibly be done by end-of-day, before I have to get back to work and explain myself.  I mostly fulfilled this goal.  Practically, it will take another day to recover from a hardware mishap (or misapprehension, more like it).  And then of course I can fiddle with the software forever.  But yes, I did successfully waste the day.

luac-cross.zip

Windows-native build of 'luac.cross', with bug fixed.

x-zip-compressed - 198.84 kB - 02/01/2019 at 02:54

Download

  • 1 × Nixie Clock, with serial port I had one on-hand, made by Sparkletube, via www.kosbo.com
  • 1 × NodeMCU dev board I had one on-hand; mine is an ancient 0.9 version (aka 'V1'). But I'm sure the later oness will work, too.
  • 1 × Level Shifter I had on-hand a SparkFun level shifter board. Mine is discontinued, though many others exist -- it's a pretty straightforward design. Plus, I'm not convinced you strictly need this at all, but I'm just paranoid. And using parts I have on-hand is /sort of/ like cleaning the garage; no?
  • 1 × Ball of Kynar wire 'wire wrap' -- whatever! we only have solder here...
  • 1 × 3M double-sided foam tape deliscious!

View all 6 components

  • Improvements - 20190131a

    ziggurat2902/07/2019 at 20:24 0 comments

    Summary

    I finally broke down and deployed an init.lua.  This autoexec's the software at boot time.  I added an emergency bypass mechanism.

    Deets

    Up until now I have been hesitant to deploy init.lua.  This is the 'autoexec' module that causes the program to be loaded on boot.  The reason for holding back is fear of 'bricking' the NodeMCU board with broken, but autoexeced, code.  It wouldn't really have been bricked, but it would be less-than-convenient to recover.

    But the project is pretty stable now, and I thought about adding a simple fail-safe:  Have init.lua check if the 'user' button is pressed.  If it is pressed, skip running the program and fall back to the Lua shell.

    Easy-peasy, or so it seemed.  It turns out that the 'user' button is special in a way.  Even just looking at it on the schematic, you can see it is kind of funky:

    The GPIO 16 (board numbered as 0) can be used as an input to read the button, or an output to light the lamp.  And apparently that's not the end of the story, because it is also used for some other ESP8266 function ('wakeup'?).

    When operated in the straightforward mode of setting the pin as an input (with pullup) and reading it, the button did read as high when not pressed, and low when pressed, but then stayed reading as low even when it was released.  Maybe that's some interaction with the other ESP8266 function.

    Anyway, I worked around that oddity by being super explicit around where I sample the button to restore it to it's prior state.  The result is this init.lua:

    -- if we hold down the user button during boot, we will skip running our program
    gpio.mode(0,gpio.INPUT,gpio.PULLUP) -- you have to go through this sequence
    local nUser = gpio.read(0)
    -- print ( "nUser = "..nUser )
    gpio.write(0,gpio.HIGH)
    gpio.mode(0,gpio.OUTPUT) -- the GPIO 16 (index 0) requires special treatment
    
    if ( gpio.LOW == nUser ) then
        print ( "Skipping running because user button down" )
    else
        -- call invoke the _init on the LFS; this will wire in the LFS in useful ways
        if ( not pcall(node.flashindex("_init")) ) then
            -- no LFS
            print ( "Horror; no LFS _init" )
        else
            --local ok, msg = 
            pcall ( function() return dofile("kosbo.lua") end )
        end
    end

    So, I flashed that and verified it was working.  So now, when I plug in the clock, it will automatically run the program and set the time from the Internet.

    Strictly, I can consider the project 'done' now.  It boots up, connects to WiFi, synchronizes time via SNTP, resets the clock immediately, and then periodically thereafter at 2 AM daily.

    Now it's time for fun oddities.  I am thinking about some web service protocol so that a web app can control it in some way.

    Next

    Some sort of server.

  • Improvements - 20190130a

    ziggurat2902/03/2019 at 19:43 0 comments

    Summary

    Improve the 'call sequencer' such that we can schedule calls even if a call sequence is currently in-progress.

    Deets

    The call sequencer that was produced for the benefit of controlling the Nixie clock has a deficiency:  you mustn't schedule a call sequence when a call sequence is already in-progress.  This would result in interleaved calls, and also consumes more timer resources.

    This currently isn't a problem since the call sequencer is only used for updating the Nixie clock once per day, so there is no likelihood of two sequences coming in at once, however I do want the mechanism to be well-behaved later, when I add a server component to issue arbitrary sequences at arbitrary times.

    Presently, the sequencer takes a sequence (a Lua table) and keeps an index into where we are in the sequence, and a periodic timer is used to issue the call and increment the index.  When we have finished, we destroy the timer.  Through the magic of closures, the timer servicing function captures the index and the call sequence, so nothing needs to be done special there to keep them alive or destroy them when done.

    In this new design, we will instead have a separate sequence object, and we will append to it sequences that are requested.  If there is not existing sequence object being serviced, we will create a new one and kick start the process.  Otherwise, the process is pretty much the same.

    function run_sequence ( sequence, period_ms )
        if ( not sequence or 0 == #sequence ) then return end --silly cases
    
        -- if we have a run sequence in process, merely append these items to it
        if ( _tblRunSequence ) then
            table_append ( _tblRunSequence, sequence )
            return
        end
    
        -- otherwise, kick off the sequence
    
        -- do the first one immediately
        local nIdx = 1
        sequence[nIdx]()
        nIdx = nIdx + 1
        -- if that was it, beat it
        if ( nIdx > #sequence ) then return end
    
        --remaining ones are to be paced out
        _tblRunSequence = table_clone(sequence) -- we make this global
        local pacingTimer = tmr.create()
        pacingTimer:alarm(period_ms, tmr.ALARM_AUTO, function(timer)
            if ( nIdx <= #_tblRunSequence ) then
                _tblRunSequence[nIdx]()
                nIdx = nIdx + 1
            end
            if ( nIdx > #_tblRunSequence ) then
                -- we are completed
                timer:stop()
                timer:unregister()
                _tblRunSequence = nil
            end
        end)
    end

    In this implementation, we create a global object _runsequence which will contain the stuff being serviced.  This object is global, because we need to access it later in subsequent call invocations, and captures won't help us here.  If it exists, we append do it and we're done.  If it doesn't exist, then we clone our given sequence into it, and kick off the timer.  And if the timer finds that it has run out of things to do, it destroys itself, and the global sequence object.  So, if there is nothing going on, all memory should be released for garbage collection.

    One quirk...

    Read more »

  • Improvements - 20190129a

    ziggurat2902/02/2019 at 03:50 0 comments

    Summary

    Armed with the power of LFS, I begin to make improvements.  In this round, I improve resetting the clock to happen once a day, instead of every 17 minutes.

    Deets

    The NodeMCU will keep it's local rtc in sync by periodically performing an SNTP transaction.  When this succeeds (or fails), our code is notified.  As it was, we used the 'success' notification to update the date/time on the Nixie clock.

    This works, however the updating process causes the display to go through a little flashy sequence because that's how it works.  Also, the SNTP module auto-syncs every 1000 sec (16 2/3 min), and this is not changeable by the caller.  So, the clock presently will go through the flashy sequence every 16 2/3 min.  Lastly, the SNTP sync will almost certainly not happen right at the stroke of 2 AM, when changing from daylight to standard time.  I wanted to improve this.

    The first thing was to /not/ always set the Nixie upon SNTP sync.  But I definitely wanted to do that the first time, because that happens right after power up, and the Nixie is surely wrong then.  This was easily done by creating a global variable that is initially 'true', and then immediately setting it to 'false'.  Even better, we can leverage the fact that in Lua the absence of a value is logically the same as 'false', so we simply delete the variable, allowing it's RAM to be reclaimed once it has served it's purpose.

    local bFirstSNTPrun = true -- so we can tell if we need to kickstart it
    
    local function sntp_syncsuccess ( seconds, microseconds, server, info )
    
        local sec, usec, rate = rtctime.get()
        local utcTM = rtctime.epoch2cal(sec)
        local localTM = localtime ( utcTM, TZ )
        print ( "sntp succeeded; current local time is:  " .. 
                string.format("%04d-%02d-%02d %02d:%02d:%02d", 
                localTM.year, localTM.mon, localTM.day, 
                localTM.hour, localTM.min, localTM.sec) )
    
        -- always update the clock on the first SNTP update, because the clock
        -- will be reset on power up and needs this asap.
        if ( bFirstSNTPrun ) then
            periodicClockUpdate(true)
            bFirstSNTPrun = nil -- delete it
        end
    end

    So, the first time sntp_syncsuccess() is invoked, bFirstSNTPrun will be 'true'.  We then update the Nixie clock, and then delete that variable.  The next time sntp_syncsuccess() happens, the variable doesn't exist, which is logically 'false' and the update doesn't happen.  The method periodicClockUpdate() will update the clock and also manage the timer that will cause subsequent updates to happen on a schedule -- namely 'happen at 2 am'.

    The periodicClockUpdate() takes a boolean indicating 'do update the Nixie' or 'don't update the Nixie', and I'll explain that forthwith, but for now obviously it should be 'true' in the one-time invocation coming from sntp_syncsuccess().

    The implementation of periodicClockUpdate() will conditionally update the Nixie from the system RTC (and adjusted to local time), and then use a timer to schedule a one-shot event to cause a new invocation of itself at a later time.  The 'later time' is a relative time, so it is necessary to compute it as:  'the number of milliseconds to the next 2 AM from now'.  This has the added wrinkle that the next 2 AM from now could be sometime later today, or it might be tomorrow.

    Lastly, it was discovered that the timer mechanism in NodeMCU has a maximum period of 6870947 milliseconds.  I don't know where this value comes from, but it is documented...

    Read more »

  • ♫Mem-ry... Not Enough Is Quite the Bind♫

    ziggurat2901/31/2019 at 15:42 0 comments

    Summary

    I set out to do some improvements, but I ran out of RAM.  So I had to make an unexpected improvement in the way of the Lua Flash Store ('LFS') on NodeMCU.

    Deets

    There were several improvements I set out to do, but with each improvement usually comes code, and with code comes RAM.  It turns out I was on the hairy edge of being out-of-RAM as it was, and as soon as I started adding any code, my unit no longer worked due to out-of-memory errors.

    When Lua code is deployed to the NodeMCU board, it is usually deployed as source.  It is stored in a filesystem (SPIFFS), and when loaded, it is compiled on-the-fly to bytecode for the virtual machine.  This is the usual arrangement for Lua programs.  Since compiling takes negligible time (and also due to the non-portability of bytecode across different Lua versions !) few folks compile in the conventional desktop arena.

    But in the embedded arena, the compiler -- fast though it may be -- does take non-negligible memory resources to execute.  In my case, the program had gotten just big enough that the compiler would run out of memory before finishing.

    The next line-of-defense in this situation would be to break up the program into multiple files, and compile them on-device into pre-compiled images (usually with the '.lc' filename extension, but that is not required).  Making multiple shorter files reduces the memory footprint the compiler needs to process a single translation unit.  In a way, this strategy is a human-guided version of a divide-and-conquer tactic.

    This will carry you a long way, but it does mean doing some surgery to your code, and ultimately it will only carry you so far.  And it still uses RAM to load the pre-compiled bits off the filesystem into working memory.

    There is also another feature of the NodeMCU environment that can be used:  the 'Lua Flash Store' ('LFS').  This is sort-of like a filesystem, but not quite, and it holds pre-compiled Lua objects.  These pre-compiled objects have at least two benefits:  1)  they are execute-in-place.  2)  they can contain read-only strings and read-only tables.

    The execute-in-place feature means that you don't have to load the pre-compiled bytecode into RAM to execute it, you can run it directly from where is sits:  in flash.  Also, putting read-only objects like strings in flash is a big help too.  Lua uses looots of strings, and in non-obvious places.  Your function names are a string.  When you call a function, that is a string lookup.  Your member names of structures are strings and involve string lookups.  The Lua runtime goes to great pains to 'intern' these strings, and avoid duplications, but when you've only got 40K of RAM, that stuff still adds up.

    Using the LFS involves more work than fragmenting the code and precompiling chunks, so naturally I chose the more difficult route.  The first exciting difficulty is creating the needed tools!

    Cross-Compiler

    The standard Lua has always included 'luac', which is a tool that just runs the compiler on your Lua source, and dumps out the byte-code to a file, rather than running it.  However, the NodeMCU project uses a modified Lua runtime that allows for objects in read-only memory, and this requires a special build of the 'luac' (called 'luac.cross') that is cognizant of these things.  Additionally, luac.cross packages the result into a form that the runtime can directly 'mount' into the execution environment.  This form is the 'LFS image'.

    For some reason, NodeMCU does not publish built binaries, so if you want to play with LFS you will need to be building from the source.  Also, NodeMCU is very much a Linux/gcc-oriented project, so I was left more-or-less out-in-the-cold on my Windows platform.  [Edit:  I later found out that I was not so out-in-the-cold, but I didn't know that until after I had done the things I...

    Read more »

  • Let's Do the Time Zone Again (it's just a [5 hour] jump to the left)

    ziggurat2901/29/2019 at 21:52 0 comments

    Summary

    I added timezone support, so the clock can display the local time.

    Deets

    NodeMCU doesn't have any timezone support -- what is there is all UTC.  So I'll have to write that myself.  Plus, I will have to deal with summer time/standard time issues.

    Configuration

    For starters, I need to specify in the configuration what timezone we are operating in.  Rather than making somthing up, I decided to use a semi-standard form of stating this information that is one of the POSIX forms for the TZ file.  Details can be found here:

    http://www.gnu.org/software/libc/manual/html_node/TZ-Variable.html

    This will provide the names of the timezone (unneeded), the offsets from UTC, and the rules for when to switch back and forth.  I am using what is called 'format 2' in that document.  Actually, I am simplifying a little bit by not supporting some of the optional bits.

    To begin with, I add another section in kosbo.cfg file; e.g.:

    -- the timezone
    timezone
    {
        TZ = "EST+5EDT,M3.2.0,M11.1.0"
    }
    

    This will necessitate another config handler function timezone():

    -- config function; set the timezone
    function timezone ( tuple )
        print('in timezone...')
        if ( tuple.TZ ) then 
            TZ = parseTimezone ( tuple.TZ )
        end
    end
    

     The timezone is just a string, and that needs to be parsed into useful parts.  This is not too hard in Lua, because we have something akin to regular expressions (called 'patterns'), with capture groups.  As can be seen, I broke that out into a utility function parseTimezone() which returns a struct of the parsed elements:

    function parseTimezone ( tz )
        if ( not tz ) then return nil end
    
        --XXX add optional start end hour? 0-24
        local pattern = "^(%a%a%a)([%+%-]?%d+)(%a%a%a),M(%d+)%.(%d+)%.(%d+),M(%d+)%.(%d+)%.(%d+)$"
        local f, l, c1, c2, c3, c4, c5, c6, c7, c8, c9 = tz:find ( pattern )
    
        if ( f ) then
            --XXX sanity checking
            return {
                stdName = c1,
                stdOffset = tonumber(c2),
                dayName = c3,
                startM = tonumber(c4),
                startW = tonumber(c5),
                startD = tonumber(c6),
                startH = 2,
                endM = tonumber(c7),
                endW = tonumber(c8),
                endD = tonumber(c9),
                endH = 2,
            }
        else
            return nil
        end
    end
    

    Lua patterns do not allow for optional capture groups, so this is why I chose to omit some of the optional parts.  Those can be accommodated, but it will require more code, so it didn't seem worth it at the moment.  The missing optional components allow the summer time offset be something other that one hour ahead of standard time, and also changing the hour when the summer time/standard time switch is made, which is by default 2 am.

    The timezone information is simply stored in a global 'TZ'.

    Adding and Subtracting Time

    Adjusting for the offset is less...

    Read more »

  • Controlling the Clock Programmatically

    ziggurat2901/29/2019 at 00:56 0 comments

    Summary

    Clock control functions have been added, and SNTP synchronization is effected.

    Deets

    Back before my first attempt failed, I had managed to control the clock a little from the serial port.  First we needed to configure it for the correct bit rate.  This was added at the start of the section of the immediately executed code:

    uart.setup(1, 9600, 8, uart.PARITY_NONE, uart.STOPBITS_1)

    According to the Nixie clock's documentation, the following commands exist:

    • digits or spaces  show on numeric display
    • 't'  show time
    • 'd'  show date
    • 'u'  update time or date
    • 'a'  show alarm; cycle through alarms
    • 'i'  increment the numeric display

    These commands have no termination (e.g. CR/LF, etc).  Experimenting, I found that the clock seems to depend on there being some sort of delay to infer that a command has been sent.  For instance, sending a partial display's worth:

    uart.write(1, "5 4 3")
    uart.write(1, " 2 1 ")
    

    will result in "5 4 3 2 1 " being shown, but if there is a delay between those two writes, then the " 2 1 " will overwrite the prior "5 4 3".  How much delay?  Unknown.

    Also, more annoyingly, you need to pace out the digits before doing updates.  For instance, to update the time, you are meant to load the display with the desired time, switch to time display via 't', and then apply the change with 'u'.  However you cannot send the digits followed by 'tu'.  It will ignore those commands.  You need to pace them out, with a little delay before the 't' and another delay before the 'u'.  How much delay?  Unknown; it's not documented as being needed at all.

    As mentioned before, delays via a spin-wait are verboten in NodeMCU, so I will need to use timers in some way.  That's a pity.  (and a PITA; You can come to my PITA pity party!)

    The documentation seems to suggest that you should send a full displays worth of digits at one time, though that doesn't seem to be completely true.  You can send a partial display so long as it starts on the left.

    Also, the 'a' command seems to do nothing on my clock.  Fortunately, I am not going to use the alarm feature.

    Documentation says that other characters are ignored, but you know I had to try.  I found some buried treasure in undocumented commands:

    • 'b'  switches to numeric display, and blanks it.  So, effectively 'display off'
    • 'r'  resets numeric display to all zeros
    • 'g'  toggles some sort of auto increment, and increments once each 8 sec.  I guess this is for testing.

    Making Clock Functions

    I made clock functions in Lua.  Most are trivial, e.g.:

    function clock_show_time()
        --switch to time display mode
        uart.write(1, "t")
    end

    and did the same sort of thing for:

    function clock_show_date()
    function clock_update()
    function clock_blank()
    function clock_zero()

    I also created a clock_send_time() and clock_send_date() function.  The clock documentation did not specify how to send the digits to cause 'u' to update the date or time correctly, but by experimentation, I found that you are expected to place the digits into the expected spots where the clock or date normally have them, and then 'u' will work.  So I created those function by using Lua's string formatting capabilities (roughly similar to a printf()):

    function clock_send_time ( tm )
        --format string for updating time
        local strTimeSet = string.format("  %02d %02d %02d", 
                tm.hour, tm.min, tm.sec)
        --send strTimeSet
        uart.write(1, strTimeSet)
    end
    
    function clock_send_date ( dt )
        --format string for updating time
        local strDateSet = string.format("    %02d%02d%02d", 
                dt.year%100,...
    Read more »

  • Khoding 50ftw4rz

    ziggurat2901/27/2019 at 18:06 0 comments

    Summary

    It is time to write the code to control the Nixie clock, and provide a service to do some things.

    Deets

    Now that the hardware is apparently stable, I can start to work on the software side.  The software in this case is written in Lua, and run in an event-driven execution environment.

    A Little About Lua

    I won't explain Lua too much except to say that it scripted, compiled to byte-code for execution, and has very few fundamental data types (notably number, string, boolean, nil, function, and 'table').  The sole structured data type is the 'table', which is an associative array.  The special case of integer keys is used to realize conventional arrays, but they are meant to be 1-relative.  To me, Lua feels a little bit like Javascript.

    When Lua code is executed, it is immediately translated into a 'byte code' form that can be executed by a virtual machine.  I say 'can be', because some statements such as 'function' are only compiled, and create an object named as declared in the source code that contains it's byte code representation.  Statements at file level are executed immediately.

    Lua scripts can be in files, but they can also be in strings.  A section of Lua code (file or string) is called a 'chunk'.

    A quirk of Lua is that named objects are global by default, unless declared 'local', or in the special cases of parameter names, etc.

    A Little About the NodeMCU Execution Environment

    The execution environment in NodeMCU is a little different than what is more commonly found in Lua environments in that it is intended to be used to define a mainly asynchronous system.  This is similar to NodeJS which was the inspiration, hence the name.  In this asynchronous environment, you try to do as little as possible in sequential steps of execution (that is a synchronous model), and rather break up your activity into a bunch of handlers that will be invoked when relevant events come in.  As such, your program when run really just defines and registers a bunch of handlers, then immediately returns control back to the system.

    This style of authoring can be a little disorienting if you are mostly used to the step-by-step style (i.e. 'synchronous') of coding, but you you will get used to it.  The existing modules are pretty rich, so your code in Lua often is rather small.  But it's definitely not a sequential step of execution from the top of your source file to the bottom.

    My first attempt at an application will by structured like this:

    (No Visio for me, tee-hee.)

    There will be three files:

    1. init.lua
      This is a specially named file that is automatically executed after the NodeMCU board has gotten the Lua environment up, just prior to running the interactive shell.
      You could put your entire program in here, but I'm not going to for reasons I'll explain later.
    2. kosbo.lua
      This will be the program itself.  It will load configuration, declare all the event handlers, utility functions, and have a little immediate code that causes all that to wire together.  This is a fast process, immediately exiting and returning to the system (which will then run the interactive Lua shell on the USB serial port).
    3. kosbo.cfg
      This will contain configuration settings.  Things like my wireless router's SSID and passphrase.

    And that's it!  'init.lua' and 'kosbo.cfg' are simple, so I'll explain them first.

    init.lua

    As mentioned, init.lua is a specially named file that works a little like autoexec.bat of olden days.  You can put your whole program here, but I actually like to put my program in a second file that is invoked from this one.  I find this handy for development.  If I have a bug in my program, I would prefer the board boot to the shell and not run my program.  Then I can manually run my program and see any sort of debugging output on the terminal.  If I had autoexec'ed my program, all that output would be lost by the time I connected the terminal...

    Read more »

  • One More Time, With Feeling

    ziggurat2901/27/2019 at 00:20 0 comments

    Summary

    I pull the project apart and put it back together.  This time it seems to be stable.

    Deets

    I licked my wounds and got over the fact that I would have to pull apart the project to try again with a new board.  Will it blow up this board, too?

    I pulled the project apart, and desoldered the headers of a new board.  But before I started with the new board, I still couldn't help myself thinking about what was it that went wrong with the prior board.  I don't want to repeat the failure.

    My old board was now cleaned up and naked, so I plugged it in one more time.  It was still non-responsive, but I believed that to be due to a bad flash, and should be recoverable.  The tools I was weren't able to reflash it, but I knew that the ESP8266 itself required some magic holding of gpio lines during reset to invoke the bootloader.  At length, I was able to enter the bootloader by diddling these lines and the nRST.  Then the flashing tool I was using was able to write an image successfully.

    After flashing, I connected to it, and got the expected prompt, so that's an improvement.  But not trusting the board, I let it sit for a while.  We'll see if I get the random garbage that I experienced before.

    After about an hour of sitting, I did not see the garbage.  Hmm.  Well, maybe because the radio is off, it hasn't heated up the chip enough.  I wrote^H^H^H^H^H cut-and-pasted a little code to set up the required WiFi parameters, and set it to auto-connect, and wrote them out.  These are in non-volatile memory, so it should happen irrespective of reboots.  I let it sit a few hours longer, demonstrably on the network by it's being able to be ping'ed, and still it seemed stable.  Hmm....

    Well, I don't know what went wrong before, but I am not heartened by the prospect that the board cannot be used in this combination without more 'stuff', and am skeptical of that notion, anyway.  So I did a bad thing, and decided to hook the old board back up to the clock.  /Not/ use the fresh, new board.  I.e., asking for further trouble.

    Before I hooked back up the old board, I did some cogitation on the previous problems.  I didn't seem to do hard damage to the board, but it still could be flaky.  It could simply be that the firmware itself is fundamentally flaky, and that all the hardware fixin' in the world will not be enough.  This does happen (I had a recent 2 mo battle with Google on a similar line), but I doubted it in this case because the NodeMCU project has been released for quite a while, and it's codebase seems to have have simmered down to a 'maintenance and feature' phase rather than a less mature 'architecture and infrastructure' phase.  Still, there could be a recent bug introduced, and my firmware was built off the tip.

    I did occur to me that perhaps there could have been coupling of extraneous signal into... something!  The way I had mounted the board before, I put the level shifter underneath the NodeMCU, and some extraneous wire wrapped around the side, underneath the PCB antenna.  That is radiating out power and the wire could make for a great receiving antenna.  Maybe.  Also, where I mounted the board is close to the high-voltage line, which is pumped at 300 KHz by the switching regulator.  So, maybe.

    It did also occur to me that the bypass caps on the power supply might be inadequate to take power of questionable cleanliness.

    At any rate, I went ahead and rewired everything, but changed the layout.  I moved the board towards the end, away from where the high-voltage goes the the Nixie board.  I also pulled the level shifter out from under the NodeMCU board, and stuck it separately on the clock board.  I also added a .1 uF bypass cap to the power tap point I was using.  While I was at it, I also put a piece of foam tape over the high-voltage pins.  I was bored with...

    Read more »

  • Eternal Darkness

    ziggurat2901/26/2019 at 20:45 0 comments

    Summary

    about 6:00 pm, Monday, January 21, 2019

    Something has gone wrong, and my NodeMCU board may be bricked.  I have spares, but I will fail to get the project done in one day, as I had hoped.

    Deets

    While fiddling with the commands and starting to plan the Lua code for the server, something started going wrong.  Initially serial data was not being responded to, and then the terminal connection locked up.  I rebooted the board and reconnected, but very shortly afterwards random garbage data was being sent to the terminal.

    So now I have to figure that out.  Could it be:

    1. the 9 V is just way too much for the input of the NodeMCU regulator, and it failed somehow?  It's rated for a max input of 20 V, but I wonder with the current needed for the radio that maybe it is overloading?  It doesn't feel hot to the touch, though.
    2. the board has become flaky somehow.  Maybe I zapped it somehow while playing with it or assembling or doing my electrical tests.
    3. something else...

    I tried reflashing, and then the flash operation failed midway, so it seems I have also bricked the board.  I have two more boards, but I have exhausted the time today for this project, so I failed to get it done in one day as I had set out to do.  *sigh*  Thems the breaks.  So further work will have to wait until the weekend.

    Next

    Cry a little.  Then start over.

  • Oh, What Tangled Wires We Weave

    ziggurat2901/25/2019 at 23:09 0 comments

    Summary

    about 4:00 pm, Monday, January 21, 2019

    Soldering stuff together.  Out of fear of destruction, this was done in steps, testing along the way.

    Deets

    First, the NodeMCU board has pin headers soldered on (or at least mine do).  These aren't handy for me, so I desoldered them.

    As mentioned before, it is necessary to tap off a component on the NodeMCU Dev board for power (in), since the desired point is not brought out to the headers.  Fortunately, there is a big fat diode that is somewhat easy-ish to solder a lead to.  Not super easy, though!  It is a power diode, and is thermally conductive.  On my first attempt, the heat from the iron on one lead conducted quickly to the other and the diode went flying off the board.  Ugh, now I have to do some SMT soldering to put it back in place, and solder the originally intended wire.  Oh, well; this is what happens.  Be quick, and flux helps in being quick.  I find a 'flux pen' useful to paint on a little flux in magic marker fashion useful in a pinch without making too much mess.

    Also, be fully aware that the large capacitor next to the diode is NOT directly connected to that diode as you might guess from the layout.  Do NOT bridge those two.

    The ground lead was straightforward.

    Not exactly pretty, but after having flung the diode off before, I am a little iron shy.  The joint is not as cold as it looks.  I wouldn't ship it so someone else, though.

    OK, having gotten the pieces prepared, it was time to assemble it, somehow.  Since there were no plans beforehand, I am winging it by using ye olde double-sided foam tape to stick the parts onto the back of the clock's PCB.

    I decided to stick the shifter onto the back of the NodeMCU Dev board:

    and this gave me an idea:  to build a 'standoff' from multiple layers of foam tape:

    This should allow me to have some clearance away from the clock PCB, which has all sorts of electrical points that I don't want to touch the module.  I found a place to put the module and stuck it on:

    You can see the clearance from the top:

    With the things mounted in place, it was time to connect the wires and test.  I am really paranoid about making a mistake and blowing up something.  The Nixie board has 175 V on it (and boy, do I know what that feels like!), so one false connection or accidental shorting would be all it takes.  Additionally, I will be connecting this to my desktop system via USB, and I most definitely do not want to destroy that.  So I do this in steps.

    First, some sanity checks before proceeding with intended connections:

    1. I connect the NodeMCU board to a USB charger.  No power to the Nixie, and no computer connection.  OK, no 'magic smoke', and some blue lights on the board blink in a familiar way.  I cycle power, and I still get the familiar blinking lights.
    2. I connect NodeMCU board to USB to computer.  The board boots and I can connect and execute some simple Lua commands on it 'Hello, World!' style.
    3. I disconnect computer, and just power on Nixie.  The clock runs as expected.
    4. I disconnect the clock power, and reverify the NodeMCU board on the computer.  Still working, yay!
    5. I disconnect computer, reconnect USB charger and Nixie power.  Nothing is burning yet.

    So that gives me confidence that there aren't any accidental connections between the boards.  Now for the first stage of coupling them together:

    1. I connect the clock's ground and 5 V to the level shifter's 'high voltage' side.
    2. I connect the level shifter's data to the clock's serial input.

    Since I now have electrical connection between the boards, I do a mini 'smoke test' with the Nixie power and USB charger power.  This time there is random data shown on the clock -- no familiar clock display!  Yikes!  After that moment of fright passes, I remember that the way the clock handles the serial data is that as soon as it receives data, it...

    Read more »

View all 13 project logs

Enjoy this project?

Share

Discussions

Similar Projects

Does this project spark your interest?

Become a member to follow this project and never miss any updates