Jump to content
43oh

Software async serial tx/rx without timer


Recommended Posts

Had some requests for this in IRC so here it is. This is an improved version of the serial tx previously posted and new serial rx code. Both tx and rx are blocking and interrupts should be disabled before calling.

 

C code to show how to use the serial_setup(), putc(), puts(), and getc() functions.

This will receive a character, increment it and echo it back. So if you type 'A', then 'B' will be echoed.

// test.c
#include "msp430g2211.h"

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

void main(void)
{
   char c;

   // Disable watchdog 
   WDTCTL = WDTPW + WDTHOLD;

   // Use 1 MHz DCO factory calibration
   DCOCTL = 0;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   // Setup the serial port
   // Serial out: P1.1 (BIT1)
   // Serial in:  P1.2 (BIT2)
   // Bit rate:   9600 (CPU freq / bit rate)  
   serial_setup(BIT1, BIT2, 1000000 / 9600);

   // Send a string
   puts("\r\nRunning...\r\n");

   for(; {           // Do forever
       c = getc();     // Get a char
       ++c;            // Increment it
       putc(c);        // Echo it back
   }
}

 

The serial tx/rx code. Just add this as a new source file in your CCS project and it will be callable from your C code.

; serial.asm
           .cdecls C, LIST, "msp430g2231.h"

           .bss    in_bit_mask, 2      ; Serial in pin
           .bss    out_bit_mask, 2     ; Serial out pin
           .bss    bit_dur, 2          ; Bit duration in cycles
           .bss    half_dur, 2         ; Half bit duration in cycles
                                       ;
           .text                       ;
           .def    serial_setup        ; void serial_setup(unsigned out_mask, unsigned in_mask, unsigned bit_duration);
           .def    putc                ; void putc(unsigned c);
           .def    puts                ; void puts(char *s);
           .def    getc                ; unsigned getc(void);
                                       ;
                                       ;
serial_setup                            ; - Setup serial I/O bitmasks and bit duration (32 minimum)
           mov     R12, &out_bit_mask  ; Save serial output bitmask
           mov     R13, &in_bit_mask   ; Save serial input bitmask
           bis.b   R12, &P1DIR         ; Setup output pin
           bis.b   R12, &P1OUT         ;
           bic.b   R13, &P1DIR         ; Setup input pin
           or      R13, R12            ;
           bic.b   R12, &P1SEL         ; Setup peripheral select
           mov     R14, R12            ;
           sub     #16, R14            ; Adjust count for loop overhead
           rla     R14                 ; Multiply by 2 because NOP is two bytes
           mov     R14, &bit_dur       ; Save bit duration
           sub     #32, R12            ; Adjust count for loop overhead
           mov     R12, &half_dur      ; Save half bit duration
           ret                         ; Return
                                       ;
                                       ; - Send a single char
putc                                    ; Char to tx in R12
                                       ; R12, R13, R14, R15 trashed
           mov     &out_bit_mask, R15  ; Serial output bitmask
           mov     &bit_dur, R14       ; Bit duration
           or      #0x0300, R12        ; Stop bit(s)
           jmp     bit_low             ; Send start bit...
                                       ;
tx_bit      mov     R14, R13            ; Get bit duration
tx_delay    nop                         ; 4 cycle loop
           sub     #8, R13             ;
           jc      tx_delay            ;
           subc    R13, PC             ; 0 to 3 cycle delay
           nop                         ; 3
           nop                         ; 2
           nop                         ; 1
                                       ;
           rra     R12                 ; Get bit to tx, test for zero
           jc      bit_high            ; If high...
bit_low     bic.b   R15, &P1OUT         ; Send zero bit
           jmp     tx_bit              ; Next bit...
bit_high    bis.b   R15, &P1OUT         ; Send one bit
           jnz     tx_bit              ; If tx data is not zero, then there are more bits to send...
                                       ;
           ret                         ; Return when all bits sent         
                                       ;
                                       ;
                                       ; - Send a NULL terminated string
puts                                    ; Tx string using putc
           push    R11                 ;
           mov     R12, R11            ; String pointer in R12, copy to R11
putsloop                                ;
           mov.b   @R11+, R12          ; Get a byte, inc pointer
           tst.b   R12                 ; Test if end of string
           jz      putsx               ; Yes, exit...
           call    #putc               ; Call putc
           jmp     putsloop            ;
putsx       pop     R11                 ;
           ret                         ;
                                       ;
getc                                    ; - Get a char
           mov     &bit_dur, R14       ; Bit duration
           mov     &in_bit_mask, R13   ; Input bitmask
           mov     #0x01FF, R12        ; 9 bits - 8 data + stop
                                       ;
rx_start                                ; Wait for start bit
           mov.b   &P1IN, R15          ; Get serial input          
           and     R13, R15            ; Mask and test bit
           jc      rx_start            ; Wait for low...
                                       ;
           mov     &half_dur, R13      ; Wait for 1/2 bit time
                                       ;
rx_delay    nop                         ; Bit delay
           sub     #8, R13             ;
           jc      rx_delay            ;
           subc    R13, PC             ; 0 to 3 cycle delay
           nop                         ; 3
           nop                         ; 2
           nop                         ; 1
                                       ;
           mov.b   &P1IN, R15          ; Get serial input          
           and     &in_bit_mask, R15   ;
           rrc     R12                 ; Shift in a bit
                                       ;
           mov     R14, R13            ; Setup bit timer
           jc      rx_delay            ; Next bit...
                                       ;
           rla     R12                 ; Move stop bit to carry
           swpb    R12                 ; Move rx byte to lower byte, start bit in msb
           ret                         ; Return with rx char and start bit in R12, stop bit in carry
                                       ;                                       
           .end                        ;

Link to post
Share on other sites

Here is an msp430-gcc port of serial.S. Only some minor changes required:

 

renamed from serial.asm to serial.S

changed header inclusion, .cdecls to #include

added ':' to all labels

changed .bss directives to .lcomm

changed .def directives to .global

fixed argument registers CCS using R12-R15 for arg1-arg4, GCC uses R15-R12 for arg1-arg4

reworked temp registers because of argument register reorder

changed OR asm statements to BIS statements

 

; serial.S - gcc port of serial.asm
#include 

;-------------------------------------------------------------------------------
; --- define PC,SP,SR gcc only knows about registers ---

#define PC r0                   
#define SP r1
#define SR r2

#define ARG1 R15
#define ARG2 R14
#define ARG3 R13
#define ARG4 R12

           .lcomm  in_bit_mask, 2      ; Serial in pin
           .lcomm  out_bit_mask, 2     ; Serial out pin
           .lcomm  bit_dur, 2          ; Bit duration in cycles
           .lcomm  half_dur, 2         ; Half bit duration in cycles
                                       ;
           .text                       ;
           .global serial_setup        ; void serial_setup(unsigned out_mask, unsigned in_mask, unsigned bit_duration);
           .global putc                ; void putc(unsigned c);
           .global puts                ; void puts(char *s);
           .global getc                ; unsigned getc(void);
                                       ;
           .p2align 1,0                ; align on a word boundary
serial_setup:				; - Setup serial I/O bitmasks and bit duration (32 minimum)
           mov     ARG1, &out_bit_mask ; Save serial output bitmask
           mov     ARG2, &in_bit_mask  ; Save serial input bitmask
           bis     ARG1, &P1DIR        ; Setup output pin
           bis     ARG1, &P1OUT        ;
           bic     ARG2, &P1DIR        ; Setup input pin
           bis     ARG2, ARG1          ;
           bic     ARG1, &P1SEL        ; Setup peripheral select
           mov     ARG3, ARG1          ;
           sub     #16, ARG3           ; Adjust count for loop overhead
           rla     ARG3                ; Multiply by 2 because NOP is two bytes
           mov     ARG3, &bit_dur      ; Save bit duration
           sub     #32, ARG1           ; Adjust count for loop overhead
           mov     ARG1, &half_dur     ; Save half bit duration
           ret                         ; Return
                                       ;
                                       ; - Send a single char
putc:					; Char to tx in R15
                                       ; R12, R13, R14, R15 trashed
           mov     &out_bit_mask, R12  ; Serial output bitmask
           mov     &bit_dur, R14       ; Bit duration
           bis     #0x0300, ARG1       ; Add Stop bit(s) to tx char
           jmp     bit_low             ; Send start bit...
                                       ;
tx_bit:     mov     R14, R13            ; Get bit duration
tx_delay:   nop                         ; 4 cycle loop
           sub     #8, R13             ;
           jc      tx_delay            ;
           subc    R13, PC             ; 0 to 3 cycle delay
           nop                         ; 3
           nop                         ; 2
           nop                         ; 1
                                       ;
           rra     ARG1                ; Get bit to tx, test for zero
           jc      bit_high            ; If high...
bit_low:    bic.b   R12, &P1OUT         ; Send zero bit
           jmp     tx_bit              ; Next bit...
bit_high:   bis.b   R12, &P1OUT         ; Send one bit
           jnz     tx_bit              ; If tx data is not zero, then there are more bits to send...
                                       ;
           ret                         ; Return when all bits sent         
                                       ;
                                       ;
                                       ; - Send a NULL terminated string
puts:                                   ; Tx string using putc
           push    R11                 ;
           mov     ARG1, R11           ; String pointer in R15, copy to R11
putsloop:                               ;
           mov.b   @R11+, ARG1         ; Get a byte, inc pointer
           tst.b   ARG1                ; Test if end of string
           jz      putsx               ; Yes, exit...
           call    #putc               ; Call putc
           jmp     putsloop            ;
putsx:      pop     R11                 ;
           ret                         ;
                                       ;
getc:                                   ; - Get a char
           mov     &bit_dur, R14       ; Bit duration
           mov     &in_bit_mask, R13   ; Input bitmask
           mov     #0x01FF, ARG1       ; 9 bits - 8 data + stop
                                       ;
rx_start:                               ; Wait for start bit
           mov     &P1IN, R12          ; Get serial input          
           and     R13, R12            ; Mask and test bit
           jc      rx_start            ; Wait for low...
                                       ;
           mov     &half_dur, R13      ; Wait for 1/2 bit time
                                       ;
rx_delay:   nop                         ; Bit delay
           sub     #8, R13             ;
           jc      rx_delay            ;
           subc    R13, PC             ; 0 to 3 cycle delay
           nop                         ; 3
           nop                         ; 2
           nop                         ; 1
                                       ;
           mov     &P1IN, R12          ; Get serial input          
           and     &in_bit_mask, R12   ;
           rrc     ARG1                ; Shift in a bit
                                       ;
           mov     R14, R13            ; Setup bit timer
           jc      rx_delay            ; Next bit...
                                       ;
           rla     ARG1                ; Move stop bit to carry
           swpb    ARG1                ; Move rx byte to lower byte, start bit in msb
           ret                         ; Return with rx char and start bit in R15, stop bit in carry

 

-rick

Link to post
Share on other sites
  • 1 month later...
unsigned state, c, n, addr, fgr, fgg, fgb, bgr, bgg, bgb, mkr, mkg, mkb;

for(state = 0;  {
   c = getc();
   if(c == '<') {
       state = 1;
   } else if(c == '>') {
       if(state == 11) {
           // End of packet - do something
       }
       state = 0;
   } else if(c >= '0' && c <= '9') {
       n = c - '0';
       if(state > 1 && n > 1) state = 0;
       switch(state) {
           case 1: ++state; addr = n; break;
           case 2: ++state; fgr = n; break;
           case 3: ++state; fgg = n; break;
           case 4: ++state; fgb = n; break;
           case 5: ++state; bgr = n; break;
           case 6: ++state; bgg = n; break;
           case 7: ++state; bgb = n; break;
           case 8: ++state; mkr = n; break;
           case 9: ++state; mkg = n; break;
           case 10: ++state; mkb = n; break;
           default: break;
       }
   } else {
       state = 0;
   }
}

Link to post
Share on other sites
  • 4 months later...

[ EDIT: be sure to see updated post below ]

 

Many thanks to Rick and oPossum for helping me get this up and running with gcc 4.6.3. In the process we found a few problems in the original code where word access is made to port registers rather than byte mode.

 

Here is what I have:

 

sw_serial.S

;------------------------------------------------------------------------
; File: sw_serial.S - msp430-gcc software only async serial routines
;
;    Copyright (C) 2011 Kevin Timmerman
;
;    This program is free software: you can redistribute it and/or modify
;    it under the terms of the GNU General Public License as published by
;    the Free Software Foundation, either version 3 of the License, or
;    (at your option) any later version.
;
;    This program is distributed in the hope that it will be useful,
;    but WITHOUT ANY WARRANTY; without even the implied warranty of
;    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;    GNU General Public License for more details.
;
;    You should have received a copy of the GNU General Public License
;    along with this program.  If not, see .
;
;
; Desc: blocking versions of putchar(), getchar(), and puts() that
;       use cycle counting for accurate sampling and sending. Features
;       very small code size, allows use of any pin, and doesn't interfere
;       with the use of timers.
;
; Change Log:
;    2012-Jan-04 rick@kimballsoftware.com
;                Changes for gcc, renamed function names to
;                allow gcc printf() to work, conditional defines to allow
;                for smaller code, disabled puts() by default so not to
;                conflict with libc version. renamed jump labels to
;                reduce symbol table pollution.
;
;    2011-Oct-23 rick@kimballsoftware.com
;                Initial port to msp430-gcc
;
;    2012-Apr-28 rockets4kids@gmail.com
;                rolled in include file
;                fixed I/O ops to byte (. mode
;
; See Also:
;   Detailed information about original code here:
;   http://www.43oh.com/forum/viewtopic.php?f=10&t=1727
;
;------------------------------------------------------------------------
; TODO: Add optional sleep mode in getchar() which will disable MCU until
;       incoming pin edge detect triggers interrupt.
; TODO: Allow for mulitple serial ports
;------------------------------------------------------------------------

#include 

//------------------------------------------------------------------------------
// define PC,SP,SR as gcc only uses the register names
//
#define PC r0                   
#define SP r1
#define SR r2

//------------------------------------------------------------------------------
// define argument registers
//  C function arguments are passed in R15-R12
//
// Note: these registers can be used without preservation
//
#define ARG1 R15
#define ARG2 R14
#define ARG3 R13
#define ARG4 R12


#define ENABLE_GETCHAR              /* enable getchar() */
#define ENABLE_PUTCHAR              /* enable putchar() */
#undef  ENABLE_PUTS                 /* disable puts() as it isn't ISO conforming */
#define ENABLE_AUTO_GIE             /* call __dint() .. __eint() on getchar() putchar() */

       .file "sw_serial.S"

;------------------------------------------------------------------------
; Unitialized SRAM local variables
;------------------------------------------------------------------------
       .lcomm  rx_bit_mask, 2      ; Serial in pin mask
       .lcomm  tx_bit_mask, 2      ; Serial out pin mask
       .lcomm  bit_dur, 2          ; Bit duration in cycles
       .lcomm  half_dur, 2         ; Half bit duration in cycles

;------------------------------------------------------------------------
; Exposed Function Symbols
;------------------------------------------------------------------------
       .text                       ; use the "text" section for all code below
       .global init_serial         ; void init_serial(unsigned out_mask, unsigned in_mask, unsigned bit_duration);
       .global init_serial1        ; void init_serial(_serial_ios *ios);
#ifdef ENABLE_GETCHAR
       .global getchar             ; int getchar(void);
#endif
#ifdef ENABLE_PUTCHAR
       .global putchar             ; int putchar(uint8_t c);
#endif
#ifdef ENABLE_PUTS
       .global puts                ; int puts(const char *s); NOTE: no appended newline
#endif

;------------------------------------------------------------------------
; Serial Function Implementations
;------------------------------------------------------------------------

       .p2align 1,0                ; align on a word boundary
;--------------------------------------------------------------------------------
; void init_serial(int txPinMask, int rxPinMask, int bitDuration)
;--------------------------------------------------------------------------------
       .type init_serial,@function
init_serial:                        ; Setup serial I/O bitmasks and bit duration (minimum of 32)
       mov     ARG1, &tx_bit_mask  ; Save serial output bitmask
       mov     ARG2, &rx_bit_mask  ; Save serial input bitmask
       bis.b   ARG1, &P1DIR        ; set output pin
       bis.b   ARG1, &P1OUT
       bic.b   ARG2, &P1DIR        ; clear input pin
       bis     ARG2, ARG1
#ifdef __MSP430FR5739__
       bic.b   ARG1, &P1SEL0       ; force output pin to digial I/O no pullups
       bic.b   ARG1, &P1SEL1       ; force output pin to digial I/O no pullups
#else
       bic.b   ARG1, &P1SEL        ; force output pin to digial I/O no pullups
#endif
       mov     ARG3, ARG1          ; Compute bit duration
       sub     #16, ARG3           ; Adjust count for loop overhead
       rla     ARG3                ; Multiply by 2 because NOP is two bytes
       mov     ARG3, &bit_dur      ; Save bit duration
       sub     #32, ARG1           ; Adjust count for loop overhead
       mov     ARG1, &half_dur     ; Save half bit duration
       ret                         ; ready to rx/tx
.Lfe1:
       .size init_serial,.Lfe1-init_serial


#ifdef ENABLE_GETCHAR
;--------------------------------------------------------------------------------
; int getchar(void) - read one character (blocking)
;--------------------------------------------------------------------------------
       .p2align 1,0                ; align on a word boundary
       .type getchar,@function
getchar:
       bic     #GIE, SR            ; disable global interrupts
       mov     &bit_dur, R14       ; Bit duration
       mov     &rx_bit_mask, R13   ; Input bitmask
       mov     #0x01FF, ARG1       ; 9 bits - 8 data + stop

.rx_start:                          ; Wait for start bit
       mov.b   &P1IN, R12          ; Get serial input
       and     R13, R12            ; Mask and test bit
       jc      .rx_start           ; Wait for low...
       mov     &half_dur, R13      ; Wait for 1/2 bit time

.rx_delay:
       nop                         ; Bit delay
       sub     #8, R13
       jc      .rx_delay
       subc    R13, PC             ; 0 to 3 cycle delay
       nop                         ; 3
       nop                         ; 2
       nop                         ; 1

       mov.b   &P1IN, R12          ; Get serial input
       and     &rx_bit_mask, R12
       rrc     ARG1                ; Shift in a bit

       mov     R14, R13            ; Setup bit timer
       jc      .rx_delay           ; Next bit...

       rla     ARG1                ; Move stop bit to carry
       swpb    ARG1                ; Move rx byte to lower byte, start bit in msb
       bis     #GIE, SR            ; enable global interrupts
       ret                         ; Return with rx char and start bit in R15, stop bit in carry
.Lfe3:
       .size getchar,.Lfe3-getchar
#endif


#ifdef ENABLE_PUTCHAR
;--------------------------------------------------------------------------------
; int putchar(uint8_t c) - writes the character c to serial out
;--------------------------------------------------------------------------------
       .p2align 1,0                ; align on a word boundary
       .type putchar,@function
putchar:                                ; Char to tx in R15, R12, R13, R14, R15 trashed
       bic     #GIE, SR            ; disable global interrupts
       mov     &tx_bit_mask, R12       ; Serial output bitmask
       mov     &bit_dur, R14       ; Bit duration
       bis     #0x0300, ARG1       ; Add Stop bit(s) to tx char
       jmp     .bit_low            ; Send start bit...

.tx_bit:
       mov     R14, R13            ; Get bit duration
.tx_delay:
       nop                         ; 4 cycle loop
       sub     #8, R13
       jc      .tx_delay
       subc    R13, PC             ; 0 to 3 cycle delay
       nop                         ; 3
       nop                         ; 2
       nop                         ; 1
       rra     ARG1                ; Get bit to tx, test for zero
       jc      .bit_high           ; If high...
.bit_low:
       bic.b   R12, &P1OUT        ; Send zero bit
       jmp     .tx_bit             ; Next bit...
.bit_high:
       bis.b   R12, &P1OUT        ; Send one bit
       jnz     .tx_bit             ; If tx data is not zero, then there are more bits to send...

       bis     #GIE, SR            ; enable global interrupts
       ret                         ; Return when all bits sent
.Lfe2:
       .size putchar,.Lfe2-putchar
#endif


#ifdef ENABLE_PUTS
;--------------------------------------------------------------------------------
; int puts(const char *s) - writes the string s to serial out.
;--------------------------------------------------------------------------------
       .p2align 1,0                ; align on a word boundary
       .type puts,@function
puts:
       push    R11                 ; TX string using putchar
       mov     ARG1, R11           ; String pointer in R15, copy to R11 (temp reg)
.putsloop:
       mov.b   @R11+, ARG1         ; Get a byte, inc pointer
       tst.b   ARG1                ; Test if end of string
       jz      .putsx              ; Yes, exit...
       call    #putchar            ; Call putchar
       jmp     .putsloop
.putsx:
       pop     R11                 ; restore original R11
       ret                         ;
.Lfe4:
       .size puts,.Lfe4-puts
#endif

 

sw_serial.h

void init_serial(unsigned txPinMask, unsigned rxPinMask, unsigned bitDuration);
int getchar(void);
int putchar(int c);
int puts(const char *s);

 

main.c

#include 
#include 
#include "sw_serial.h"

void main(void)
{
   int c;

   // Disable watchdog 
   WDTCTL = WDTPW + WDTHOLD;

   // Use 1 MHz DCO factory calibration
   DCOCTL = 0;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   // Setup the serial port
   // Serial out: P1.1 (BIT1)
   // Serial in:  P1.2 (BIT2)
   // Bit rate:   9600 (CPU freq / bit rate)  
   init_serial (BIT1, BIT2, 1000000 / 9600);

   // Send a string
   puts ("\r\nRunning...\r\n");

   for (; {           // Do forever
       c = getchar();     // Get a char
       putchar (c);        // Echo it back
   }
}

 

Makefile

lazy.elf: sw_serial.S main.c
msp430-gcc -Os sw_serial.S main.c -o lazy.elf -mmcu=msp430g2211

install: lazy.elf
mspdebug rf2500 "prog lazy.elf"

clean:
rm -f *~ *.elf

Link to post
Share on other sites

I did not realize it at the time, but the code I was working from hed edits beyond oPossum's original code. I have gone back and re-patched for gcc from oPossum's code -- hopefully not introducing any new bugs this time!

 

I did make one change, to re-name all the the public functions to start with "serial_" for the sake of clarity.

 

 

serial.S

#include 

#define PC r0                   
#define SP r1
#define SR r2
#define ARG1 R15
#define ARG2 R14
#define ARG3 R13
#define ARG4 R12

; serial.asm

           .lcomm  in_bit_mask, 2      ; Serial in pin
           .lcomm  out_bit_mask, 2     ; Serial out pin
           .lcomm  bit_dur, 2          ; Bit duration in cycles
           .lcomm  half_dur, 2         ; Half bit duration in cycles
                                       ;
           .text                       ;
           .global serial_setup        ; void serial_setup(unsigned out_mask, unsigned in_mask, unsigned bit_duration);
           .global serial_putc         ; void serial_putc(unsigned c);
           .global serial_puts         ; void serial_puts(char *s);
           .global serial_getc         ; unsigned serial_getc(void);
                                       ;
           .p2align 1,0                ; align on a word boundary (GCC)
                                       ;
serial_setup:                           ; - Setup serial I/O bitmasks and bit duration (32 minimum)
           mov     ARG1, &out_bit_mask ; Save serial output bitmask
           mov     ARG2, &in_bit_mask  ; Save serial input bitmask
           bis.b   ARG1, &P1DIR        ; Setup output pin
           bis.b   ARG1, &P1OUT        ;
           bic.b   ARG2, &P1DIR        ; Setup input pin
           bis     ARG2, ARG1          ;
           bic.b   ARG1, &P1SEL        ; Setup peripheral select
           mov     ARG3, ARG1          ;
           sub     #16, ARG3           ; Adjust count for loop overhead
           rla     ARG3                ; Multiply by 2 because NOP is two bytes
           mov     ARG3, &bit_dur      ; Save bit duration
           sub     #32, ARG1           ; Adjust count for loop overhead
           mov     ARG1, &half_dur     ; Save half bit duration
           ret                         ; Return
                                       ;
                                       ; - Send a single char
serial_putc:                            ; Char to tx in ARG1
                                       ; ARG1, ARG2, ARG3, ARG4 trashed
           mov     &out_bit_mask, ARG4 ; Serial output bitmask
           mov     &bit_dur, ARG3      ; Bit duration
           bis     #0x0300, ARG1       ; Stop bit(s)
           jmp     bit_low             ; Send start bit...
                                       ;
tx_bit:     mov     ARG3, ARG2          ; Get bit duration
tx_delay:   nop                         ; 4 cycle loop
           sub     #8, ARG2            ;
           jc      tx_delay            ;
           subc    ARG2, PC            ; 0 to 3 cycle delay
           nop                         ; 3
           nop                         ; 2
           nop                         ; 1
                                       ;
           rra     ARG1                ; Get bit to tx, test for zero
           jc      bit_high            ; If high...
bit_low:    bic.b   ARG4, &P1OUT        ; Send zero bit
           jmp     tx_bit              ; Next bit...
bit_high:   bis.b   ARG4, &P1OUT        ; Send one bit
           jnz     tx_bit              ; If tx data is not zero, then there are more bits to send...
                                       ;
           ret                         ; Return when all bits sent         
                                       ;
                                       ;
                                       ; - Send a NULL terminated string
serial_puts:                            ; Tx string using serial_putc
           push    R11                 ;
           mov     ARG1, R11           ; String pointer in ARG1, copy to R11
putsloop:                               ;
           mov.b   @R11+, ARG1         ; Get a byte, inc pointer
           tst.b   ARG1                ; Test if end of string
           jz      putsx               ; Yes, exit...
           call    #serial_putc        ; Call serial_putc
           jmp     putsloop            ;
putsx:      pop     R11                 ;
           ret                         ;
                                       ;
serial_getc:                            ; - Get a char
           mov     &bit_dur, ARG3      ; Bit duration
           mov     &in_bit_mask, ARG2  ; Input bitmask
           mov     #0x01FF, ARG1       ; 9 bits - 8 data + stop
                                       ;
rx_start:                               ; Wait for start bit
           mov.b   &P1IN, ARG4         ; Get serial input          
           and     ARG2, ARG4          ; Mask and test bit
           jc      rx_start            ; Wait for low...
                                       ;
           mov     &half_dur, ARG2     ; Wait for 1/2 bit time
                                       ;
rx_delay:   nop                         ; Bit delay
           sub     #8, ARG2            ;
           jc      rx_delay            ;
           subc    ARG2, PC            ; 0 to 3 cycle delay
           nop                         ; 3
           nop                         ; 2
           nop                         ; 1
                                       ;
           mov.b   &P1IN, ARG4         ; Get serial input          
           and     &in_bit_mask, ARG4  ;
           rrc     ARG1                ; Shift in a bit
                                       ;
           mov     ARG3, ARG2          ; Setup bit timer
           jc      rx_delay            ; Next bit...
                                       ;
           rla     ARG1                ; Move stop bit to carry
           swpb    ARG1                ; Move rx byte to lower byte, start bit in msb
           ret                         ; Return with rx char and start bit in ARG1, stop bit in carry
                                       ;                                       

 

serial.h

void serial_setup (unsigned out_mask, unsigned in_mask, unsigned duration);
void serial_putc (unsigned);
void serial_puts (char *);
unsigned serial_getc (void);

 

 

main.c

#include 
#include "serial.h"


void main(void)
{
   char c;

   // Disable watchdog 
   WDTCTL = WDTPW + WDTHOLD;

   // Use 1 MHz DCO factory calibration
   DCOCTL = 0;
   BCSCTL1 = CALBC1_1MHZ;
   DCOCTL = CALDCO_1MHZ;

   // Setup the serial port
   // Serial out: P1.1 (BIT1)
   // Serial in:  P1.2 (BIT2)
   // Bit rate:   9600 (CPU freq / bit rate)  
   serial_setup (BIT1, BIT2, 1000000 / 9600);

   // Send a string
   serial_puts ("\r\nRunning...\r\n");

   for (; {           // Do forever
       c = serial_getc ();     // Get a char
       serial_putc (c);        // Echo it back
   }
}

 

 

Makefile

main.elf: serial.S main.c
msp430-gcc -Os serial.S main.c -o main.elf -mmcu=msp430g2211

install: main.elf
mspdebug rf2500 "prog main.elf"

clean:
rm -f *~ *.elf

Link to post
Share on other sites
  • 9 months later...

I'm not a real ASM guy, but I'm quite familiar with it, so I'll give it a try:

This is my modified serial_putc:

                                        ; - Send a single char
serial_putc:                            ; Char to tx in ARG1
                                        ; ARG1, ARG2, ARG3, ARG4 trashed
            push    R10                 ;>Have another register to play with
            push    R11                 ;>Have another register to play with
            mov     #0, R11             ;>Clear out R11 (odd parity with #1)
            mov     #0x0006, R10        ;>Set R10 to "parity and stop to go"
            mov     &out_bit_mask, ARG4 ; Serial output bitmask
            mov     &bit_dur, ARG3      ; Bit duration
            sub     #8, ARG3            ;>We need another 8 cycles for parity magic
            bis     #0x0600, ARG1       ;>Stop bit(s)
            jmp     bit_low             ; Send start bit...
                                        ;
tx_bit:     mov     ARG3, ARG2          ; Get bit duration
tx_delay:   nop                         ; 4 cycle loop
            sub     #8, ARG2            ;
            jc      tx_delay            ;
            subc    ARG2, PC            ; 0 to 3 cycle delay
            nop                         ; 3
            nop                         ; 2
            nop                         ; 1
            xor     ARG1, R11           ;>calculate parity (1 cycle)
            cmp     R10, ARG1           ;>next bit to send is parity? (1 cycle)
            jne     bit_out_w           ;>no? output bit (2 cycles)
            bit     #1, R11             ;>parity bit set? (1 cycle)
            addc    #0, ARG1            ;>add parity to data byte (1 cycle)
            jmp     bit_out             ;>skip the compensating delay (2 cycles)
bit_out_w:  nop                         ;>
            nop                         ;>
            nop                         ;>
            nop                         ;>
bit_out:    rra     ARG1                ; Get bit to tx, test for zero
            jc      bit_high            ; If high...
bit_low:    bic.b   ARG4, &P1OUT        ; Send zero bit
            jmp     tx_bit              ; Next bit...
bit_high:   bis.b   ARG4, &P1OUT        ; Send one bit
            jnz     tx_bit              ; If tx data is not zero, then there are more bits to send...
                                        ;
            pop     R11                 ;>Restore R11
            pop     R10                 ;>Restore R10
            ret                         ; Return when all bits sent         

You can use it for either parity (even or odd).

 

I'll be writing the receive part now, do you want to actually check the parity, or just assume everything is ok?

This is the receive routine, I did not verify the parity (since you cannot return any error anyway). Instead, the parity bit is stored in the higher byte of the result. Would you change

void main(void)
{
    char c;
...

to

void main(void)
{
    short c;
...

you could check bit 0x0100 for the parity information and process it in C code if you wish. The parity bit is passed as is, so it's not status information on the validity of the data.

serial_getc:                            ; - Get a char
            mov     &bit_dur, ARG3      ; Bit duration
            mov     &in_bit_mask, ARG2  ; Input bitmask
            mov     #0x03FF, ARG1       ;>10 bits - 8 data + parity + stop
                                        ;
rx_start:                               ; Wait for start bit
            mov.b   &P1IN, ARG4         ; Get serial input          
            and     ARG2, ARG4          ; Mask and test bit
            jc      rx_start            ; Wait for low...
                                        ;
            mov     &half_dur, ARG2     ; Wait for 1/2 bit time
                                        ;
rx_delay:   nop                         ; Bit delay
            sub     #8, ARG2            ;
            jc      rx_delay            ;
            subc    ARG2, PC            ; 0 to 3 cycle delay
            nop                         ; 3
            nop                         ; 2
            nop                         ; 1
                                        ;
            mov.b   &P1IN, ARG4         ; Get serial input          
            and     &in_bit_mask, ARG4  ;
            rrc     ARG1                ; Shift in a bit
                                        ;
            mov     ARG3, ARG2          ; Setup bit timer
            jc      rx_delay            ; Next bit...
                                        ;
            rla     ARG1                ; Move stop bit to carry
            rla     ARG1                ;>Move parity bit to carry
            addc    #0, ARG1            ;>Set LSB to parity bit
            swpb    ARG1                ;>Move rx byte to lower byte, start or parity bit in msb
; Any real parity checking could be done here, or on-the-fly during the delay cycles
            ret                         ; Return with rx char and start bit in ARG1, stop bit in carry
                                        ;       
Link to post
Share on other sites
  • 11 months later...

Instead of breakpoint debugging I am using fast logging (http://forum.43oh.com/topic/2646-software-debugging-without-hardware-tools). On MSP430F5xx USB devices by adding one extra CDC (http://forum.43oh.com/topic/2972-sbw-msp430f550x-based-programmer/?p=25923) and on MSP430x2xx devices by hardware UART (http://forum.43oh.com/topic/2972-sbw-msp430f550x-based-programmer/?p=34398).

 

Right now, working on project based on entry level MSP430x2xx devices without hardware UART, and don't like at all timer based soft UART. Code presented here is universal, with LP 9600 bps as target rate. It is too slow for debugging, and I'm not using LP locked to 9600 bps. Just removed from original code, part related to delay, and it is (almost) perfectly matched to 115200 bps with MSP430x2xx running on 1 MHz. And total code size (for UART TX) is close to nothing.

#define DBGPDIR	P1DIR
#define DBGPOUT	P1OUT
#define DBGPBIT	BIT2

;--------------------------------------------------------------------
; DbgInit
;--------------------------------------------------------------------

DbgInit		bis.b #DBGPBIT, &DBGPDIR	; TX pin
		bis.b #DBGPBIT, &DBGPOUT	; TX High
		ret

;--------------------------------------------------------------------
; DbgUartTX R15 -> Uart
;--------------------------------------------------------------------

DbgUartTX	; Byte R15 -> Uart
		; 1000000 / 115200 = 8.681  9 CLK

		push.w R14
		push.w R15
		mov.w #DBGPBIT, R14
		and.w #000FFh, R15
		add.w #00300h, R15		; Stop bit(s)
		jmp DbgUartTXLow		; Send start bit...
DbgUartTXBit	rra.w R15			; 1 Get bit to tx, test for zero
		jc DbgUartTXHigh		; 2 If high...
DbgUartTXLow	bic.b R14, &DBGPOUT		; 4 Send zero bit
		jmp DbgUartTXBit		; 2 Next bit...
DbgUartTXHigh	bis.b R14, &DBGPOUT		; 4 Send one bit
		jnz DbgUartTXBit		; 2 If tx data is not zero, then more bits to send...
		pop.w R15
		pop.w R14
		ret
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...