AZ® Controller API

Last modified by Max on 2025/03/02 15:18

Contents

Controller concept

The controller emulates up to 32 AZ® disk devices on a single microSD card. Each emulated disk (pseudo disk) is represented on the card by a file in terms of the FAT32 file system, so there are no questions about placing pseudo disks on a large-capacity storage device - just place the card in the card reader, connect it to the PC, copy files of the required size (and with the required content) to the card, move it to the controller, and that's it! Moreover, after working with the card on the PDP-11/DVK/BK/UKNC, you can remove it from the controller, place it back in the card reader and copy the resulting disk image files to the PC, where you can work with them by any means - for example, connect to the emulator, archive and put them somewhere for storage, send them to a conference, etc. It is also not particularly difficult to copy some material found on the Internet onto a card, move it to the controller and use this material on the PDP-11/DVK/UKNC.

Controller registers

The controller has 4 registers on the QBUS

  • 177220 - Command and Status Register (CSR)
  • 177222 - Data Register (DR)
  • 177224 - Primary Boot Register (BOOT1)
  • 177226 - Alternate Boot Register (BOOT2)

The CSR register accepts commands in bits D0-D5 and the interrupt enable bit in bit D6, all write only, always zero is read. In bit D7, the readiness bit is read. One in it means that the previous command has been executed and the controller is ready for exchange. Zero means that the controller is busy executing the previous operation, the other registers are disabled, access to any of them will cause Trap to 4. If the execution of the previous command caused an error, bit D15 is set simultaneously with bit D7.

Writing to registers is done only in words; byte writing is not allowed.

All data exchange is conducted via DR. For commands with a single-word argument, this argument should be sent to DR, and then the command should be sent to CSR. For commands exchanging with the controller buffer, on the contrary, the command should be issued and only after it a data block of a certain length should be received or transmitted.

Interruptions

Most commands are executed almost instantly - within the execution time of one or two CPU commands. But the commands for exchanging with the micro-SD card still take time. You can wait for these operations to complete either by polling the D7 CSR bit, or (for example, if you have a multitasking OS that has somewhere to utilize this time) - set the D6 CSR bit (interrupt enable) and do something else. When the operation is completed, an interrupt will occur and you can continue operations on it.

Namespaces

As already mentioned, the disk device presented to the PDP-11 system is a physical file on some file system. Starting with the V17 firmware, network functionality has appeared, it allows using network drives provided by MAXIOL Landisk® technology. Accordingly, for complete unification and ensuring complete "transparency", all differences are reduced to the mount point, at the moment these are the following spaces:

  • 0:/ - local files on the MicroSD card
  • R:/ - network files in the file repository
  • N:/ - network files in the archive https://mirrors.pdp-11.ru/

Planned network spaces (functionality will be implemented in the following firmware):

  • P:/ - personal cloud, available only under one account
  • S:/ - cloud that allows you to share your files with other members

Buffers

The controller has several buffers for different purposes, all buffers have word sizes because they are transmitted through a 16-bit register.

  • Main buffer - IOBUF [258]
  • Non-volatile memory buffer - cmosmem_buffer[256]
  • Clock buffer - output timestamp_out_buffer[14]
  • Clock buffer - input timestamp_in_buffer [7]
  • IP information buffer - ipdata
  • Map size buffer - sizecard

Main command block

The command is sent to the CSR, to bits 5-0. It should be sent only as a word, byte writing is not allowed. The command bits are for writing only, always zero is read. The command codes are given in octal.

These commands use the main buffer for their operation (IOBUF [258]).

000: Controller reset

The command code is 000. The command stops all controller operations, if possible. Its completion should be waited for by polling. Example program:

;.............................
        MOV     #AZ$CSR,R3
1$:     CLR     @R3; Send the "Reset" command
        TSTB    @R3; Check the controller's readiness
        BPL     1$; If it's not ready, reset it again                                                    
               ; and check again
        TST     (R3)+; Check for an error,
                  ; change the address at the same time
                  ;  (will be useful later)
        BMI     ERR1
;...............................

Note on the reset command. In fact, it is instantaneous - if the controller is not busy executing an operation that cannot be interrupted, specifically - SD data exchange. While the SD data exchange is in progress, the controller does not accept any commands and the reset command can be skipped. Therefore, if the controller is busy (bit D7 is zero), the reset command is issued again. This happens quite rarely (for example, double-clicking Ctrl/C when rewriting), usually, during normal operations, the wait for writing or reading is performed in a special place and, upon completion of this operation, not a reset is performed, but completely different actions.

001: Select device

The controller supports up to 32 pseudo disks. The "device selection" command selects one of them for operation. Command code 001. To select a device, you should send the number of the drive you are going to work with to the data register (177222) and then send the code 001 to the CSR. The command is executed instantly, i.e. during the time the CPU sends the "device selection" code. When attempting to select an AZ disk that is not assigned an image file, an error is returned in bit D15 of the CSR register.

Пример программы:

;.......................

SetUni = 001; Symbolic name of the command
     ; "Device selection"

; The address CSR+2=DR remained in R3 from the previous fragment

; We assume that in R0 in bits 0-3 there is the number of the
; device, the remaining bits are zeros, the procedure for  ;calculating this number is not shown.

        MOV     R0,@R3; Let's send to DR the number of disk AZ, with
               ; which we are going to work with

        MOV     #SetUni,-(R3); and send the command "Drive selection", with the correction of the address in R3, which
; now points to CSR again.

        TST     (R3)+; Let's check for an error and again
              ; move the address in R3 to DR
        BMI     ERR2

;........................

002: Set block number, low bits of block number

The controller provides the machine with QBUS as disks AZ0 - AZ7 file-images of the DSK type on the micro-SD card. The size of these file-images and, accordingly, pseudo-disks, can be any, up to 4G each. Addressing on these pseudo-disks is direct - the block number received via QBUS, after shifting, is used as an offset from the beginning of the corresponding file-image. In fact, this is something like LBA on a PC.

There are PDP-11 operating systems that support such disks - RSX-11, DIAMS, and some others. However, the most common OS - RT-11 - uses a WORD (16 bits) for the block number, and the code 0177777 is used in some places for special purposes and is not suitable as a disk size, so disks with a maximum number of blocks of 0177776, i.e. 65534 blocks (33553408 bytes or 32767 K bytes), can be used for RT-11. Therefore, there are two commands for setting the block number: to set the low-order bits of the block number - code 002 and to set the high-order bits of the block number - code 012. If the block number fits into 16 bits (for RT-11 - always), it is enough to use the command to set the low-order bits of the block number, the high-order bits are cleared. If the number does not fit into 16 digits, then first the lower bits must be output, and then the higher ones. If you try to immediately transmit the higher bits without first transmitting the lower ones, an error is returned. If the transmitted address goes beyond the file-image boundary, an error is also returned, no matter at what stage - either when transmitting the lower 16 bits of the block number, or when transmitting the higher ones.

To perform these actions, you must send the required part of the block number bits to DR and then send the command code to CSR, after which you must check for an error. The commands are instantaneous, i.e. they are executed in one QBUS access cycle.

Example of a program - 16-bit  version block number:

;.......................................

SetBlk=002; Symbolic name of the command
; "Set the lower 16 bits of the block number"

; The address DR
; (177222) remained in R3 from the previous fragment
; We assume that the cell labeled BLCUR contains the 16-
; bit disk address (the block number to be
; input or output). The procedure for obtaining this number is not
; shown

        MOV     BLCUR,@R3;We place the block number to be exchanged in DR.
                 

        MOV     #SetBlk,-(R3); We send the command to the CSR
; do not forget that the address in R3 will decrease by
; 2 before sending and will remain so

        TST     @R3; We check for an error

        BMI     ERR3

; In the 32-bit version, the same actions should be
; repeat for the senior 16 bits (actually,
; senior 7, the rest should be zeros, because the maximum
; size of a pseudo disk is 4G) of the disk address.

; Note that in R3 the CSR address remains, and not
; DR, as in the two previous fragments. This is done
; on purpose.

;.......................................

003: Open HFS Table of Contents

Sequence of actions:

• reset the controller
• send the command "Accept data block to buffer" to the CSR and transmit the entire line with the full path text (Full Path) to the required table of contents word by word. The line must end with a zero byte (0x00) and be no longer than 384 bytes (192 words).
• send the "Open table of contents" command code to the CSR
• wait for it to finish (the command is long)
• check for errors

Example utility - AZDIR

Example program:

;.............................
AZ$CSR = 177220
WrBuf = 016
OpnDir = 003

        MOV     #AZ$CSR,R3
; The DirPtr cell contains a pointer to the beginning of the field with Full
; Path. We assume that the string is terminated by three zero
; bytes in order to recognize the end of the transfer of the
; string word by word by zero. Indeed, if the number of
; characters in the string is even, then the next two bytes of zeros
; form a zero word; if it is odd, then one zero
; will go away with the last character of the string, and the zero
; word is formed by the second and third zero bytes,
; ending the string. That is, such an end of the string,
; transferred word by word, is quite reliable.
        MOV     DirPtr,R2

        MOV     #WrBuf,(R3)+; We issue the "Write to
                     ; buffer" command and transmit the string
11$:    MOV     (R2)+,@R3; word by word,
        BNE     11$  ;until zero is sent,

        MOV     #OpnDir,-(R3); We issue the "Open
                       ; table of contents" command

12$:    TSTB    @R3; and wait for the controller to
        BPL     12$; execute it,
        TST     @R3; after which we check for an error.
        BMI     Err10; Error -->
;.............................

004: Mount a disk

Sequence of actions:
• Reset the controller
• Send a line to the controller with a record similar to the lines describing the disks in the AZ.INI file
• Issue the "Mount disk" command
• Wait for it to finish (the command is long)
• Check for errors
The selected AZnn drive MUST NOT have a disk mounted. If it is mounted, the old disk should be unmounted before mounting a new one - command 014

Example program:

;..................
AZMNT = 004
MDLEN = MDEND-MDTXT+2

        MOV     #AZ$CSR,R3

20$:    CLR     @R3;
        TSTB    @R3; Reset the controller
        BPL     20$;

        MOV     #WrBuf,(R3)+; Send it a string
        MOV     #MDTXT,R2; with the assignment command
        MOV     #MDLEN/2,R1;
21$:    MOV     (R2)+,@R3;
        SOB     R1,21$;

        MOV     #AZMNT,-(R3); and pass it to
22$:    TSTB    @R3   ; execution
        BPL     22$   ;

        TST     @R3; then check for an error
        BMI     Err11;
;.........................

MDTXT:  .ASCII  "D04=0:/DISKS/SYSTEM/51SYS_DS.DSK"
MDEND:  .BYTE   0,0
;..................

In this fragment, the 51SYS_DS.DSK image file is mounted on the AZ4 disk, located in the SYSTEM folder, which is located in the DISKS folder, which is located in the root directory of the micro-SD card.

005: Reading block into buffer

The controller has a built-in buffer for 256 words (512 bytes). In fact, this is part of the STM32 microcontroller's RAM, allocated in its program for this buffer. All exchange in the main command block goes through this buffer.

The AZ disk memory is represented as a set of blocks, each 512 bytes in size. Such a block is the only available unit for exchanging data with AZ disks. The blocks are numbered from zero to 65533 for the 16-bit version or up to 8388607 for the 32-bit version - this is when using AZ drives of the maximum permissible capacity. No one prevents you from using drives of a smaller capacity - the actual size of the drive is equal to the size of the file-image mounted on this drive. An error will be registered if you try to access beyond the file-image.

Command 005 - reading a block from MicroSD to the buffer. From the pseudo-disk AZn, previously selected by the command "Select device", the block whose number is transmitted by the command (commands) "Set block number" is started for reading. Long-term command.

In fact, a block from a MicroSD card is read in about 500-800 µs. During this time, the controller goes into a state that during the discussion of the project was called "I think, please do not interfere." Namely, for the entire time of its execution, all device registers are disabled, except for CSR, in which zero is read as long as the controller is busy executing this command. After the block is read, the remaining controller registers are connected to the QBUS, bit D7 (ready) is set in CSR and, if bit D6 (interrupt enable) was set in CSR, an interrupt with vector 0174 is generated.

An example without interruptions is trivial:

;...................................

CmdRea=005; symbolic name of the "Read block" command

; In R3 we have the CSR address left from the previous fragment.
; We send the read command code there
        MOV     #CmdRea,@R3
2$:     TSTB    @R3   Let's check the ready bit
        BPL     2$; Not ready -> we go to check again
; once
        TST     @R3; Let's check for an error
        BMI     ERR4
; Here again, unlike the fragments of pp. 3.1 and
; 3.2, we have the CSR address left in R3, not DR.

;...................................

006: Write block from buffer to disk

Command code 006. The buffer contents are written to the selected pseudo-disk at the specified disk address (block number). Before writing, checks are performed (1) "was there a write to the buffer?", if not, an error is returned and (2) "is the buffer completely filled?", if not (for the last shortened block of the file), the rest of the buffer is cleared with zeros. Then the block is written to the media. The operation is lengthy, after it is started the controller, as with reading, goes into the "I think, please do not interfere" state. And just as with reading, you must wait for the end of this operation, by the same means as with reading.

Example of a program without interrupts:

;...............................................................

CmdWri=006; symbolic name of the "Write
; block" command

; In R3 we have the DR address left over from the previous fragment.
; We correct it to the CSR and send the
; write command code there
        MOV     #CmdWri,-(R3)
5$:     TSTB    @R3   ; Let's check the ready bit
        BPL     5$; Not ready -> we go and check again once
        TST     @R3; Let's check for an error
        BMI     ERR5

;....................................................................

007: Get disk size

There are two commands for obtaining the size of a pseudo-disk, i.e. the AZn file-image mounted on the selected pseudo-drive.

If the OS being used (or a program working with disks without an OS) can work with large (more than 32M) disks, you should use the command with the code 017. The sequence of actions: reset the controller (command 000), select the drive (p. 3.2) and send the code 017 to the CSR, and then, without any waiting, read from DR first the lower word, and then the higher word of the size of the selected drive (image file).

If the OS you are using cannot work with disks larger than 32M (RT-11), you should use the 007 command - get the pseudo-disk size with a limit of up to 32M. The steps are similar: reset the controller, select the disk, send the 007 code to the CSR and read one word of the pseudo-disk size from DR. If the size of the image file mounted on the selected pseudo-drive is larger than 65534 blocks, the controller returns the number 65534 instead of this "large" size. We remind you that the number 65535 is used in some places for special purposes and cannot be the disk size.

We also remind you that if the image file is not mounted on this drive, the sequence of actions will not work (command 001 select device) and the program execution will simply not reach this point. Therefore, these commands do not provide for errors.

Example of a program with "small" disks

;......................................

GetSiz=007; Get the "small" disk size

; From fragment 3.2 (disk selection) we have in R3
; DR address (177222)

        MOV     #GetSiz,-(R3); send the command
        TST     (R3)+;  return the address in R3 back to DR
        MOV     @R3,DskSiz
;......................................

010: Allow network operation

Command code 010. Having completed the sequence of actions for transferring the next portion of data, and expecting that the next request will not follow immediately, you can "utilize" the processor time of the STM32 microcontroller, which is the basis of AZ - occupy it with servicing the network. In the same RT-11, this can be done before exiting the AZ driver, before the .DRFIN macro command, which completes the execution of the input-output request.

Indeed, the I/O operation is completed, the CPU program in the system will prepare a new portion of data for output, or figure out (based on the previously read data) where it should read something else, or generally think about something of its own. smile.gif In other words, after the I/O request is completed, there is a fairly high probability that there will be a pause in working with the AZ disks. So, the time of this pause can be given to servicing the network. To do this, before executing the .DRFIN macro in RT-11 or its analogue in other OS, you should send code 110 (enable network plus enable interrupts) to the CSR.

In this case, the interrupt will not occur, it is activated only upon completion of "long" operations that transfer the controller to the "Thinking, please do not interfere" state, and the interrupt enable trigger set to "1" also enables network operation if it is activated. When the next input-output operation is started, the actions in paragraph 3.1 (controller reset) will also reset this trigger, after which the network service program, having detected the reset of this trigger, will stop (suspend) its work and return control to the main disk service program AZ. The maximum that can be noticed from the CPU side is a small (10-20 µs) delay in executing the reset command, but this is a very reasonable price for network capabilities.

011: Get AZn drive assignment table

Command code 011. Upon receiving this command, the controller switches from the block buffer to its internal assignment table (32 lines of 140 bytes each)*. Before issuing this command, the controller should be reset. After issuing this command, command 015 (read buffer) should be issued, but in this case it will not be the buffer that is read, but the same table, sequentially, word by word.

* starting with v17, the file name length is no longer 130 bytes, but 386 bytes (the last word is zero, to end the line)

AZSMNT utility example

Example program:

;...................................
AZ$CSR = 177220; Controller CSR
RdBuf = 012; Command "Read from controller memory"
RdTbl = 011; Command "Read assignment table"
TblSiz = 1120.; Table length in bytes (decimal)

; We assume that R2 contains the address of the first word of the
; memory area for the assignment table. We do not show the procedure for obtaining this address.

        MOV     #AZ$CSR,R3; Preparing the controller CSR
10$:    CLR     @R3;
        TSTB    @R3; Reset the controller
        BPL     10$;

        MOV     #RdTbl,@R3;Command "Transfer
                     ; table"

        MOV     #RdBuf,(R3+); Command "Read from
; controller memory. At the same time, move the address in R3
; to the DR of the controller (177222).

        MOV     #TblSiz/2,R1; Prepare the word counter

11$:    MOV     @R3,(R2)+; Send the current word
SOB R1,11$; and repeat 560 times
;...................................

012: Setting the block number, block number high bits

The controller provides the machine with MPI as disks AZ0 - AZ7 file-images of the DSK type on the micro-SD card. The size of these file-images and, accordingly, pseudo-disks, can be any, up to 4G each. Addressing on these pseudo-disks is direct - the block number obtained by QBUS, after shifting, is used as an offset from the beginning of the corresponding file-image. In fact, this is something like LBA on a PC.

There are PDP-11 operating systems that support such disks - RSX-11, DIAMS, and some others. However, the most common OS - RT-11 - uses a WORD (16 bits) for the block number, and the code 0177777 is used in some places for special purposes and is not suitable as a disk size, so disks with a maximum number of blocks of 0177776, i.e. 65534 blocks (33553408 bytes or 32767 K bytes), can be used for RT-11. Therefore, there are two commands for setting the block number: to set the low-order bits of the block number - code 002 and to set the high-order bits of the block number - code 012. If the block number fits into 16 bits (for RT-11 - always), it is enough to use the command to set the low-order bits of the block number, the high-order bits are cleared. If the number does not fit into 16 digits, then first the lower bits must be output, and then the higher ones. If you try to immediately transmit the higher bits without first transmitting the lower ones, an error is returned. If the transmitted address goes beyond the file-image boundary, an error is also returned, no matter at what stage - either when transmitting the lower 16 bits of the block number, or when transmitting the higher ones.

To perform these actions, you should send the required part of the block number bits to DR and then send the command code to CSR, after which you should check for an error. The commands are instantaneous, i.e. they are executed in one cycle of access via MPI.

013: Read HFS TOC entry

Command code 013, the command reads the TOC record into the internal memory area and switches the pointer to it for data transfer via DR. The TOC must be open before this.

The procedure is as follows:
• Reset the controller.
• Issue the command "Read the table of contents entry" to the CSR and wait for it to finish.
• Issue the command "Read from controller memory" to the CSR
• Read 11 words of the table of contents entry from DR

The table of contents entry has the format:

Offset (octal)NameValue
0fSizeFile size in bytes, low word
2fSizeFile size in bytes, high word
4fDateDate in MS-DOS format
6fTimeTime in MS-DOS format
10fAttrAttributes 1 byte
10fNameNAME.FILE TYPE, 8+1+3+1 = 13 bytes

The offsets are specified in octal. The formula in the fName line means that there must first be a name, maximum of eight characters, then a period, then a type, up to three characters, and a terminating zero byte 0x00. If the type is not specified, the period is also not needed.

File attributes in fAttr byte (octal):

001 - Read Only
002 - Hidden
004 - System
020 - Directory
040 - Archive

Example program

;......................................
RdDir = 013; command code "Read table of contents entry"
RdBuf = 015

        MOV     #AZ$CSR,R3

15$:    CLR     @R3;
        TSTB    @R3;  Reset the controller
        BPL     15$;

        MOV     #RdDir,@R3; Ask the controller
16$:    TSTB    @R3; to read into its memory
        BPL     16$; table of contents entry

        MOV     @RdBuf,(R3)+;
        MOV     DIRREC,R2; And transfer it to itself in
        MOV     #11.,R1; memory area, pointer
17$:    MOV     @R3,(R2)+; to which lies in cell
        SOB     R1,17$; DIRREC.

;......................................

014: Unmount disk

Command code 014, to unmount the disk, you should reset the controller, send the AZ drive number to the controller DR, which should be unmounted, and send the 014 code to the controller CSR, then wait for the operation to complete (it takes a long time) and check for an error. An error is issued if the drive has not been mounted.
AZUMNT utility example

015: Start transferring the read block

The command code is 015. Having received this command, the controller is configured to output word by word the contents of the same built-in buffer for 256 words, which will be output sequentially through the DR register. No waiting is required, we simply send a word from DR to sequential memory cells 256 times, and that's it. If less than 256 words are needed (the last shortened block of the file), then the remainder can simply be discarded without reading, resetting the controller at the beginning of the next operation will also reset this remainder.

Example program:

;..................................
RdBuf=015; symbolic name of the command

; In R3 from the previous fragment there is the address of the CSR
; (177220)

; We assume that in R2 we have the address of the first word
; of memory, where the read block should be placed.
; The program for obtaining this address is not given.

        MOV     #400,R1; Prepare the word counter
                 ; 0400 oct = 256 dec

        MOV     #RdBuf,(R3)+; and send the command
; RdBuf to the CSR. The address in R3 will point to DR (177222).

3$:     MOV     @R3,(R2)+we will send the next word to
; memory
        SOB     R1,3$; and repeat this 256 (0400)
                  ; times
;..................................

That's it, reading is complete.

To write the opposite way, you first need to transfer the entire data block from the CPU memory to the controller and then issue the command "Write the contents of the buffer to disk"

016: Receive data block into buffer

Command code 016. The command sets the controller to receive a block of data and place it in the buffer. The next 256 write cycles to DR will place the data transferred via the QBUS in the buffer.

Example program:

;..................................

WrBuf=016; Symbolic name of the command

; Before writing, you need to perform the same actions as in
; pp. 3.1.-3.3. Usually, this is the same program,
; just after point 3.3. a check is performed "What
; is required: reading or writing?" and a branch is made to the
; reading or writing program.

; After the fragment in point 3.3., the CSR address
; (177220) remains in R3. We will assume that R2 contains the address in the CPU
; memory where the block to be written is located.
; The program for obtaining this address is not shown.

        MOV     #400,R1;  Preparing the counter

        MOV     #WrBuf,(R3)+;  Let's forward the command to the CSR and
; switch the address in R3 to
; DR

4$:     MOV     (R2)+,@R3;  Let's forward the next word
; data
        SOB     R1,4$; and repeat this 256 times
;..................................

017: Get ramdisk size, large

There are two commands to get the size of a pseudo-disk, i.e. the AZn file-image mounted on the selected pseudo-drive.

If the OS being used (or a program working with disks without an OS) can work with large (more than 32M) disks, you should use the command with the code 017. The sequence of actions: reset the controller (p. 3.1), select the drive (p. 3.2) and send the code 017 to the CSR, and then, without any waiting, read from DR first the lower word, and then the higher word of the size of the selected drive (image file).

If the OS you are using cannot work with disks larger than 32M (RT-11), you should use the 007 command - get the pseudo-disk size with a limit of up to 32M. The steps are similar: reset the controller, select the disk, send the 007 code to the CSR and read one word of the pseudo-disk size from DR. If the size of the image file mounted on the selected pseudo-drive is larger than 65534 blocks, the controller returns the number 65534 instead of this "large" size. We remind you that the number 65535 is used in some places for special purposes and cannot be the disk size.

We also remind you that if the image file is not mounted on this drive, the sequence of actions will not work (command 001 select device) and the program execution will simply not reach this point. Therefore, these commands do not provide for errors.

Example of a program with large disks

;......................................

GetBig=017; Get the "big" disk size

; From fragment 3.2 (disk selection) we have in R3
; DR address (177222)

        MOV     #GetBig,-(R3); send the command
        TST     (R3)+; return the address in R3 back to DR
        MOV     @R3,BigSiz
        MOV     @R3,BigSiz+2
;......................................

020: Get extended diagnostic code

Command code 020, after resetting the controller, you should issue this command in the CSR and then read two words of extended diagnostics from DR. The command is instant, no waiting is required.

027: Get firmware version AZ STM32

Command code 027, returns 2 words

first word - 06404 = high byte 13. this is the firmware version, low byte 4. this is the hardware version - i.e. AZБК in this case second word - 037 = this is the maximum mountable disk - 31.

;-------------------------------------------------------------
; getting STM32 firmware version - result in R1 R1=0 error
GTSTMV:         MOV     #AZ$CSR,R1
1$:             CLR     (R1)            ; Send "Reset" command
                TSTB    (R1)            ;Check controller readinessконтроллера
                BPL     1$              ;  If not ready, reset again
                mov     #27,(R1)
                TST     (R1)+           ; Check for error
                BMI     2$
                mov     (R1),R1
                return
2$:             CLR     R1
                return
;-------------------------------------------------------------

030: No operation

The main purpose of this command is to set the interrupt enable bit from the controller. The command transfers the interrupt enable bit, which is in the same word with it, but is not part of it (remember, the command is located in bits D0 - D5, and the interrupt enable bit is D6), to the corresponding trigger of the controller and does not affect the processes in the controller in any other way. Control of this trigger works even in the "Thinking, please do not interfere" state, and this is the main feature of the "no operation" command.

The command has the code 0030. Sending the code 0130 to the CSR will enable interrupts from the controller, sending the code 0030 will disable them. An example is not given due to its triviality.

Command block for working with non-volatile memory

The interface provides any AZ controller with access to 255 words of non-volatile memory, all commands set the ready bit upon completion. This allows you to save user settings in non-volatile memory, for example, this is used in AZBK ??- there are saved settings for more comfortable operation of the controller.

All commands in this block use a non-volatile memory buffer for their operation.

021: Read non-volatile memory block into buffer

Command code 021, this command causes a block of non-volatile memory to be read into the non-volatile memory buffer.

022: Transfer the read block of non-volatile memory from the buffer to the bus

Command code 022, this command ensures that the non-volatile memory buffer is transferred to the DR register for reading.

Example program

AZ$CSR = 177220; command and status register (CSR)
AZ$DR = 177222; data register (DR)


; trap 50 - reset AZ
; результат в R1  =0 ok
AZreset:        MOV     #AZ$CSR,R1
1$:             CLR     (R1); Send the "Reset" command
                TSTB    (R1); Check the controller readiness
                BPL     1$; If not ready, reset again
; once and check again
                TST     (R1); Check for an error,
                BMI     0ERR$
                CLR     R1
                return
0ERR$:          CLR     R1
                COM     R1
                return


; trap 54 - reading non-volatile memory of block 1 EEPROM to the buffer from the address ADREEPROMMEM
; result R3 - address, if R3=0 error
; read status in R1 0 - ok
; 1 - size does not match saved
; 2 - version error
; 3 - checksum error
ReadEEPROM:     push    R2
                call    AZreset; reset
                tst     R1
                bne     0ERR$
; теперь читаем
                MOV     #AZ$CSR,R1
                mov     #21,(R1); read block 1 of non-volatile memory into buffer
0$:             TSTB    (R1); check execution result
                BPL     0$; wait
                mov     #22,(R1); send read block of non-volatile memory from buffer to bus
1$:             TSTB    (R1); check execution result
                BPL     1$; wait
                TST     (R1)+; increment
                mov     #ADREEPROMMEM,R3
                mov     #256.,R2; read 256. words; first word is reading result
2$:             mov     (R1),(R3)+; read block of words into memory
                sob     R2,2$
                mov     #ADREEPROMMEM,R3; successful
                mov     (R3),R1
                br      0END$
0ERR$:          CLR     R3
0END$:          pop     R2
                return

obviously, after reading the memory, it is necessary to check the result code in the first word - see the decoding of error codes

Examples of returned data for commands

sequentially issuing the command 021 and then 022 will allow reading 256 words from non-volatile memory
Attention! The first word will be the reading success status

  • 0 - ok
  • 1 - size does not match saved
  • 2 - version error
  • 3 - checksum error

023: Receive data from the bus into the buffer for subsequent writing into the buffer

Command code 023, this command allows you to fill the non-volatile memory buffer

024: Write from buffer to non-volatile memory block

Command code 024, this command causes a non-volatile memory block to be written from the non-volatile memory buffer.

Example program

AZ$CSR = 177220; Command and Status Register (CSR)
AZ$DR = 177222; Data Register (DR)


; trap 50 - reset AZ
; результат в R1  =0 ok
AZreset:        MOV     #AZ$CSR,R1
1$:             CLR     (R1); Send the "Reset" command
                TSTB    (R1); Check the controller readiness
                BPL     1$; If not ready, reset again
; once and check again
                TST     (R1); Check for an error,
                BMI     0ERR$
                CLR     R1
                return
0ERR$:          CLR     R1
                COM     R1
                return

; trap 55 - write non-volatile memory from the buffer at address ADREEPROMMEM в блок 1 EEPROM
WriteEEPROM:    push    R1
                push    R2
                push    R3
                call    AZreset; reset
                tst     R1
                bne     0ERR$

                MOV     #AZ$CSR,R1
                mov     #23,(R1);command that we will write data to the buffer
0$:             TSTB    (R1); check the result of executio
                BPL     0$; wait
                TST     (R1)+; increment
                mov     #ADREEPROMMEM+2,R3
                mov     #255.,R2; write 255. words; skip the first word - the result of reading
1$:             mov     (R3)+,(R1); send to the controller
                sob     R2,1$
                tst     -(R1); decrement
                mov     #24,(R1); write from the buffer to block 1 of non-volatile memory
2$:             TSTB    (R1); check the result of execution
                BPL     2$; we are waiting
                br      0END$
0ERR$:          CLR     R3
0END$:          pop     R3
                pop     R2
                pop     R1
                return

Please note that when recording, the buffer immediately comes with the data, i.e. there is no first word with the statu

Block of commands for working with RTC and NTP

The AZ® controller has 2 sources of date-time, the first is the RTC built into the STM32, the second is the clock in the TCP/IP stack. The RTC clock works autonomously with a 2032 battery installed. The clock in the TCP/IP stack is set based on data from the NTP server.

Buffer format timestamp (readable)

The controller API immediately prepares time in several formats, so that it can be conveniently used on the PDP-11 side

Information

datetime buffer format octal offset - those words datetime buffer format

[0]=rtc_rt11date();
[2]=rt11 time 50Hz big word;
[4]=rt11 time 50Hz little word;
[6]=rt11 time 60Hz big word;
[10]=rt11 time 60Hz little word;
[12]=rtc_fat_date();
[14]=rtc_fat_time();
[16]=year+2000;
[20]=month;
[22]=day;
[24]=wday;
[26]=hour;
[30]=min;
[32]=sec;

SimpleIN buffer format (when writing)

the format is simplified as much as possible, for work with PDP-11

Information

offset in octal - those words

[0]=year, the lower two digits are 22 and not 2022error
[2]=month; month
[4]=day; day
[6]=wday; day of the week =0 not set, 1 - Monday 2 - Tuesday etc.
[10]=hour; hour
[12]=min; minute
[14]=sec; second

031: Get time from RTC to timestamp buffer

Command code 031, this command uses RTC clock as a source of filling the timestamp buffer

Example program:

; trap 61 - reading clock data from autonomous RTC clock
; R3 - buffer address where to read
; result in R3 address if successful. R3=0 if error
GetDateFromRTC: push    R0
                push    R1
                push    R2
                call    AZreset;  reset
                tst     R1
                bne     G60ERR
                MOV     #AZ$CSR,R1
                mov     #31,(R1)
                br      G60; let's go there because further code is the same

032: Get time from timestamp buffer

Command code 032, this command sends the contents of the timestamp buffer to the bus

; working with clock
; trap 60 - reading clock data from TCP/IP stack
; R3 - buffer address where to read
; result in R3 address if successful. R3=0 if error
GetDateFromLAN: push    R0
                push    R1
                push    R2
                call    AZreset; reset
                tst     R1
                bne     G60ERR
                MOV     #AZ$CSR,R1
                mov     #42,(R1)
G60:            TSTB    (R1); check execution result
                BPL     G60; wait
                mov     #32,(R1)
1$:             TSTB    (R1); check execution result
                BPL     1$; ждем
                TST     (R1)+;  increment
                mov     R3,R0; remember R3 address
                mov     #10.,R2;  read 10 words
2$:             mov     (R1),(R3)+; read block of words into memory
                sob     R2,2$
                mov     R0,R3; successful, return address to R3
                br      0END$
G60ERR:         CLR     R3
0END$:          pop     R2
                pop     R1
                pop     R0
                return

It is worth checking the correctness of the received time:

; trap 63 - check time correctness
; R3 - buffer address, result in R3, if buffer address then OK, =0 error
CheckDateTime:  Cmp     6(r3),#2021.
                Blos    1err
                Cmp     6(r3),#2100.
                Bhi     1err
0ok$:           return
1err$:          clr     R3
                return

033: Write time-date to SimpleIN buffer

Command code 033, this command receives data from the bus into the SimpleIN buffer

The operation of this command is similar to the operation of commands 023 and 016.

034: Set RTC based on buffer data

Command code 034, this command sets the RTC based on the data in the SimpleIN buffer

This command executes quickly, but to avoid problems, a wait loop is recommended.

035: Stimulate time request from NTP server, set based on response

Command code 035, this command sends a request to the NTP server (set in the AZ.INI file or received from DHCP) and sets the clock in the TCP/IP stack.

Example program: sending a request to set the time from an NTP server

; trap 62 - sending a request to set the time from the NTP server
GetDateNTPtoNET:push    R1
                call    AZreset; reset
                tst     R1
                bne     0ERR$
                MOV     #AZ$CSR,R1
                mov     #35,(R1)
0$:             TSTB    (R1); check the result of execution
                BPL     0$; wait
0ERR$:          pop     R1
                return

The command execution takes 1-2 seconds on average. This command requires the TCP/IP stack to work, so waiting cycles are needed when the stack is enabled.

An example of a polling cycle to get time from the network

; date-time
                mov     #S_DateTime_0,R3; "Lan Date:"
                trap    10
                mov     #20,R4; number of wait cycles
$datry:         trap    62; sent a request to the NTP server
                mov     #110,@#AZ$CSR; enable the network
                trap    47; waiting
                trap    47; waiting
                mov     #ADRTMPSTR,R3
                trap    60; read the date-time into the buffer
                trap    63; checked the date-time
                tst     R3
                bne     $ok
$sob:           sob     R4,$datry
                mov     #S_DateTime_2,R3; print error
                trap    7
                br      $go

$ok:            mov     #ADRTMPSTR,R3
                trap    24; print date
                trap    25; time
$go:            mov     #110,@#AZ$CSR;  let's turn on the network

Here we explicitly send a request to the NTP server, then turn on the network and wait for the result, periodically polling and checking the correctness of the result.

036: Setting RTC based on TCP/IP stack clock

Command code 036, this command sets the RTC based on the clock in the TCP/IP stack. You must first set the clock in TCP/IP - command 036.

Example program:

; trap 64 - set RTC time based on stack time
; R1 - result R1=0 - OK
SetDateNETtoRTC:call    AZreset; reset
                tst     R1
                bne     0ERR$
                MOV     #AZ$CSR,R1
                mov     #36,(R1)
0$:             TSTB    (R1); check execution result
                BPL     0$; wait              
                clr     R1
0ERR$:          return

042: Get time from TCP/IP stack clock into timestamp buffer

Command code 042, this command uses the TCP/IP stack clock as a source for filling the timestamp buffer.

Example program:

; working with clock
; trap 60 - reading clock data from TCP/IP stack
; R3 - buffer address where to read
; result in R3 address if successful. R3=0 if error
GetDateFromLAN: push    R0
                push    R1
                push    R2
                call    AZreset; reset
                tst     R1
                bne     G60ERR
                MOV     #AZ$CSR,R1
                mov     #42,(R1)
G60:            TSTB    (R1); check execution result
                BPL     G60; wait
                mov     #32,(R1)
1$:             TSTB    (R1); check execution result
                BPL     1$; wait
                TST     (R1)+;  increment
                mov     R3,R0; remember R3 address
                mov     #10.,R2; читаем 10 слов
2$:             mov     (R1),(R3)+; read block of words into memory
                sob     R2,2$
                mov     R0,R3; successful, return address to R3
                br      0END$
G60ERR:         CLR     R3
0END$:          pop     R2
                pop     R1
                pop     R0
                return

All commands set the ready bit upon completion.

AZБК® specific commands

These commands are intended for operation of the AZБК® controller, developed for the BK series of computers - BK-0010/BK-0010.01/BK-0011M.

Other AZ® controllers ignore these commands.

037: Restart of the AZБК® controller and the entire computer

Command code 037, this command restarts the AZ® microcontroller, which also causes a restart of the BK-0010/BK-0010.01/BK-0011M itself

Example program

AZ$CSR = 177220; command and status register (CSR)
AZ$DR = 177222; data register (DR)


; trap 57 - full restart
AZcouldReboot:  call    AZreset; reset AZ so it is ready to receive
                mov     #037,@#AZ$CSR
                return

044: Saving a screenshot to a file

Command code 044, this command is designed to take a memory image of the specified size (or determined automatically based on saved parameters) technically, the command can serve as a debugging tool because it is capable of taking a memory image the general limitation on taking a memory image is 2MB per image

service memory page 76(8) is used as parameters

Structure of filling information about a screenshot

// screenshot header structure
typedef __packed struct screen_header
{
   unsigned short int                tag;                //  must be equal to 0240
   
   unsigned int                      begin_adress;       // start address in words - forward task in physical addresses
   unsigned int                      length;             // length in words - forward task in physical addresses
   
   unsigned short int                begin_page;         // start page - number - forward task in page numbers
   unsigned short int                len_pages;          // number of pages - forward task in page numbers
   
   unsigned short int                R177300;            //
   unsigned short int                R177302;            //
   unsigned short int                R177304;            //
   unsigned short int                R177306;            //
   unsigned short int                R177310;            //
   unsigned short int                R177312;            //
   unsigned short int                R177314;            //
   unsigned short int                R177316;            //
   unsigned short int                R177320;            //
   unsigned short int                R177322;            //
   unsigned short int                R177324;            //
   unsigned short int                R177326;            //
   unsigned short int                R177330;            //
   unsigned short int                R177332;            //
   unsigned short int                R177334;            //    
   unsigned short int                R177336;            //
   unsigned short int                R177340;            // - Window activation control register - window masks
   unsigned short int                R177342;            // - Control register r/o per window
   unsigned short int                R177344;            // - Shadow window control register - window masks
   unsigned short int                R177346;            // - Mapper control register
   unsigned short int                R177350;            // - copy by record register 177130 in memory management write mode in SMK
   unsigned short int                R177352;            // - copy by record register 177716 in memory management write mode in BK11M
   
   unsigned short int                R177230;            // - control register
   unsigned short int                R177232;            // - display start page number register - upper page (layer 0)
   unsigned short int                R177240;            // - display start page number register - upper page (layer 1)
   unsigned short int                R177242;            // - display start page number register - upper page (layer 2)
   unsigned short int                R177244;            // - vertical scroll register layer 2
   unsigned short int                R177246;            // - vertical scroll register layer 1
   unsigned short int                R177250;            // - vertical scroll register layer 0
   unsigned short int                R177252;            // - horizontal scroll register layer 0
   unsigned short int                R177254;            // - horizontal scroll register layer 1
   unsigned short int                R177256;            // - horizontal scroll register layer 2
   
   unsigned short int                paldata[338];    //
} screen_header_t;

Example code for filling a memory page

;--------------------------------------------------
; 76th page map - we prepare data for the screenshot command there
SCR_PAGE      = 130000            ; we temporarily attach the 76th page to the 77th - that is, into the 130000 window
SCR_TAG       = SCR_PAGE+0        ; here is the input - 240 - 1 word
SCR_ADDR_CONF = SCR_TAG+2         ; here is the command with addresses - address+length 24 bits - 4 words
SCR_PAGE_CONF = SCR_ADDR_CONF+8.  ; here is the command with pages - the starting page and the number of pages - 2 words
SCR_MEM_CONF  = SCR_PAGE_CONF+4.  ; here is the memory configuration from the registers - 22 words
SCR_VGA_CONF  = SCR_MEM_CONF+44.  ; here video controller configuration - 10 words
SCR_PAL       = SCR_VGA_CONF+20.  ; here 338. values (words) of palette 338 words
;--------------------------------------------------
; trap 41 - preparation of default information for screenshot functionality
PrepSRC:        jsr     R5, PUSHA            ; batch saving of registers
                mov     @#177326,-(SP)       ; save page 130k which was before the call
                mov     #76,@#177326         ; hook 76th page into window

                mov     #100377,R3           ; constant-filler
                mov     #SCR_PAGE,R4
                mov     #2047.,R2
4$:             mov     R3,(R4)+
                sob     R2,4$

                mov     #240,@#SCR_TAG       ; put the tag

         ; clean the address section - default is automatic address detection
                clr     R3
                mov     #SCR_ADDR_CONF,R4
                mov     #10,R2
2$:             mov     R3,(R4)+
                sob     R2,2$

         ; memory configuration - default
                mov     #SCR_MEM_CONF,R4
                mov     #30,(R4)+            ;177300
                mov     #31,(R4)+            ;177302
                mov     #32,(R4)+            ;177304
                mov     #33,(R4)+            ;177306
                mov     #04,(R4)+            ;177310
                mov     #05,(R4)+            ;177312
                mov     #06,(R4)+            ;177314
                mov     #07,(R4)+            ;177316
                mov     #20,(R4)+            ;177320
                mov     #21,(R4)+            ;177322
                mov     #22,(R4)+            ;177324
                mov     #23,(R4)+            ;177326
                mov     #120,(R4)+           ;177330
                mov     #121,(R4)+           ;177332
                mov     #110,(R4)+           ;177334
                mov     #100,(R4)+           ;177336

                mov     #170000,(R4)+        ;177340
                mov     R3,(R4)+             ;177342
                mov     #7777,(R4)+          ;177344
                mov     #40404,(R4)+         ;177346
                mov     R3,(R4)+             ;177350
                mov     #16000,(R4)+         ;177352

         ;Video controller configuration - default
         ;  177230-177256
                mov     #SCR_VGA_CONF,R4

                mov     #12201,(R4)+         ;177230
                mov     #4,(R4)+             ;177232
                mov     R3,(R4)+             ;177240
                mov     R3,(R4)+             ;177242
                mov     R3,(R4)+             ;177244
                mov     R3,(R4)+             ;177246
                mov     R3,(R4)+             ;177250
                mov     R3,(R4)+             ;177252
                mov     R3,(R4)+             ;177254
                mov     R3,(R4)+             ;177256

         ; download the palette - take the default one from this ROM
                mov     #SCR_PAL,R4
                mov        #PalData,R2
                mov        #338.,R3
1$:             mov     (R2)+,(R4)+
                sob        R3,1$

                mov     (SP)+,@#177326; return the page from which the call was made
                return

There are three options for specifying memory areas.

  1. specify the address and length 24-bit - see format, if they are not there - the system looks further
  2. specify the page number and page quantity, if they are not there
  3. the system looks further - that is, it makes a screenshot based on the data about registers 177230, etc.

The screenshot is saved in the format
- page 76 - its first kilobyte
- the memory image itself (if the mode is layered - then all three layers)

Before calling the command, you can load the file name for saving the screenshot [in the cmosmem buffer], but if it is missing (there will be no name in the buffer - a line ending with 0), the system will generate its own name based on the following rule: default path for saving screenshots
0:/SCREENS/
name format - DDHHMISS.SCR
where DD is two digits of the day of the month, HH is the hour, MI is the minute, SS is the second

If an error occurs during the command execution, the name will be "ERROR *"
for example
"ERROR f_open 6"

Example program

; update the information in the screenshot header
;--------------------------------------------------
; 76th page map - we prepare data for the screenshot command there
SCR_PAGE      = 130000            ; we temporarily attach the 76th page to the 77th - that is, into the 130000 window
SCR_TAG       = SCR_PAGE+0        ; here is the input - 240 - 1 word
SCR_ADDR_CONF = SCR_TAG+2         ; here is the command with addresses - address+length 24 bits - 4 words
SCR_PAGE_CONF = SCR_ADDR_CONF+8.  ; here is the command with pages - the starting page and the number of pages - 2 words
SCR_MEM_CONF  = SCR_PAGE_CONF+4.  ; here is the memory configuration from the registers - 22 words
SCR_VGA_CONF  = SCR_MEM_CONF+44.  ; here video controller configuration - 10 words
SCR_PAL       = SCR_VGA_CONF+20.  ; here 338. values (words) of palette 338 words
;--------------------------------------------------
                mov     @#177326,R5       ; save page 130k which was before the call
                mov     #76,@#177336         ; hook the 76th page into the window

         ;video controller configuration
         ;  177230-177256
                mov     #SVGAC,R4

                mov     @#177230,(R4)+;177230 - control register
                mov     @#177232,(R4)+;177232 - register - top page (layer 0)
                mov     @#177240,(R4)+;177240 - register - top page (layer 1)
                mov     @#177242,(R4)+;177242 - register - top page (layer 2)
                mov     @#177244,(R4)+;177244 - vertical scroll register layer 2
                mov     @#177246,(R4)+;177246 - vertical scroll register layer 1
                mov     @#177250,(R4)+;177250 - vertical scroll register layer 0
                mov     @#177252,(R4)+;177252 - horizontal scroll register layer 0
                mov     @#177254,(R4)+;177254 - horizontal scroll register layer 1
                mov     @#177256,(R4)+;177256 - horizontal scroll register layer 2

                mov     R5,@#177336; return the page from which the call was made


         ;-------------------------
                MOV     #AZ$CSR,R3     ; Preparing controller CSR
                MOV     #AZ$DR,R4     ; Preparing controller DR
20$:            CLR     (R3)         ; Reset the controller
                TSTB    (R3)                
                BPL     20$
               
         ; clear the memory block for the name - so that the system makes a default file name
                mov     #23,(R3)     ; command that we will write data to the buffer
128$:           TSTB    (R3)         ; check the result of execution
                BPL     128$         ; wait
                clr     R1    

                mov     #256.,R2     ;
129$:           mov     R1,(R4)      ; give to the controller
                sob     R2,129$


                MOV     #044,(R3)    ; screenshot command
22$:            TSTB    (R3)         ;
                BPL     22$          ;

         ; get the screenshot name
                mov     #22,(R3)     ; give the read memory block from the buffer to the bus
121$:           tstb    (R3)         ; check the result of execution
                bpl     121$         ; wait
               
                mov     #BUF,R1
                mov     #256.,R2     ; read 256. words; the first word is the result of reading
122$:           mov     (R4),(R1)+   ; read a block of words into memory
                sob     R2,122$

                .PRINT  #RESOK
                .Print  #BUF
             
                mov     #110,@#AZ$CSR; enable the network by default, the network should be constantly enabled
                .Exit 

To unpack a screenshot, you can use this utility - https://master.pdp-11.ru/screen_unpack/

Commands for working with the TCP/IP stack

The following commands operate on the TCP/IP stack information buffer.

040: Get IP address and other TCP/IP stack settings to buffer

Command code 040, this command fills the buffer with information from the TCP/IP stack with current (actual) information.

041: Reading ip address buffer

Command code 041, this command transfers the buffer to the bus

This pair of commands allows you to get current information from the stack

  • IP address
  • MASK mask
  • GW gateway
  • NTP address of the NTP server
  • DNS1 primary DNS address
  • DNS2 backup DNS address

accordingly it is 12 words

Example program:

; trap 52 - reading a block of IP addresses into the IPADDDBLOCK memory block (8 cells)
; result in R3 = 0 if error, otherwise the address where it was read (IPADDDBLOCK)
GetIPaddrs:     push    R1  
                push    R2  
                call    AZreset; reset  
                tst     R1  
                bne     0ERR$  
                MOV     #AZ$CSR,R1  
                mov     #40,(R1)  
0$:             TSTB    (R1); read addresses into its memory  
                BPL     0$; wait  
                mov     #41,(R1)  
1$:             TSTB    (R1); prepare buffer  
                BPL     1$; wait  
                TST     (R1)+; increment  
                mov     #IPADDDBLOCK,R3  
                mov     #12.,R2  
2$:             mov     (R1),(R3)+; read block of words into memory  
                sob     R2,2$  
                mov     #IPADDDBLOCK,R3; success  
                br      0END$  
0ERR$:          CLR     R3  
0END$:          pop     R2  
                pop     R1  
                return  

Example return data:

Information

Data examples - words returned in octal format
124300 116400 - IP address 192.168.0.157
177777 000377 - MASK mask 255.255.255.0
124300 000400 - GW gateway 192.168.0.1
124300 000400 - NTP address of NTP server 192.168.0.1
124300 050000 - DNS1 address of primary DNS 192.168.0.90
124300 055000 - DNS2 address of backup DNS 192.168.0.80

043: Read MAC address into ip buffer

Command code 043, this command reads the current actual MAC address into the IP address buffer i.e. first 043 and then 041 commands

Example program:

; trap 72 - reading the MAC address into the IPADDDBLOCK memory block (12 cells)  
; result in R3 = 0 if error, otherwise the address where it was read (IPADDDBLOCK)  
GetMACaddrs:    push    R1  
                push    R2  
                call    AZreset     ; reset  
                tst     R1  
                bne     0ERR$  
                MOV     #AZ$CSR,R1  
                mov     #43,(R1)  
0$:             TSTB    (R1)        ; read addresses into its memory  
                BPL     0$          ; wait  
                mov     #41,(R1)  
1$:             TSTB    (R1)        ; prepare buffer  
                BPL     1$          ; wait  
                TST     (R1)+       ; increment  
                mov     #IPADDDBLOCK,R3  
                mov     #12.,R2  
2$:             mov     (R1),(R3)+  ; read block of words into memory  
                sob     R2,2$  
                mov     #IPADDDBLOCK,R3; success  
                br      0END$  
0ERR$:          CLR     R3  
0END$:          pop     R2  
                pop     R1  
                retur  

Commands for working with a MicroSD card at the file level

These commands are designed to work with a MicroSD card at the file system level and allow you to read/write files without mounting files as disk images.

These commands use a 256-word buffer that is used in the interface for working with non-volatile memory (see commands 022 023 above)

Limitations - the length of the full path to the file is 256 bytes

050: Set the name of the file we will read

Command code 050, this command sets the name of the file that we will read, while opening the file for reading, and also obtaining its properties.

051: Get file size for reading (or its status) on BUS

Command code 051, this command transmits the file size or reading error to the MPI. The file size is 31 bits, the most significant bit is an error indicator. Accordingly, the maximum file size that can be worked with via this interface is limited to 2 ^ 31 bytes (2GB).

The error generation looks like this:
sizeanyfile=1<<31 + FFres; // if the most significant bit of a 32-bit word is set, then the error code is in the lower part

FFres = FatFS error

typedef enum {
    FR_OK = 0,                /* (0) Succeeded */
    FR_DISK_ERR,            /* (1) A hard error occurred in the low level disk I/O layer */
    FR_INT_ERR,                /* (2) Assertion failed */
    FR_NOT_READY,            /* (3) The physical drive cannot work */
    FR_NO_FILE,                /* (4) Could not find the file */
    FR_NO_PATH,                /* (5) Could not find the path */
    FR_INVALID_NAME,        /* (6) The path name format is invalid */
    FR_DENIED,                /* (7) Access denied due to prohibited access or directory full */
    FR_EXIST,                /* (8) Access denied due to prohibited access */
    FR_INVALID_OBJECT,        /* (9) The file/directory object is invalid */
    FR_WRITE_PROTECTED,        /* (10) The physical drive is write protected */
    FR_INVALID_DRIVE,        /* (11) The logical drive number is invalid */
    FR_NOT_ENABLED,            /* (12) The volume has no work area */
    FR_NO_FILESYSTEM,        /* (13) There is no valid FAT volume */
    FR_MKFS_ABORTED,        /* (14) The f_mkfs() aborted due to any problem */
    FR_TIMEOUT,                /* (15) Could not get a grant to access the volume within defined period */
    FR_LOCKED,                /* (16) The operation is rejected according to the file sharing policy */
    FR_NOT_ENOUGH_CORE,        /* (17) LFN working buffer could not be allocated */
    FR_TOO_MANY_OPEN_FILES,    /* (18) Number of open files > _FS_LOCK */
    FR_INVALID_PARAMETER    /* (19) Given parameter is invalid */
} FRESULT;

http://elm-chan.org/fsw/ff/doc/open.html

052: Read a block of a set file into a buffer

Command code 052, this command reads a file into a non-volatile memory buffer.

As a result, the file reading scheme looks like this
023 - fill the file name into the buffer
050 - set the file for reading
051 - read the file length or file open error
if there is an error - repeat 023 050 051 from the beginning
if everything is ok - start reading the file
052 - read the file block into the buffer
022 - take data from the buffer
repeat the 052 022 pairs the required number of times in order to read the entire file
once the file is read - the last 052 command will close it automatically.

Example program:

                call    AZRST; reset  

; load the file name into the buffer  
7$:             mov     #23,(R1); command to write data to the buffer  
5$:             TSTB    (R1); check execution result  
                BPL     5$  ; wait  
                TST     (R1)+; increment  
                mov     #FILNM,R3  
1$:             mov     (R3),(R1); send to controller  
                tst     (R3)+  
                bne     1$  
                tst     -(R1); decrement  

; set the file for reading  
                mov     #50,(R1); set file for reading  
2$:             TSTB    (R1); check execution result  
                BPL     2$  ; wait  

; read the file length  
                mov     #51,(R1); set file for reading  
3$:             TSTB    (R1); check execution result  
                BPL     3$  ; wait  
                TST     (R1)+; increment  
                mov     #FILSZ,R3  
                mov     (R1),(R3)+; read from controller  
                mov     (R1),(R3); read from controller  

; display the file length on the screen  
                clr     R0  
                mov     #FILSZ+2,R3  
                mov     (R3),R1  
                call    DNOZ  
                mov     -(R3),R1  
                call    DNOZ  
                .print   #STMS2  

; read the file  
                mov     R1,R4; R1 holds the file length  
                MOV     #AZ$CSR,R1  
                mov     #BUFFL,R5  

                bit     #1,R4; if an odd number of bytes  
                beq     47$  
                inc      R4; add 1 more byte as we read words  

47$:            tst     R4  
                beq     45$ ; nothing left to read - exit  

                mov     #52,(R1); read block into buffer  
4$:             TSTB    (R1); check execution result  
                BPL     4$  ; wait  
                mov     #22,(R1); start reading buffer  
51$:            TSTB    (R1); check execution result  
                BPL     51$ ; wait  

                cmp     R4,#512.; compare with buffer size in bytes  
                Blos    44$ ; less than buffer size left  

                .print  #STMS1  
                mov     #256.,R2  
                TST     (R1)+; move to data register  
46$:            mov     (R1),(R5)+; read into buffer  
                sob     R2,46$  
                sub     #512.,R4; subtract  
                TST     -(R1); move to command register  
                br      47$  

44$:            .print  #STMS3  
                mov     R4,R2  
                asr     R2  ; /2 since reading words  
                TST     (R1)+; move to data register  
43$:            mov     (R1),(R5)+; read into buffer  
                sob     R2,43$  

45$:            clr     (R5); set end of file marker  

; file read - display on screen  
                .print   #STMS4  
                .print  #BUFFL  
                .print   #STMS5  

                mov     #110,@#AZ$CSR; enable network  
                .Exit  

053: set the name of the file that we will write

Command code 053, this command opens a file for writing, receives opening parameters (or errors).

054: Set file length

Command code 054, this command sets the expected file length, this is necessary for the correct formation of the file at the file level of the MicroSD card, as well as for organizing data transfer.

055: write data from buffer to file

Command code 055, this command writes data from the non-volatile memory buffer to an open file for writing. The last command 055 will automatically close the file when the declared file length is reached.

The command flow chart for writing is as follows
023 - fill the file name into the buffer
053 - set the file for reading
051 - file opening/creation status
if an error - repeat from the beginning 023 053 051
if everything is ok - move on
054 - set the file length, i.e. we must immediately declare what the file length will be
023 - fill the data block into the buffer
055 - write from the buffer to the file
repeat the 023 055 pairs the required number of times in order to write the entire file
once the file is written - the last 055 command will close it automatically

Example program:

; load the file name into the buffer  
                MOV     #AZ$CSR,R1  
17$:            mov     #23,(R1); command to write data to the buffer  
15$:            TSTB    (R1)    ; check execution result  
                BPL     15$     ; wait  
                TST     (R1)+   ; move to data register  
                mov     #FILNM2,R3  
11$:             mov    (R3),(R1); send to controller  
                tst     (R3)+  
                bne     11$  
                tst     -(R1)   ; move to command register  

; set the file for writing  
                mov     #53,(R1); set file for writing  
12$:            TSTB    (R1)    ; check execution result  
                BPL     12$     ; wait  

; read file creation status  
                mov     #51,(R1)  
13$:            TSTB    (R1)    ; check execution result  
                BPL     13$     ; wait  
                TST     (R1)+   ; move to data register  
                mov     #STATS,R3  
                mov     (R1),(R3)+; read from controller  
                mov     (R1),(R3); read from controller  

; check here - if the file is created, both words should be zero  
                mov     #STATS,R3  
                TST     (R3)+  
                BNE     66$  
                TST     (R3)  
                BEQ     60$  
66$:            .print  #ERRMS1 ; print error  
                .exit  

60$:            MOV     #AZ$CSR,R1  
                mov     #54,(R1); set file length to be written  
23$:            TSTB    (R1)    ; check execution result  
                BPL     23$     ; wait  
                TST     (R1)+   ; move to data register  
                mov     #FILSZ,R3  
                mov     (R3)+,(R1); write to controller  
                mov     (R3),(R1); write to controller  

                tst     -(R1)   ; move to command register  

; write file  
                mov     @#FILSZ,R4; file length  
                MOV     #AZ$CSR,R1  
                mov     #BUFFL,R5; file buffer  

                bit     #1,R4   ; if an odd number of bytes  
                beq     147$  
                inc      R4   ; add 1 more byte since reading words  

147$:           tst     R4      ; check length  
                beq     145$    ; nothing left to write - exit  

                mov     #23,(R1); write to buffer  
151$:           TSTB    (R1)    ; check execution result  
                BPL     151$    ; wait  

                cmp     R4,#512.; compare with buffer size in bytes  
                Blos    144$    ; less than buffer size left  

                .print  #STMS6  ; writing full block  
                mov     #256.,R2  
                TST     (R1)+   ; move to data register  
146$:           mov     (R5)+,(R1); write to controller buffer  
                sob     R2,146$  
                sub     #512.,R4; subtract  
                TST     -(R1)   ; move to command register  

                mov     #55,(R1); write buffer to file  
104$:           TSTB    (R1)    ; check execution result  
                BPL     104$    ; wait  
                br      147$    ; loop back  

144$:            .print  #STMS7 ; writing last block  
                TST     (R1)+   ; move to data register  
                mov     R4,R2  
                asr     R2      ; /2 since writing words  
143$:           mov     (R5)+,(R1); write to controller buffer  
                sob     R2,143$  

                TST     -(R1)   ; move to command register  
                mov     #55,(R1); write last buffer to file  
105$:           TSTB    (R1)    ; check execution result  
                BPL     105$    ; wait  

145$:           .print #STMSE   ; end  
                mov     #110,@#AZ$CSR; enable network  
                .Exit           ; exit  

the example is completely in the form of the RT11 utility and is posted here

056: Get data on the size of the map into the sizecard buffer

Command code 056, this command reads the parameters of the MicroSD card into the sizecard buffer

057: Reading sizecard buffer

Command code 057, this command returns the sizecard buffer (2 words)

sizecard buffer contains 2 words 16bit
first word - total card size available for FAT in MB
second word - free card size in MB

Example program:

; trap 51 - get the total/free size of the SD card in megabytes  
; result in R1 - total; R2 - free  
GetSizeSD:      call    AZreset  ; reset  
                tst     R1  
                bne     0ERR$  
                MOV     #AZ$CSR,R1  
                mov     #56,(R1)  
1$:             TSTB    (R1)     ; prepare buffer  
                BPL     1$       ; wait  
                mov     #57,(R1)  
2$:             TSTB    (R1)     ; prepare buffer  
                BPL     2$       ; wait  
                mov     @#AZ$DR ,R1  ; total megabytes  
                mov     @#AZ$DR ,R2  ; free megabytes  
                return  
                clr     R1  
                clr     R2  
                return  

Example data
035521 - total megabytes on the card - 15185.
035417 - free megabytes - 15119.

Hall of Fame API Command Block

This block of commands is intended for interaction with the server Hall of Fame

025: Инициализация Hall of Fame (HOF)

Command code 025, this command establishes a connection to the Hall of Fame server, initializes the encrypted tunnel and prepares the API for work.

Example program:

AZ$CSR          = 177220                 ; command and status register (CSR)
AZ$DR           = 177222                 ; data register (DR)

; buffers
SNDBUF:         .BLKW 256.               ; send buffer
RCVBUF:         .BLKW 256.               ; receive buffer
SIDMEM:         .BLKB 34.                ; SID
SIDCST:         .ASCII    \{"SID":"\       ; SID header
                .even

HOFINI:      ; HOF initialization
             ; The result is a JSON response:
             ; {"SID":"session hash","RESULT":"OK"}
             ; or an error:
             ; {"RESULT":"ERROR","DESCRIPTION":"SERVER_ERROR"}
             ; {"RESULT":"ERROR","DESCRIPTION":"CONNECTION_ERROR"}
             ; The result is placed in SNDBUF
             ; R5 indicates success: 1 = SID exists, 0 = no SID

                mov     R5, -(SP)
                mov     R4, -(SP)
                mov     R3, -(SP)
                mov     R2, -(SP)
                mov     R1, -(SP)
                mov     R0, -(SP)

                mov     #3,R5        ; number of attempts

220$:           mov     #AZ$CSR,R1
                clr     (R1)         ; Send "Reset" command
221$:           tstb    (R1)         ; Check controller readiness
                bpl     221$         ; If not ready, wait

                mov     #25,(R1)     ; initialization - command 025
20$:            tstb    (R1)         ; check execution result
                bpl     20$          ; wait

             ; Retrieve the result
                mov     #22,(R1)     ; output the read memory block from buffer to the bus
21$:            tstb    (R1)         ; check execution result
                bpl     21$          ; wait
                tst     (R1)+        ; increment
                mov     #SNDBUF,R3
                mov     #256.,R2     ; read 256 words; first word is the read result
22$:            mov     (R1),(R3)+   ; read block of words into memory
                sob     R2,22$


             ; Determine if SID exists
                mov     #4,R0
                mov     #SNDBUF,R1
                mov     #SIDCST,R2
23$:            cmp     (R1)+,(R2)+
                bne     24$
                sob     R0,23$
                clr     R5              
                inc     R5      
                br      26$          ; success

24$:         ; SID not found!
                sob     R5,220$
                clr     R5           ; error - no SID  

26$:            mov     (SP)+, R0
                mov     (SP)+, R1
                mov     (SP)+, R2
                mov     (SP)+, R3
                mov     (SP)+, R4
                mov     (SP)+, R5
                return

026: Exchange with Hall of Fame (HOF)

Command code 026, this command makes a direct exchange with Hall of Fame

Example program:

             ;4. user authentication  
             ;technically, this is sending a JSON  
             ;{"SID":"session hash","CMD":"AUTH_USER","NIKNAME":"user nickname","PASSWORD":"user password"}  
             ;the response is also JSON  
             ;{"SID":"session hash","RESULT":"OK","UID":"user hash"}  
             ;or  
             ;{"SID":"session hash","RESULT":"ERROR","DESCRIPTION":"USER_NOT_FOUND_OR_WRONG_PASSWORD"}  
             ;{"SID":"session hash","RESULT":"ERROR","DESCRIPTION":"SERVER_ERROR"}  
             ;{"SID":"session hash","RESULT":"ERROR","DESCRIPTION":"SESSION_NOT_EXISTS_OR_EXPIRED"}  

             ; load command CMD04  
                mov     #CMD04,R1  
                mov     #ADRMEM,R2  
                add     #42.,R2      ; shift the pointer to the SID block length  
33$:            movb   (R1)+,(R2)+  
                bne     33$  

                .Print    #ADRMEM  

                .Print    #HOF05  
             ; send the command and wait for a response  
             ; load into buffer  
                MOV     #AZ$CSR,R1  
331$:           TSTB    (R1)         ; Check controller readiness  
                BPL     331$         ; If not ready, wait                
                mov     #23,(R1)     ; command to write data into buffer  
34$:            TSTB    (R1)         ; check execution result  
                BPL     34$          ; wait  
                TST     (R1)+        ; increment  
                mov     #ADRMEM,R3  
                mov     #256.,R2     ;  
35$:            mov     (R3)+,(R1)   ; send to controller  
                sob     R2,35$  
                tst     -(R1)        ; decrement  

             ; exchange - command 026  
                MOV     #AZ$CSR,R1  
361$:           TSTB    (R1)         ; Check controller readiness  
                BPL     361$         ; If not ready, wait                
                mov     #26,(R1)  
36$:            TSTB    (R1)         ; check execution result  
                BPL     36$          ; wait  

             ; receive result  
371$:           TSTB    (R1)         ; Check controller readiness  
                BPL     371$         ; If not ready, wait                
                mov     #22,(R1)     ; send buffer to the bus  
37$:            TSTB    (R1)         ; check execution result  
                BPL     37$          ; wait  
                TST     (R1)+        ; increment  
                mov     #ADRMEM,R3  
                mov     #256.,R2     ; read 256 words; first word is the read result  
38$:            mov     (R1),(R3)+   ; read block of words into memory  
                sob     R2,38$