вторник, 15 июля 2014 г.

Plotting live data from MSP430 ADC in Python

I've finally got something for post.

Last month I've been playing with the TI MSP430 Launchpad and when I work with ADC it lacks of visualization. Since Launchpad have UART-USB interface, I decided to plot incoming data.

I'm using MSP430G2553, and all code was written for this controller.

Firmware

Firmware of controller is pretty straightforward in large scale: it just sends value from ADC to the UART continously - with one note: before start to send anything, we need to make a "handshake" - receive start symbol from computer. So, high-level algorithm will be like this:
1) Initialize UART[1] (9600 baud) and ADC[2]
2) Wait for start signal ("handshake")
3) In forever loop send temperature to UART

ADC initialization to read temperature (10 channel):
void ADC_init(void) {
    ADC10CTL0 = SREF_1 + REFON + ADC10ON + ADC10SHT_3;
    ADC10CTL1 = INCH_10 + ADC10DIV_3;
}


int getTemperatureCelsius()
{
    int t = 0;
    __delay_cycles(1000); // Not neccessary.
    ADC10CTL0 |= ENC + ADC10SC;
    while (ADC10CTL1 & BUSY);
    t = ADC10MEM;
    ADC10CTL0 &=~ ENC;
    return(int) ((t * 27069L - 18169625L) >> 16);  // magic conversion to Celsius
}


Handshake:
  // UART Handshake...
  unsigned char c;
  while ((c = uart_getc()) != '1');
  uart_puts((char *)"\nOK\n");


We're waiting for '1' and sending "OK" when we receive it.

After that, program starts to send temperature indefinitely:


  while(1) {
    uart_printf("%i\n", getTemperatureCelsius());
    P1OUT ^= 0x1;
  }


uart_printf converts integer value into string and send over UART [3].

The source code of firmware in the bottom of this post.

Plotting Application

I love matplotlib in python, it's great library to plot everything.To read data from UART, I used pySerial library. That's all we need.

When we connect launchpad to computer, device /dev/ttyACM0 is created. It's serial port which we need to use.

Application consists of two threads:
  1. Serial port processing
  2. Continous plot updating

Serial port processing

Let's define global variable data = deque(0 for _ in range(5000)), it will contain data to plot and dataP = deque(0 for _ in range(5000)), it will contain approximated values.

In the serial port thread, we need to open connection:
ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
then, make a "handshake":
    ok = b''
    while ok.strip() != b'OK':
        ser.write(b"1")
        ok = ser.readline()
    print("Handshake OK!\n")

As you see, we're waiting the "OK" in response to "1". After "handshake", we can start reading data:

    while True:
        try:
            val = int(ser.readline().strip())
            addValue(val)
        except ValueError:
            pass

UART is not very stable, so sometimes you can receive distorted data. That's why I eat exceptions.

addValue here is function that processes data and puts it to data variable:

avg = 0
def addValue(val):
    global avg

    data.append(val)
    data.popleft()
   
    avg = avg + 0.1 * (val - avg)
    dataP.append(avg)
    dataP.popleft()


Also it calculates weighted moving average:

Continous plot updating 

First, let's create figure with two plots:

    fig, (p1, p2) = plt.subplots(2, 1)
    plot_data, = p1.plot(data, animated=True)
    plot_processed, = p2.plot(data, animated=True)
    p1.set_ylim(0, 100)  # y limits
    p2.set_ylim(0, 100)


To draw animated plot, we need to define function that will update data:

    def animate(i):
        plot_data.set_ydata(data)
        plot_data.set_xdata(range(len(data)))
        plot_processed.set_ydata(dataP)
        plot_processed.set_xdata(range(len(dataP)))
        return [plot_data, plot_processed]

    ani = animation.FuncAnimation(fig, animate, range(10000),
                                  interval=50, blit=True)

And show the plot window:
    plt.show()

Here's the result of program's work:

On the first plot it's raw data received through serial port, on the second it's average.

Source codes


Desktop live plotting application:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import serial
import threading
from collections import deque

data = deque(0 for _ in range(5000))
dataP = deque(0 for _ in range(5000))
avg = 0

def addValue(val):
    global avg

    data.append(val)
    data.popleft()
    
    avg = avg + 0.1 * (val - avg)
    dataP.append(avg)
    dataP.popleft()


def msp430():
    print("Connecting...")
    ser = serial.Serial('/dev/ttyACM0', 9600, timeout=1)
    print("Connected!")

    # Handshake...
    ok = b''
    while ok.strip() != b'OK':
        ser.write(b"1")
        ok = ser.readline()
        print(ok.strip())
    print("Handshake OK!\n")

    while True:
        try:
            val = int(ser.readline().strip())
            addValue(val)
            print(val)
        except ValueError:
            pass


if __name__ == "__main__":
    threading.Thread(target=msp430).start()

    fig, (p1, p2) = plt.subplots(2, 1)
    plot_data, = p1.plot(data, animated=True)
    plot_processed, = p2.plot(data, animated=True)
    p1.set_ylim(0, 100)
    p2.set_ylim(0, 100)
    def animate(i):
        plot_data.set_ydata(data)
        plot_data.set_xdata(range(len(data)))
        plot_processed.set_ydata(dataP)
        plot_processed.set_xdata(range(len(dataP)))
        return [plot_data, plot_processed]

    ani = animation.FuncAnimation(fig, animate, range(10000), 
                                  interval=50, blit=True)
    plt.show()


MSP430 full firmware:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
/*
NOTICE
Used code or got an idea from:
    UART: Stefan Wendler - http://gpio.kaltpost.de/?page_id=972
    ADC: http://indiantinker.wordpress.com/2012/12/13/tutorial-using-the-internal-temperature-sensor-on-a-msp430/
    printf: http://forum.43oh.com/topic/1289-tiny-printf-c-version/
*/

#include <msp430g2553.h>

// =========== HEADERS ===============
// UART
void uart_init(void);
void uart_set_rx_isr_ptr(void (*isr_ptr)(unsigned char c));
unsigned char uart_getc();
void uart_putc(unsigned char c);
void uart_puts(const char *str);
void uart_printf(char *, ...);
// ADC
void ADC_init(void);
// =========== /HEADERS ===============


// Trigger on received character
void uart_rx_isr(unsigned char c) {
  P1OUT ^= 0x40;
}

int main(void)
{
  WDTCTL = WDTPW + WDTHOLD;
  
  BCSCTL1 = CALBC1_8MHZ; //Set DCO to 8Mhz
  DCOCTL = CALDCO_8MHZ; //Set DCO to 8Mhz
  
  P1DIR = 0xff;
  P1OUT = 0x1;
  ADC_init();
  uart_init();
  uart_set_rx_isr_ptr(uart_rx_isr);

  __bis_SR_register(GIE); // global interrupt enable

  // UART Handshake...
  unsigned char c;
  while ((c = uart_getc()) != '1');
  uart_puts((char *)"\nOK\n");

  ADC10CTL0 |= ADC10SC;
  while(1) {
    uart_printf("%i\n", getTemperatureCelsius());
    P1OUT ^= 0x1;
  } 
}

// ========================================================
// ADC configured to read temperature
void ADC_init(void) {
    ADC10CTL0 = SREF_1 + REFON + ADC10ON + ADC10SHT_3;
    ADC10CTL1 = INCH_10 + ADC10DIV_3;
}

int getTemperatureCelsius()
{
    int t = 0;
    __delay_cycles(1000);
    ADC10CTL0 |= ENC + ADC10SC;
    while (ADC10CTL1 & BUSY);
    t = ADC10MEM;
    ADC10CTL0 &=~ ENC;
    return(int) ((t * 27069L - 18169625L) >> 16);
}


// ========================================================
// UART
#include <legacymsp430.h>

#define RXD BIT1
#define TXD BIT2

/**
* Callback handler for receive
*/
void (*uart_rx_isr_ptr)(unsigned char c);

void uart_init(void)
{
  uart_set_rx_isr_ptr(0L);

  P1SEL = RXD + TXD;
  P1SEL2 = RXD + TXD;

  UCA0CTL1 |= UCSSEL_2; //SMCLK
  //8,000,000Hz, 9600Baud, UCBRx=52, UCBRSx=0, UCBRFx=1
  UCA0BR0 = 52; //8MHz, OSC16, 9600
  UCA0BR1 = 0; //((8MHz/9600)/16) = 52.08333
  UCA0MCTL = 0x10|UCOS16; //UCBRFx=1,UCBRSx=0, UCOS16=1
  UCA0CTL1 &= ~UCSWRST; //USCI state machine
  IE2 |= UCA0RXIE; // Enable USCI_A0 RX interrupt
}

void uart_set_rx_isr_ptr(void (*isr_ptr)(unsigned char c))
{
  uart_rx_isr_ptr = isr_ptr;  
}

unsigned char uart_getc()
{
  while (!(IFG2&UCA0RXIFG)); // USCI_A0 RX buffer ready?
  return UCA0RXBUF;
}

void uart_putc(unsigned char c)
{
  while (!(IFG2&UCA0TXIFG)); // USCI_A0 TX buffer ready?
     UCA0TXBUF = c; // TX
}

void uart_puts(const char *str)
{
  while(*str) uart_putc(*str++);
}

interrupt(USCIAB0RX_VECTOR) USCI0RX_ISR(void)
{
  if(uart_rx_isr_ptr != 0L) {
   (uart_rx_isr_ptr)(UCA0RXBUF);
  }
}



// ========================================================
// UART PRINTF
#include "stdarg.h"

static const unsigned long dv[] = {
//  4294967296      // 32 bit unsigned max
    1000000000,     // +0
     100000000,     // +1
      10000000,     // +2
       1000000,     // +3
        100000,     // +4
//       65535      // 16 bit unsigned max     
         10000,     // +5
          1000,     // +6
           100,     // +7
            10,     // +8
             1,     // +9
};

static void xtoa(unsigned long x, const unsigned long *dp)
{
    char c;
    unsigned long d;
    if(x) {
        while(x < *dp) ++dp;
        do {
            d = *dp++;
            c = '0';
            while(x >= d) ++c, x -= d;
            uart_putc(c);
        } while(!(d & 1));
    } else
        uart_putc('0');
}

static void puth(unsigned n)
{
    static const char hex[16] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
    uart_putc(hex[n & 15]);
}
 
void uart_printf(char *format, ...)
{
    char c;
    int i;
    long n;
    
    va_list a;
    va_start(a, format);
    while(c = *format++) {
        if(c == '%') {
            switch(c = *format++) {
                case 's':                       // String
                    uart_puts(va_arg(a, char*));
                    break;
                case 'c':                       // Char
                    uart_putc(va_arg(a, char));
                    break;
                case 'i':                       // 16 bit Integer
                case 'u':                       // 16 bit Unsigned
                    i = va_arg(a, int);
                    if(c == 'i' && i < 0) i = -i, uart_putc('-');
                    xtoa((unsigned)i, dv + 5);
                    break;
                case 'l':                       // 32 bit Long
                case 'n':                       // 32 bit uNsigned loNg
                    n = va_arg(a, long);
                    if(c == 'l' &&  n < 0) n = -n, uart_putc('-');
                    xtoa((unsigned long)n, dv);
                    break;
                case 'x':                       // 16 bit heXadecimal
                    i = va_arg(a, int);
                    puth(i >> 12);
                    puth(i >> 8);
                    puth(i >> 4);
                    puth(i);
                    break;
                case 0: return;
                default: goto bad_fmt;
            }
        } else
bad_fmt:    uart_putc(c);
    }
    va_end(a);
}



Sources
Комментариев нет
Отправить комментарий