Jump to content
43oh

Compact async serial tx


Recommended Posts

Uses software delay loop instead of timer [interrupt] for small code size.

Minimum bit duration is 18 clock cycles. One cycle granularity.

I think this code is optimal (can not be done with fewer instruction words or cycles).

 

tx_char                                 ; Char to tx in R14
                                       ; R10, R11, R12, R15 trashed
           mov     #65553 - 104, R10   ; Bit duration (18 minimum)
           mov     #0x02, R15          ; Serial output bitmask
           mov     #10, R12            ; 10 bit times (may be reduced to 9 at high bit rates)
           bic.b   R15, &P1OUT         ; Start bit
           or      #0xFF00, R14        ; Stop bit(s)
           nop                         ;
bit_loop    mov     R10, R11            ; Get bit duration
bit_time    nop                         ; 4 cycle loop
           add     #4, R11             ;
           jnc     bit_time            ;
           rla     R11                 ; 0 to 3 cycle delay
           add     R11, PC             ;
           nop                         ;
           nop                         ;
           nop                         ;
           rrc     R14                 ; Get bit to tx
           jc      bit_high            ; If high...
           bic.b   R15, &P1OUT         ; Send zero bit
           dec     R12                 ; Dec bit count
           jmp     bit_loop            ; Next bit...
bit_high    bis.b   R15, &P1OUT         ; Send one bit
           dec     R12                 ; Dec bit count
           jnz     bit_loop            ; Next bit...
           ret                         ; Return when all bits sent         

Link to post
Share on other sites

This is intriguing to me. Assembly is efficient both in size and in speed.

 

Could I make a .lib file with this inside in binary form then link it in with my project?

 

Could you show me how I could call this routine?

Link to post
Share on other sites

Try this. Works on Launchpad with "MSP430 Application UART" at 9600,8,N,1

 

main.c

#include "msp430g2211.h" // or g2231

// Functions in serial.asm (this really should be in a header file)
void putc(unsigned);
void puts(char *);

void main(void)
{
   char c;

   DCOCTL = 0;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   WDTCTL = WDTPW + WDTHOLD;
   P1DIR = 0xDA;
   P1REN = 0x25;
   P1OUT = 0x07;
   P1SEL = 0x11;

   puts("Test\r\n");
   for(c = 33; c < 127; ++c) putc(c);
   puts("\r\n");

   for(;;
}

 

serial.asm


           .cdecls C, LIST, "msp430g2211.h"
;            .cdecls C, LIST, "msp430g2231.h"

           .text
           .global putc, puts          ; Global symbols can be called by other source files

putc                                    ; Char to tx in R12
                                       ; R10, R11, R14, R15 trashed
           mov     #65553 - 104, R10   ; Bit duration (18 minimum)
           mov     #0x02, R15          ; Serial output bitmask
           mov     #10, R14            ; 10 bit times (may be reduced to 9 at high bit rates)
           bic.b   R15, &P1OUT         ; Start bit
           or      #0xFF00, R12        ; Stop bit(s)
           nop                         ;
bit_loop    mov     R10, R11            ; Get bit duration
bit_time    nop                         ; 4 cycle loop
           add     #4, R11             ;
           jnc     bit_time            ;
           rla     R11                 ; 0 to 3 cycle delay
           add     R11, PC             ;
           nop                         ;
           nop                         ;
           nop                         ;
           rrc     R12                 ; Get bit to tx
           jc      bit_high            ; If high...
           bic.b   R15, &P1OUT         ; Send zero bit
           dec     R14                 ; Dec bit count
           jmp     bit_loop            ; Next bit...
bit_high    bis.b   R15, &P1OUT         ; Send one bit
           dec     R14                 ; Dec bit count
           jnz     bit_loop            ; Next bit...
ret_ins     ret                         ; Return when all bits sent         
                                       ;
                                       ;
puts                                    ; Tx string using putc
           mov     R12, R9             ; String pointer in R12, copy to R9
putsloop                                ;
           mov.b   @R9+, R12           ; Get a byte, inc pointer
           tst.b   R12                 ; Test if end of string
           jz      ret_ins             ; Yes, exit...
           call    #putc               ; Call putc
           jmp     putsloop            ; Loop...
                                       ;
           .end                        ;

Link to post
Share on other sites

Thank you very much for posting that assembly language example. It's just the push I needed to start playing with MSP430 assembly language.

 

... I think this code is optimal (can not be done with fewer instruction words or cycles).

Would something like the code below work? It looks a bit tighter to me but this is my first attempt at MSP430 assembler and I'm not sure I'm counting cycles correctly (I may have to adjust "overhead" and "loop" values). Do you know of a way to display cycles in the CCS debugger?

 

Cheerful regards, Mike

 

;
;  put232 subroutine, 9600 baud, Tx char must be in R12 before entry (27 words)
;
put232
       mov     #2, R15                 ; bit mask for P1.1 'tx' pin
       or      #0x0300, R12            ; insert two stop bit(s)
       rla.w   R12                     ; insert a '0' start bit
nxtbit  rra.w   R12                     ; put bit 0 in Carry.  a '0' bit?  (1)
       jnc     send_0                  ; yes, branch, else                (2)
send_1  bis.b   R15, &P1OUT             ; send a '1' bit                   (4)
       jmp     dprep                   ;                                  (2)
send_0  bic.b   R15, &P1OUT             ; send a '0' bit                   (4)
       jmp     dprep                   ;                                  (2)
dprep   mov     #(104-20)/3+1, R11      ; R11 = (delay-overhead)/loop+1    (2)
       jmp     dloop-(104-20)%3*2      ; account for delay %3 cycles      (2)
       nop                             ; (delay-overhead)%loop == 2 entry ( )
       nop                             ; (delay-overhead)%loop == 1 entry ( )
dloop   dec.w   R11                     ; decrement counter (3 cycle loop) (2)
       jnz     dloop                   ; delay done? no, jump, else       (2)
       tst.w   R12                     ; all bits sent?                   (1)
       jnz     nxtbit                  ; no, branch, else, fall thru      (2)
       ret                             ; exit

Link to post
Share on other sites

Nice code. I like it. Great to have another MSP430 asm programmer here.

 

To count cycles in CCS debugger, select Clock Enable from the Target menu. Note that the cycle count of the first instruction executed after enabling the clock may be incorrect, usually +1. Double click on the clock to reset it.

 

Here is revised code that uses 25 words, 17 cycles/bit minimum, and can be packaged as a library.

 

serial.asm

           .cdecls C, LIST, "msp430g2211.h"

           .bss    bit_dur, 2
           .bss    bit_mask, 2

           .text
           .global serial_setup, putc, puts

serial_setup                            ; - Setup serial output bitmask and bit duration (17 minimum)
           mov     R12, &bit_mask      ; Save serial output bitmask
           inv     R13                 ; Delay loop counts up until overflow, so
                                       ;  count must be inverted
           add     #17, R13            ; Adjust count for loop overhead
           mov     R13, &bit_dur       ; Save bit duration initial count
           ret                         ; Return
                                       ;
                                       ; - Send a single char
putc                                    ; Char to tx in R12
                                       ; R11, R12, R14, R15 trashed
           mov     &bit_dur, R14       ; Bit duration initial count
           mov     &bit_mask, R15      ; Serial output bitmask
           or      #0x0300, R12        ; Stop bit(s)
           jmp     bit_low             ; Send start bit...
bit_loop    mov     R14, R11            ; Get bit duration
bit_time    nop                         ; 4 cycle loop
           add     #4, R11             ;
           jnc     bit_time            ;
           rla     R11                 ; 0 to 3 cycle delay
           add     R11, PC             ;
           nop                         ;
           nop                         ;
           nop                         ;
           rra     R12                 ; Get bit to tx, test for zero
           jc      bit_high            ; If high...
bit_low     bic.b   R15, &P1OUT         ; Send zero bit
           jmp     bit_loop            ; Next bit...
bit_high    bis.b   R15, &P1OUT         ; Send one bit
           jnz     bit_loop            ; If tx data is not zero, then there are more bits to send...
ret_ins     ret                         ; Return when all bits sent         
                                       ;
                                       ;
                                       ; - Send a NULL terminated string
puts                                    ; Tx string using putc
           mov     R12, R9             ; String pointer in R12, copy to R9
putsloop                                ;
           mov.b   @R9+, R12           ; Get a byte, inc pointer
           tst.b   R12                 ; Test if end of string
           jz      ret_ins             ; Yes, exit...
           call    #putc               ; Call putc
           jmp     putsloop            ;
                                       ;
           .end                        ;

 

main.c


#include "msp430g2211.h"

// Functions in serial.asm (this really should be in a header file)
void serial_setup(unsigned mask, unsigned duration);
void putc(unsigned);
void puts(char *);

void main(void)
{
   char c;

   DCOCTL = 0;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   WDTCTL = WDTPW + WDTHOLD;
   P1DIR = 0xDA;
   P1REN = 0x25;
   P1OUT = 0x07;
   P1SEL = 0x11;

   // Port 1.1, 9600 bps (@ 1 MHz clock)   
   serial_setup(1 << 1, 1000000 / 9600);

   puts("Test\r\n");
   for(c = 33; c < 127; ++c) putc(c);
   puts("\r\n");

   for(;;
}

Link to post
Share on other sites

If I were to change the DCO to a different frequency, what impact would that have on your code?

 

BTW, I think that your code is extremely valuable. Why? Because it's so d*** small!

 

Although, you might cause a revolution here with it. People might start coding in assembly again! ;)

 

Good work!

Link to post
Share on other sites
To count cycles in CCS debugger, select Clock Enable from the Target menu. Note that the cycle count of the first instruction executed after enabling the clock may be incorrect, usually +1. Double click on the clock to reset it.

 

Got it. Thanks...

Link to post
Share on other sites
... Here is revised code that uses 25 words ...

Very nice! It looks like you took my lead and got rid of the bit counter. Bravo!

 

Since the Zero flag is unchanged during bis, bic, and jmp instructions, it looks like I can further optimize my Put232 subroutine down to about 19 words of program memory (using constants for delay and pin mask).

 

put232                                  ; { Mike McLaren, K8LH }  19 words
       or      #0x0300, R12            ; insert two stop bit(s)           (2)
       rla.w   R12                     ; insert a '0' start bit           (1)
delay   mov     #(104-16)/3+1, R11      ; (delay-overhead)/loop+1          (2)
       jmp     dloop-(104-16)%3*2      ; account for %3 cycles            (2)
       nop                             ; entry (delay-overhead)%3 == 2    (1)
       nop                             ; entry (delay-overhead)%3 == 1    (1)
dloop   dec.w   R11                     ; decrement counter (3 cycle loop) (1)
       jnz     dloop                   ; delay done? no, jump, else       (2)
       rra.w   R12                     ; put bit 0 in Carry.  a '1' bit?  (1)
       jc      send1                   ; yes, branch, else                (2)
send0   bic.b   #BIT2, &P1OUT           ; send a '0' on P1.1 'tx' pin      (4)
       jmp     delay                   ;                                  (2)
send1   bis.b   #BIT2, &P1OUT           ; send a '1' on P1.1 'tx' pin      (4)
       jnz     delay                   ; jmp for isochronous loop timing  (2)
       ret                             ; exit

Link to post
Share on other sites
Be aware that constants -1, 0, 1, 2, 4, and 8 are one word and one cycle shorter than all other constants. Use caution with constants in timing critical code.

 

I'm not following you. Sounds like an interesting caveat though. Is there a page number in SLAU144h or SLAU131e I can refer to?

 

Regards, Mike

Link to post
Share on other sites
I think you're all nuts here. This isn't 1965's mission to moon with 70k of memory, just use 2553.

Just kidding :)

 

Yeah, sorry Rob. I got caught up in oPossum's "this code is optimal (can not be done with fewer instruction words or cycles)" claim, which sounded like a challenge to me (grin).

 

I really like assembly language on PIC devices so I thought it might be fun to learn assembly language on the MSP430 value line devices. So far, the von Neumann architecture on the MSP430 is proving difficult (for me), compared to the Harvard architecture on the PIC.

 

Cheerful regards, Mike

Link to post
Share on other sites
Be aware that constants -1, 0, 1, 2, 4, and 8 are one word and one cycle shorter than all other constants. Use caution with constants in timing critical code.

 

I'm not following you. Sounds like an interesting caveat though. Is there a page number in SLAU144h or SLAU131e I can refer to?

 

Regards, Mike

 

"3.2.4 Constant Generator Registers CG1 and CG2" (page 47) in "MSP430x2xx Family User's Guide" (slau144h)

 

It is a quirk of the MPS430 architecture that allows smaller, faster code and is used by several pseudo-ops.

Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...