VC++を使ってUSBでPICマイコンとの接続を実験する
2019/07/28
修正 2019/08/04


さて、PICマイコンを使った実験を最後に行ったのは、ナント!、2017年の5月ということで、もう2年も前になるのである。
当然のように私の頭の中は完全に放電して空っぽになってしまっており、何〜にも残っていないのである。

そこで、とにかく手っ取り早く、安く、簡単に実験可能なものを探すことにした。

選んだのは秋月電子が出している「PIC18F14K50使用USB対応超小型マイコンボード」というもので、動作に必要な部品は全部付いているし、こちらがやることは付属の3つのピンの足をハンダ付けするだけである。

値段も安く、秋葉原に行ったときに、秋月電子に寄って800円で買ってきたのである。



早速ハンダ付けを行い、ブレッドボードに組み込む。

test_pgm066
右端にあるのが超小型マイコンボードで、L字型のピンを入れても2×3センチ程度の物である。
回路としては以下のようなものにした。

1、RC0〜RC3までの4本のピンに、抵抗とLEDを付けた
2、RB4ピンに1Kオーム程度の半固定抵抗を付けた
3、VPPピンに1Kオーム程度のプルアップ抵抗を付けたスイッチを付け、押すとグランドに落ちるようにした
※VPPピンはPICkit3で使用するので、ワニ口クリップで配線してある。



なぜこういう回路にしたかといえば、PICを出しているMicrochipという会社が提供しているサンプルがこの回路で動いているような定義になっていたからである。
もっとも、パソコン側のサンプルプログラムをじっくり見たら、LEDのRC2とRC3、そして半固定抵抗の値を入力するA/Dコンバーターを使うプログラムは無かった。
PICの方にはどうやら使うプログラムが入っているようなので、後で追加してみようかとも思うのである。

サンプルの中で、PIC18F14K50を使って、一番面白そうなのが「hid_custom」に入っていた「low_pin_count_usb_development_kit_pic18f14k50.x」だったのである。
早速、MPLABでコンパイルをやってみる。

コンパイルに問題が無ければ、PICkit3を使ってマイコンボードに書き込む。

それも問題が無ければ、PICのUSBとパソコンのUSBを繋いでみる。

test_pgm067
PICを繋げる前のデバイスマネージャーを見ると、こんな感じである。

test_pgm068
PICを繋げると、ヒューマンインターフェイスデバイスの中に、「HID準拠ベンダー定義デバイス」というのが追加されて出てくる。

USBに接続すると、PICマイコンのRC0のLEDが点滅するが、3秒ほどで沈黙する。

PIC側の処理はこれで終了で、な〜んにもする必要が無いのである。
ちょっと拍子抜けするくらい簡単であるが、問題はこれからである。




さて、ここまではハンダ付け程度で済んだのであるが、ここからはPC側のプログラムを開発して、このPICのUSBとの通信にトライする。
PC側の開発環境は、以前に導入したMicrosoft Visual Studio2017のVC++である。

参考にするのはやはりMicrochip社のPC側のプログラムであるが、VC++の2010Expressで書かれており、私の持っているものと違うのである。
test_pgm069
メニューに適当な名称(USBのチェック)とかいう項目を追加し、そのコマンドで開始するコマンドハンドラーをビュークラスに追加する。


まず重要なのがUSBの接続方法でHIDを使うという処理をする関数のインクルードで、以下のようなものである。
これらをビュークラスのプログラムの前に入れておく。
※includeの上位3行のカッコの中の「"」は、表示の為に入れたものでプログラム上は無い。

#include <"devguid.h">
#include <"regstr.h">
#include <"setupapi.h">
#pragma	comment(lib,"setupapi.lib")
また、HIDを使うというGUIDというものがあり、以下のようなもので、各プログラムで使用する。

//Globally Unique Identifier (GUID) for HID class devices. Windows uses GUIDs to identify things.
GUID InterfaceClassGuid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };

私の記憶では、このGUID自体を検索して引っ張ってくるルーチンを作った記憶があるのだが、残念ながら具体的にどうやったのか、全く記憶から消えてしまっているのである。
何か作ったら、ちゃんとこのHPのように具体的な処理を書き残しておかないと、バカだから忘れてしまうのである。


最初の処理は、HIDデバイスの情報を取ってくるところから開始する。

色々参考にして、以下のようなプログラムを作って実行する。

void CGetUSBDataView::On32777()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	bool	bRet;
	DWORD InterfaceIndex = 0;
	DWORD ErrorStatus;
	DWORD dwRegType;
	DWORD dwRegSize;
	CString DeviceIDFromRegistry;
	CAtlString		strMessage;

	//Globally Unique Identifier (GUID) for HID class devices.  Windows uses GUIDs to identify things.
	GUID InterfaceClassGuid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };

	HDEVINFO DeviceInfoTable = INVALID_HANDLE_VALUE;
	PSP_DEVICE_INTERFACE_DATA InterfaceDataStructure = new SP_DEVICE_INTERFACE_DATA;
	SP_DEVINFO_DATA DevInfoData;
	PBYTE PropertyValueBuffer;

	//First populate a list of plugged in devices (by specifying "DIGCF_PRESENT"), which are of the specified class GUID. 
	DeviceInfoTable = SetupDiGetClassDevs(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

	while (1) {
		InterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		bRet = SetupDiEnumDeviceInterfaces(DeviceInfoTable, NULL, &InterfaceClassGuid, InterfaceIndex, InterfaceDataStructure);

		if (bRet) {
			// MessageBox(_T("bRet = true"));
			ErrorStatus = GetLastError();
			if (ERROR_NO_MORE_ITEMS == ErrorStatus)	//Did we reach the end of the list of matching devices in the DeviceInfoTable?
			{	//Cound not find the device.  Must not have been attached.
				MessageBox(_T("ERROR_NO_MORE_ITEMS == ErrorStatus"));
				SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
				break;
			}
		}
		else {
			// MessageBox(_T("bRet = false"));
			ErrorStatus = GetLastError();
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			break;
		}

		//Initialize an appropriate SP_DEVINFO_DATA structure.  We need this structure for SetupDiGetDeviceRegistryProperty().
		DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
		SetupDiEnumDeviceInfo(DeviceInfoTable, InterfaceIndex, &DevInfoData);

		//First query for the size of the hardware ID, so we can know how big a buffer to allocate for the data.
		SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, NULL, 0, &dwRegSize);

		//Allocate a buffer for the hardware ID.
		PropertyValueBuffer = (BYTE *)malloc(dwRegSize);
		if (PropertyValueBuffer == NULL)	//if null, error, couldn't allocate enough memory
		{	//Can't really recover from this situation, just exit instead.
			MessageBox(_T("PropertyValueBuffer == NULL"));
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			break;
		}

		//Retrieve the hardware IDs for the current device we are looking at.  PropertyValueBuffer gets filled with a 
		//REG_MULTI_SZ (array of null terminated strings).  To find a device, we only care about the very first string in the
		//buffer, which will be the "device ID".  The device ID is a string which contains the VID and PID, in the example 
		//format "Vid_04d8&Pid_003f".
		SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, PropertyValueBuffer, dwRegSize, NULL);
		
		DeviceIDFromRegistry = (wchar_t *)PropertyValueBuffer;
		free(PropertyValueBuffer);		//No longer need the PropertyValueBuffer, free the memory to prevent potential memory leaks
		
		strMessage += DeviceIDFromRegistry;
		strMessage += _T("\n");

		InterfaceIndex++;
	}

	MessageBox(strMessage);
}
test_pgm070
実行すると、左のようなポップアップが出て、HIDを使っているGUIDから検索出来るベンダーIDの一覧表が出る。

3番目が、今回接続したPICマイコンのベンダーIDである。



次に、この一覧表の中でPICのUSBを選んで接続するプログラムにする。

まずは、上位のビュークラスに以下のようなMicrochip社のベンダーIDとプロダクトID、そしてハンドルを追加する。

//Modify this value to match the VID and PID in your USB device descriptor.
//Use the formatting: "Vid_xxxx&Pid_xxxx" where xxxx is a 16-bit hexadecimal number.
#define MY_DEVICE_ID  "Vid_04d8&Pid_003F"	

//  Variables that need to have wide scope.
HANDLE WriteHandle = INVALID_HANDLE_VALUE;	//Need to get a write "handle" to our device before we can write to it.
HANDLE ReadHandle = INVALID_HANDLE_VALUE;	//Need to get a read "handle" to our device before we can read from it.

コマンドハンドラーを以下のようなプログラムにする。

void CGetUSBDataView::OnUsbCheck2()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	CDC* pDC = GetDC();

	CString str01;
	bool	bRet;
	DWORD InterfaceIndex = 0;
	DWORD ErrorStatus;
	DWORD dwRegType;
	DWORD dwRegSize;
//	CAtlString		DeviceIDFromRegistry;
	CString DeviceIDToFind = _T(MY_DEVICE_ID);
	CString DeviceIDFromRegistry;
	int MatchFound;
	DWORD StructureSize = 0;

	//Globally Unique Identifier (GUID) for HID class devices.  Windows uses GUIDs to identify things.
	GUID InterfaceClassGuid = { 0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30 };
	HDEVINFO DeviceInfoTable = INVALID_HANDLE_VALUE;
	PSP_DEVICE_INTERFACE_DATA InterfaceDataStructure = new SP_DEVICE_INTERFACE_DATA;
	SP_DEVINFO_DATA DevInfoData;
	PBYTE PropertyValueBuffer;
	PSP_DEVICE_INTERFACE_DETAIL_DATA DetailedInterfaceDataStructure = new SP_DEVICE_INTERFACE_DETAIL_DATA;


//	memset(&test_guid, 0, sizeof(GUID)); //allocate
//	HidD_GetHidGuid(&test_guid); //define the HID GUID

	//First populate a list of plugged in devices (by specifying "DIGCF_PRESENT"), which are of the specified class GUID. 
	DeviceInfoTable = SetupDiGetClassDevs(&InterfaceClassGuid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);

	while (1) {

		InterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
		bRet = SetupDiEnumDeviceInterfaces(DeviceInfoTable, NULL, &InterfaceClassGuid, InterfaceIndex, InterfaceDataStructure);

		if (bRet) {
//			MessageBox(_T("bRet = true"));
			ErrorStatus = GetLastError();
			if (ERROR_NO_MORE_ITEMS == ErrorStatus)	//Did we reach the end of the list of matching devices in the DeviceInfoTable?
			{	//Cound not find the device.  Must not have been attached.
				MessageBox(_T("ERROR_NO_MORE_ITEMS == ErrorStatus"));
				SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
				break;
			}
		}
		else{
			MessageBox(_T("bRet = false"));
			ErrorStatus = GetLastError();
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			break;
		}

		//Initialize an appropriate SP_DEVINFO_DATA structure.  We need this structure for SetupDiGetDeviceRegistryProperty().
		DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
		SetupDiEnumDeviceInfo(DeviceInfoTable, InterfaceIndex, &DevInfoData);

		//First query for the size of the hardware ID, so we can know how big a buffer to allocate for the data.
		SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, NULL, 0, &dwRegSize);

		//Allocate a buffer for the hardware ID.
		PropertyValueBuffer = (BYTE *)malloc(dwRegSize);
		if (PropertyValueBuffer == NULL)	//if null, error, couldn't allocate enough memory
		{	//Can't really recover from this situation, just exit instead.
			MessageBox(_T("PropertyValueBuffer == NULL"));
			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
			break;
		}

		//Retrieve the hardware IDs for the current device we are looking at.  PropertyValueBuffer gets filled with a 
		//REG_MULTI_SZ (array of null terminated strings).  To find a device, we only care about the very first string in the
		//buffer, which will be the "device ID".  The device ID is a string which contains the VID and PID, in the example 
		//format "Vid_04d8&Pid_003f".
		SetupDiGetDeviceRegistryProperty(DeviceInfoTable, &DevInfoData, SPDRP_HARDWAREID, &dwRegType, PropertyValueBuffer, dwRegSize, NULL);

		//Now check if the first string in the hardware ID matches the device ID of my USB device.

		DeviceIDFromRegistry = (wchar_t *)PropertyValueBuffer;
		free(PropertyValueBuffer);		//No longer need the PropertyValueBuffer, free the memory to prevent potential memory leaks

		//	両方共小文字に変更
		DeviceIDFromRegistry = DeviceIDFromRegistry.MakeLower();
		DeviceIDToFind = DeviceIDToFind.MakeLower();

		// VID/PID が一致するかチェック
		MatchFound = DeviceIDFromRegistry.Find(DeviceIDToFind);

		if (MatchFound != -1) {

//			str01.Format(_T("MatchFound = %d"), MatchFound);
//			MessageBox(str01);

			DetailedInterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
			
			//First call populates "StructureSize" with the correct value
			SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, NULL, NULL, &StructureSize, NULL);
			DetailedInterfaceDataStructure = (PSP_DEVICE_INTERFACE_DETAIL_DATA)(malloc(StructureSize));		//Allocate enough memory
			
			if (DetailedInterfaceDataStructure == NULL)	
			{	
				MessageBox(_T("DetailedInterfaceDataStructure == NULL"));
				SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.
				break;
			}

			DetailedInterfaceDataStructure->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

			//Now call SetupDiGetDeviceInterfaceDetail() a second time to receive the goods.  
			SetupDiGetDeviceInterfaceDetail(DeviceInfoTable, InterfaceDataStructure, DetailedInterfaceDataStructure, StructureSize, NULL, NULL);

			//We now have the proper device path, and we can finally open read and write handles to the device.
			//We store the handles in the global variables "WriteHandle" and "ReadHandle", which we will use later to actually communicate.
			WriteHandle = CreateFile((DetailedInterfaceDataStructure->DevicePath), GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

			ErrorStatus = GetLastError();
			if (ErrorStatus == ERROR_SUCCESS) { 
				pDC->TextOutW(0, text_line_count, _T("WriteHandle 取得成功"));
				text_line_count += text_line_add;
//				MessageBox(_T("ToggleLED_btn->Enabled = true"));
//				ToggleLED_btn->Enabled = true; 
			}

			ReadHandle = CreateFile((DetailedInterfaceDataStructure->DevicePath), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);

			ErrorStatus = GetLastError();
			if (ErrorStatus == ERROR_SUCCESS)
			{
				pDC->TextOutW(0, text_line_count, _T("ReadHandle 取得成功"));
				text_line_count += text_line_add;
//				MessageBox(_T("GetPushbuttonState_btn->Enabled = true"));
//				GetPushbuttonState_btn->Enabled = true;		//Make button no longer greyed out
//				StateLabel->Enabled = true;					//Make label no longer greyed out
			}
			pDC->TextOutW(0, text_line_count, _T("USB への接続成功"));
			text_line_count += text_line_add;

			SetupDiDestroyDeviceInfoList(DeviceInfoTable);	//Clean up the old structure we no longer need.

			break;

		}

		InterfaceIndex++;
		
	}

//	str01.Format(_T("LOOP 終了 = %d"), 1);
//	MessageBox(str01);

	ReleaseDC(pDC);
}

test_pgm071
成功すれば、クライアント領域に成功の文字が出る。

test_pgm072
また、PICマイコンのRC0のLEDが点滅したら、めでたしめでたしである。




さて、readとwriteのハンドルが手に入ったので、いよいよ自分でPICとの通信にチャレンジする。

メニューに「LEDの点滅」とかなんとか、適当なコマンドを追加し、コマンドハンドラーも追加する。

void CGetUSBDataView::On32774()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。

	DWORD BytesWritten = 0;
	unsigned char OutputPacketBuffer[65];	//Allocate a memory buffer equal to our endpoint size + 1

	if (WriteHandle == INVALID_HANDLE_VALUE) return;

	OutputPacketBuffer[0] = 0;			
	OutputPacketBuffer[1] = 0x80;		

	WriteFile(WriteHandle, &OutputPacketBuffer, 65, &BytesWritten, 0);	

}
このプログラムは、パソコンからPICに65バイトのデータを送っているだけのものだが、ちゃんとPIC側が受け取れば、2バイト目に入っている0x80をチェックしてPICマイコンはRC1を反転させるプログラムになっているのである。

test_pgm073
実行すると、目出度くLEDは反転し、コマンドを実行する度に点いたり消えたりする。





最後に行うのは、PICマイコン側に付いているスイッチの状態をパソコン側に知らせる双方向の試験である。

手続きとしては、パソコン側から「スイッチはどうなってますか?」という問い合わせを行い、PIC側は、「スイッチの状態を聞いてきたのでデータを送り返します」という動作である。

スイッチのチェックというコマンドとコマンドハンドラーを追加し、以下のようなプログラムを入れる。


void CGetUSBDataView::OnButonChk()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	DWORD BytesWritten = 0;
	DWORD BytesRead = 0;
	unsigned char OutputPacketBuffer[65];	//Allocate a memory buffer equal to our endpoint size + 1
	unsigned char InputPacketBuffer[65];	//Allocate a memory buffer equal to our endpoint size + 1
	CDC* pDC = GetDC();

	CString str01;
	SYSTEMTIME systime_s;  // 処理の開始時刻
	SYSTEMTIME systime_e;  // 処理の終了時刻

	if (WriteHandle == INVALID_HANDLE_VALUE) return;

	InputPacketBuffer[0] = 0;				//The first byte is the "Report ID" and does not get transmitted over the USB bus.  Always set = 0.
	OutputPacketBuffer[0] = 0;				//The first byte is the "Report ID" and does not get transmitted over the USB bus.  Always set = 0.

	OutputPacketBuffer[1] = 0x81;

	GetLocalTime(&systime_s);		// 開始時刻を取得

	WriteFile(WriteHandle, &OutputPacketBuffer, 65, &BytesWritten, 0);

	ReadFile(ReadHandle, &InputPacketBuffer, 65, &BytesRead, 0);

	GetLocalTime(&systime_e);		// 終了時刻を取得

	if (InputPacketBuffer[2] == 0x01)
	{
		str01.Format(_T("ボタンは押されていない   開始 %u:%u:%u.%u 終了 %u:%u:%u.%u"),
			systime_s.wHour, systime_s.wMinute, systime_s.wSecond,
			systime_s.wMilliseconds,
			systime_e.wHour, systime_e.wMinute, systime_e.wSecond,
			systime_e.wMilliseconds);
		//MessageBox(_T("ボタンは押されていない"));
	}
	else //InputPacketBuffer[2] must have been == 0x00 instead
	{
		str01.Format(_T("ボタンは押されている    開始 %u:%u:%u.%u 終了 %u:%u:%u.%u"),
			systime_s.wHour, systime_s.wMinute, systime_s.wSecond,
			systime_s.wMilliseconds,
			systime_e.wHour, systime_e.wMinute, systime_e.wSecond,
			systime_e.wMilliseconds);
		//MessageBox(_T("ボタンは押されていた  "));
	}
	pDC->TextOutW(0, text_line_count, str01);
	text_line_count += text_line_add;

	ReleaseDC(pDC);
}
test_pgm074
コマンドの実行時に、PIC側のスイッチの状況が返る。
ついでに、パソコン側でPICとの応答時間を調べてみる。

今回は、PIC側ではたいした処理をしていないので、ほぼ、USBの通信の時間が測れていると思われるのである。





今回は、そう大きなトラブルも無く、なかなか良好な感じだったのである。
まぁ、サンプルをそのまま移したようなものなので、当然と言えば当然なのかもしれないが・・・・

次は、A/Dコンバーターのテストを行ってみようと思うのである。
これについては、パソコン側のサンプルが無いのだが、まぁ、なんとかチャレンジしようと思うのである。



A/Dコンバーターを使用するプログラムは、Microchip社のサンプルを見ると、そう面倒なことをやっているわけではないようで、以下のようなプログラムをPC側で用意する。

まず、タイマーで起動するコマンドハンドラーを作り、以下のようなプログラムにする。

PICマイコンに送る指令はHEXで"37"である。
PICマイコンでは"37"を受け取ると、RB4ピンの電圧を電源との差で10ビットに返還してパソコン側へ2バイトで送り返す。

受け取ったパソコン側では、2バイトのデータを一つの整数にして表示する。


void CGetUSBDataView::OnTimer(UINT_PTR nIDEvent)
{
	// TODO: ここにメッセージ ハンドラー コードを追加するか、既定の処理を呼び出します。
	CDC* pDC = GetDC();
	CString str01;
	SYSTEMTIME systime_s;  // 処理の開始時刻
	SYSTEMTIME systime_e;  // 処理の終了時刻

	DWORD BytesWritten = 0;
	DWORD BytesRead = 0;
	unsigned char OutputPacketBuffer[65];	//Allocate a memory buffer equal to our endpoint size + 1
	unsigned char InputPacketBuffer[65];	//Allocate a memory buffer equal to our endpoint size + 1
	int adc_in1;
	int adc_in2;

	if (WriteHandle == INVALID_HANDLE_VALUE) return;

	InputPacketBuffer[0] = 0;				//The first byte is the "Report ID" and does not get transmitted over the USB bus.  Always set = 0.
	OutputPacketBuffer[0] = 0;				//The first byte is the "Report ID" and does not get transmitted over the USB bus.  Always set = 0.

	OutputPacketBuffer[1] = 0x37;

	GetLocalTime(&systime_s);		// 開始時刻を取得
	WriteFile(WriteHandle, &OutputPacketBuffer, 65, &BytesWritten, 0);
	ReadFile(ReadHandle, &InputPacketBuffer, 65, &BytesRead, 0);
	GetLocalTime(&systime_e);		// 終了時刻を取得

	adc_in1 = InputPacketBuffer[2];
	adc_in2 = InputPacketBuffer[3];
	adc_in1 = adc_in1 + (adc_in2 << 8);

	str01.Format(_T("data = %u     開始 %02u:%02u:%02u.%03u 終了 %02u:%02u:%02u.%03u    "),adc_in1,
		systime_s.wHour, systime_s.wMinute, systime_s.wSecond,
		systime_s.wMilliseconds,
		systime_e.wHour, systime_e.wMinute, systime_e.wSecond,
		systime_e.wMilliseconds);

	pDC->TextOutW(0, text_line_count, str01);

	ReleaseDC(pDC);
	CView::OnTimer(nIDEvent);
}
パソコンのメニューに「タイマー起動」とかなんとかのコマンドを追加し、コマンドハンドラーを以下のように作る。


void CGetUSBDataView::OnTimer1()
{
	// TODO: ここにコマンド ハンドラー コードを追加します。
	if (WriteHandle == INVALID_HANDLE_VALUE) return;

	SetTimer(5678, 1000, NULL);
}
test_pgm075
タイマーを起動させると、秒毎に値が0〜1024の間で返る。

ドライバーで、半固定のボリュームを回すと、値が次々に変化する。

今回はテストなので1秒で動作しているが、そうとう早くても大丈夫なようである。





A/D変換が出来たし、せっかくの夏休みなので、自由研究的に1日の気温の変化を記録するプログラムにしてみた。

test_pgm078
回路としては、半固定ボリュームを温度センサーに差し替えただけである。

温度センサーは、秋月電機で購入したLM61というもので、1本60円なりのものである。
データシートによると、10mV/1℃ということである。

このセンサーを1.5mほどのエナメル線の先に付けて、窓の外の外気温を測ってみようという計画である。


PC側のプログラムとしては、あまり大きな変更は無く、ドキュメントクラスに取得した温度データと時間を貯めるだけである。

1分毎に計測するとして、1日は24時間なので1440個は最低必要である。
余裕をみて2日分を用意する。
あとは、そのデータをファイルに出力するルーチンを追加するだけである。

タイマーの部分を1分毎の発生に変更してスタートである。

test_pgm079
測定は8月1日の14時頃から8月2日の14時まで24時間行った。

夕立が来るかもしれないので、温度センサーをビニールの袋に入れて、窓の外へ出し、プラスティック製の安物温度計も一緒に出す。
虫よけの網と窓ガラスの間なので、ちょっと建物と近すぎるような気もするのだが、いかんせん良い場所が無い。

test_pgm076
結果をファイルに出力し、テキストで表示すると左のようなもので、値としては180〜198の範囲で、温度としては30℃〜36℃という範囲である。

どうも時期が悪く、秋とか春とか、もうちょっと朝夕の温度変化の激しい時期に行うべきだったのである。


test_pgm077
せっかくなのでファイルをエクセルで取り込んでグラフにすると、こんな感じである。

今日の午前中に、二つのピョンと飛び出した部分があるが、これは建物の隙間から直接太陽がセンサーに当たってしまった時間があり、おそらくそのせいなのである。



考察としては、以下のものがある。

1、夕立が来るかもしれないので、センサーをビニールで包んだが、これはあまり良くないかもしれない。
しょせん、電気を使って温度を感知しているわけで、ほんのすこしであろうともセンサーは発熱しているはずなのである。

やはり、雨のあたらない、風通しの良い百葉箱のような物の中にむき出しで設置すべきであろう。

2、ちゃんとした温度計で校正しておくべきで、いっしょに置いてある温度計は、とんでもない安物なのでだいたいの温度しか計れない。
まぁ、無いよりはマシであるが・・・・

3、思ったより値のバラつきがあり、プラスマイナス1くらいの値は常に変動している。
かすかな風の影響であろうか?
このあたりも考慮して、ビニール袋を外して一定の温度の場所で校正するべきであろう。

又は、センサーへのケーブルを数メートル伸ばしているのでノイズが乗っているのかもしれない。



ということで、長いケーブルが原因でのノイズの可能性は、要するにケーブルを使わない場合と比べてみれば良いので、そう難しいものでもない。
test_pgm080
そこで、センサーを直接ブレッドボードに取り付けてしばらく計測してみる。

計測は8月3日の14時30分頃からだいたい1時間半くらいである。
部屋の中は冷房が入っており、かなり気温は一定である。

test_pgm081
前回のデータと比べると、明らかにケーブルの無い方がバラつきが少なく、どうやらケーブルが長いためにノイズが乗っていたようである。

これを防ぐには、単純で安いエナメル線ではダメで、ツイストペアとかシールド線とかを使わないとならない。
さて、どうすべきか?
なんだかあまり深入りをしたくない気分になってきたのである。

一番簡単なのは、ブレッドボードを止めて、ちゃんとしたプリント板に配線し、センサーとPICマイコンを近くに配置して、USBケーブルを長くして外へ出すべきなのだろう。




ここまでのテストでは、PIC側のプログラムには手を加えなかったが、これ以上のことをやろうとすると、PIC側のプログラムも色々変えないとならない。
さて、次の目標は何にしましょうか?


HOME