Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Example of DTM Directive in MPASM

Status
Not open for further replies.

jpanhalt

Well-Known Member
Most Helpful Member
Does anyone have a worked example of the how to use the "dtm" directive in MPASM (assembly)?

I have been messing with it all morning without luck.

Consider, for example, some code that prints, "ANGLE."

That is easily done with something like:

Code:
movlw     'A'    
call <a routine that prints the ascii equivalent of that literal>
movlw     'N'
etc.

Or, one can do it a little more clearly by making a table call, such as :
Code:
Table
      BRA
      dt "ANGLE",0

I used the above method in the example posted in another thread.

I can't get dtm to work the same way. When I use dtm, I can observe in the disassembly listing creation of the "movlw 0xxx" instructions, but I can't access them. BRW returns nothing. I haven't tried a more complicated method of putting a label, then going to that label and using an addlw and PCL as one does with tables on the non-enhanced mid-range devices.

Secondly, while most assembler directives appear in blue, the dtm directive appears in magenta, like a label. If I put it in the first column, however, the assembler screams at me for putting a directive there.

Unfortunately, the example in the Assembler Guide is not very helpful -- at least to me.

John
 
Last edited:
Yes, it is an extended mid-range processor, the 16F1519. Here is a related thread: https://www.electro-tech-online.com/threads/glcd-mpasm-character-set-lookup-table-s.134866/

At his point, I am trying just to learn how to use some of its enhancements. Some, like the indirect addressing one, are quite useful. So far, DTM has me stumped, but I will put more time into it today. To repeat, it seems to be doing what it says it does. But, what use is a list of movlw's, if you can't easily access them individually or get in between to add an instruction, like a call?

John
 
On the enhanced midrange PICs you can read the program memory using the FSRx/ INDFx registers as program memory is now mapped from address 0x8000

When you read program memory this way only the low 8 bits of the program memory word are accessible (in the INDFx register). The low 8 bits of the MOVLW instruction contain the literal data so you can read a table using indirect memory access.

This is quite an informative document
https://www.electro-tech-online.com/custompdfs/2013/07/en542713.pdf
 
Last edited:
Something like this

Setup up the FSR register to point to the base of the table. When you do this the assembler is smart enough to know you are reading from program memory and sets the high address of 'mytext' to point to program memory.

You can then use the MOVIW to read data pointed to by FSR into the W register and post increment the FSR register in a single instruction.

Code:
	movlw	high mytext
	movwf	FSR0H
	movlw	low mytext
	movwf	FSR0L
	
readLoop	
        moviw	FSR0++
	skpnz
	return
	call	print
	bra	readLoop

mytext	dtm "This is the DTM table",0
 
Last edited:
That (INDF/FSR w/moviw) is what I did in the project to which I linked. The table was 8x96.

I was beginning to think about using indirect addressing to reach the table(s) created by dtm. The movlw, call method is quite fast but clumsy looking. BTW, the magenta color and the name of this directive makes me wonder if it is just a super macro by Microchip. From a speed standpoint, the clumsy method will probably beat the new indirect addressing method.

Will play more this evening. Sorry for delay in response. I have quite limited Internet access right now.

John
 
Sorry I didn't look at the code example link there so missed that.

So far as I can see DTM creates a table of MOVLW and DT does it with RETLW. Since the indirect addressing method accesses only the low byte of the programme word it doesn't really matter whether it is a MOVLW or RETLW instruction since the instruction itself isn't used, all it needs is the literal data from the low 8 bits.

I can only imagine this new directive is preferred since it uses a MOVLW instead of RETLW. If your code gets into the table data, why it would I don't know if it's working and tested, then it would just 'walk' its way through the table, where a RETLW would pop an invalid return address off the stack and go who-knows-where.

To me the indirect addressing methods are a real plus point to the instruction set. Specifically they make table reads much more straightforward. The MOVIW FSR++ takes 2 instruction clock cycles so it has to be faster than a CALL, computed jump and RETLW. The CALL/RETLW is four instructions clock cycles on its own.

Actually, thinking about the call BRW into a table of RETLW could be faster depending on the size of the table, and then again not since you still need to load Wreg with a pointer and increment it
 
Last edited:
I know this is off topic, but from this thread and your other thread it's obvious you are making a complex project with graphics LCD and a lot of features and being held up on what are really assembler issues.

While you are still in the steeper part of the learning curve have you considered switching to a C compiler? The issues with buffers, fonts etc that you have been getting in assembler all disappear if you are working in C, it just makes a lot of sense for larger projects (especially with graphics and fonts etc).
 
@geko

I can only imagine this new directive is preferred since it uses a MOVLW instead of RETLW. If your code gets into the table data, why it would I don't know if it's working and tested, then it would just 'walk' its way through the table, where a RETLW would pop an invalid return address off the stack and go who-knows-where.

That is an interesting observation. I had a similar thought this morning. More about that below.

To introduce that, I did some experiments with three working versions:

Fast and Ugly Code
Code:
;Angle     
     movlw      'A'                ;A
     ;call      Out_Str         
     movlw     'N'
     ;call      Out_Str
     movlw     'G'
     ;call      Out_Str
     movlw     'L'
     ;call      Out_Str
     movlw     'E'
     ;call      Out_Str
     movlw     ':'
     ;call      Out_Str
     return                        ;B A to B = 3 uS/6 cycles

Pretty Fast Code (geko)
Code:
        movlw	high mytext    ;A
	movwf	FSR1H
	movlw	low mytext
	movwf	FSR1L
     
readLoop	
        moviw	FSR1++
	skpnz
	return                   ;B A to B = 18.5 uS / 37 cycles
	;call	Out_Str
	bra	readLoop
 
mytext
	dtm "ANGLE",0


Half-Fast'd Code
Code:
;Print
     clrf      Temp           ;A
Angle_p                       
     movfw     Temp           
     Pagesel   Angle_t
     call      Angle_t
     iorlw     0x00
     btfsc     STATUS,2
     return		      ;B A to B = 47.5 uS/95 cycles
     ;call      Out_Str
     incf      Temp     
	goto      Angle_p
Angle_t
     brw
     dt "ANGLE:",0

Thus, the old table look-up table method, while readable, is quite slow. The new indirect addressing method is both readable and relatively fast. For long strings, the difference is probably not much between indirect addressing and the ugly method.

Now, back to the quote. I have spent way too much time trying to use dtm without indirect addressing. It is quite frustrating to load the byte for each character into w, watch it on a debug window, and not be able to do anything with it. Admittedly, I have not tried every possibility. If someone knows how to do that, please contribute.

The thought then occurred to me, why is the movlw created by dtm put there? It can't be accessed without indirect addressing, and dtm is only available on the enhanced/extended mid-range chips. That is, chips that have enhanced indirect addressing and the moviw instruction. Maybe, it is just a placeholder? It takes no additional cycles, can do no harm, and it prevents packing the bytes. Note, the other directives may pack the bytes, particularly ascii bytes. Packed bytes take time to unpack.

More generally, on re-reading the software migration note from Microchip for the n^th time, it seems that Microchip wants to discourage use of the "old" dt-based table look-up and encourage using indirect addressing.

John
 
All the DTM directive is doing is to make it easy to create a table of data. It's no different to the DT directive other than one creates a list of MOVLW instructions and the other RETLW.


With the DT directive, the RETLW instruction is used because in order to read the data the computed GOTO method is used to execute the RETLW which gets the literal word data into the Wreg. On the older midrange PICs that was the only way to read data from program memory, okay some of the newer devices could read the program FLASH but before that RETLW was the only way.


Using the indirect program memory access method of the enhanced midrange devices the instruction itself isn't executed so it has no relevance. It's just a carrier to get the 8 bit literal data for the table into program memory.

In fact you could use a IORLW or ANDLW or ADDLW or RETLW to put the table data byte into program memory; they will never be executed.


The indirect read using the MOVIW FSRx++ instruction is going to be much faster and more compact, particularly for big tables since it increments the 16 bit address pointer automatically, no need to worry about tables crossing page boundaries or having to do a manual increment of a two byte address pointer.
 
Last edited:
UPDATE
Using indirect addressing, I got all of the screen layout routines onto one printed page, compared to the 5 or 6 pages it took previously (attached below).

There is a conditional branch at "Tips." The whole thing is much more readable, but a little (67 cycles) slower than the ugly method for the whole thing. Aslo changed the end of file identifier, since some are legitimately 0. The <name>_d labels are for numeric data input.

John

Code:
;*******************************************************************************
;                             SCREEN LAYOUT
;*******************************************************************************
;Label         CS1/CS2    XPos       YPos          
Offset_t        ;CS1      0x02       0x04
     dtm 0x42,0x04,"OFFSET:",-1    
;Offset_d        CS1      0x20       0x06
Angle_t         ;CS2      0x00       0x01
     dtm 0x00,0x01,"ANGLE:",-1 
;Angle_d         CS2      0x15       0x02         
Tips_t          ;CS2      0x00       0x04
     dtm 0x00,0x04,"TIPS",0x20,-1,"HIGH:",-1,"LOW:",-1
;Tips_d          CS2      0x14       0x06  
;*******************************************************************************
Offset 
     movlw	HIGH Offset_t    ;start
	movwf	FSR1H
	movlw	LOW Offset_t
	movwf	FSR1L
	goto 	LoadXY
Angle
     movlw	HIGH Angle_t    
	movwf	FSR1H
	movlw	LOW Angle_t
	movwf	FSR1L
	goto 	LoadXY
Tips	
     movlw	HIGH Tips_t 
	movwf	FSR1H
	movlw	LOW Tips_t
	movwf	FSR1L
	call 	LoadXY
Tips_HL 
     btfss     b_sign         ;when b_sign set, slope negative
     goto      _High
     goto      _Low 	                  
_High 
     call      Print_t 
     goto      Tips_d 
_Low      
     addfsr     1,6
     call      Print_t
     goto      Tips_d
;following will be added to generic subroutines     
LoadXY
     moviw     FSR1++
     movwf     XPos
     moviw     FSR1++
     movwf     YPos
     call      SetLCDAddress
     call      SetLCD_Y
Print_t   
     moviw	FSR1++
     movwf     w_shad
     addlw     1
     btfsc     STATUS,2
     return                        
	movfw     w_shad                   
	call	     Out_Str             
	bra	     Print_t
 
Hi Mike,

It is a KS0108b screen with 128X64 pixels.

XPos is 0 to 63 for each controller. There are two controllers (CS1 and CS2). YPos is 0 to 7 (aka Page).

The datasheet refers to the vertical axis as X; I went with the more conventional assignment and call the vertical axis, Y, and horizontal axis, X.

John
 
Thank you, John.

May I ask what your SetLCDAddress, SetLCD_Y, and Out_Str routines look like, please?

Also, may I ask why you're not using a '0' string terminator since you're always sending an ASCII character through the print subroutine? Only the X and Y values might contain a '0' but you're processing those two bytes outside of the print loop.

Finally, one of the neat features of the enhanced mid-range parts is that, much like the 18F devices, you have access to the stack and you can support "in-line strings" by preceeding the string table with a "call" and pulling the string address off the stack. Here's an example which is by no means smaller, faster, or better than the methods you've come up with;

Code:
;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
        radix   dec
PutStr  macro   x,y,text        ; put string macro
        call    PutText         ;
        dt      x,y,text,0      ;
        endm
;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Code:
        radix   dec
;
;  Offset
;
        PutStr  66,04,"OFFSET"  ; htab 66, vtab 04
;
;  Angle
;
        PutStr  00,01,"ANGLE:"  ; htab 00, vtab 01
;
;  Tips
;
        PutStr  00,04,"TIPS"    ; htab 00, vtab 04
tneg    btfss   b_sign          ; neg? yes, skip, else
        bra     tpos            ; branch
        PutStr  05,04,"LOW:"    ; htab 05, vtab 04
        bra     tdec            ;
tpos    PutStr  05,04,"HIGH:"   ; htab 05, vtab 04
tdec
Code:
;''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
PutText
        banksel TOSL            ; bank 31                         |B31
        movf    TOSL,W          ; top of stack lo                 |B31
        movwf   FSR1L           ;                                 |B31
        movf    TOSH,W          ; top of stack hi                 |B31
        movwf   FSR1H           ;                                 |B31
        bsf     FSR1H,7         ; set FSR1.15 for rom access      |B31
        movlb   0               ; bank 0                          |B0
        moviw   INDF1++         ;                                 |B0
        call    setlcd_x        ; write LCD address               |B0
        moviw   INDF1++         ;                                 |B0
        call    setlcd_y        ; write LCD page                  |B0
GetChar
        moviw   INDF1++         ; wreg = table data               |B0
        skpnz                   ; terminator (0)? no, skip, else  |B0
        bra     PutExit         ; branch (exit)                   |B0
        call    Out_Str         ; send char to LCD                |B0
        bra     GetChar         ; loop                            |B0
PutExit
        banksel TOSL            ; bank 31                         |B31
        movf    FSR1L,W         ; adjust return address           |B31
        movwf   TOSL            ;                                 |B31
        movf    FSR1H,W         ;                                 |B31
        movwf   TOSH            ;                                 |B31
        movlb   0               ; bank 0                          |B0
        return                  ;                                 |B0
 
Last edited:
Hi Mike,

Re: edit

The GLCD I am using does not have a built-in character set. Some of my characters contain bytes that at '0', so I switched to -1. Of course, that will be a problem, if there are bytes that are FF, but I have included a blank row of pixels above and below each character (i.e, my big numbers at 9X14) to avoid it.

As for the original question:

I must apologize that I cannot provide a nice, clean set of those routines right now. I am transitioning homes, and this program development has involved 3 computers at 3 locations -- the new home doesn't have Internet yet, so I must use McDonald's. The PC I am using right now at my Cleveland location does not have any of those files on it.

The LCD control routines I am using are mostly from Pommie with only minor modifications. Probably the most significant of those being replacement of his WaitNotBusy with a fixed delay of 2 NOPs. So far, no problems at 8 MHz.

Most of the rough code is here: https://www.electro-tech-online.com...racter-set-lookup-table-s.134866/#post1131833

The file, LCD code raw 061913 FSR clean, has most of the current code. The include file is labeled LCD.asm (extension needs to be changed obviously).

An updated excerpt is here:
https://www.electro-tech-online.com...racter-set-lookup-table-s.134866/#post1134536

My immediate need is to clean up the files, get rid of the clutter, and make a compact working version so I can proceed with connecting an accelerometer module (see my blog) to the GLCD module with wireless (XBee).

Since posting this question, I have used the dtm directive in a double-table look-up for big numbers and for indirect addressing. One trivial thing I like about it is that the movlw is easy to spot in a disassembly listing for trouble shooting. I still don't understand how one could actually access the table value in w from the movlw without using the moviw instruction and indirect addressing. Perhaps, it is only designed for that purpose.

When I do get the code cleaned up and organized, I will be putting it in my blog for easy reference.

You have been a lot of help on this on-going project, and I regret that I just don't have access to what you want to see and probably won't until tomorrow at the earliest.

Regards,

John
 
Thanks for the info', John. In the past I always used different routines for printing a string and printing a character for the reason you mentioned (character table data may contain any value, 0..255). Also, as you've noticed, printing a character from a character table may get quite a bit more involved since you need to support different font sizes, proportional fonts, etc..

Good luck on your project and I look forward to watching your progress.

Cheerful regards, Mike
 
Last edited:
UPDATE

After a little playing with using the stack, I got it to work essentially as Mike wrote it. (He didn't know that I didn't put XY data to SetLCD_X and SetLCD_Y in w. Those data needed to be moved to their respective registers.) Thank you, again.

With Big_Str, I had to incorporate a counter so the next X-position for a string could be calculated. That worked OK, and in the example attached hereto, you will note that not all of the big characters are the same width, but spacing is only one column, except for the slash and inch, for which it is intended to be different (e.g., check #4 vs #3).

Two fonts.png



General impression: I can see that using the stack can be quite useful, but I need a lot more experience with it. I may just go back to the older method. The PutStr macro is exceptionally helpful. It may not save steps, but it certainly makes that part of the code more readable.

Specific questions:
1) Is there any way to read the current X-position? That is, rather than calculating it, it would be nice just to read it.
2) Note that for slash and inch characters, I gave them consecutive non-ascii numbers so my table would stay small. Obviously, I could add them at the proper ascii positions and fill the table with nulls. For this demo, I simply built up the 3/4" string in pieces. Using the macro as shown, is there a way to include the information with something like: {'3' 0x0b '4' 0x0a} in the single space for "text"?

Regards,

John

OT: I got fiber optic Internet late Monday. Spent a good part of yesterday setting up things. This is my first day without a visit to McDonald's. I did not have Internet at my new home until now. Communication should be a lot easier from here on.

View attachment TOS_2 Fonts.asm
 
Last edited:
Status
Not open for further replies.

Latest threads

Back
Top