This total reimplimentation of the split flap logic uses c++ 23 object oreinented abstractions with the Raspberry Pi Pico 2W. It will not work on any Arduino microcontrollers
WARNING: The Pico is a 3.3V microcontolller and can only use 3.3V logic. Do not connect to 5V input logic directly. 5V output logic may work NOTE do not expect to be able to easily solder the Pico chip directly to a curcit board due to the package type
- Uses modern c++ 23 object orientated abstractions
- Single microcontolller needed
- Abstraction proides flexability
- Wide motor support (software only)
- I/O Flexability (software only)
- Reduces comonents, connectors, and PCB size
- Seperate Virutal display support for large displays
- Improved accuracy
- Impliments a cron based sceduling system (backend delay is a priority queue)
- Impliments a priority system
- Manages overflow smoothy like a ticker
- Automatic formatting of date time templates
- Automatic allignment
- HomeAssistant compatability via MQTT
- Confuguration via JSON
The Pico 2W is used as it is one of the best microcontollers controllers avalable for cheap. The Pico is much faster than the Arduinos, has much more memory, has file system capabilities, and has Programable Input Output.
The single microcontroller eliminates the needed I2C or any other protocol to comunicat between controllers. This results in a simpeler and smaller PCB with overall cheaper components The PCB avoids more and dificult components to solder. For example instead of custom logic level shifters, it uses an IC. This creates an easier to assemble and overall cheaper control logic
The abstraction done with object orientated programming makes it easier to perform complex tasks and make it more flexable.
Due to the motor abstraction different motors can be used for the same display. This is not recomended as motors of different coil ammounts cannot be used on the same output abstraction. The implimentation assumes the first one is the same amount of coils for them all.
Input/Output abstraction allows the use of any motor control method. Including shift registers and muxes beeing the default as they are cheep It is posible to mix and match I/O in the split flap sections
The SplitFlap is treated a if it were just a text display. This allows simplified display of text.
The controller can controll all the motors in a non-blocking loop. Meaning only one Raspberry Pi Pico 2W is needed for a large amount of flaps, formating text, WiFi logic, and scheduling. All flaps able to display text smoothly in paralell. Because of this I2C comunication is not used, but feel free to add it if you need it.
No naïve assumptions that hall effect sensors can be read on the same pin without a mux. No blocking loop preventing smoth and paralell display of text. No weak, slow, and overly expensive controllers used that hamper the smoothness of the split flap.
The PCB impliments a direct connection using extention boards and pass though connection for the hall sensors. This removes the need for daisy chaining JST connectors. It also allows for longer displays and voltage injection in the extentions
The display allows for seperate split flaps sections to be created using the same controller. These displays act independently from eachother implimenting their own DisplayArbiter for priority. This includes allowing characters to be shifted out at the same time on seperate flaps. This makes it easy to add score boards or other independent dispalys as if they were controlled by a seperate controller. Simply point to the desired split flap. A list MQTT topics allow for the directed display of messages to a specific section.
The original code does not allow for a per flap calibration offset. This can cause flaps that are just a little off if the magnet is placed just a little off.
Note: the original code only allows one value to be used but no easy way to write to the EEPROM.
The program impliments hall effect sensor hiatius calculation and offset You will notice the split flap takes longer to initalize due to this. Hiatius is the middel of the hall effect sensor on activation range. So a devation of the standard range of the hall effect sensor does not effect the flaps. TODO impliment a tracking of the hiatus delta over time to detect issues with the hall sensors
TODO The program is mostly backwards compatable with the arduino slave system. However, you will be loosing some calibration features. This results in a $8 + S&H to upgrade the original split flap https://www.adafruit.com/product/6315
The designed pcbs will require modification of the back pannel so that it does not intersect.
The program impliments a DisplayArbiter and a Scheduler to ensure seamless desplaying of messages based on priority as soon as posible. Reducing missed messages when flooded with MQTT requests
The Scheduler allows messages to be displayed at a given time, say a clock will be displayed every minute. Thanks to formating a date time template can be defined and evaluated every time the message is scheduled.
For example a clock that updates every minute can be implimented as the folowing. Using seconds is not recomeneded due to the slow nature of the split flap:
{
"cron": "0 1 * * * *", #For every minute at second 0
"text": "{:%I:%M %p}",
"cmd" : "^", #Auto center the text
"priority": 0, #Allow it to be overwritten by other messages
"discard" : true #So the message does not display later
}06:32 PM
//TODO
All scheduled messages are saved to cron.json so the tasks are perserved on restart.
This part impliments a priority system for the messages. If the current message is a lower priority, say just a clock. An incomming message will discard the clock messages until the message is done. If the message has the same priority it will replace the prior message The implimentation allows messages to queue in the display if the display is beeing flooded with requests if not set to be discarded. After a defined period the message will go stale and be discarded.
The split flap display can deal with the message if it is too long. Simply by shifitng the letters in over time to the display.
S: Shift the letters out by one characterQ: Queue each part that fits on the display at a timeD: Discard the extra characters //TODO
For example:
MESSAGE 123456789 with Shift mode S, 18 characters will display like this on a 12 character display
MESSAGE 123
ESSAGE 1234
SSAGE 12345
SAGE 123456
AGE 1234567
GE 12345678
E 123456789
The same message with Queue mode Q will display like this:
MESSAGE 123
456789
With discard D the message would be
MESSAGE 123
There is no need to manualy format the given message to fit the display.
To align the message simply insert the alingment controll characters into the cmd part of the message.
<: Aligns to the left^: Centers the message : DEFAULT>: Aligns to the right
If the message has unique characters that only some flaps have it will try to find a valid position.
For example if you want the degree symbol ° on some flaps.
For example for a flap setup of xxxx°xxxx°x can be sent with message of 73°F/23°C to easily display 73°F/23°C.
Keep in mind the current logic does not modify the center of the text so a message of 73°F/!23°C will fail to display correctly.
The display allows date and time templates to be used that is evaluated at display time.
This is how the clock and date is implimented.
Note: It must be scheduled to correctly display the correct date or time.
For example the clock needs to be updated every minute at second 0 for the time to be correct
Must be wraped in { }
See https://www.w3schools.com/cpp/ref_ctime_strftime.asp for more details
For example a date shown every half hour can be implimented as the folowing:
{
"cron": "0 0,30 * * * *",
"text": "{:%I:%M%p ON %m-%d-%Y}",
"cmd" : "^",
"priority": 0,
"discard" : true
}10:30AM ON 01-06-2026
05:00PM ON 01-10-2026
The display impliments MQTT to suport home assistant with JSON
{
["cmd": "<std::string: [S/Q]: Overflow managment [</^/>]: Alignment DEFAULT: ^>",]
"text": "<std::string: The message to display>",
["section" : "<uint8_t: section of the display to use DEFAULT: UINT8_MAX>",]
["priority": "<uint8_t: Priority of the message bigger has more priority>"],
"durationS": "<uint32_t: Duration in Seconds to be displayed for each part if overflowed DEFAULT: 5 minutes>",
["disposable" : "<bool: If unable to display now discard DEFAULT: false>",]
["staleAt": "<uint32_t: Duration in Seconds to discard after DEAFULT: 1 month>",]
["cron": "<std::string: Cron shcedule string>"]
}Technicaly text if optional but does nothing if blank.
If multiple topics are configured in the config.json each topic will corispond to a section starting at index 0 if no section value is passed.
For example with the topics:
["home/splitflap/text0", "home/splitflap/text1", "home/splitflap/text2"]
To use section 1 use the topic "home/splitflap/text1"
If the amount of topics is greater than the sections it will recycle them.
For example given the prior topics and only 2 sections, if "home/splitflap/text2" is sent data it will display on section 0. This allows for multiple topics to be assinged to one section (and prevent errors)
See https://github.com/PerMalmberg/libcron?tab=readme-ov-file#supported-formatting for libcron's supported formating. Note the extended second support
The configuration of the display is done in config.json.
WARNING the WiFi password is stored in plain text.
An IoT network is recomended.
{
"I2C":{
# For backwads Compatability
"SCL": "<int: SCL Pin>",
"SDA": "<int: SDA Pin>"
},
"WIFI":{
"SSID": "SSID IN PLAIN TEXT",
"Pass": "PASSWORD IN PLAIN TEXT, USE IOT NETWORK",
"Region": "INT: Region ID TODO"
},
"MQTT":{
"IP": ["<int array 4: IP address>"],
"ID": "<std::string: ID of the splitflap>",
"Topic": ["<std::string array: MQTT topics to listen to>"[...]]
},
"Output":{
["<Output ID TYPE SHIFT>":{
"Type":"Shift",
"OE": "<int: Output Enable Pin>",
"Data": "<int: Data Pin>",
"Clock": "<int: Clock Pin>",
"Latch": "<int: Latch pin>"
}],
["<Output ID TYPE PIN>":{
"Type":"Pin",
"Output":["<int array 4: output pins>"]
}]
[...]
},
"Input":{
["Input ID : TYPE MUX":{
"Type":"Mux",
"Bits": "<int: Bits to use for the mux>",
"Select":["<int array: slection pins>"],
"Input": "<int: read pin>"
}],
["Input ID : TYPE PIN":{
"Type":"Pin",
"Input": "<int: read pin>"
}]
[...]
},
"Motor":{
["Motor ID":{
"TimeoutS": "<int: timeout in seconds>",
"DelayUs": "<int: delay in MicroSeconds>",
"Coils": "<int: amout of coils MUST BE THE SAME FOR THE SAME OUTPUT>",
"Steps": "<int: amount of steps for a full rotation>",
"StepSequence": ["<int array: Step sequence for motor>"[...]]
}]
[...]
},
"SplitFlap":[
[
{
"Count": "<int: Duplication ammount>",
"Flaps": "<int: Count of flaps>",
"Dir": "<int: Durection 1 or -1>",
"Output": "<std::string: Output ID>",
"OutputIndexStart": "<int: Output ID start>",
"Input": "<std::string: Input ID>",
"InputIndexStart": "<int: Input ID start>",
"IndexStart": "<int: Index start>",
"Calibration": ["<int array: recycled array for calibration offset>"[...]],
"Letters": ["<std::string array: recycled array for letters>"[...]],
"Motor": "<std::string: Motor ID>"
}
[...]
],
[
{
"Legacy": true,
"Count": "<int: Duplication ammount>",
"Flaps": "<int: Count of flaps>",
"IndexStart":"<int: Index start>",
"Letters": ["<std::string array: recycled array for letters>"[...]]
}
[...]
]
[...]
]
}
The program uses the Pico SDK for c++ 23 along with other libraries all managed with cmake.
- Pico SDK https://github.com/raspberrypi/pico-sdk
- libcron https://github.com/PerMalmberg/libcron
- nlohmann/json https://github.com/nlohmann
A pre compiled debug and production (-o3) code will be provided to make it easy to use. Feel free to use it instead of compling it yourself.
Note: Binary files may differ depending on the compiler and system used
MQTT::mqtt_incoming_data_cb(){
Interpreter::getCron();
Interpreter::getSection();
splitFlaps->mutex_enter_blocking();
splitFlaps->sceduleDisplay();
conductor->schedule();
splitFlap->display();
GOTO<"DISPLAY">
//**OR**
splitFlaps->display();
splitFlap->display();
"DISPLAY"
splitFlaps->mutex_exit();
return;
}Ensure the following are installed on your host machine:
cmakeninja-buildgcc-arm-none-eabilibnewlib-arm-none-eabibuild-essential
- Configure: ```bash cmake -B build -G Ninja -DPICO_BOARD=pico2