PIC18Fを使ったUSBデータロガーの製作

[公開:any]

[電子工作/PIC]
[PIC,PIC18F,PIC18F2550,LM35DZ,LM358]

origin 2009-06-15


 連続変化するアナログデータを定期的にサンプリングして、PCに取り込むためのデータロガーとして、秋月電子で購入して組み立てたPICNICを使用しています。LAN接続で遠隔地のデータが収集できるので大変便利です。特に無線LANと組み合わせると電源さえあれば、無線LANの届く範囲内のどこにあってもデータが得られます。

PICNIC

 ただ、PICNICは、PCとのインターフェースにイーサネットによるLANを使用しているので、場所(環境)が変わるとIPアドレス関連の設定が必要となります。このため、万人が使いこなせるというわけにはいきません。そこで、もう少し手軽に利用できるデータロガーを作成します。
 PCとのインターフェースでは、現状ではUSBが一番手軽です。以前、「AVRとFT232RLを使ったUSB−シリアル通信の実験」でUSBを試していますが、今回はUSBインターフェース内蔵のPIC18F2550を使用して製作します。

 USBデータロガーをいつものようにブレッドボードで開発します。気温(室温)の変化をロギングすることを目的とするので、とりあえず2系統の温度を読み取れるようにします。PIC18Fのポートをやりくりすれば、あと3系統ほど追加できると思います。今回は温度センサーを使いますが、PICへ取り込める電圧レベルに変換すれば、色々な用途があると思います。

USBデータロガーの開発

 回路図です。USBは、バスパワーで使用します。手持ちの3台のPCのUSB電圧を調べてみると、5.07、4.96、4.81Vと結構バラツキがありました。PIC18FのAD変換リファレンス電圧としてUSB電源をそのまま使用すると、当然ながら正常なAD変換精度が得られません。よって、シャントレギュレータTL431を使用してリファレンス電圧を生成することにします。なお、PIC18の仕様では、正確な10ビットのAD変換を得るためには、3V以上のリファレンス電圧が必要となります。10ビット(1024)で計算処理がやりやすいように3.072Vを抵抗(可変抵抗)で分圧生成します。これにより、AD変換の最小単位は、3.072/1024=3mVとなります。
 温度センサーは、専用ICのLM35DZを使用します。LM35DZは、10mv/1℃の出力となるので、そのままPIC18Fに入力すると約0.33℃の分解能しか得られません。そこで、LM35DZの出力をオペアンプのLM358で3倍に増幅し、30mV/1℃とすることで、0.1℃単位の分解能を得ます。

PIC18F USBデータロガー回路図

 PCBEで作成した基板への実装図です。リファレンス電圧を生成する可変抵抗とオペアンプに使用する可変抵抗は、細かな調整が可能な多回転タイプを使用しました。

USBデータロガー基板実装図

オペアンプ用のバイパスコンデンサは半田面に実装。これをつけないとノイズで1〜2ビット程度の変動が発生。

 完成した基板を、秋月電子のポリカーボネートケース中サイズに組み込みました。リファレンス電圧の3.072Vをマルチメータを使って正確に調整します。オペアンプの出力は、LM35DZの出力がキッチリと3倍になるように調整します。
 LM35DZのケーブルは、ある程度長さがあるため、シールドケーブルを使用します。ノイズ等の影響でAD変換値(気温)が安定しない場合は、LM35DZの出力にローパスフィルタを追加するなどの対策が必要になります。今回は、1m程度のシールドケーブルを使用しましたが、特に問題なく動作しています。時々、1ビット(0.1℃)の変動が出ますが、PC側のソフトウェアで移動平均処理してノイズを取り除けばよいでしょう。

USBデータロガー基板表USBデータロガー全体

 PIC18FでUSBを実装するには、Microchip社の「USB Framework for PIC18, PIC24 and PIC32」という専用のフレームワークを使います。電子工作の実験室さんにもPIC18FとUSBフレームワークを使用したサンプル事例がありますが、フレームワークのバージョンが古いもののようです。古くても問題ありませんが、どうせなら・・ということで、Microchip社のWebサイトから最新版の「MCHPFSUSB Framework v2.4」をダウンロードして使用することにします。
 最新のフレームワークを使用したプログラム開発を進めながら、ついでに簡単な解説コンテンツを作成しておこうと資料を準備していると、YS電子工作ラボさんで既に詳しく解説されていました。英語の訳も付いてわかりやすいのでお薦めです。

 USBフレームワークは、CDC-Basicというタイプを使用して、PC側ではUSBを経由したシリアル通信として認識させます。PC側では、ハイパーターミナル(Windows標準装備)やTeraTerm等のシリアル通信ソフトでデータを取り出すことができます。ただ、特定のタイミングでデータを取得したりファイルに保存したりするのが難しいので、専用アプリを作って使用しています。LinuxなどのUNIX系OSでは、シェルスクリプトとawkやperlなどの処理言語で処理するのが簡単でしょう。

USBデータロガー用Windowsアプリケーション

 作成したプログラムです。MPLAB V8.30とMPLAB C18 Student Editionで作成しました。USBフレームワークは、多くのファイルがスケルトンとして準備されていますが、変更が必要なファイルは2〜3ファイルと少数です。先に紹介したサイトに詳しく解説されているので、説明は省略しますが、今回は、main.cの一部の関数のみを目的にあわせて変更し、その他のファイルは一切変更無しにしました。また、プリプロセッサ命令から不要と思われる処理を取り除いて単純化し、コメント類もすべて削除しています。

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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
/** INCLUDES *******************************************************/
#include "GenericTypeDefs.h"
#include "Compiler.h"
#include "usb_config.h"
#include "./USB/usb_device.h"
#include "./USB/usb.h"
#include "./USB/usb_function_cdc.h"

#include "lcd.h"
#include <adc.h>

/** CONFIGURATION **************************************************/
#pragma config FOSC=HSPLL_HS, PLLDIV=5, CPUDIV=OSC1_PLL2 
#pragma config USBDIV=2, PWRT=ON, FCMEN=ON, IESO=ON
#pragma config BOR=ON, BORV=2, WDT=OFF, LVP=OFF, VREGEN=ON
#pragma config MCLRE=OFF, PBADEN=OFF, WDTPS=1024
#pragma config LPT1OSC=OFF, CCP2MX=OFF, DEBUG=OFF

#pragma config CP0=OFF, CP1=OFF, CP2=OFF, CP3=OFF, CPB=OFF
#pragma config CPD=OFF, WRT0=OFF, WRT1=OFF, WRT2=OFF, WRT3=OFF
#pragma config WRTB=OFF, WRTC=OFF, WRTD=OFF, EBTR0=OFF
#pragma config EBTR1=OFF, EBTR2=OFF, EBTR3=OFF, EBTRB=OFF

/** Variables ****************************************************************/
#pragma udata
char USB_In_Buffer[64];
char USB_Out_Buffer[64];
unsigned char mode;
unsigned char tmp[18];
unsigned int ad0, ad1;

/** HardwareProfile *********************************************************/
#define self_power    0
#define USB_BUS_SENSE    0
#define mLED_1    LATCbits.LATC1
#define mLED_2    LATCbits.LATC2

/** Private Prototypes ******************************************************/
static void InitializeSystem(void);
void ProcessIO(void);
void USBDeviceTasks(void);
void YourHighPriorityISRCode();
void YourLowPriorityISRCode();
void UserInit(void);

/** VECTOR REMAPPING ****************************************************/
#define REMAPPED_RESET_VECTOR_ADDRESS            0x00
#define REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS    0x08
#define REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS    0x18
    
#pragma code REMAPPED_HIGH_INTERRUPT_VECTOR = REMAPPED_HIGH_INTERRUPT_VECTOR_ADDRESS
void Remapped_High_ISR (void)
{
    _asm goto YourHighPriorityISRCode _endasm
}
#pragma code REMAPPED_LOW_INTERRUPT_VECTOR = REMAPPED_LOW_INTERRUPT_VECTOR_ADDRESS
void Remapped_Low_ISR (void)
{
    _asm goto YourLowPriorityISRCode _endasm
}
    
#pragma code
    
#pragma interrupt YourHighPriorityISRCode
void YourHighPriorityISRCode()
{
    USBDeviceTasks();
}
#pragma interruptlow YourLowPriorityISRCode
void YourLowPriorityISRCode()
{
    INTCONbits.TMR0IF = 0;
    mLED_2 = !mLED_2;
    lcd_gotopos(0, 0);
    sprintf(tmp, "1:%2d.%1d゚C", (int)(ad0 * 0.1), ad0 - ((int)(ad0 * 0.1) * 10));
    lcd_putstr(tmp);
    lcd_gotopos(0, 1);
    sprintf(tmp, "2:%2d.%1d゚C", (int)(ad1 * 0.1), ad1 - ((int)(ad1 * 0.1) * 10));
    lcd_putstr(tmp);
}

/** DECLARATIONS ***************************************************/
#pragma code
void delay_ms(unsigned int t)
{
    for(; t > 0; t--)
        Delay1KTCYx(12);    // 1ms: 0.001 / ((1 / (48000000 / 4)) * 1000) = 3 
}

void main(void)
{   
    InitializeSystem();

    USBDeviceAttach();

    while(1) {
        ProcessIO();        
    }
}


static void InitializeSystem(void)
{
    UserInit();
    USBDeviceInit();    //usb_device.c.  Initializes USB module SFRs and firmware
}

void UserInit(void)
{
    TRISA = 0b00001011;            // RA0,1 A/D input RA4 Vref input
    TRISB = 0;                    // all output for lcd
    TRISC = 0b11000000;            // RC1,2 LED
    LATC = 0;

    ADCON0 = 0b00000001;
    ADCON1 = 0b00011101;
    ADCON2 = 0b10110110;

    T0CON = 0b10000110;        // Timer0 16bit mode, prescaler 1/128.
    INTCONbits.TMR0IE = 1;        // Timer0 Interrupt Enable

    RCONbits.IPEN = 1;        // Interruput Priority Enable
    INTCON2bits.TMR0IP = 0;    // Timer0 Interrupt is Low Priority

    mode = 0;

    lcd_init();
    lcd_cls();

    INTCONbits.GIEH = 1;
    INTCONbits.GIEL = 1;
}

void ProcessIO(void)
{   
    BYTE numBytesRead;

    if((USBDeviceState < CONFIGURED_STATE)||(USBSuspendControl==1)) {
        return;
    }

    SetChanADC(ADC_CH0);
    ConvertADC();
    while(BusyADC());
    ad0 = ReadADC();

    SetChanADC(ADC_CH1);
    ConvertADC();
    while(BusyADC());
    ad1 = ReadADC();

    if(mUSBUSARTIsTxTrfReady()) {
        numBytesRead = getsUSBUSART(USB_In_Buffer,64);
        if(numBytesRead != 0) {
              switch(USB_In_Buffer[0])    {
                   case '0':
                    mode = 0;
                    mLED_1 = 0;
                     break;                          
                   case '1':            // One shot Data send
                    mode = 0;
                    mLED_1 = 0;
                    sprintf(USB_Out_Buffer, "%4d %4d\r\n", ad0, ad1);
                        mUSBUSARTTxRam((BYTE*)(USB_Out_Buffer), 11);
                      break;
                case '2':            // Bulk Data send
                    mode = 2;
                    mLED_1 = 1;
                    break;
                   default:
                        break;
            }
         }
        if(mode == 2) {
            sprintf(USB_Out_Buffer, "%4d %4d\r\n", ad0, ad1);
                mUSBUSARTTxRam((BYTE*)(USB_Out_Buffer), 11);    // データ送信
        }
    }
    CDCTxService();
}

// ここから下は変更なし。コメントを削除したのみ。

void USBCBSuspend(void)
{
    #if defined(__C30__)
    #if 0
        U1EIR = 0xFFFF;
        U1IR = 0xFFFF;
        U1OTGIR = 0xFFFF;
        IFS5bits.USB1IF = 0;
        IEC5bits.USB1IE = 1;
        U1OTGIEbits.ACTVIE = 1;
        U1OTGIRbits.ACTVIF = 1;
        Sleep();
    #endif
    #endif
}

#if 0
void __attribute__ ((interrupt)) _USB1Interrupt(void)
{
    #if !defined(self_powered)
        if(U1OTGIRbits.ACTVIF) {
            IEC5bits.USB1IE = 0;
            U1OTGIEbits.ACTVIE = 0;
            IFS5bits.USB1IF = 0;
        
            //USBClearInterruptFlag(USBActivityIFReg,USBActivityIFBitNum);
            USBClearInterruptFlag(USBIdleIFReg,USBIdleIFBitNum);
            //USBSuspendControl = 0;
        }
    #endif
}
#endif

void USBCBWakeFromSuspend(void)
{

}

void USBCB_SOF_Handler(void)
{

}

void USBCBErrorHandler(void)
{
    
}

void USBCBCheckOtherReq(void)
{
    USBCheckCDCRequest();
}

void USBCBStdSetDscHandler(void)
{

}

void USBCBInitEP(void)
{
    CDCInitEP();
}

void USBCBSendResume(void)
{
    static WORD delay_count;
    
    USBResumeControl = 1;                // Start RESUME signaling
    
    delay_count = 1800U;                // Set RESUME line for 1-13 ms
    do {
        delay_count--;
    } while(delay_count);
    USBResumeControl = 0;
}

#if defined(ENABLE_EP0_DATA_RECEIVED_CALLBACK)
void USBCBEP0DataReceived(void)
{

}
#endif

BOOL USER_USB_CALLBACK_EVENT_HANDLER(USB_EVENT event, void *pdata, WORD size)
{
    switch(event) {
        case EVENT_CONFIGURED: 
            USBCBInitEP();
            break;
        case EVENT_SET_DESCRIPTOR:
            USBCBStdSetDscHandler();
            break;
        case EVENT_EP0_REQUEST:
            USBCBCheckOtherReq();
            break;
        case EVENT_SOF:
            USBCB_SOF_Handler();
            break;
        case EVENT_SUSPEND:
            USBCBSuspend();
            break;
        case EVENT_RESUME:
            USBCBWakeFromSuspend();
            break;
        case EVENT_BUS_ERROR:
            USBCBErrorHandler();
            break;
        case EVENT_TRANSFER:
            Nop();
            break;
        default:
            break;
    }      
    return TRUE; 
}

▲ページ Top へ...