oPossum 1,083 Posted May 28, 2011 Share Posted May 28, 2011 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 SirZusa and babu 2 Quote Link to post Share on other sites
zeke 693 Posted May 28, 2011 Share Posted May 28, 2011 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? Quote Link to post Share on other sites
oPossum 1,083 Posted May 28, 2011 Author Share Posted May 28, 2011 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 ; bluehash 1 Quote Link to post Share on other sites
Mac 67 Posted May 29, 2011 Share Posted May 29, 2011 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 oPossum 1 Quote Link to post Share on other sites
oPossum 1,083 Posted May 31, 2011 Author Share Posted May 31, 2011 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(;; } RobG 1 Quote Link to post Share on other sites
zeke 693 Posted May 31, 2011 Share Posted May 31, 2011 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! Quote Link to post Share on other sites
oPossum 1,083 Posted May 31, 2011 Author Share Posted May 31, 2011 Just change the serial_setup() call. For example, 57600 bps with 16 Mhz clock... serial_setup(1 << 1, 16000000 / 57600); zeke 1 Quote Link to post Share on other sites
Mac 67 Posted May 31, 2011 Share Posted May 31, 2011 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... Quote Link to post Share on other sites
Mac 67 Posted May 31, 2011 Share Posted May 31, 2011 ... 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 Quote Link to post Share on other sites
oPossum 1,083 Posted May 31, 2011 Author Share Posted May 31, 2011 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. Quote Link to post Share on other sites
RobG 1,892 Posted May 31, 2011 Share Posted May 31, 2011 I think you're all nuts here. This isn't 1965's mission to moon with 70k of memory, just use 2553. Just kidding Great work everybody, really tempted to do asm again... kidding again. Quote Link to post Share on other sites
Mac 67 Posted May 31, 2011 Share Posted May 31, 2011 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 Quote Link to post Share on other sites
Mac 67 Posted May 31, 2011 Share Posted May 31, 2011 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 Quote Link to post Share on other sites
Rickta59 589 Posted June 1, 2011 Share Posted June 1, 2011 This is a great thread. So what changes do I need to make to get this to compile with msp430-gcc? -rick Quote Link to post Share on other sites
oPossum 1,083 Posted June 1, 2011 Author Share Posted June 1, 2011 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. Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.