Jump to content
43oh

oPossum

Members
  • Content Count

    925
  • Joined

  • Last visited

  • Days Won

    103

Posts posted by oPossum

  1. This can be made a bit more efficient by using the C standard library function div(). It will provide the quotient (/) and remainder (%) with a single math operation rather than two.

     

    For example, for two digits...

    div_t d = div(n, 10);
    text_buffer[0] = '0' + d.quot;  // tens
    text_buffer[1] = '0' + d.rem;   // ones
    
    For three digits...

    div_t d = div(n, 100);
    text_buffer[0] = '0' + d.quot;  // hundreds
    d = div(d.rem, 10);
    text_buffer[1] = '0' + d.quot;  // tens
    text_buffer[2] = '0' + d.rem;   // ones
    
    For four digits...

    div_t d = div(n, 1000);
    text_buffer[0] = '0' + d.quot;  // thousands
    d = div(d.rem, 100);
    text_buffer[1] = '0' + d.quot;  // hundreds
    d = div(d,rem, 10);
    text_buffer[2] = '0' + d.quot;  // tens
    text_buffer[3] = '0' + d.rem;   // ones
    
    etc...
  2. my problem is that the reading are'nt stable, every few cycles they "jump" to a very low (low number , as in small distance) and gradually climb back to the right value.

    i'm clueless why.

    ideas?

    Turning off interrupts does not stop the timer output from toggling. So you are enabling the next sensor at some time within the timer period. The exact time will keep shifting in and out of sync.

     

    The fix is to change the sensor mux in the timer overflow interrupt when the output is low.

     

    Give this a try...

     

    #include <msp430.h>
    #include <stdint.h>
    
    static const unsigned long smclk_freq = 16000000UL; // SMCLK frequency in hertz
    static const unsigned long bps = 9600UL;            // Async serial bit rate
    
    static uint32_t upd = 0;                            // Ultrasonic Pulse Duration (500 ns resolution)
    static unsigned cur_sen = 7;                        // Current sensor being used
                                                        //
    #pragma vector = TIMER1_A1_VECTOR                   // Timer 1A vectored interrupt
    __interrupt void timer1_a1_isr(void)                //
    {                                                   //
        static uint32_t rise = 0;                       // Pulse rise time
        static uint32_t fall;                           // Pulse fall time
                                                        //
        switch(TA1IV) {                                 // TA0IV must be read to reset interrupt
            case 0x02:                                  // 0x02 == CCR1 (capture/compare)
                if(TA1CCTL1 & CCI) {                    // - Rising edge
                    rise = TA1CCR1;                     // Save rise time
                    fall = 0;                           // Reset fall time
                } else {                                // - Falling edge
                    if(rise) {                          // Make sure rising edge has occurred
                                                        // Handle possible pending overflow interrupt
                        if((TA1CTL & TAIFG) && (TA1R < 0x1000))
                            fall += TA1CCR0;            //
                        fall += TA1CCR1;                // Get fall time, add to overflow time
                        if(!upd) upd = fall - rise;     // Update time if mainline code is ready for it
                        rise = 0;                       // Clear rise time to ensure next rising edge is used
                    }                                   //
                }                                       //
                break;                                  //
            case 0x0A:                                  // 0x0A == TAIFG (overflow)
                fall += TA1CCR0;                        // Update overflow time
                if(P2IN & BIT0) {                       // If output is high
                    ++cur_sen;                          //  increment current sensor
                    cur_sen &= 7;                       //
                } else {                                // If output is low
                                                        //  set mux to next sensor
                    P1OUT = (P1OUT & ~(BIT3 | BIT4 | BIT5)) | (((cur_sen + 1) << 3) & (BIT3 | BIT4 | BIT5));
                }                                       //
                break;                                  //
        }                                               //
    }                                                   //
    
    void putc(const unsigned c)                         // Output single char to serial
    {                                                   //
        while(!(IFG2 & UCA0TXIFG));                     // Wait for ready (not busy)
        IFG2 &= ~UCA0TXIFG;                             // Reset busy flag
        UCA0TXBUF = c;                                  // Tx char
    }                                                   //
                                                        //
    void puts(const char *s) { while(*s) putc(*s++); }  // Output string to serial
                                                        //
    void print_u32(uint32_t n, const unsigned dp)       // Print 32 bit unsigned with optional decimal place
                                                        // 6 decimal digits (0 -> 999999)
    {                                                   //
        unsigned c;                                     //
        c = '0'; while(n >= 100000UL) n -= 100000UL, ++c; putc(c);
        if(dp == 5) putc('.');                          //
        c = '0'; while(n >= 10000) n -= 10000, ++c; putc(c);
        if(dp == 4) putc('.');                          //
        c = '0'; while(n >= 1000) n -= 1000, ++c; putc(c);
        if(dp == 3) putc('.');                          //
        c = '0'; while(n >= 100) n -= 100, ++c; putc(c);
        if(dp == 2) putc('.');                          //
        c = '0'; while(n >= 10) n -= 10, ++c; putc(c);  //
        if(dp == 1) putc('.');                          //
        c = '0'; while(n) --n, ++c; putc(c);            //
    }                                                   //
    
    void main(void)                                     //
    {                                                   //
        WDTCTL = WDTPW | WDTHOLD;                       // Disable watchdog reset
        DCOCTL = 0;                                     // Run at 16 MHz
        BCSCTL1 = CALBC1_16MHZ;                         //
        DCOCTL = CALDCO_16MHZ;                          //
                                                        //
        P1DIR = BIT2 | BIT3 | BIT4 | BIT5;              // Setup GPIO
        P1SEL  = BIT1 | BIT2;                           // UART
        P1SEL2 = BIT1 | BIT2;                           //
                                                        //
        P2OUT = 0;                                      //
        P2DIR = BIT0;                                   // Timer 1 capture/compare IO
        P2SEL = BIT0 | BIT1;                            //
        P2SEL2 = 0;                                     //
                                                        //
        const unsigned long brd = (smclk_freq + (bps >> 1)) / bps; // Bit rate divisor
        UCA0CTL1 = UCSWRST;                             // Hold USCI in reset to allow configuration
        UCA0CTL0 = 0;                                   // No parity, LSB first, 8 bits, one stop bit, UART (async)
        UCA0BR1 = (brd >> 12) & 0xFF;                   // High byte of whole divisor
        UCA0BR0 = (brd >> 4) & 0xFF;                    // Low byte of whole divisor
        UCA0MCTL = ((brd << 4) & 0xF0) | UCOS16;        // Fractional divisor, oversampling mode
        UCA0CTL1 = UCSSEL_2;                            // Use SMCLK for bit rate generator, release reset
                                                        //
                                                        // Timer 1 compare 0
        TA1CCR0 = 64000;                                // 64 ms
        TA1CCTL0 = OUTMOD_4;                            // Toggle mode
                                                        // Timer 1 capture 1
        TA1CCTL1 = CM_3 | SCS | CAP | CCIE;             // Rising and falling, sync, capture, interrupt
                                                        // Timer 1 config
        TA1CTL = TASSEL_2 | ID_3 | MC_1 | TAIE;         // SMCLK, /8, count up, overflow interrupt
                                                        //
        _enable_interrupts();                           // Enable interrupts
                                                        //
        for(; {                                       // For-ever
            if(upd) {                                   // Check for pulse duration measuement available
                                                        // Calculate inches
                const uint32_t in = ((upd * 5534) + (upd >> 1)) >> 14;
                                                        // Calculate mm
                const uint32_t mm = (upd * 5623) >> 16;
                                                        //
                putc('0' + cur_sen);                    // Print current sensor #
                putc(' ');                              //
                print_u32(in, 2);                       // Print inches
                puts(" in  ");                          //
                print_u32(mm, 0);                       // Print mm
                puts(" mm");                            //
                puts("\r\n");                           //
                upd = 0;                                // Ready for next
            }                                           //
        }                                               //
    }                                                   //
    
  3. The usual way to generate PWM with the MSP430 is to set the timer to up mode, CCR0 to the PWM period, and then use CCR1 and up in reset/set mode to set the PWM pulse width. This provides a precise hardware driven PWM with minimal CPU overhead. The only ISR required is the CCR0 interrupt so new PWM values can be updated without creating glitches.

     

    It is possible to use CCR0 to do PWM in addition to CCR1 and up by using continuous mode and output compare toggle mode. This requires an ISR for each CCR used. It is glitch free and allows different PWM periods for each output if desired.

     

    The basic ISR is simple:

    __interrupt void isr_ccr0(void)                 // - ISR for CCR0
    {                                               //
        static unsigned s = 0;                      // State
        static unsigned d;                          // On duration
                                                    //
        TA1CCR0 += (s++ & 1) ? (pwm_period[0] - d) : (d = pwm_on[0]);
    }                                               //
    
    Each time the ISR occurs, the output time is advanced in to the future. The lsb of a state variable toggles between adding the on duration or off duration to the previous time. When the lsb is 0, then the on duration is used, and also saved for later. When the lsb is 1, then the off duration is calculated from the saved on duration and the PWM period. Toggling of the output pin is automatic because the CCR output mode is set to toggle.

     

     

    Complete code for 3 PWM outputs using CCR0, CCR1, and CCR2 of Timer 1

     

    #include <msp430.h>
    
    static unsigned pwm_on[3];                      // PWM on duration
    static unsigned pwm_period[3];                  // Total PWM period - on duration + off duration
                                                    //
    #pragma vector = TIMER1_A0_VECTOR               // - ISR for CCR0
    __interrupt void isr_ccr0(void)                 //
    {                                               //
        static unsigned s = 0;                      // State
        static unsigned d;                          // On duration
                                                    //
        TA1CCR0 += (s++ & 1) ? (pwm_period[0] - d) : (d = pwm_on[0]);
    }                                               //
                                                    //
    #pragma vector = TIMER1_A1_VECTOR               // - ISR for CCR1, CCR2
    __interrupt void isr_ccr12(void)                //
    {                                               //
        static unsigned s1 = 0;                     // State
        static unsigned d1;                         // On duration
        static unsigned s2 = 0;                     //
        static unsigned d2;                         //
                                                    //
        switch(TA1IV) {                             //
            case 0x02:                              // - CCR1
                TA1CCR1 += (s1++ & 1) ? (pwm_period[1] - d1) : (d1 = pwm_on[1]);
                break;                              //
            case 0x04:                              // - CCR2
                TA1CCR2 += (s2++ & 1) ? (pwm_period[2] - d2) : (d2 = pwm_on[2]);
                break;                              //
        }                                           //
    }                                               //
                                                    //
    static void pwm_init(void)                      //
    {                                               //
        P2DIR |= (BIT0 | BIT2 | BIT4);              // PWM outputs on P2.0, P2.2, P2.4
        P2SEL |= (BIT0 | BIT2 | BIT4);              // Enable timer compare outputs
        P2SEL2 &= ~(BIT0 | BIT2 | BIT4);            //
                                                    //
        TA1CTL = TASSEL_2 | ID_3 | MC_2;            // Setup timer 1 for SMCLK / 8, continuous mode
                                                    //
        TA1CCTL0 = TA1CCTL1 = TA1CCTL2 = OUTMOD_4 | CCIE; // Set timer output to toggle mode, enable interrupt
        TA1CCR0 = TA1CCR1 = TA1CCR2 = TA1R + 1000;  // Set initial interrupt time
    }                                               //
                                                    //
    int main(void)                                  //
    {                                               //
        WDTCTL = WDTPW | WDTHOLD;                   //
                                                    //
        DCOCTL = 0;                                 // Run DCO at 8 MHz
        BCSCTL1 = CALBC1_8MHZ;                      //
        DCOCTL  = CALDCO_8MHZ;                      //
                                                    //
        pwm_init();                                 // Initialize PWM
                                                    //
                                                    // Setup PWM period
        pwm_period[0] = pwm_period[1] = pwm_period[2] = 20000;
        pwm_on[0] = 1000;                           // Setup servo times
        pwm_on[1] = 1500;                           //
        pwm_on[2] = 2000;                           //
                                                    //
        _enable_interrupts();                       // Enable all interrupts
                                                    //
        for(; {                                   //
        }                                           //
                                                    //
        return 0;                                   //
    }                                               //
    
  4. How practical is it to realize this on IR led / detector for an 8 channel servo control?

     

    I think that would work. Range could be up to about 20 feet if a modulated (~38 kHz) carrier is used. It should work about as well as a TV remote - not perfect but good enough for some applications.

  5. First thing. Your coding style is definitely ASM!  Did you start with the PIC or the x86?

     

     

    Started with the RCA CDP1802 in the early 80's, then the Motorola 6809 and Zilog Z80. After that is quite a blur - learned so many over the next 10 years. Started using PIC in the late 80's. Latest chip for me is the Parallax Propeller - it is very nice but not easy to work with.

     

    What other awesome projects are you working on?

     

     

    Several things related to building a quadcopter. There will be future postings on conversion of old PPM RC radios to PCM. This post was just the first step. Next up will be TMS (transition minimized signaling) using a 8b/11b code I have developed.

     

    Also working on a MIDI driven laser spirograph. Have to build the galvos next.

     

  6. PPM (pulse position modulation) is a common protocol used with radio control. It used to be the over that air protocol, but has been mostly replaced with PCM (pulse code modulation). It lives on in the form of trainer ports, radio to tx module link, and even some modern PCM receivers can output PPM (typically on the battery connector). It is also a handy way to drive up to 8 servos using a single pin of the microcontroller (and a CD4017 decoder). PPM is sort of a concatenation of several (often 8) RC servo signals in succession. The time between narrow (a few hundred microseconds) pulses determines with width of each successive servo pulse. A duration of more than about 4000 microseconds designates the end of a frame. The typical total frame duration is 20,000 microseconds (20 ms).

     

    post-229-0-40047400-1404173046_thumb.jpg

     

    The PPM transmit code uses a single timer capture/compare unit in toggle mode. The initial state is set with OUTMOD_0 to allow for active low or active high output. The elegantly simple MSP430 timer makes this code very compact and efficient.

     

    static void init_ppm_tx(unsigned pol)           // - Initialize PPM transmission
    {                                               // pol = 0: idle low, pulse high
                                                    // pol = 1: idle high, pulse low
                                                    //
        P2DIR |= BIT0;                              // PPM output on P2.0
        P2SEL |= BIT0;                              // Enable timer compare output
        P2SEL2 &= ~BIT0;                            //
                                                    //
        TA1CCTL0 = OUTMOD_0 | (pol ? OUT : 0);      // Set initial state of output (polarity)
        TA1CCTL0 = OUTMOD_4 | CCIE;                 // Set timer output to toggle mode, enable interrupt
        TA1CCR0 = TA1R + 1000;                      // Set initial interrupt time
    }                                               //
                                                    //
    static unsigned st[8];                          // Servo transmit times
                                                    //
    static const unsigned pulse_duration = 200;     // Duration of on time of each pulse
    static const unsigned frame_duration = 20000;   // Total duration of a complete frame
                                                    //
    #pragma vector = TIMER1_A0_VECTOR               // - ISR for PPM transmission
    __interrupt void isr_ccr0(void)                 //
    {                                               //
        static unsigned state = 0;                  // State / pulse index
        static unsigned td = 0;                     // Total duration of channel pulses
                                                    //
        if(state < (sizeof(st) / sizeof(st[0])) * 2 + 1) { // Check if not done with all times
            if(state & 1) {                         // Alternate between rising & falling edges
                                                    // Setup the time until the next rising edge
                const unsigned t = st[state >> 1];  // Get time from array
                TA1CCR0 += (t - pulse_duration);    // Add to falling edge time, compensate for pulse width
                td += t;                            // Update total frame duration
            } else {                                //
                TA1CCR0 += pulse_duration;          // Add pulse duration to rising edge time
            }                                       //
            ++state;                                // Increment state
        } else {                                    // Final pulse in frame (off time only)
            TA1CCR0 += (frame_duration - pulse_duration - td); // Set rising edge time to make desired frame duration
            td = 0;                                 // Reset total frame time
            state = 0;                              // Reset state
        }                                           //
    }                                               //
                                                    //
    
    The PPM decoder is a bit more complicated. It checks for vaild pulse widths and ignores anything out of spec. Two capture/compare units are used. The first handles measuring the time between edges, and the second provides a timeout that detects an idle line (to prevent timer wraparound from creating alias pulses). Either the rising of falling edge can be used.

     

    static void init_ppm_rx(unsigned edge)          // - Initialize PPM reception
    {                                               // edge = 0: capture on rising edge
                                                    // edge = 1: capture on falling edge
                                                    //
        P2DIR &= ~BIT1;                             // PPM input on P2.1
        P2SEL |= BIT1;                              // Enable time capture input
        P2SEL2 &= ~BIT1;                            //
                                                    //
        TA1CCTL1 = (edge ? CM_2 : CM_1) | CCIS_0 | CAP | CCIE; // CCR1 capture mode, enable interrupt
        TA1CCTL2 = CCIE;                            // CCR2 enable interrupt
    }                                               //
                                                    //
    static unsigned sr[8];                          // Servo receive times
    static unsigned rx_frame = 0;                   // Incremented after every received frame
                                                    //
    static const unsigned min_pw = 1500 - 1100;     // 400 us minimum
    static const unsigned max_pw = 1500 + 1100;     // 2600 us maximum
    static const unsigned min_reset = 4000;         // minimum time between frames
    static const unsigned rx_timeout = 24000;       // maximum time between pulses
                                                    //
    #pragma vector = TIMER1_A1_VECTOR               // - ISR for PPM reception
    __interrupt void isr_ccr12(void)                //
    {                                               //
        static unsigned pt, et;                     // Previous time, elapsed time
        static unsigned state;                      // Received pulse index / state
        static unsigned pd[8];                      // Received pulse durations - the size of this array must
                                                    //  match what the transmitter is sending
                                                    //
        switch(TA1IV) {                             //
            case 0x02:                              // - CCR1
                et = TA1CCR1 - pt;                  // Calculate elapsed time since last edge
                pt = TA1CCR1;                       // Save current edge time
                if(et > min_reset) {                // Check for a pulse that is too long to be a channel
                                                    // Check if all pulses received, and no more
                    if(state == sizeof(pd) / sizeof(pd[0])) {
                        memcpy(sr, pd, sizeof(pd)); // Copy to foreground array
                        ++rx_frame;                 // Increment frame count
                    }                               //
                    state = 0;                      // Begin next frame
                } else if(et < min_pw || et > max_pw) { // Check if pulse is out of range
                    state = 0x80;                   // Go to idle state
                } else {                            // Save pulse if room in array
                    if(state < sizeof(pd) / sizeof(pd[0]))
                        pd[state] = et;             //
                    if(state < 0x40) ++state;       // Next state - limit max value in case of noisy input
                }                                   //
                TA1CCR2 = pt + rx_timeout;          // Reset timeout
                break;                              //
            case 0x04:                              // - CCR2
                state = 0x81;                       // Go to timeout state
                break;                              //
        }                                           //
    }                                               //
                                                    //
    
    Here is a simple test program that prints the pulse times. The times are represented as the deviation from 1500 microseconds. That is the typical center time for a RC servo. Each field is a fixed width with a sign followed by 3 digits. Jumper P2.0 to P2.1 to feed the PPM output to the PPM input.

     

    post-229-0-60367000-1404173645_thumb.png

     

    #include <msp430.h> 
    #include <stdlib.h>
    
    static void putc(char c)                        // -  Put char to serial
    {                                               //
        while(!(IFG2 & UCA0TXIFG));                 //
        UCA0TXBUF = c;                              //
    }                                               //
                                                    //
    static void put_pw(unsigned pw)                 // - Print pulse width to serial
    {                                               // Sign and three digits
        unsigned n;                                 // Deviation from 1500 us
        if(pw < 1500) {                             // If less than 1500
            n = 1500 - pw;                          // Calculate deviation
            putc('-');                              // Print sign
        } else {                                    // Equal to or more than 1500
            n = pw - 1500;                          // Calculate deviation
            putc((pw == 1500) ? ' ' : '+');         // Print sign
        }                                           //
        div_t d = div(n, 100);                      //
        putc(d.quot + '0');                         // First digit - hundreds
        d = div(d.rem, 10);                         //
        putc(d.quot + '0');                         // Second digit - tens
        putc(d.rem + '0');                          // Third digit - ones
    }                                               //
                                                    //
    static const unsigned long smclk_freq = 8000000UL; // SMCLK frequency in hertz
    static const unsigned long bps = 9600UL;        // Async serial bit rate
                                                    //
    int main(void)                                  //
    {                                               //
        const unsigned long brd = (smclk_freq + (bps >> 1)) / bps;  // Bit rate divisor
                                                    //
        WDTCTL = WDTPW | WDTHOLD;                   //
                                                    //
        DCOCTL = 0;                                 // Run DCO at 8 MHz
        BCSCTL1 = CALBC1_8MHZ;                      //
        DCOCTL  = CALDCO_8MHZ;                      //
                                                    //
        P1DIR = 0;                                  //
        P1SEL = BIT1 | BIT2;                        // Enable UART pins
        P1SEL2 = BIT1 | BIT2;                       //
                                                    //
        P2DIR = 0;                                  //
        P2SEL = 0;                                  //
        P2SEL2 = 0;                                 //
                                                    //
        TA1CTL = TASSEL_2 | ID_3 | MC_2;            // Setup timer 1 for SMCLK / 8, continuous mode
                                                    //
        init_ppm_rx(0);                             // Initialize PPM receive
                                                    //
        init_ppm_tx(0);                             // Initialize PPM transmit
                                                    //
                                                    // Initialize UART
        UCA0CTL1 = UCSWRST;                         // Hold USCI in reset to allow configuration
        UCA0CTL0 = 0;                               // No parity, LSB first, 8 bits, one stop bit, UART (async)
        UCA0BR1 = (brd >> 12) & 0xFF;               // High byte of whole divisor
        UCA0BR0 = (brd >> 4) & 0xFF;                // Low byte of whole divisor
        UCA0MCTL = ((brd << 4) & 0xF0) | UCOS16;    // Fractional divisor, oversampling mode
        UCA0CTL1 = UCSSEL_2;                        // Use SMCLK for bit rate generator, release reset
                                                    //
                                                    //
        st[0] = 1000;                               // Setup servo transmit times
        st[1] = 1200;                               //
        st[2] = 1400;                               //
        st[3] = 1500;                               //
        st[4] = 1600;                               //
        st[5] = 1800;                               //
        st[6] = 2000;                               //
        st[7] = 1520;                               //
                                                    //
        _enable_interrupts();                       // Enable all interrupts
                                                    //
        for(; {                                   //
            unsigned n;                             //
                                                    // Print received pulse times to serial
            for(n = 0; n < sizeof(sr) / sizeof(sr[0]); ++n)
                put_pw(sr[n]);                      //
            putc('\r'); putc('\n');                 //
        }                                           //
                                                    //
        return 0;                                   //
    }                                               //
    
    A bargraph can be displayed with some custom software.

     

    post-229-0-82057000-1404173746_thumb.png

    servo_test.cpp

     

  7. This code was written when the MSP430 Lauchpad shipped with chips that had 128 bytes of RAM, 2k of flash, and no hardware serial port. So it was important to keep code very compact and a software UART was needed.

     

    Your F6137 chip has many hardware UARTS - so it would be wise to use them rather than software UART. Just write a putc() function that sends a char to the UART and you don't need the assembly code.

  8. Thanks, so if I simply add a low value resistor before the TVS I should be ok? I figure this will protect the current sense IC also so I wouldn't need anything like in the current sense datasheet I linked a snippet of? 

    Would this be the correct thinking?

    The pic you posted has resistors (Rprotect) ahead of the TVS. That is the source resistance I was referring to. Sorry that was not clear. Use the circuit in that pic.

×
×
  • Create New...