• Please review our updated Terms and Rules here

Accessing VRAM in tiny model .com program for MSDOS

Mills32

Experienced Member
Joined
Sep 25, 2018
Messages
149
Location
Spain
HI, I want to create a small .com program using the tiny memory model, but I don't know how to access video ram (in text mode) to draw some ascii "images".

I'm writting stuff to vram using pointers like this:

Code:
unsigned char *TILE_MAP = (unsigned char *)0xB8000000L; // this points to VRAM

Then TILE_MAP[0] is the first character, and TILE_MAP[1] its colors, but that pointer does not work in the tiny model.
I guess print functions work because they use some bios calls to write to vram, but I can't use that for my code.... Well maybe I can, but I don't want to use them for the moment.

So how can I point to VRAM in the tiny model?

Thanks!
 
A more experienced coder may be able to give you a better answer, but as I understand it, the tiny model defaults to near access for pointers. I don't know if the tiny model allows for access outside it's 64K limitation since it doesn't contain relocation data, but you would have to explicitly specify your pointer as FAR (some compilers use __far keyword). You may have to experiment to verify, but I think the code should probably look something like this:

Code:
unsigned char *TITLE_MAP = (unsigned char __far *)0xB8000000L; // this points to VRAM
 
Thanks.

I discovered you can change the used segment in asm by just storing 0xB800 to ds. But i'd love to use the far pointer solution, if it works, because c compiler will do it better than me in asm.
 
Post #3 is the 'correct' answer.

I wouldn't change DS (unless you save the original value and restore it again after you have finished with it).

You have segment ES if you want to 'play around' with memory outside of your tiny model. Don't forget you have to use the ES segment override prefix with any instruction/addressing mode that assumes DS.

This is why a 'pure C' solution would be the best solution.

Dave
 
I disagree that a pure C solution would be best or "do it better than me in ASM". In the tiny model, it's assumed everything is referenced by DS, so you can set ES to B800h and just leave it there, and then reference VRAM with es:di, es:bx, es:si, etc. If you go the full C route, you'll be reloading or dereferencing a memory-based pointer every single time.
 
Thanks. This is for a little menu to load programs (with nice ascii graphics in cga), it just needs to be small in ram (that's why I wanted to create a .com file with tiny memory model), It does not matter if it is slow
 
Agree with Trixter.

You didn't specify the CPU family. If it's a 386 or above, you can use 32 bit registers as well as two extra (FS GS) segment registers to really speed things up. What assembly gets you is the ability to plan out your register usage for a given task. Mixed memory model addressing in C can get to be very cumbersome.
 
Agree with Trixter.

You didn't specify the CPU family. If it's a 386 or above, you can use 32 bit registers as well as two extra (FS GS) segment registers to really speed things up. What assembly gets you is the ability to plan out your register usage for a given task. Mixed memory model addressing in C can get to be very cumbersome.
Oh sorry this is for 8088/86.
 
Then you don't have FS/GS/32-bit registers, but it's still way faster and smaller:

Code:
msg:    db 'Howdy',0
...
        mov     es,0b800h            ;load es with base segment of CGA video ram
        mov     si,offset msg        ;load si with the starting offset of our text message
        mov     ah,07                ;color attribute, background color (0) + foreground color (7)
put_screen:
        lodsb                        ;load al from ds:[si], increment si
        or      al,al                ;is it 0?
        jz      end_of_string        ;if so, no more string to print
        stosw                        ;store ax to es:[di], increment di
        jmp     put_screen           ;keep going until we hit a null (0) byte
end_of_string:
...

No high-level language can beat that, at least not on this age/capabilities of CPU.
 
You can also load ES directly from a memory location, so the constant 0b800h can be stored at a labeled location within the code segment, and ES loaded directly from it. You will have to use the CS: segment override with the mov though to prevent the default DS from being used.

Dave
 
Thanks a lot! it is working very well, for the moment I'll use inline assembly, then, I'll move everything to asm to reduce size even more

menu_006.png

I'm going to read tiny 32x16 ascii images (this flanders was the first test), I don't know why the read file is not working in inline assembly

Code:
asm mov ah,0x3D                    //open file
    asm mov al,0                    //Read only
    asm mov dx,offset filename        //ASCIIZ filename to open
    asm int 21h
    asm mov handle,ax
    asm mov ah,0x3F                    //Read file
    asm mov bx,handle
    asm mov cx,1024                    //number of bytes to read
    asm mov dx,offset thumbnail            //were to put read data
    asm int 21h
    asm mov ah,0x3E                    //Close file
    asm mov bx,handle              
    asm int 21h

The read part always freezes the program.
 
You're not checking to see if the open file handle was successful (ie if carry flag is set) so you might have an error and not know it...

This would be worth single-stepping through in a debugger (turbo debugger, the dosbox debugger, etc.) to watch the status of the registers and flags. It should be obvious when doing so where the trouble is.
 
You can also load ES directly from a memory location, so the constant 0b800h can be stored at a labeled location within the code segment, and ES loaded directly from it. You will have to use the CS: segment override with the mov though to prevent the default DS from being used.

You can also use the "twofer" load

Code:
        LES     reg,ptrloc

ptrloc  DW      offs,seg

...or you can pass the segment address as an argument on the stack and either move it or pop it to ES.

Another puzzle about the 8086 ISA is why there's an LES, but no "twofer" SES.
 
Code:
asm mov ah,0x3D                    //open file
    asm mov al,0                    //Read only
    asm mov dx,offset filename        //ASCIIZ filename to open
    asm int 21h
    asm mov handle,ax
    asm mov ah,0x3F                    //Read file
    asm mov bx,handle
    asm mov cx,1024                    //number of bytes to read
    asm mov dx,offset thumbnail            //were to put read data
    asm int 21h
    asm mov ah,0x3E                    //Close file
    asm mov bx,handle            
    asm int 21h

The read part always freezes the program.

Just in case someone tries to use inline assembly, the "offset" command won't always work, you have to do this:

Code:
unsigned char filename = {"myfile.bin"};
unsigned char *name = &filename[0];

//Now use "name" in assembly
asm mov dx,name       //ASCIIZ filename to open
 
That's entirely consistent--"name" after all is a pointer. So you're loading the value of the pointer, not its offset.

Scalar arrays in C are pointers. There's no difference between name[] and *name.

Now try
Code:
    unsigned char name;
 
I converted the program to assembly, it was fun, until I tried to execute programs, (that was not fun).

Finally I got it working, but then I realized the exec function (21 4B) does not run .bat files, so I found "function "2E", which is supposed to run everything. It does run any bat, com or exe I tested, but it never returns to menu.

This int 2e function destroys all registers, even ss and sp, but in tiny model, ss should be the same as ds and cs, I think. So I stored ds in a variable (in cs or ds, or ss segment), and then I restore it after the program returns to menu:

Code:
           ;run program "exec1"         
           mov     si,offset exec1
           int     2Eh
          
           ;we are back, restore ds
           mov ax,save_DS
           mov ds,ax
      
           ;try something, like writting a face to VRAM
           mov ax,0B800h
           mov es,ax
           mov di,180
           mov word ptr es:[di],0f01h

But after int 2eh, nothing works, not even pointing to vram again and trying to write a character, I can use a function to terminate the program, that's the only thing that works.
 
Last edited:
Back
Top