Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 19:10 12 Jul 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : Dot-matrix printer interface for the MM

     Page 2 of 4    
Author Message
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 01:23pm 05 May 2014
Copy link to clipboard 
Print this post

Acknowledged - pun INTENDED!!!!

I won't bother with the ACK, and will drop the series resistors to about 330 and let the forum know what happens. I will just add these to the point-to-point socket I have with the 10k's already on it, and run a test or two before I get too carried away making a PCB - need to know it works first!
Smoke makes things work. When the smoke gets out, it stops!
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 02:06am 14 May 2014
Copy link to clipboard 
Print this post

Just a little update for you all.

None of the printers seemed to like the series resistors on the data lines. I went down to 180R, but the printer either would not respond, or it would only print some of the letters.

I have removed these resistors. In an ideal world, perhaps a buffer would be called for, but we'll wait and see what happens without one. I still have the 10k pull-up resistors.

After testing three Panasonic, two Star, one Epson, one OKI and one other printer whose brand name escapes me at the moment, I have had to change the code to allow for 10ms pulses. This has made the printing routine a little slower then I planned, but still only about 390mS to print a line, which is not too bad.

The Panasonic printers were all happy with 1mS pulses, but one of the Star and the OKI printer printed incomplete lines with 1mS pulses. Increased to 10mS pulse-rate, and all printers I could find to test were happy.

Current code is:


setpin D2,9:setpin D3,9:setpin D4,9:setpin D5,9:setpin D6,9
setpin D7,9:setpin D8,9:setpin D9,9:setpin D10,9:pin(D10)=1
port(D2,8)=1

P$="Hello world. This is a test message."

Timer=0
PRINTER (P$)
T=timer

print T

end

sub PRINTER (P$)
for X=1 to len(P$)
C$=mid$(P$,X,1)
port(D2,8)=asc(C$):pulse D10,10:pause 10
next
port(D2,8)=13:pulse D10,10:pause 10
port(D2,8)=10:pulse D10,10:pause 10
end sub


I have not made use of the other control lines - does not seem to be needed - not in my application anyway... I have made provision for this in the PCB though, so if I decided later I wanted to, I can still do this.Edited by Grogster 2014-05-15
Smoke makes things work. When the smoke gets out, it stops!
 
Goeytex
Regular Member

Joined: 12/05/2014
Location: United States
Posts: 74
Posted: 04:17am 14 May 2014
Copy link to clipboard 
Print this post

Hey Grog,

Adding a 10k pullup on a data line puts 10K in parallel with the internal pullup reducing the total pullup resistance and therefore increasing the sink current necessary to pull the parallel data line low.

I don't think adding the 10K pullups is buying you anything and may not be advised.

If you want some protection, then you might try removing the 10K pullups and then add a 100R series resistor. Your scope should show if the pin is being pulled low enough.
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2437
Posted: 05:10am 14 May 2014
Copy link to clipboard 
Print this post

the strobe pin is active low, and data is latched on the 1->0 transition, that is the leading edge of the pulse. my feeling is that you should have a small delay between setting up the data pins and pulsing the strobe line low to ensure the data has 'settled':


Do
Loop Until Pin(D11)=0
Port(D2,8)=asc(C$)
Pause 1
Pulse D10,10

(assuming D11 is the busy line)

the Do Loop Until above monitors the busy line and will save sending data when it can not be processed. This will likely allow you to shorten the strobe pulse down to 1mS, and remove the need for a pause after the strobe pulse. without knowing exactly how the firmware in the printer works, it really is a tad risky to blindly send data, even if delays have been added in.

the CR/LF, i would handle by simply adding a CR/LF to the end of the string before printing. this removes the need for duplicating all the handshaking:


sub PRINTER (P$)
P$ = P$ + chr$(13) + chr$(10)
...



rob :-)
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2437
Posted: 06:18am 14 May 2014
Copy link to clipboard 
Print this post

from this page:
http://www.fapo.com/1284elec.htm

the following may be of interest:

The requirements for the Level II drivers and receivers are defined at the connector interface. The driver requirements are:

1. The open circuit high-level output voltage shall not exceed +5.5V.
2. The open circuit low-level output voltage shall be no less than -0.5V.
3. The DC steady state, high-level output voltage shall be at least +2.4V at a source current of 14mA.
4. The DC steady state, low-level output voltage shall not exceed +0.4V at a sink current of 14mA.
5. The driver output impedance (Ro), measured at the connector, shall be 50 +/- 5 ohms at 1/2 the actual driver Voh minus Vol voltage.
6. The driver slew rate shall be 0.05-0.40 V/nS

Like the driver requirements, the receiver requirements are defined at the connector interface. The receiver requirements are:

1. The receiver shall withstand peak input voltage transients between -2.0V and +7.0V without damage or improper operation.
2. The receiver high-level input threshold shall not exceed 2.0V
3. The receiver low-level input threshold shall be at least 0.8V.
4. The receiver shall provide at least 0.2V input hysteresis, but not more than 1.2V.
5. The receiver high-level sink current shall not exceed 20uA at +2.0V.
6. The receiver low-level input source current shall not exceed 20uA at +0.8V.
7. Circuit and stray capacitance shall not exceed 50pF.

 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 01:21pm 14 May 2014
Copy link to clipboard 
Print this post

Thanks guys. I will try removing the 10's, and putting in series 100's, and also like Rob's idea of adding the CR/LF to the main string - why did I not think of that before?!(rhetorical!)

EDIT:

  robert.rozee said  without knowing exactly how the firmware in the printer works, it really is a tad risky to blindly send data, even if delays have been added in.


With old dot-matrix printers, I would have expected that it was a standard. While you do need drivers for modern printers, old LPT printers are a legacy device, so I would have thought that what works on one, should work on the other 99% of the time, as the pinouts are all the same, and the way you clock data into the printer is the same for all printers etc, so you would not THINK you'd need to ensure THAT much compatibility, once you get it working on a couple, all dot-matrix printers should then work. False logic?

I would agree that different printers will have different control-codes to support their special features like NLQ or fonts ets, but the basic ASCII character set should always work on any printer, I would have thought. Something else to think about anyway! Edited by Grogster 2014-05-16
Smoke makes things work. When the smoke gets out, it stops!
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 04:16pm 14 May 2014
Copy link to clipboard 
Print this post

OK, here is an update.

I removed the 10k pull-ups, and installed 100R series resistors on all lines.
I incorporated Rob's suggestions - thanks.

Everything working fine now even with 1mS pulses on the printers that would not acknowledge before - me happy.

Here is the updated code with Rob's suggestions:


setpin D2,9:setpin D3,9:setpin D4,9:setpin D5,9:setpin D6,9
setpin D7,9:setpin D8,9:setpin D9,9:setpin D10,9:pin(D10)=1
setpin D11,2:setpin D12,9:pin(D12)=1:setpin D13,2:port(D2,8)=1
pause 1:pulse D12,1:pause 1 'Reset printer before doing anything

P$="Hello world. This is a test message."

Timer=0
PRINTER (P$)
T=timer

print T

end

sub PRINTER (P$)
P$=P$ + chr$(13) + chr$(10)
for X=1 to len(P$)
do:loop until pin(D11)=0
C$=mid$(P$,X,1)
port(D2,8)=asc(C$)
pause 1:pulse D10,1:pause 1
next
end sub


Port D pins 2-9 are the data lines, D10 is STROBE, D11 is BUSY, D12 is RESET and D13 is PAPER OUT. Paper Out is not shown as being used here, but it is going to be implemented soon.

The T reference at the end of the test routine does not stay constant anymore, it varies anywhere from 138mS to about 280mS or so me thinks that use of the BUSY line is a good idea, and since I used that idea from Rob, the printer always prints perfectly. Last night, I was getting a few lines with missing characters, and could not obtain the same result on every line with the non-Panasonic printers. Now they are all working great, so I think that BUSY line really is helping things. Even at 280mS or so, this is only about half the time I had last night, so very happy indeed thus far.Edited by Grogster 2014-05-16
Smoke makes things work. When the smoke gets out, it stops!
 
shoebuckle
Senior Member

Joined: 21/01/2012
Location: Australia
Posts: 189
Posted: 01:30pm 12 Jun 2014
Copy link to clipboard 
Print this post

CircuitGizmos' Printer code is now in the MMBasic Library.
http://geoffg.net/maximite.html#Downloads
Hugh
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2437
Posted: 04:18am 13 Jun 2014
Copy link to clipboard 
Print this post

there is an error in the explanation of how the library printer code works:

'What happens with the use of the BUSY line, is that the code sends a byte to
'the printer, then waits for the printer itself to signal that it is NOT busy
'(in other words, that it is ready for another byte of data), then it clocks
'another byte to the printer. Only once the full message has been sent, and
'then you send the printer a CR(decimal 13), does the printer actually print
'out what has been clocked into its data buffer up to that point.


a correct explanation is that the busy check is done before each character is sent. this is so that (1) no character is sent while the printer is still busy, and (2) the print routine does not unnecessarily wait for the last character of the line (LF) to complete.

the last sentence of the above, "Only once [...] that point", is incorrect in the sense that when the printer prints - be it one character at a time, when a CR/LF is received, or when a whole page has been received - is determined entirely by the printer and the firmware therein. the sentence, therefore, is misleading and should be removed.


next, the following should be removed, as the checking of BUSY before sending a character ensures that no buffer overrun will occur, irrespective of the buffer size:

'Different printers have different data buffer sizes, so you will need to
'check this with your specific printer if your messages are expected to be
'long. Most printers can accept up to 80 bytes(characters) without any
'kind of buffer overrun.



it is also not prudent to configure the output pins as 'open collector'. the drivers need to be able to both source and sink 14mA to meet the IEEE1284 specifications (specs given earlier in this thread). when the earlier discussions were going on i didn't realise that mode 9 of SETPIN was open collector, otherwise i would have commented then.

and finally, in a good library routine there should be some sort of timeout provision so that the routine will not loop forever on the printer being stuck, for whatever reason, in a BUSY state.


rob :-)Edited by robert.rozee 2014-06-14
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 05:06pm 13 Jun 2014
Copy link to clipboard 
Print this post

I'm happy enough with all of those changes, rob.

How about you take all that guff I wrote, and re-write the text to your satisfaction, and then I will ask Hugh to alter it in the library.

Do you have a suggestion other then open-collector then?
Pins can be either input, or output or open-collector output, but they can't be both at the same time - IE: the MM pins can't be input and output at the same time, as far as I know....

It's easy to add a timeout for the busy being held-up.

My submission is just a starting point - by all means, Rob - take it, improve it, and resubmit it. The library is a collection of useful programs for others to use, but it is not a MM code bible - I make no claim to this being the best way or the "Approved" way of doing things, only that this code and method works fine on the printers I have tested it with. Edited by Grogster 2014-06-15
Smoke makes things work. When the smoke gets out, it stops!
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2437
Posted: 04:09am 17 Jun 2014
Copy link to clipboard 
Print this post

hi Grogster,

I may have been a bit hard on your submission - when i read through it first time i didn't actually realize the comments were written by you, i thought they'd been added in by a 3rd party. i also tend to pounce quite hard on documentation, as people will often follow that rather than the code when writing their own versions.

i've gone through and done an editing of the comments, as well as changing around a few things in the code: i believe that it is allowable to write to a port before setting it as an output to ensure that there are no glitches introduced.

i've not changed the open-collector settings in the code yet, as it is likely to work ok as it stands with most printers - most implement TTL inputs with pullups to 5v. but i am not certain pullups can be guaranteed in every printer. as i understand the IEEE1284 specifications, it would seem to be equally valid to have pulldowns to 0v.

thinking through this more, i can see an interesting problem arising with a number of printer pins having pullups to 5v and a micromite driving the printer. the micromite has only 7 pins that are 5v tolerant, necessitating the use of at least some 3v3 pins.

the possibility then arises for pullups in the printer to generate enough current flow through the protection diodes on the micromite's pins (irrespective of being open collector or not) to lift the supply voltage above the 3.6v maximum. this would be bad. very bad.

micromite solutions:
1. place a 3v3 zener across Vcc.
2. place 3v3 zeners across each of D0..D7, STROBE, RESET at the micromite
3. the same as 2, but in a "T" network with 47r resistors on each side
4. use a proper 3v3 to 5v level shifter

alas, 1. is morally wrong, 2. feels inadequate, 3. is getting there but i worry a little about the data pins not swinging high enough, and 4. adds complexity to what was previously a simple solution.


anyways, see what you think of the below...

rob :-)



'=========================================================== ==================
'DOT-MATRIX PRINTER ROUTINE

'Concept by CircuitGizmos with BUSY line suggestions by robert.rozee
'of TBS forums - Thanks guys!

'FULL INFORMATION THREAD CAN BE FOUND HERE:
'http://www.thebackshed.com/forum/forum_posts.asp?TID=6520&P N=0&TPN=1

'Version 1.0 - Initial submission to library.

'Works with B/W MM, Colour MM, and MicroMite.
'=========================================================== ==================

'This routine was developed out of CircuitGizmos orignal idea to use a
'dot-matrix printer within MMBasic. I have refined the routine,
'incorporating ideas from others and make use of the BUSY line from the
'printer to ensure that data is clocked to the printer only when it is
'not busy - this ensures no incorrect printouts result from sending data
'to the printer faster than the printer can deal with it.

'Any data you want printed is sent to the subroutine in P$. The sub will
'clock the data to the printer making use of the BUSY line, and then return
'to the main program. NOTE: the subroutine will loop forever while the
'printer remains BUSY, so if this is likely to happen (an example is if the
'paper runs out) it would be a good idea to add some sort of timeout to the
'do:loop part of the code and suitable error reporting.

'I am using port D for this, but you could use other pins.
'IT IS VITAL that you select 5v tolerant pins for any pins that the printer
'will be driving itself - BUSY and PAPER OUT. the parallel-printer standard is
'that all data lines sourced from the printer are idle-high at 5v, and if you
'connect these directly to any MM 3v3 pins, you will kill the MM chip. For
'output pins (data 0..7, STROBE, RESET) things becomes more complicated. if 5v
'tolerant pins are used with open-collector outputs there is no issue, but this
'is not guaranteed to drive every printer (however, it should be ok with most).
'if pins are not driven as open collector, there is the possibility of current
'flowing back through the MM's internal protection diodes lifting Vcc above
'3v3. in this case consider using a level-shifting buffer.

'it is recommended that 100R resistors be placed in series with all line (both
'input and output) as an extra layer of protection.
'YOU HAVE BEEN WARNED!

'In my example, I am using port D2-D13 for the printer interface.
'D2-D9 is the 8-bit parallel data bus (data 0 through data 7), D10 is
'the STROBE line, and D11 is the RESET line used to reset the printer when
'you first run the code. D12 is the BUSY line, and D13 is the PAPER OUT line
'which is not currently used in this version of the code (but may be later).

'What happens with the use of the BUSY line, is that the code waits for the
'printer to signal that it is NOT busy (in other words, that it is ready for
'another byte of data) BEFORE each byte is sent. When exactly characters are
'printed depends upon the printer and its buffer size. Some printers may print
'characters as they arrive, some may buffer a one or two, some may buffer a
'whole line or more and only print when a CR or LF is received.

'As for the circuit, you should put a 100-ohm resistor in series with ALL
'data and control pins in use, to limit any current into or out of the
'MM port. There is no need for any kind of pull-up on the printer-port
'as this is taken care of by the printer electronics. While the IEEE1284
'specifications say driven lines need to be able to sink AND source up to
'14mA, the input current is usually just a TTL load with something like
'10k pullups within the printer.

'////////////////
'It is important that you set the port to open-collector outputs(xx,9),
'as this along with the 100R resistors, limits the current on any one
'printer line.
'//////////////// (not a solution with a micromite)


'Usage is simple: Call PRINTER(P$), where P$ is the text you want the
'printer to print out. Generally speaking, the routine returns within
'250ms for a standard 80 character line, but if timing is important, then
'you will need to check this aspect BEFORE using this routine.

'Grogster @ The Backshed Forums - 08/06/2014, 8:52PM



'===== PUT THIS AT THE START OF YOUR CODE, TO INITIALIZE THE PRINTER =====

setpin D2,9: setpin D3,9: setpin D4,9: setpin D5,9
setpin D6,9: setpin D7,9: setpin D8,9: setpin D9,9
port(D2,8)=1 pin(D10)=1: setpin D10,9

pin(D11)=1: setpin D11,9: pause 1:pulse D11,1:pause 1 ' reset printer

setpin D12,2: setpin D13,2 ' these are inputs and must be 5v tolerant


' {MAIN CODE HERE}


'=======================
'PRINTER SUBROUTINE CODE
'=======================

sub PRINTER (P$)
PO$=P$ + chr$(10)
for X=1 to len(PO$)
do:loop until pin(D12)=0
C$=mid$(PO$,X,1)
port(D2,8)=asc(C$)
pause 1:pulse D10,1
next
end sub



 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 01:23pm 17 Jun 2014
Copy link to clipboard 
Print this post

Looks good to me, Rob.

I feel it only fair, that you should also be listed as a writer of the explanation text, as you have added to that and clarified some aspects better then I have, IMHO.

I have never checked this code on the MicroMite, but if it is short of 5v tolerant pins, perhaps it should not be suggested that you can use this with the MicroMite?

Thoughts?
Opinions?

So are you now happy enough with the open-collector output idea, with the mention of the fact that there is a possibility that this concept may not work with every printer in the world?

My tests did involve four different brands of printer(Panasonic, OKI, Epson and Star), and they all worked happily with this concept ONCE we incorporated your suggestion of using the BUSY line - some of those four test printers also did not like the data from the MM when NOT using the BUSY line, but as soon as I added that into the code at your suggestion, ALL of the printers printed perfectly.

Any more comments or suggestions welcomed.

Once you are happy, I can re-submit to the library, so that is kept up to date.


Smoke makes things work. When the smoke gets out, it stops!
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 09:38pm 17 Jun 2014
Copy link to clipboard 
Print this post

I am rewriting the code to allow for user-set timeouts, CR or LF or both, and PAPER-OUT.

This is a work in progress, and I will test at the weekend, then resubmit to the library as version 1.1

EDIT: OK, here is the first draft code......(ignoring the initalize code)


'=======================
'PRINTER SUBROUTINE CODE
'=======================

sub PRINTER (P$,T,CR,LF)
local C$,P$,PO$,T,Y,X
Y=0:TOE=0:POE=0:T=T*2
If CR=1 and LF=0 then PO$=P$ + chr$(13)
If CR=0 and LF=1 then PO$=P$ + chr$(10)
If CR=1 and LF=1 then PO$=P$ + chr$(13) + chr$(10)
for X=1 to len(PO$)
do
pause 0.5
Y=Y+1
loop until pin(D12)=0 or Y=T or pin(D13)=1
If Y=T then TOE=1:exit sub 'Check TOE once the sub ends, to see if there was a TimeOut Error.
If pin(D13)=1 then POE=1:exit sub 'Check POE once the sub ends, to see if there was a PaperOut Error.
C$=mid$(PO$,X,1)
port(D2,8)=asc(C$)
pause 1:pulse D10,1:Y=0:TOE=0:POE=0
next
end sub


This SHOULD timeout with a setting of T passed to the sub, and also will add CR or LF or both to the string. It should also exit with errors such as paper-out.

I plan to test this at the weekend - does anyone see anything obviously wrong here?Edited by Grogster 2014-06-19
Smoke makes things work. When the smoke gets out, it stops!
 
Geoffg

Guru

Joined: 06/06/2011
Location: Australia
Posts: 3285
Posted: 03:43am 18 Jun 2014
Copy link to clipboard 
Print this post

You use T as a parameter to the subroutine, then define it as a local variable. That will throw an error ("Variable already Local").

Geoff
Geoff Graham - http://geoffg.net
 
robert.rozee
Guru

Joined: 31/12/2012
Location: New Zealand
Posts: 2437
Posted: 04:43am 18 Jun 2014
Copy link to clipboard 
Print this post

  Grogster said  are you now happy enough with the open-collector output idea


i can't think of a better solution than open-collector and 5v tolerant pins. it is not ideal, but the best we have. if not open-collector then there is the risk of the device (32MX150 or 32MX695) being powered via the output pins directly. if not 5v tolerant then the same risk exists via the internal protection diodes. either case is bad news for the processor as there is a risk of Vcc being dragged too high.

for the micromite (32MX150) the safest solution - given a lack of sufficient 5v tolerant pins - is to use an external octal level converter or individual transistors (with pullups, if needed, to the 5v output from the printer).


if you wanted to be really adventurous, you could write a version of the code that uses the ACK signal tied to an interrupt. you then just print the first byte of your output string and let the interrupt routing suck up the rest of the characters as the printer is ready. this would mean printing would occur in the background while the rest of your program carries on doing other stuff.

you may also care to experiment with swapping round the order of CR and LF. some printers may return quicker if the LF occurs before the CR (the CR initiating actual printing).


rob :-)
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 01:10pm 18 Jun 2014
Copy link to clipboard 
Print this post

@ Geoff - well spotted. Thanks. I also do that with P$. Will change that....

@ Rob - thanks, will have a play with all of this at the weekend, if I can find the time - the weekend never seems to have enough hours!!!

Edited by Grogster 2014-06-19
Smoke makes things work. When the smoke gets out, it stops!
 
Herry

Senior Member

Joined: 31/05/2014
Location: Australia
Posts: 261
Posted: 08:56pm 18 Jun 2014
Copy link to clipboard 
Print this post

Will someone do the impossible and develop a method of writing to a USB laser printer?Edited by Herry 2014-06-20
Senior?!  Whatever it says, I'm a complete and utter beginner...
 
TassyJim

Guru

Joined: 07/08/2011
Location: Australia
Posts: 6269
Posted: 09:13pm 18 Jun 2014
Copy link to clipboard 
Print this post

A lot of currently available printers rely on the host PC to do most of the work so they are definitely out of the picture.

If the laser printer understands PCL, the first problem is solved.

That still leaves the problem of a USB host device. I would prefer to stick with a network aware printer (which works with PCL)
A USB network server might do the job.

Jim
VK7JH
MMedit
 
Grogster

Admin Group

Joined: 31/12/2012
Location: New Zealand
Posts: 9593
Posted: 09:27pm 18 Jun 2014
Copy link to clipboard 
Print this post

  Herry said   Will someone do the impossible and develop a method of writing to a USB laser printer?


Heh, heh - yeah, that is why dot-matrix is the printer of choice here, and that I did not even bother with USB laser-printers. While the idea is nice, I doubt it could be done at a MM code level - ever.

There may well be a 3rd party printer controller module though, which could accept serial commands etc, and talk to a USB printer.

The problem is also with standards and drivers.

Dot-matrix printers, while very old by today's standards, ARE a standard, whereas USB printers rely on the printer driver to act as the interface between the USB port, and the computer. Printer drivers for the MM are totally out of the question.
Smoke makes things work. When the smoke gets out, it stops!
 
VK2MCT
Senior Member

Joined: 30/03/2012
Location: Australia
Posts: 120
Posted: 12:52am 19 Jun 2014
Copy link to clipboard 
Print this post

Something like a HP LaserJet P2035 seems to have a LPT.
around $250 at estore.com.au
I expect it would have the problem of not printing until a full page is sent or a FF sent.

John B.
 
     Page 2 of 4    
Print this page
The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025