メニューに適当な名称(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);
}
実行すると、左のようなポップアップが出て、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);
}
成功すれば、クライアント領域に成功の文字が出る。
また、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を反転させるプログラムになっているのである。
実行すると、目出度く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);
}
コマンドの実行時に、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);
}
タイマーを起動させると、秒毎に値が0〜1024の間で返る。
ドライバーで、半固定のボリュームを回すと、値が次々に変化する。
今回はテストなので1秒で動作しているが、そうとう早くても大丈夫なようである。
A/D変換が出来たし、せっかくの夏休みなので、自由研究的に1日の気温の変化を記録するプログラムにしてみた。
回路としては、半固定ボリュームを温度センサーに差し替えただけである。
温度センサーは、秋月電機で購入したLM61というもので、1本60円なりのものである。
データシートによると、10mV/1℃ということである。
このセンサーを1.5mほどのエナメル線の先に付けて、窓の外の外気温を測ってみようという計画である。
PC側のプログラムとしては、あまり大きな変更は無く、ドキュメントクラスに取得した温度データと時間を貯めるだけである。
1分毎に計測するとして、1日は24時間なので1440個は最低必要である。
余裕をみて2日分を用意する。
あとは、そのデータをファイルに出力するルーチンを追加するだけである。
タイマーの部分を1分毎の発生に変更してスタートである。
測定は8月1日の14時頃から8月2日の14時まで24時間行った。
夕立が来るかもしれないので、温度センサーをビニールの袋に入れて、窓の外へ出し、プラスティック製の安物温度計も一緒に出す。
虫よけの網と窓ガラスの間なので、ちょっと建物と近すぎるような気もするのだが、いかんせん良い場所が無い。
結果をファイルに出力し、テキストで表示すると左のようなもので、値としては180〜198の範囲で、温度としては30℃〜36℃という範囲である。
どうも時期が悪く、秋とか春とか、もうちょっと朝夕の温度変化の激しい時期に行うべきだったのである。
せっかくなのでファイルをエクセルで取り込んでグラフにすると、こんな感じである。
今日の午前中に、二つのピョンと飛び出した部分があるが、これは建物の隙間から直接太陽がセンサーに当たってしまった時間があり、おそらくそのせいなのである。
考察としては、以下のものがある。
1、夕立が来るかもしれないので、センサーをビニールで包んだが、これはあまり良くないかもしれない。
しょせん、電気を使って温度を感知しているわけで、ほんのすこしであろうともセンサーは発熱しているはずなのである。
やはり、雨のあたらない、風通しの良い百葉箱のような物の中にむき出しで設置すべきであろう。
2、ちゃんとした温度計で校正しておくべきで、いっしょに置いてある温度計は、とんでもない安物なのでだいたいの温度しか計れない。
まぁ、無いよりはマシであるが・・・・
3、思ったより値のバラつきがあり、プラスマイナス1くらいの値は常に変動している。
かすかな風の影響であろうか?
このあたりも考慮して、ビニール袋を外して一定の温度の場所で校正するべきであろう。
又は、センサーへのケーブルを数メートル伸ばしているのでノイズが乗っているのかもしれない。
ということで、長いケーブルが原因でのノイズの可能性は、要するにケーブルを使わない場合と比べてみれば良いので、そう難しいものでもない。
そこで、センサーを直接ブレッドボードに取り付けてしばらく計測してみる。
計測は8月3日の14時30分頃からだいたい1時間半くらいである。
部屋の中は冷房が入っており、かなり気温は一定である。
前回のデータと比べると、明らかにケーブルの無い方がバラつきが少なく、どうやらケーブルが長いためにノイズが乗っていたようである。
これを防ぐには、単純で安いエナメル線ではダメで、ツイストペアとかシールド線とかを使わないとならない。
さて、どうすべきか?
なんだかあまり深入りをしたくない気分になってきたのである。
一番簡単なのは、ブレッドボードを止めて、ちゃんとしたプリント板に配線し、センサーとPICマイコンを近くに配置して、USBケーブルを長くして外へ出すべきなのだろう。
ここまでのテストでは、PIC側のプログラムには手を加えなかったが、これ以上のことをやろうとすると、PIC側のプログラムも色々変えないとならない。
さて、次の目標は何にしましょうか?
HOME