Author Topic: Advantest R6581 VFD replacement  (Read 10260 times)

0 Members and 1 Guest are viewing this topic.

Offline Mickle T.Topic starter

  • Frequent Contributor
  • **
  • Posts: 494
  • Country: ru
Advantest R6581 VFD replacement
« on: September 08, 2024, 08:29:54 am »
As is well known, the Advantest R6581 DMM has two main problems, the second of which is a custom VFD display that is not available commercially.
As a result of reverse engineering the multimeter firmware and analyzing the circuit diagram, I was able to find at least two possible ways to replace the VFD that has become unusable:
1) A difficult to implement, but flexible and interesting way: installing a microcontroller module that "spy" on reading the external dual-port display buffer from the Hitachi H8 controller on the front panel board. The resulting ASCII character codes and their attributes can be rendered on any graphic display.
2) A simple and cheap to implement way: installing a microcontroller module that "spy" on writing to the shift registers on the front panel board of the multimeter. The resulting bit image of characters can be output only to a limited number of displays with a resolution of at least 256x48 pixels.

If you choose the second way (which is what I did), then replacing the display will cost about $20 and half an hour of your time. As a basis, I took the most common STM32 Blue Pill (STM32F103C8T6) and 3.12" OLED with SSD1322 controller. Attached is the schematic diagram and the project archive in STM32CubeIDE.

Offline TheDefpom

  • Frequent Contributor
  • **
  • Posts: 807
  • Country: nz
  • YouTuber Nerd - I Fix Stuff
    • The Defpom's Channel
Re: Advantest R6581 VFD replacement
« Reply #1 on: September 09, 2024, 08:54:56 am »
That is excellent, thank you for providing this !

Now I just need to figure out how to use the STM32 IDE and get a blue pill board and ST-link.

I've never used an STM32 so a bit of STM32 learning is in my near future, which is probably a good thing anyway !
Cheers Scott

Check out my Electronics Repair, Mailbag, or Review Videos at https://www.youtube.com/TheDefpom
 
The following users thanked this post: eplpwr

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #2 on: September 27, 2024, 07:29:37 am »
In another thread a 4.58" TFT bar display was proposed as a possible replacement. In comparison to original VFD it might look like this. Don't know yet how it fits inside the R6581.
The controller uses a STM32H730 and its LTDC peripheral. It fits to the back of the TFT. At 300 MHz the MCU needs about 5 msec to paint the image in its video memory. TFT refresh rate came out as 41 Hz.

Regards, Dieter
« Last Edit: September 27, 2024, 09:21:12 am by dietert1 »
 
The following users thanked this post: branadic, gamalot, bsw_m, damien

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #3 on: September 28, 2024, 07:08:18 pm »
Thanks to Mickle T.s great reengineering it was fairly easy to connect the TFT display to one of our R6581. Also i unmounted the R6581 display board to check available space. The TFT and its controller can become one module and fit very similar to the original VFD.
The firmware reads all display data packets and paints each one of them, so there is no visible delay in comparison to the VFD. I also added kind of OCR to the firmware. Right now it only recognizes that strange "DISPLAY OFF" message and turns the TFT backlight off. Maybe one can substitute the 5x7 matrix by a higher resolution font.

Regards, Dieter
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #4 on: September 29, 2024, 02:10:49 pm »
In order to train the OCR i would like to post various text messages to the R6581 display via GPIB. Many GPIB devices support some kind of message display but i couldn't find the proper GPIB command in the R6581 manual. Who can help with hint?

Regards, Dieter
 

Offline Mickle T.Topic starter

  • Frequent Contributor
  • **
  • Posts: 494
  • Country: ru
Re: Advantest R6581 VFD replacement
« Reply #5 on: September 29, 2024, 02:43:43 pm »
The original, unmodified firmware does not contain commands for text output and complete blanking of the display.
 
The following users thanked this post: dietert1

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #6 on: October 09, 2024, 08:57:05 am »
Unsoldering the original VFD display was quite some exercise. Meanwhile some brackets (3D printed) were glued to the boards as support for the 4.58" TFT display and its graphics card. A narrow fit!
By the way: The VFD cathodes need about 4 W for heating, while the TFT needs about 20 mA x 12 V = 240 mW for its backlight plus 150 mW for the graphics card. Roughly a 10x reduction.

Regards, Dieter
« Last Edit: October 09, 2024, 09:45:53 am by dietert1 »
 
The following users thanked this post: branadic, Mickle T., langlv, bsw_m

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #7 on: October 11, 2024, 03:13:45 pm »
First image shows the +12 V backlight supply. It has a voltage doubler and a LM7812.
Meanwhile i finished two R6581T with the replacement TFT display.

The interesting number in the image is the upper one with 15.57 uV. This is with an 18 MOhm resistor and a 1 uF cap across input, so input offset current < 1 pA. That R6581T got a mosfet input mux. The other one got a mains preregulator similar to the Keithley 2002. So there is more work to do..

Regards, Dieter
 
The following users thanked this post: branadic, Mickle T., langlv, bsw_m

Offline kjk24

  • Regular Contributor
  • *
  • Posts: 52
  • Country: 00
Re: Advantest R6581 VFD replacement
« Reply #8 on: October 11, 2024, 06:28:40 pm »
Thank you 👍 verry much
best regards
kai
-------------------------------
I7 &
E4 1.19.8
 
The following users thanked this post: Mickle T.

Offline TheDefpom

  • Frequent Contributor
  • **
  • Posts: 807
  • Country: nz
  • YouTuber Nerd - I Fix Stuff
    • The Defpom's Channel
Re: Advantest R6581 VFD replacement
« Reply #9 on: October 13, 2024, 04:30:40 am »
Well thanks to Mickle T. I have now replaced my display with a 3.12" OLED, it was always in my plans to do something to replace it, and thankfully I didn't have to sit down for days trying to work it out !

Not sure I went with the best colour, maybe I should have gone with Yellow instead, but it is still a lot better than it was.

I am watching the 4.58" display version with interest (I have a display here already), as that would be even better as then the text would not need to be shortened, would also be nice to use a smoother font for the 5x7, but now I am just being picky LOL.

I have recorded a video of the conversion which I will publish on my channel at some point, I didn't go into detail about the programming of the STM32, as I did that a couple of weeks ago (first time using an STM32), I might have to record something to fill that information gap.
Cheers Scott

Check out my Electronics Repair, Mailbag, or Review Videos at https://www.youtube.com/TheDefpom
 
The following users thanked this post: Mickle T., bsw_m

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #10 on: October 15, 2024, 03:50:21 pm »
Hi all,

Just a wee note.......I have an R6581T on it's way, and I think the VFD is ok (famous last words!), but decided to put together one of these OLED solutions anyway. Those that know me will know I like modifying/upgrading my gear!

I have the .HEX file and have uploaded it to one of my BluePill STM32 boards, however, in regards to the toolchain my app of choice is Visual Studio (I don't use Eclipse) for everything including Win apps in VB, Arduino ATMEGA & STM32, so decided to try VisualGDB plugin for Visual Studio (VS 2022) and lo & behold I was able to import Mickle T's source, set up the configuration and compile a .HEX file of my own.

Looking forward to receiving my OLED and trying the original Mickle T HEX file as well as the one compiled by Visual Studio.....see if it works!

Anyone else tried this?.......so far VS VisualGDB plugin is looking workable!

UPDATE:
No R6581T yet so scoped the STM32 pins with original HEX, got some waveforms, then uploaded my own HEX file from VS and scoping them again I get exact same waveforms, so looking good.
Original HEX file = 29.6KB
VS HEX file = 39.9KB, so I can only assume VS debugging overheads & optimization.
I haven't started looking at Mickle T's source code at all, but looking forward to having a look around, and knowing me tweaking.

UPDATE:
The VS2022 compiled project does work with the R6581T & OLED.

Ian.

« Last Edit: October 26, 2024, 06:28:33 pm by IanJ »
Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 
The following users thanked this post: Mickle T., coromonadalix, bsw_m

Offline TheDefpom

  • Frequent Contributor
  • **
  • Posts: 807
  • Country: nz
  • YouTuber Nerd - I Fix Stuff
    • The Defpom's Channel
Re: Advantest R6581 VFD replacement
« Reply #11 on: October 19, 2024, 03:28:34 am »
Here is my video about replacing the display on my unit:

Cheers Scott

Check out my Electronics Repair, Mailbag, or Review Videos at https://www.youtube.com/TheDefpom
 
The following users thanked this post: Mickle T., Thilo78, bsw_m

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #12 on: October 19, 2024, 10:30:10 am »
Hi all,

Below is a link to my conversion of Mickle T's original source project to a Visual Studio project (Windows).
It compiles to a hex file just fine..........Untested though, although the uploaded HEX file does seem to scope the same signals on the STM32 board.
No source changes were necessary at all.

VS2022 64-bit, Community Edition
Requires VisualGDB plugin for VS (30-day demo version will do) - https://visualgdb.com

https://github.com/Ian-Johnston/OLED_SSD1322_DMA

Ian.

Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 
The following users thanked this post: branadic, Mickle T., Thilo78

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #13 on: October 28, 2024, 05:52:55 pm »
Here is a zip archive with that H730 LTDC TFT replacement display project. As i have some ready to use PCBs left i thought about making them into kits, but i don't have time to support that.

Regards, Dieter
 
The following users thanked this post: IanJ, branadic, Mickle T., langlv, bsw_m

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #14 on: October 28, 2024, 08:51:29 pm »
Here's my OLED conversion photos below.
And my YT video: https://youtu.be/QyP6tK9EIsg

I also modified the OLED display (SSD1322 iREF pin) to increase the brightness just a touch to compensate for the smoked perspex. Instructions are in the video.

A big thanks to Mickle T......what a difference this has made to my R6581T.

Ian.



« Last Edit: October 29, 2024, 10:10:03 am by IanJ »
Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #15 on: October 28, 2024, 09:17:06 pm »
Here is a zip archive with that H730 LTDC TFT replacement display project. As i have some ready to use PCBs left i thought about making them into kits, but i don't have time to support that.

Regards, Dieter

Hi,

Any chance you can export the gerbers?
Looks like and older Eagle binary .brd file which Kicad etc can't open.
Thanks,
Ian.
« Last Edit: October 28, 2024, 09:24:00 pm by IanJ »
Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #16 on: October 28, 2024, 11:44:17 pm »
Yes, it's Eagle version 4.16. Can't you just order the PCB using the board file? I never had to supply Gerber files. The PCB is four layers, size 65 x 40 mm.
In case somebody wants to design his own PCB i added a schematic. R7 .. R12 are three voltage dividers for the 5V logic data inputs. Later i checked that those STM32H730 pins are 5 V tolerant and omitted R10 .. R12.

Regards, Dieter
« Last Edit: October 28, 2024, 11:48:28 pm by dietert1 »
 
The following users thanked this post: IanJ, branadic, Mickle T., langlv, bsw_m

Offline branadic

  • Super Contributor
  • ***
  • Posts: 2476
  • Country: de
  • Sounds like noise
Re: Advantest R6581 VFD replacement
« Reply #17 on: November 07, 2024, 07:25:07 pm »
Quote
Hi,

Any chance you can export the gerbers?
Looks like and older Eagle binary .brd file which Kicad etc can't open.
Thanks,
Ian.

Workflow is easy, open it with Eagle 7 Lite, save it and you can import it to KiCAD. Attached is the Eagle 7 board file.

-branadic-
Computers exist to solve problems that we wouldn't have without them. AI exists to answer questions, we wouldn't ask without it.
 
The following users thanked this post: IanJ, dietert1

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #18 on: November 16, 2024, 10:22:05 pm »
Hi all,

So far so good, a day spent on Visual Studio and I have mapped the incoming bitmap data to ASCII characters, getting ready for driving a TFT display.

Screenshot attached, a live debug session in VS with the STM32 and that's "+ 1.2345638    VDC" appearing in the code (G1 to G18) from the R6581T.

Next, I'll deal with the AUX display G19 to G47, and the annunciators after that.
From there get a LT7680 TFT driver loaded and start send data to the new display and playing with fonts etc.

Again, completely impossible without MickleT's great work on the original reverse engineering.

PS. C is pretty new to me albeit I've done loads of C++, so it's not too far away.

Ian.
Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 
The following users thanked this post: Mickle T.

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #19 on: November 17, 2024, 01:39:26 pm »
Incase anyone is interested, here's my MAIN.C code which creates ASCII vars/arrays for the main, aux and annunciators.
It's working as far as I can monitor the STM32 live vars in VS2022 and they are all working.
There may be some individual ASCII character issues, I still need to verify some of them i.e. the lower case ones and adjust the hex accordingly.

PS. I am not a software programmer, it's a WIP, so please be gentle!

Next step is the TFT LCD driver......at which point I will disable the existing OLED I/O as I need the pins.

Ian.

Code: [Select]
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  * Original code by MickleT, this version modded by IanJ,
  * for 4.58" 960x320 TFT LCD (ST7701S) and using LT7680 adaptor
  * Visual Studio 2022 with VisualGDB plugin:
  * To upload HEX from VS2022 = BUILD then PROGRAM AND START WITHOUT DEBUGGING
  * Use LIVE WATCH to view variables live debug
  *
  */

/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "dma.h"
#include "spi.h"
#include "gpio.h"
#include <stdarg.h>
#include <stdio.h>
#include <string.h>
#include <stdint.h>

/* Private includes ----------------------------------------------------------*/
#include "SSD1322_OLED_lib/SSD1322_HW_Driver.h"
#include "SSD1322_OLED_lib/SSD1322_API.h"
#include "SSD1322_OLED_lib/SSD1322_GFX.h"


/* Private variables ---------------------------------------------------------*/
static char main_display_debug[LINE1_LEN + 1]; // Main display debug string
#define FONT_HEIGHT 7

// Buffers for each LCD graphical item
char LCD_buffer_packets[128];  // For packet data
char LCD_buffer_bitmaps[128];  // For decoded bitmap data
char LCD_buffer_chars[128];    // For decoded characters
char G1, G2, G3, G4, G5, G6, G7, G8, G9, G10, G11, G12, G13, G14, G15, G16, G17, G18;   // Main
char G19, G20, G21, G22, G23, G24, G25, G26, G27, G28, G29, G30, G31, G32, G33, G34, G35, G36, G37, G38, G39, G40, G41, G42, G43, G44, G45, G46, G47; // Aux
_Bool Annunciators[18]; // Array for MAIN annunciators. The order on LCD left to right = 8,7,6,5,4,3,2,1,017,16,15,14,13,12,11,10,9
_Bool AnnunciatorsReordered[19]; // G1 to G18 in left-to-right order


//******************************************************************************

// SPI receive buffer for packets data
volatile uint8_t rx_buffer[PACKET_WIDTH*PACKET_COUNT];

// Array with character bitmaps
uint8_t chars[CHAR_COUNT][CHAR_HEIGHT];

// Array with annunciators flags (boolean)
uint8_t flags[CHAR_COUNT];

// When scanning the display, the order of the characters output is not sequential due to optimization of the VFD PCB layout.
// The Reorder[] array is used as a lookup table to determine the correct position of characters.
const uint8_t Reorder[PACKET_COUNT] = { 8, 7, 6, 5, 4, 3, 2, 1, 0, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 17, 16, 15, 14, 13, 12, 11, 10, 9, 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36 };
// Order of data to the display (effectively into the shift registers):
// 8, 7, 6, 5, 4, 3, 2, 1, 0,                                               // MAIN: G9 to G1
// 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35,  // AUX: G19 to G36
// 17, 16, 15, 14, 13, 12, 11, 10, 9,                                       // MAIN: G18 to G10
// 46, 45, 44, 43, 42, 41, 40, 39, 38, 37, 36                               // AUX: G47 to G37
// But the actual display left to right:
// G1 to G18 - MAIN
// G19 to G47 - AUX
// AnnunciatorsReordered[1] to AnnunciatorsReordered[18]


// Used for line-by-line recoding and scaling of the characters of the main display line.
// All 32 possible variants of a 5-pixel wide character bitmap string are converted into
// the corresponding 10 OLED display pixels with 16 color gradations and packed into 5 bytes.
const uint8_t Upscale1[32][5] = {   
        { 0x00, 0x00, 0x00, 0x00, 0 }, { 0x00, 0x00, 0x00, 0x00, 0xFF }, { 0x00, 0x00, 0x00, 0xFF, 0 }, { 0x00, 0x00, 0x00, 0xFF, 0xFF },
{ 0x00, 0x00, 0xFF, 0x00, 0 }, { 0x00, 0x00, 0xFF, 0x00, 0xFF }, { 0x00, 0x00, 0xFF, 0xFF, 0 }, { 0x00, 0x00, 0xFF, 0xFF, 0xFF },
{ 0x00, 0xFF, 0x00, 0x00, 0 }, { 0x00, 0xFF, 0x00, 0x00, 0xFF }, { 0x00, 0xFF, 0x00, 0xFF, 0 }, { 0x00, 0xFF, 0x00, 0xFF, 0xFF },
{ 0x00, 0xFF, 0xFF, 0x00, 0 }, { 0x00, 0xFF, 0xFF, 0x00, 0xFF }, { 0x00, 0xFF, 0xFF, 0xFF, 0 }, { 0x00, 0xFF, 0xFF, 0xFF, 0xFF },
{ 0xFF, 0x00, 0x00, 0x00, 0 }, { 0xFF, 0x00, 0x00, 0x00, 0xFF }, { 0xFF, 0x00, 0x00, 0xFF, 0 }, { 0xFF, 0x00, 0x00, 0xFF, 0xFF },
{ 0xFF, 0x00, 0xFF, 0x00, 0 }, { 0xFF, 0x00, 0xFF, 0x00, 0xFF }, { 0xFF, 0x00, 0xFF, 0xFF, 0 }, { 0xFF, 0x00, 0xFF, 0xFF, 0xFF },
{ 0xFF, 0xFF, 0x00, 0x00, 0 }, { 0xFF, 0xFF, 0x00, 0x00, 0xFF }, { 0xFF, 0xFF, 0x00, 0xFF, 0 }, { 0xFF, 0xFF, 0x00, 0xFF, 0xFF },
{ 0xFF, 0xFF, 0xFF, 0x00, 0 }, { 0xFF, 0xFF, 0xFF, 0x00, 0xFF }, { 0xFF, 0xFF, 0xFF, 0xFF, 0 }, { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }
};

// Used for line-by-line recoding and scaling of the characters of the auxiliary display line.
// All 32 possible variants of a 5-pixel wide character bitmap string are converted into
// the corresponding 5 OLED display pixels with 16 color gradations and packed into 3 bytes.
const uint8_t Upscale2[32][3] = {
{ 0x00, 0x00, 0x00 }, { 0x00, 0x00, 0xF0 }, { 0x00, 0x0F, 0x00 }, { 0x00, 0x0F, 0xF0 }, { 0x00, 0xF0, 0x00 }, { 0x00, 0xF0, 0xF0 },
{ 0x00, 0xFF, 0x00 }, { 0x00, 0xFF, 0xF0 }, { 0x0F, 0x00, 0x00 }, { 0x0F, 0x00, 0xF0 }, { 0x0F, 0x0F, 0x00 }, { 0x0F, 0x0F, 0xF0 },
{ 0x0F, 0xF0, 0x00 }, { 0x0F, 0xF0, 0xF0 }, { 0x0F, 0xFF, 0x00 }, { 0x0F, 0xFF, 0xF0 }, { 0xF0, 0x00, 0x00 }, { 0xF0, 0x00, 0xF0 },
{ 0xF0, 0x0F, 0x00 }, { 0xF0, 0x0F, 0xF0 }, { 0xF0, 0xF0, 0x00 }, { 0xF0, 0xF0, 0xF0 }, { 0xF0, 0xFF, 0x00 }, { 0xF0, 0xFF, 0xF0 },
{ 0xFF, 0x00, 0x00 }, { 0xFF, 0x00, 0xF0 }, { 0xFF, 0x0F, 0x00 }, { 0xFF, 0x0F, 0xF0 }, { 0xFF, 0xF0, 0x00 }, { 0xFF, 0xF0, 0xF0 },
    { 0xFF, 0xFF, 0x00 }, { 0xFF, 0xFF, 0xF0 }
};

// A 256x5 pixel sprite (128x5 bytes) is used to draw the annunciators by copying rectangular areas into the OLED buffer.
const uint8_t Sprites[5][128] = {
        { 0x00,0x00,0x00,0xF0,0xF0,0xF0,0x00,0x00,0x00,0xF0,0xFF,0xF0,0x0F,0x00,0x00,0x0F,0x00,0xF0,0xF0,0xFF,0xF0,0xF0,0x00,0xF0,0x00,0xF0,0x0F,0xFF,0x00,0xF0,0x0F,0x0F,
          0x0F,0x0F,0x00,0xF0,0x00,0x0F,0xFF,0x00,0xFF,0xF0,0x00,0xF0,0x00,0xF0,0x0F,0x00,0xFF,0xF0,0x00,0x0F,0xF0,0x00,0xFF,0xFF,0x00,0x0F,0xFF,0x0F,0xF0,0x0F,0xF0,0x00,
          0x0F,0x0F,0x00,0xF0,0xFF,0xF0,0xF0,0x00,0xFF,0x0F,0xF0,0x00,0xF0,0x00,0xFF,0x00,0xFF,0x00,0xF0,0x0F,0xF0,0x0F,0xFF,0x0F,0x00,0xF0,0xFF,0xF0,0x0F,0x00,0xF0,0x00,
          0x00,0xFF,0xF0,0x0F,0xF0,0x0F,0xFF,0x0F,0x00,0x0F,0x00,0xFF,0xF0,0xF0,0x0F,0x0F,0x00,0x00,0xF0,0xFF,0xF0,0xF0,0x0F,0x00,0x0F,0xFF,0x0F,0xF0,0x0F,0xFF,0x00,0x00 },
        { 0x00,0x00,0x00,0x0F,0xFF,0x00,0x00,0x00,0x00,0xF0,0xF0,0x0F,0x0F,0x00,0x00,0xF0,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x00,0xF0,0x0F,0x0F,0x0F,0x0F,0x00,0xFF,0x0F,0x0F,
          0x0F,0x0F,0x00,0xF0,0x00,0x0F,0x00,0xF0,0xF0,0x00,0x00,0xFF,0x0F,0xF0,0xF0,0xF0,0x0F,0x00,0x00,0xF0,0x0F,0x00,0x00,0x0F,0x00,0x0F,0x00,0x0F,0x0F,0x0F,0x0F,0x00,
          0x0F,0x0F,0xF0,0xF0,0xF0,0x0F,0x0F,0x00,0xF0,0x0F,0x0F,0x0F,0x0F,0x00,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x0F,0x0F,0x00,0x0F,0x0F,0x0F,0x0F,0x00,0x0F,0x0F,0x0F,0x00,
          0x0F,0x00,0x00,0x0F,0x0F,0x0F,0x00,0x0F,0xF0,0xFF,0x00,0x0F,0x00,0xF0,0x0F,0x0F,0x00,0x00,0xF0,0x0F,0x00,0xFF,0x0F,0x00,0x0F,0x00,0x0F,0x0F,0x0F,0x0F,0x00,0x00 },
        { 0x00,0x00,0x0F,0xFF,0x0F,0xFF,0x00,0x00,0x00,0xF0,0xF0,0x0F,0x0F,0x00,0x00,0xFF,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x00,0xF0,0x0F,0x0F,0x0F,0xFF,0x00,0xF0,0xFF,0x0F,
          0x0F,0x0F,0x00,0xF0,0x00,0x0F,0x00,0xF0,0xFF,0x00,0x00,0xF0,0xF0,0xF0,0xFF,0xF0,0x0F,0x00,0x00,0xFF,0xFF,0x00,0x00,0xF0,0x00,0x0F,0xF0,0x0F,0x0F,0x0F,0x0F,0x00,
          0x0F,0x0F,0x0F,0xF0,0xFF,0x0F,0x0F,0x00,0xFF,0x0F,0x0F,0x0F,0x0F,0x00,0xF0,0xF0,0xFF,0x0F,0xFF,0x0F,0x0F,0x0F,0xFF,0x0F,0x0F,0x0F,0x0F,0x00,0x0F,0x0F,0x0F,0x0F,
          0x0F,0x0F,0xF0,0x0F,0x0F,0x0F,0xF0,0x0F,0x0F,0x0F,0x00,0x0F,0x00,0xF0,0x0F,0xF0,0x00,0x00,0xF0,0x0F,0x00,0xF0,0xFF,0x00,0x0F,0xFF,0x0F,0x0F,0x0F,0x0F,0x00,0x00 },
        { 0x00,0x00,0x00,0x0F,0xFF,0x00,0x00,0x00,0x00,0xF0,0xF0,0x0F,0x0F,0x00,0x00,0xF0,0xF0,0xF0,0xF0,0x0F,0x0F,0x0F,0x00,0xF0,0x0F,0x0F,0x0F,0x00,0x00,0xF0,0x0F,0x0F,
          0x0F,0x0F,0x00,0xF0,0x00,0x0F,0x00,0xF0,0xF0,0x00,0x00,0xF0,0x00,0xF0,0xF0,0xF0,0x0F,0x00,0x00,0xF0,0x0F,0x00,0x0F,0x00,0x00,0x0F,0x00,0x0F,0xF0,0x0F,0xF0,0x00,
          0x0F,0x0F,0x00,0xF0,0xF0,0x0F,0x0F,0x00,0xF0,0x0F,0xF0,0x0F,0x0F,0x00,0xFF,0x00,0xF0,0x0F,0x0F,0x0F,0xF0,0x00,0x0F,0x0F,0x0F,0x0F,0x0F,0x00,0x0F,0x0F,0x0F,0x00,
          0x0F,0x00,0xF0,0x0F,0xF0,0x0F,0x00,0x0F,0x00,0x0F,0x00,0x0F,0x00,0xF0,0x0F,0x0F,0x00,0x00,0xF0,0x0F,0x00,0xF0,0x0F,0x00,0x00,0x0F,0x0F,0xF0,0x0F,0x0F,0x00,0x00 },
        { 0x00,0x00,0x00,0xF0,0xF0,0xF0,0x00,0x00,0x00,0xF0,0xFF,0xF0,0x0F,0xFF,0x00,0xF0,0xF0,0xFF,0xF0,0x0F,0x00,0xF0,0x00,0xFF,0xF0,0xF0,0x0F,0x00,0x00,0xF0,0x0F,0x0F,
          0xFF,0x0F,0xF0,0xFF,0x00,0x0F,0xFF,0x00,0xF0,0x00,0x00,0xF0,0x00,0xF0,0xF0,0xF0,0x0F,0x00,0x00,0xF0,0x0F,0x00,0xFF,0xFF,0x00,0x0F,0xFF,0x0F,0x0F,0x0F,0x0F,0x00,
          0x0F,0x0F,0x00,0xF0,0xF0,0x00,0xF0,0x00,0xF0,0x0F,0x0F,0x00,0xF0,0x00,0xF0,0xF0,0xFF,0x0F,0x0F,0x0F,0x0F,0x0F,0xFF,0x0F,0xF0,0xF0,0x0F,0x00,0x0F,0xF0,0xF0,0x00,
          0x00,0xFF,0xF0,0x0F,0x0F,0x0F,0xFF,0x0F,0x00,0x0F,0x00,0x0F,0x00,0xFF,0x0F,0x0F,0x00,0x00,0xFF,0x0F,0x00,0xF0,0x0F,0x00,0x0F,0xFF,0x0F,0x0F,0x0F,0xF0,0xF0,0x00 } };

// Declare bytes array for a OLED frame buffer.
// Dimensions are divided by 2 because one byte contains two 4-bit grayscale pixels
uint8_t tx_buffer[64 * 256 / 2];

// Flag indicating finish of SPI transmission to OLED
volatile uint8_t SPI1_TX_completed_flag = 1;

// Flag indicating finish of SPI start-up initialization
volatile uint8_t Init_Completed_flag = 0;

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);


//******************************************************************************

// Buffer to store the converted string representation of the main display line
//char main_display_line[CHAR_COUNT + 1]; // +1 for null terminator
static char main_display_line[CHAR_COUNT + 1]; // Static ensures scope is global within the file

//SPI transmission finished interrupt callback
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi)
{
    if (hspi->Instance == SPI1)
    {
        SPI1_TX_completed_flag = 1;
    }
}
//******************************************************************************
static uint8_t InverseByte(uint8_t a)
{
    a = ((a & 0x55) << 1) | ((a & 0xAA) >> 1);
    a = ((a & 0x33) << 2) | ((a & 0xCC) >> 2);
    return (a >> 4) | (a << 4);
}


//******************************************************************************
// Function to map character bitmaps to ASCII characters
typedef struct {
    uint8_t bitmap[7]; // 7 bytes for 5x7 character bitmaps
    char ascii;        // Corresponding ASCII character
} BitmapChar;


// Font data: 96 characters, 7 bytes per character (each row)
const BitmapChar bitmap_characters[] = {
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ' '},  // Space
    {{0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x04}, '!'},  // 0x21, !
    {{0x09, 0x09, 0x12, 0x00, 0x00, 0x00, 0x00}, '"'},  // 0x22, "
    {{0x0A, 0x0A, 0x1F, 0x0A, 0x1F, 0x0A, 0x0A}, '#'},  // 0x23, #
    {{0x04, 0x0F, 0x14, 0x0E, 0x05, 0x1E, 0x04}, '$'},  // 0x24, $
    {{0x19, 0x19, 0x02, 0x04, 0x08, 0x13, 0x13}, '%'},  // 0x25, %
    {{0x04, 0x0A, 0x0A, 0x0A, 0x15, 0x12, 0x0D}, '&'},  // 0x26, &
    {{0x04, 0x04, 0x08, 0x00, 0x00, 0x00, 0x00}, '\''}, // 0x27, '
    {{0x02, 0x04, 0x08, 0x08, 0x08, 0x04, 0x02}, '('},  // 0x28, (
    {{0x08, 0x04, 0x02, 0x02, 0x02, 0x04, 0x08}, ')'},  // 0x29, )
    {{0x04, 0x15, 0x0E, 0x1F, 0x0E, 0x15, 0x04}, '*'},  // 0x2A, *
    {{0x00, 0x04, 0x04, 0x1F, 0x04, 0x04, 0x00}, '+'},  // 0x2B, +
    {{0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x08}, ','},  // 0x2C, ,
    {{0x00, 0x00, 0x00, 0x1F, 0x00, 0x00, 0x00}, '-'},  // 0x2D, -
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x0C, 0x0C}, '.'},  // 0x2E, .
    {{0x01, 0x01, 0x02, 0x04, 0x08, 0x10, 0x10}, '/'},  // 0x2F, /
    {{0x0E, 0x11, 0x13, 0x15, 0x19, 0x11, 0x0E}, '0'},  // 0x30, 0
    {{0x04, 0x0C, 0x04, 0x04, 0x04, 0x04, 0x0E}, '1'},  // 0x31, 1
    {{0x0E, 0x11, 0x01, 0x02, 0x04, 0x08, 0x1F}, '2'},  // 0x32, 2
    {{0x1F, 0x02, 0x04, 0x02, 0x01, 0x11, 0x0E}, '3'},  // 0x33, 3
    {{0x02, 0x06, 0x0A, 0x12, 0x1F, 0x02, 0x02}, '4'},  // 0x34, 4
    {{0x1F, 0x10, 0x1E, 0x01, 0x01, 0x11, 0x0E}, '5'},  // 0x35, 5
    {{0x06, 0x08, 0x10, 0x1E, 0x11, 0x11, 0x0E}, '6'},  // 0x36, 6
    {{0x1F, 0x01, 0x02, 0x04, 0x08, 0x08, 0x08}, '7'},  // 0x37, 7
    {{0x0E, 0x11, 0x11, 0x0E, 0x11, 0x11, 0x0E}, '8'},  // 0x38, 8
    {{0x0E, 0x11, 0x11, 0x0F, 0x01, 0x02, 0x0C}, '9'},  // 0x39, 9
    {{0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x0C, 0x00}, ':'},  // 0x3A, :
    {{0x00, 0x0C, 0x0C, 0x00, 0x0C, 0x04, 0x08}, ';'},  // 0x3B, ;
    {{0x02, 0x04, 0x08, 0x10, 0x08, 0x04, 0x02}, '<'},  // 0x3C, <
    {{0x00, 0x00, 0x1F, 0x00, 0x1F, 0x00, 0x00}, '='},  // 0x3D, =
    {{0x08, 0x04, 0x02, 0x01, 0x02, 0x04, 0x08}, '>'},  // 0x3E, >
    {{0x0E, 0x11, 0x01, 0x02, 0x04, 0x00, 0x04}, '?'},  // 0x3F, ?
    {{0x0E, 0x11, 0x17, 0x15, 0x17, 0x10, 0x0F}, '@'},  // 0x40, @
    {{0x04, 0x0A, 0x11, 0x11, 0x1F, 0x11, 0x11}, 'A'},  // 0x41, A
    {{0x1E, 0x11, 0x11, 0x1E, 0x11, 0x11, 0x1E}, 'B'},  // 0x42, B
    {{0x0E, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0E}, 'C'},  // 0x43, C
    {{0x1C, 0x12, 0x11, 0x11, 0x11, 0x12, 0x1C}, 'D'},  // 0x44, D
    {{0x1F, 0x10, 0x10, 0x1C, 0x10, 0x10, 0x1F}, 'E'},  // 0x45, E
    {{0x1F, 0x10, 0x10, 0x1F, 0x10, 0x10, 0x10}, 'F'},  // 0x46, F
    {{0x0E, 0x11, 0x10, 0x10, 0x13, 0x11, 0x0F}, 'G'},  // 0x47, G
    {{0x11, 0x11, 0x11, 0x1F, 0x11, 0x11, 0x11}, 'H'},  // 0x48, H
    {{0x0E, 0x04, 0x04, 0x04, 0x04, 0x04, 0x0E}, 'I'},  // 0x49, I
    {{0x02, 0x04, 0x04, 0x04, 0x04, 0x04, 0x02}, 'J'},  // 0x4A, J
    {{0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x11}, 'K'},  // 0x4B, K
    {{0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x1F}, 'L'},  // 0x4C, L
    {{0x11, 0x1B, 0x1F, 0x1F, 0x11, 0x11, 0x11}, 'M'},  // 0x4D, M
    {{0x11, 0x1B, 0x1F, 0x1F, 0x1B, 0x11, 0x11}, 'N'},  // 0x4E, N
    {{0x0E, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, 'O'},  // 0x4F, O
    {{0x1E, 0x11, 0x11, 0x1E, 0x10, 0x10, 0x10}, 'P'},  // 0x50, P
    {{0x0E, 0x11, 0x11, 0x11, 0x11, 0x13, 0x0E}, 'Q'},  // 0x51, Q
    {{0x1E, 0x11, 0x11, 0x1E, 0x14, 0x12, 0x11}, 'R'},  // 0x52, R
    {{0x0E, 0x11, 0x10, 0x0E, 0x01, 0x11, 0x0E}, 'S'},  // 0x53, S
    {{0x1F, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04}, 'T'},  // 0x54, T
    {{0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0E}, 'U'},  // 0x55, U
    {{0x11, 0x11, 0x11, 0x11, 0x11, 0x0A, 0x04}, 'V'},  // 0x56, V
    {{0x11, 0x11, 0x1F, 0x1F, 0x1F, 0x11, 0x11}, 'W'},  // 0x57, W
    {{0x11, 0x11, 0x0A, 0x04, 0x04, 0x0A, 0x11}, 'X'},  // 0x58, X
    {{0x11, 0x11, 0x11, 0x0A, 0x04, 0x04, 0x04}, 'Y'},  // 0x59, Y
    {{0x1F, 0x01, 0x02, 0x04, 0x08, 0x10, 0x1F}, 'Z'},  // 0x5A, Z
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, '['},  // 0x5B, [
    {{0x10, 0x08, 0x04, 0x02, 0x01, 0x02, 0x04}, '\\'}, // 0x5C, \
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, ']'},  // 0x5D, ]
    {{0x00, 0x10, 0x08, 0x04, 0x02, 0x01, 0x01}, '^'},  // 0x5E, ^
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1F}, '_'},  // 0x5F, _
    {{0x01, 0x02, 0x04, 0x00, 0x00, 0x00, 0x00}, '`'},  // 0x60, `
    {{0x00, 0x00, 0x0E, 0x01, 0x0F, 0x11, 0x0F}, 'a'},  // 0x61, a
    {{0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x0E}, 'b'},  // 0x62, b
    {{0x00, 0x00, 0x0E, 0x10, 0x10, 0x10, 0x0E}, 'c'},  // 0x63, c
    {{0x01, 0x01, 0x0D, 0x13, 0x11, 0x11, 0x0E}, 'd'},  // 0x64, d
    {{0x00, 0x00, 0x0E, 0x11, 0x1F, 0x10, 0x0F}, 'e'},  // 0x65, e
    {{0x06, 0x09, 0x08, 0x1C, 0x08, 0x08, 0x08}, 'f'},  // 0x66, f
    {{0x00, 0x00, 0x0F, 0x11, 0x0F, 0x01, 0x1F}, 'g'},  // 0x67, g
    {{0x10, 0x10, 0x16, 0x19, 0x11, 0x11, 0x11}, 'h'},  // 0x68, h
    {{0x08, 0x00, 0x08, 0x18, 0x08, 0x08, 0x1C}, 'i'},  // 0x69, i
    {{0x02, 0x00, 0x06, 0x02, 0x02, 0x12, 0x0C}, 'j'},  // 0x6A, j
    {{0x10, 0x10, 0x12, 0x14, 0x18, 0x14, 0x12}, 'k'},  // 0x6B, k
    {{0x18, 0x08, 0x08, 0x08, 0x08, 0x08, 0x1C}, 'l'},  // 0x6C, l
    {{0x00, 0x00, 0x1A, 0x15, 0x15, 0x15, 0x15}, 'm'},  // 0x6D, m
    {{0x00, 0x00, 0x16, 0x19, 0x11, 0x11, 0x11}, 'n'},  // 0x6E, n
    {{0x00, 0x00, 0x0E, 0x11, 0x11, 0x11, 0x0E}, 'o'},  // 0x6F, o
    {{0x00, 0x00, 0x1E, 0x11, 0x1E, 0x10, 0x10}, 'p'},  // 0x70, p
    {{0x00, 0x00, 0x0D, 0x13, 0x0F, 0x01, 0x01}, 'q'},  // 0x71, q
    {{0x00, 0x00, 0x16, 0x19, 0x10, 0x10, 0x10}, 'r'},  // 0x72, r
    {{0x00, 0x00, 0x0E, 0x10, 0x0E, 0x01, 0x1E}, 's'},  // 0x73, s
    {{0x08, 0x08, 0x1C, 0x08, 0x08, 0x09, 0x06}, 't'},  // 0x74, t
    {{0x00, 0x00, 0x11, 0x11, 0x11, 0x13, 0x0D}, 'u'},  // 0x75, u
    {{0x00, 0x00, 0x11, 0x11, 0x11, 0x0A, 0x04}, 'v'},  // 0x76, v
    {{0x00, 0x00, 0x11, 0x11, 0x15, 0x15, 0x0A}, 'w'},  // 0x77, w
    {{0x00, 0x00, 0x11, 0x0A, 0x04, 0x0A, 0x11}, 'x'},  // 0x78, x
    {{0x00, 0x00, 0x11, 0x11, 0x0F, 0x01, 0x0E}, 'y'},  // 0x79, y
    {{0x00, 0x00, 0x1F, 0x02, 0x04, 0x08, 0x1F}, 'z'},  // 0x7A, z
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, '{'},  // 0x7B, {
    {{0x01, 0x02, 0x04, 0x00, 0x04, 0x02, 0x01}, '|'},  // 0x7C, |
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, '}'},  // 0x7D, }
    {{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, '~'},  // 0x7E, ~
};


// Convert a 5x7 bitmap to an ASCII character
// The bitmap (7 rows of 5 bits each) is compared row by row against the font_data.
// The comparison involves the 7 rows of the bitmap against the corresponding 7 rows in each font_data entry.
char BitmapToChar(const uint8_t *bitmap) {
    // Iterate over the bitmap_characters array
    for (int i = 0; i < sizeof(bitmap_characters) / sizeof(BitmapChar); i++) {
        // Compare the input bitmap with the current character's bitmap
        if (memcmp(bitmap, bitmap_characters[i].bitmap, FONT_HEIGHT) == 0) {
            return bitmap_characters[i].ascii; // Return the matching ASCII character
        }
    }
    // If no match is found, return '?'
    return '?';
}






//******************************************************************************

// Each character on the display is encoded by a matrix of 40 bits packed
// into 5 consecutive bytes. 5x7=35 bits (S1-S35) define the pixel image of the character,
// 1 bit (S36) is the annunciator, 4 bits are not used. To optimize VFD PCB routing,
// the bit order in packets is shuffled:
//
// S17 S16 S15 S14 S13 S12 S11 S10
// S9  S8  S7  S6  S5  S4  S3  S2
// S1  S36 0   0   0   0   S35 S34
// S33 S32 S31 S30 S29 S28 S27 S26
// S25 S24 S23 S22 S21 S20 S19 S18
//
// The Packets_to_chars function sorts the character bitmap, extracts the annunciator
// flag, and stores the result in separate arrays chars[][] and flags[]
//
// 0 0 0 S1  S2  S3  S4  S5
// 0 0 0 S6  S7  S8  S9  S10
// 0 0 0 S11 S12 S13 S14 S15
// 0 0 0 S16 S17 S18 S19 S20
// 0 0 0 S21 S22 S23 S24 S25
// 0 0 0 S26 S27 S28 S29 S30
// 0 0 0 S31 S32 S33 S34 S35
//
void Packets_to_chars(void)
{
    for (int i = 0; i < PACKET_COUNT; i++) { 
        uint8_t d0 = rx_buffer[i * PACKET_WIDTH + 0];
        uint8_t d1 = rx_buffer[i * PACKET_WIDTH + 1];
        uint8_t d2 = rx_buffer[i * PACKET_WIDTH + 2];
        uint8_t d3 = rx_buffer[i * PACKET_WIDTH + 3];
        uint8_t d4 = rx_buffer[i * PACKET_WIDTH + 4];
       
        chars[Reorder[i]][0] = 0x1F & InverseByte((d1 << 4) | ((d2 & 0x80) >> 4));
        chars[Reorder[i]][1] = 0x1F & InverseByte((d0 << 7) | ((d1 & 0xF0) >> 1));
        chars[Reorder[i]][2] = 0x1F & InverseByte((d0 & 0xFE) << 2);
        chars[Reorder[i]][3] = 0x1F & InverseByte(((d0 & 0xC0) >> 3) | (d4 << 5));
        chars[Reorder[i]][4] = 0x1F & InverseByte(d4 & 0xF8);
        chars[Reorder[i]][5] = 0x1F & InverseByte(d3 << 3);
        chars[Reorder[i]][6] = 0x1F & InverseByte((d2 << 6) | ((d3 & 0xE0) >> 2));
        flags[Reorder[i]] = (d2 & 0x40) == 0x40;

        // Update annunciator boolean array for MAIN annunciators (G1 to G18)
        if (i < 18) {
            Annunciators[i] = flags[Reorder[i]];
        }

   
    }
    // Null-terminate the main display line string
    main_display_line[LINE1_LEN] = '\0';
}


void ReorderAnnunciators(void) {
    // Map Annunciators[] to AnnunciatorsReordered[] for left-to-right order.
    AnnunciatorsReordered[1] = Annunciators[8];  // G1
    AnnunciatorsReordered[2] = Annunciators[7];  // G2
    AnnunciatorsReordered[3] = Annunciators[6];  // G3
    AnnunciatorsReordered[4] = Annunciators[5];  // G4
    AnnunciatorsReordered[5] = Annunciators[4];  // G5
    AnnunciatorsReordered[6] = Annunciators[3];  // G6
    AnnunciatorsReordered[7] = Annunciators[2];  // G7
    AnnunciatorsReordered[8] = Annunciators[1];  // G8
    AnnunciatorsReordered[9] = Annunciators[0];  // G9
    AnnunciatorsReordered[10] = Annunciators[17]; // G10
    AnnunciatorsReordered[11] = Annunciators[16]; // G11
    AnnunciatorsReordered[12] = Annunciators[15]; // G12
    AnnunciatorsReordered[13] = Annunciators[14]; // G13
    AnnunciatorsReordered[14] = Annunciators[13]; // G14
    AnnunciatorsReordered[15] = Annunciators[12]; // G15
    AnnunciatorsReordered[16] = Annunciators[11]; // G16
    AnnunciatorsReordered[17] = Annunciators[10]; // G17
    AnnunciatorsReordered[18] = Annunciators[9];  // G18
}


void Main_Aux_TFT_Display(void) {
    // Clear LCD buffers
    memset(LCD_buffer_packets, 0, sizeof(LCD_buffer_packets));
    memset(LCD_buffer_bitmaps, 0, sizeof(LCD_buffer_bitmaps));
    memset(LCD_buffer_chars, 0, sizeof(LCD_buffer_chars));

    ReorderAnnunciators();  // re-order the annunciators so Annunnciator[1] is above G1
char annunciator_debug[256] = "Annunciators: "; // Buffer for annunciator state debug

    for (int i = 0; i <= 17; i++) {      // G1 to G18
        // Use already-decoded data from Packets_to_chars
        uint8_t *bitmap = chars[i]; // Get the bitmap for this character
        char ascii_char = BitmapToChar(bitmap); // Convert bitmap to ASCII character

        // MAIN Update individual variables
        if (i == 0) G1 = ascii_char;
        else if (i == 1) G2 = ascii_char;
        else if (i == 2) G3 = ascii_char;
        else if (i == 3) G4 = ascii_char;
        else if (i == 4) G5 = ascii_char;
        else if (i == 5) G6 = ascii_char;
        else if (i == 6) G7 = ascii_char;
        else if (i == 7) G8 = ascii_char;
        else if (i == 8) G9 = ascii_char;
        else if (i == 9) G10 = ascii_char;
        else if (i == 10) G11 = ascii_char;
        else if (i == 11) G12 = ascii_char;
        else if (i == 12) G13 = ascii_char;
        else if (i == 13) G14 = ascii_char;
        else if (i == 14) G15 = ascii_char;
        else if (i == 15) G16 = ascii_char;
        else if (i == 16) G17 = ascii_char;
        else if (i == 17) G18 = ascii_char;
   
        // Append MAIN to debug buffers for additional debugging
        snprintf(LCD_buffer_bitmaps + strlen(LCD_buffer_bitmaps),
                 sizeof(LCD_buffer_bitmaps) - strlen(LCD_buffer_bitmaps),
                 "%d : [%02X, %02X, %02X, %02X, %02X, %02X, %02X]\n",
                 i, bitmap[0], bitmap[1], bitmap[2], bitmap[3],
                 bitmap[4], bitmap[5], bitmap[6]);
   
            // Append annunciator states to debug string
        snprintf(annunciator_debug + strlen(annunciator_debug),
                 sizeof(annunciator_debug) - strlen(annunciator_debug),
                 "G%d=%s ", i + 1, Annunciators[i] ? "ON" : "OFF");
        }

    // Null-terminate the Main display debug string
    main_display_debug[LINE1_LEN] = '\0';

    for (int i = 18; i <= 46; i++) {      // G19 to G47
        // Use already-decoded data from Packets_to_chars
        uint8_t *bitmap = chars[i]; // Get the bitmap for this character
        char ascii_char = BitmapToChar(bitmap); // Convert bitmap to ASCII character
   
    // AUX Update individual variables
        if (i == 18) G19 = ascii_char;
        else if (i == 19) G20 = ascii_char;
        else if (i == 20) G21 = ascii_char;
        else if (i == 21) G22 = ascii_char;
        else if (i == 22) G23 = ascii_char;
        else if (i == 23) G24 = ascii_char;
        else if (i == 24) G25 = ascii_char;
        else if (i == 25) G26 = ascii_char;
        else if (i == 26) G27 = ascii_char;
        else if (i == 27) G28 = ascii_char;
        else if (i == 28) G29 = ascii_char;
        else if (i == 29) G30 = ascii_char;
        else if (i == 30) G31 = ascii_char;
        else if (i == 31) G32 = ascii_char;
        else if (i == 32) G33 = ascii_char;
        else if (i == 33) G34 = ascii_char;
        else if (i == 34) G35 = ascii_char;
        else if (i == 35) G36 = ascii_char;
        else if (i == 36) G37 = ascii_char;
        else if (i == 37) G38 = ascii_char;
        else if (i == 38) G39 = ascii_char;
        else if (i == 39) G40 = ascii_char;
        else if (i == 40) G41 = ascii_char;
        else if (i == 41) G42 = ascii_char;
        else if (i == 42) G43 = ascii_char;
        else if (i == 43) G44 = ascii_char;
        else if (i == 44) G45 = ascii_char;
        else if (i == 45) G46 = ascii_char;
        else if (i == 46) G47 = ascii_char;

        // Append AUX to debug buffers for additional debugging
        snprintf(LCD_buffer_bitmaps + strlen(LCD_buffer_bitmaps),
                 sizeof(LCD_buffer_bitmaps) - strlen(LCD_buffer_bitmaps),
                 "%d : [%02X, %02X, %02X, %02X, %02X, %02X, %02X]\n",
                 i, bitmap[0], bitmap[1], bitmap[2], bitmap[3],
                 bitmap[4], bitmap[5], bitmap[6]);
        }


    // Null-terminate the Aux display debug string
    main_display_debug[LINE2_LEN] = '\0';
}



//************************************************************************************************************************************************************


// Main
int main(void)
{

// MCU Configuration--------------------------------------------------------

// Reset of all peripherals, Initializes the Flash interface and the Systick.
HAL_Init();

// Configure the system clock
SystemClock_Config();

// Initialize all configured peripherals
MX_GPIO_Init();
MX_DMA_Init();
MX_SPI1_Init();
MX_SPI2_Init();

/* Infinite loop */

SSD1322_API_init();
Init_Completed_flag = 1; // Now is a safe time to enable the EXTI interrupt handler
set_buffer_size(256, 64); // SSD1322 OLED size

while (1)
{
Packets_to_chars();
Main_Aux_TFT_Display();
fill_buffer(tx_buffer, 0); // Clearing OLED frame buffer

// Drawing (recoding, horizontal and vertical scaling) of the characters of the main display line
for (int i = 0; i < CHAR_HEIGHT; i++)
for (int j = 0; j < LINE1_LEN; j++)
for (int k = 0; k < 5; k++)
tx_buffer[2 + j * 7 + k + (LINE1_Y + i * 5 + 0) * 128] = tx_buffer[2 + j * 7 + k + (LINE1_Y + i * 5 + 1) * 128] =
tx_buffer[2 + j * 7 + k + (LINE1_Y + i * 5 + 2) * 128] = tx_buffer[2 + j * 7 + k + (LINE1_Y + i * 5 + 3) * 128] = Upscale1[chars[j][i]][k];

// Drawing (recoding and vertical scaling) of the characters of the auxiliary display line.
for (int i = 0; i < CHAR_HEIGHT; i++)
for (int j = 0; j < LINE2_LEN; j++)
for (int k = 0; k < 3; k++)
tx_buffer[7 + j * 4 + k + (LINE2_Y + i * 2 + 0) * 128] = tx_buffer[7 + j * 4 + k + (LINE2_Y + i * 2 + 1) * 128] = Upscale2[chars[j + LINE1_LEN][i]][k];

// Drawing of the annunciators
for (int k = 0; k < LINE1_LEN; k++)
if (flags[k])
for (int i = 0; i < 5; i++)
for (int j = 0; j < 7; j++)
tx_buffer[1 + j + k * 7 + i * 128] = Sprites[i][1 + k * 7 + j];

send_buffer_to_OLED(tx_buffer, 0, 0); // Send the frame buffer content to OLED
HAL_GPIO_TogglePin(GPIOC, TEST_OUT_Pin); // Test LED toggle
}
;

}


// System Clock Configuration
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = { 0 };
RCC_ClkInitTypeDef RCC_ClkInitStruct = { 0 };

//Initializes the RCC Oscillators according to the specified parameters in the RCC_OscInitTypeDef structure.
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}

// Initializes the CPU, AHB and APB buses clocks
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
                            | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}


// This function is executed in case of error occurrence.
void Error_Handler(void)
{
// User can add his own implementation to report the HAL error return state
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
   ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 

Offline Mickle T.Topic starter

  • Frequent Contributor
  • **
  • Posts: 494
  • Country: ru
Re: Advantest R6581 VFD replacement
« Reply #20 on: November 17, 2024, 02:37:44 pm »
Ian, thanks for the MAIN.C source code. I'm not a software programmer either, but it's never too late to learn :)
AFAIK, the Advantest display module uses the second half of the ASCII table to store special characters. In DIAG mode, characters with codes 0xE0...0xE8 are displayed on the display to simulate vertical and horizontal fill.
 
The following users thanked this post: IanJ

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #21 on: November 17, 2024, 04:34:27 pm »
You can find very efficient code for all this in the archive i posted above (file: inData_Format.c). Except i used int64 (8 bytes per image). There is a character table for most of the characters i saw during data taking and configuration and there is an optimized binary search to do the OCR.
In the end i finished without fully implementing a higher resolution font as i knew that characters would be missing, e.g. those graphics characters that are now mentioned by Mickle T. One might also follow a hybrid approach, like replacing known character images by higher resolution ones while leaving unknown characters as they are. The STM32H730 MCU i selected has enough free flash for a very nice and complete font, even with anti-aliasing.

Regards, Dieter
« Last Edit: November 17, 2024, 04:38:52 pm by dietert1 »
 

Offline IanJ

  • Supporter
  • ****
  • Posts: 1849
  • Country: scotland
  • Full time EE & Youtuber/Creator
    • IanJohnston.com
Re: Advantest R6581 VFD replacement
« Reply #22 on: November 17, 2024, 05:29:14 pm »
You can find very efficient code for all this in the archive i posted above (file: inData_Format.c). Except i used int64 (8 bytes per image). There is a character table for most of the characters i saw during data taking and configuration and there is an optimized binary search to do the OCR.
In the end i finished without fully implementing a higher resolution font as i knew that characters would be missing, e.g. those graphics characters that are now mentioned by Mickle T. One might also follow a hybrid approach, like replacing known character images by higher resolution ones while leaving unknown characters as they are. The STM32H730 MCU i selected has enough free flash for a very nice and complete font, even with anti-aliasing.

Regards, Dieter

Thanks, I grabbed a few of the hex character fonts (some lower case and symbols) that I hadn't verified yet and converted them to my format.

Cheers,

Ian.
Ian Johnston - Original designer of the PDVS2mini || Author of WinGPIB
Website: www.ianjohnston.com
YouTube: www.youtube.com/user/IanScottJohnston, Odysee: https://odysee.com/@IanScottJohnston, Twitter(X): https://twitter.com/IanSJohnston, Github: https://github.com/Ian-Johnston?tab=repositories
 

Offline dietert1

  • Super Contributor
  • ***
  • Posts: 2475
  • Country: br
    • CADT Homepage
Re: Advantest R6581 VFD replacement
« Reply #23 on: November 17, 2024, 06:04:54 pm »
By the way, my project outputs OCR'd display data via the debug uart (Uart1 meant to be connected via STLink).

Regards, Dieter
 

Offline vinovino

  • Contributor
  • Posts: 19
  • Country: ph
    • EEVblog Electronics Community Forum
Re: Advantest R6581 VFD replacement
« Reply #24 on: November 21, 2024, 02:26:37 am »
Can this VFD replacement work on Advantest R6243 display?
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf