ホーム | 電子工作 | 腕ロボット | 二足歩行ロボット | 無線コントローラ | 工作機械 | 動画 | 自作ソフト | Web作成 | テキスト | その他 |
二足歩行ロボット歩く 2014年9月〜2014年12月) | ||
次のプログラムはロボット側のマイコンのためのものです。 12個のサーボモータを順番に制御しています。 |
||
/*vb側はnisokurtest/**/ #include "3048f.h" #include "nisokusrv.h" unsigned char sci1_rxWait(void); void init_io(void) { /*ITU0、1のクロックを8分週 3125:1ms、4687.5:1.5ms、6250:2ms、62500:20ms*/ ITU0.TCR.BYTE = 0x03; /* ITU0、TCNTクリア禁止 clock 1/8 */ ITU1.TCR.BYTE = 0x03; /* ITU1、TCNTクリア禁止 clock 1/8 */ ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */ ITU.TSTR.BIT.STR1 = 1; /* ITU1 TCNTカウント開始 */ /* シリアル通信初期化*******************************/ SCI1.SCR.BYTE = 0x00; /* SCI1設定 stop,内部クロック */ SCI1.SMR.BYTE = 0x00; /* data8.stop1,pari non */ SCI1.BRR = 40; /* 19200bps */ ITU0.TCNT = 0; /* ITU0 TCNTカウントを0にクリア */ while(ITU0.TCNT <= 3125);/*1ms待つ*/ SCI1.SCR.BYTE = 0x30; /* Tx,Rx有効 ,割込み無効 */ SCI1.SSR.BYTE &= 0x80; /* エラーフラグのクリア */ return; } /* SCI1から1文字受信する****************************/ char sci1_rx(void){ char data; if((SCI1.SSR.BYTE & 0x78)==0) return 0; /* 受信とエラー以外は0を返す*/ if(SCI1.SSR.BIT.RDRF == 1){ /* データ受信が正常 */ data = SCI1.RDR; /* データを受け取りdataに保存 */ SCI1.SSR.BIT.RDRF = 0; /* 受信フラグのクリア*/ return(data); } else{ /* データ受信にエラー発生 */ SCI1.SSR.BYTE &= 0xc7; /* エラーフラグをクリア */ return(0xff); /* エラー時はFFを返す */ } } /*unsigned int型変数(2バイト)をシリアルポートから入力する*/ /*備考:データが来るまで待つ */ unsigned int sci_read_uintA(void) { unsigned int data; unsigned char data_L, data_H; /*unsigned charのデータ2個を受け取る*/ data_H = sci1_rxWait(); data_L = sci1_rxWait(); /*unsigned intのデータに変換する*/ data = (unsigned int)data_H * 256 + (unsigned int)data_L; return(data); } /* SCI1から1文字受信する データが来るまで待つ*/ unsigned char sci1_rxWait(void) { unsigned char data; while((SCI1.SSR.BYTE & 0x78)==0) ; /* 受信とエラー以外は待つ*/ if(SCI1.SSR.BIT.RDRF == 1){ /* データ受信が正常 */ data = SCI1.RDR; /* データを受け取りdataに保存 */ SCI1.SSR.BIT.RDRF = 0; /* 受信フラグのクリア*/ return(data); } else{ /* データ受信にエラー発生 */ SCI1.SSR.BYTE &= 0xc7; /* エラーフラグをクリア */ return((unsigned char)0xff); /* エラー時はFFを返す */ } } /* SCI1に1文字送信する */ void sci1_tx(char data){ while(SCI1.SSR.BIT.TDRE == 0); /*未送信データが送られるまで待つ*/ SCI1.TDR = data; /*送信データのセット*/ SCI1.SSR.BIT.TDRE = 0; /*送信フラグのクリア*/ return; } /* SCI1に文字列を送信する 文字列は'\0'で締めくくっておく**************/ void sci1_strtx(char *str){ while(*str != '\0'){ /* 文字が\0になるまで繰り返す */ sci1_tx(*str); /* 1文字送信*/ str++; /* 次の文字に移る*/ } return; } /*サーボ番号をピン番号に割り当てる*/ void srvWari(int srvNo, int x) { switch(srvNo){ case 0: P3.DR.BIT.B0 = x;break; case 1: P3.DR.BIT.B1 = x;break; case 2: P3.DR.BIT.B2 = x;break; case 3: P3.DR.BIT.B3 = x;break; case 4: P3.DR.BIT.B4 = x;break; case 5: P3.DR.BIT.B5 = x;break; case 6: P3.DR.BIT.B6 = x;break; case 7: P3.DR.BIT.B7 = x;break; case 8: P1.DR.BIT.B0 = x;break; case 9: P1.DR.BIT.B1 = x;break; case 10:P1.DR.BIT.B2 = x;break; case 11:P1.DR.BIT.B3 = x;break; } } void order3(unsigned int *order,unsigned int *data); void order3(unsigned int *order,unsigned int *data) { int i,max,min; max = 0; min = 65535; for(i=0;i<=2;i++) { if( data[i]>=max) { max = data[i]; order[0] = i; } if( data[i]<=min) { min = data[i]; order[2] = i; } } for(i=0;i<=2;i++){ if(i!= order[0] && i!=order[2]) order[1] = i; } } /*プログラムの始まり*/ main() { char signal; int i, j,k,count,Width, pWidth[12],checkState[12],servoNo,switchSignal; int id, idTog, *p, countNum; unsigned int junNo[4]; unsigned int data[4], sData1,sData2,max_num, min_num; unsigned int out_data[4]; P2.DDR = 0x00; /*port2入力に設定 ディップスイッチ用*/ P2.PCR.BYTE = 0xff; /*port2プルアップon */ P3.DDR = 0xff; /*PORT3を出力に設定*/ P1.DDR = 0xff; /*PORT1を出力に設定*/ ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */ /*H8ハードの初期化を行う*/ init_io(); Width = 4687;/*パルス幅 初期値*/ /*無限ループ開始*/ switchSignal ='1'; idTog = 0; while(1){ /*switch3,switch5は故障で使用不可*/ if (P2.DR.BIT.B1 == 0 || P2.DR.BIT.B2 == 0 || P2.DR.BIT.B3 == 0 || P2.DR.BIT.B4 == 0){ if (P2.DR.BIT.B4 == 0) { /*switch2 前進*/ id=2; p = ForWalk[0]; countNum = ForWalkNum; } else if (P2.DR.BIT.B3 == 0) { /*switch4 後退*/ id=5; p = BackWalk[0]; countNum = BackRunNum; } else if (P2.DR.BIT.B2 == 0) { /*switch6 左回転*/ idTog = idTog % 2 +1; id=idTog + 7; if(id==8) {p = LeftRoundF[0]; countNum = LeftRoundFNum;} if(id==9) {p = LeftRoundL[0]; countNum = LeftRoundLNum;} } else if (P2.DR.BIT.B1 == 0) { /*switch7 右回転*/ idTog = idTog % 2 +1; id=idTog + 9; if(id==10) {p = RightRoundF[0]; countNum = RightRoundFNum;} if(id==11) {p = RightRoundL[0]; countNum = RightRoundLNum;} } for(count = 1;count<=countNum ;count++){ /*パルスを生成*/ /*IT1のカウント値をゼロにする*/ ITU1.TCNT = 0; for(i=0;i<=3;i++) { /*IT0のカウント値をゼロにする*/ ITU0.TCNT = 0; for(j=0;j<=2;j++) { k = 3*i+j; srvWari(k,1); out_data[j] = *(p+12*count-12+k); data[j] = 7365 - out_data[j]; } order3(junNo,out_data);/*out_dataの大小を決める*/ for(j=0;j<=2;j++){ while (ITU0.TCNT < data[junNo[j]]); srvWari(3*i+junNo[j] ,0); } }/*i end*/ while(ITU1.TCNT < 60000);/*ITU1が20ms経過するまで待つ*/ ITU1.TCNT = 0; while(ITU1.TCNT < 20000);/*待ち時間*/ }/*for count end*/ } /*switch 2,4,6,7 end*/ else if (P2.DR.BIT.B0 == 0) { /*switch1 */ signal = sci1_rx();/*コマンドが受信されているか確認する*/ if(signal !=0) sci1_tx(signal); if(signal == 'A'){/*RCサーボ制御信号出力ON/OFF命令受信*/ /*スイッチデータ受信*/ switchSignal = sci1_rx(); } if (switchSignal == '1'){ if( signal =='B') { for(i=0;i<12;i++) pWidth[i] = sci_read_uintA();/*パルス幅12個分*/ for(i=0;i<12;i++) checkState[i] =sci_read_uintA();/*サーボチェック状態*/ for(i=0;i<12;i++) if(checkState[i]==1) { servoNo = i; Width = pWidth[i]; } } for(i=1;i<=20;i++){ ITU0.TCNT = 0; /* ITU0 TCNTカウントを0にクリア */ srvWari(servoNo, 1);/*該当のピン出力を1にする*/ while(ITU0.TCNT< 7375 - Width);/*設定パルス幅*/ srvWari(servoNo, 0);/*該当のピン出力を0にする*/ while(ITU0.TCNT < 40000);/*13msまで待つ*/ } } } } } | ||
下半身ロボット歩く 2014年2月〜2014年8月) | ||
H8/3067はFLASHROM128KB、RAM4KB、ワークエリアとして32KBのSRAMを搭載 して20MH動作。 H8/3052はFLASHROM512Kバイト,RAM8KB内蔵の25MHz動作です。 H8/3052はH8/3067と比較すると、SRAMがない分FLASHROMとRAMの容量が4倍、2倍で、 さらに動作周波数が1.25倍です。 H8/3052は、SRAMがないことに目をつぶれば高い能力を有しているので私はH8/3052を 使っています。 この書物では、PCでモーションパターンをCSVファイルに作成して、それをケーブル経由でロボットの マイコン内部のSRAMに転送してロボットを歩かせています。 私の使っているマイコンであるH8(3052)では、SRAMがありませんが、その分ROMの容量が 4倍なのでROMにモーションデータを置こうと思います。プログラム本体はROMに置かれるのでプログラム 中にモーションデータを置こうと思いました。 プログラム中に歩行モーションデータを持つことにより、PCからプログラムをロボットのマイコンに転送した あとはPCとロボットとのケーブルをはずしてロボットのみで動くことができるようになりました。 これは僕にとってすごいことでした。 吉野耕司氏のサイトには、書物のための支援ページがありそこにCSVファイルを作製するC言語プログラムが 載っています。これをダウンロードして安直に使うことができます。 このプログラムで生成されるのは次のようなCSVファイルです。 id, 0 , 腰を落とす data_num, 30 2920, 2579, 1679, 3120, 2175, 3100, 1633, 1404, 2696, 2911, 2829, 1425, 2919, 2580, 1680, 3119, 2175, 3100, 1633, 1404, 2696, 2911, 2829, 1425, id, 0 , 腰を落とす とありますが、「『腰を落とす』という動作で、『そのID番号が0』という意味です。 この動作のほかに、全部で11個ほどの動作がありその動作ごとに30行ほどがついています。 data_num, 30 とありますが、これは「このようなデータが30行続いています。」という意味です。 すると、すべての動作を合わせると何百行にもなります。 C言語プログラムで使えるデータ形式は次のような形です。 int Wdown[][12]={{2920, 2579, 1679, 3120, 2175, 3100, 1633, 1404, 2696, 2911, 2829, 1425}, {2919, 2580, 1680, 3119, 2175, 3100, 1633, 1404, 2696, 2911, 2829, 1425}, {2916, 2583, 1683, 3116, 2175, 3100, 1633, 1404, 2696, 2911, 2829, 1425}, int WdownNum = 30; 手作業でこの形に直すのは簡単ですが、百行を超えると嫌になってきます。 そこでこのための変換プログラムを作ることにしました。 以下にこの変換プログラム(C言語DOS)を載せます。 マイコンのためのプログラムのメインのソースファイルにデータ部分を入れると非常に見にくく なります。 そこでこのデータだけを別のファイル(motionData.c)に持ちます。 この別ファイルのデータを使うために外部変数を使うという宣言文であるextern int が要ります。 このextern 文をヘッダーファイル中(nisokusrv.h)に入れます。まず変換プログラムです。 | ||
#include "stdio.h" #include "string.h" #include "stdlib.h" #include "conio.h" #include "osamu.h" /* コンマ区切りの文字列を部分部分に分解する。 byte *string 文字列motionData.c *ptr[] 部分文字列のポインタ 戻り値=部分文字列の数 */ int csv_part(char *string,char *ptr[]) { char *pointer; int i; i=0; ptr[i] = pointer = string; while(1){ if ((pointer=strstr(ptr[i],","))==NULL) break; i++; *pointer = '\0'; ptr[i] = pointer+1; } return(i+1); } /*the end of csv_part()*/ int main(void){ char numStr[20],*ptr[30]; int data_num,i,num[20],k,num1; errno_t err; FILE *fpo2,*fp; char line[256],str[300]; printf("\n"); printf("*** 番号を選んで下さい ***\n"); printf(" 1:CSVから配列のファイルを作る\n"); printf(" 2:終了(1以外も・・・)\n"); scanf_s ("%d",&num1, 3); if(num1 != 1) exit(0); fopen_s(&fpo2, "motionData.c" , "wt") ; //ファイルを開く if ((err = fopen_s(&fp,"pen4_motiondata.csv" , "rt")) != 0) exit(0); while(1){ if(fgets(line, 256, fp) == NULL) break;//1行目を読み込む if (strstr(line,"id")!=NULL) { data_num=csv_part(line,ptr); strcpy_s(str,ptr[1]); i =atoi(str); fgets(line, 256, fp); //2行目を読み込む data_num=csv_part(line,ptr); num[i] = atoi(ptr[1]);//何行あるかを読み込む switch(i){ case 0: { strcpy_s(str,"int Wdown[][12]={{"); break; } case 1: strcpy_s(str,"};\nint Stand[][12]={{");break; case 2: strcpy_s(str,"};\nint ForWalk[][12]={{");break; case 3: strcpy_s(str,"};\nint FirstOne[][12]={{");break; case 4: strcpy_s(str,"};\nint LastOne[][12]={{");break; case 5: strcpy_s(str,"};\nint BackWalk[][12]={{");break; case 6: strcpy_s(str,"};\nint BackFirst[][12]={{");break; case 7: strcpy_s(str,"};\nint BackLast[][12]={{");break; case 8: strcpy_s(str,"};\nint LeftRoundF[][12]={{");break; case 9: strcpy_s(str,"};\nint LeftRondL[][12]={{");break; case 10: strcpy_s(str,"};\nint RightRoundF[][12]={{");break; case 11: strcpy_s(str,"};\nint RightRoundL[][12]={{");break; } fgets(line, 256, fp); } else strcpy_s(str,",\n{"); *(line+strlen(line)-3)=0; strcat_s(str,line); strcat_s(str,"}"); fprintf(fpo2, str); } strcpy_s(str,"};\n");//最終行の設定 fprintf(fpo2, str); k = i; for(i=0;i<=k;i++) { switch(i){ case 0: { strcpy_s(str,"int WdownNum = "); break; } case 1: strcpy_s(str,"int StandNum = ");break; case 2: strcpy_s(str,"int ForWalkNum = ");break; case 3: strcpy_s(str,"int FirstOneNum = ");break; case 4: strcpy_s(str,"int LastOneNum = ");break; case 5: strcpy_s(str,"int BackRunNum = ");break; case 6: strcpy_s(str,"int BackFirstNum = ");break; case 7: strcpy_s(str,"int BackLastNum = ");break; case 8: strcpy_s(str,"int LeftRoundFNum = ");break; case 9: strcpy_s(str,"int LeftRondLNum = ");break; case 10: strcpy_s(str,"int RightRoundFNum = ");break; case 11: strcpy_s(str,"int RightRoundLNum = ");break; } _itoa_s(num[i],numStr,10); strcat_s(str,numStr); strcat_s(str,";\n"); fprintf(fpo2, str); } fclose(fp); fclose(fpo2); printf("書き込み終了です"); _getch(); return 1; } | ||
次にヘッダーファイル(nisokusrv.h)です。 | ||
extern int Wdown[][12]; extern int Stand[][12]; extern int ForWalk[][12]; extern int FirstOne[][12]; extern int LastOne[][12]; extern int BackWalk[][12]; extern int BackFirst[][12]; extern int BackLast[][12]; extern int LeftRoundF[][12]; extern int LeftRondL[][12]; extern int RightRoundF[][12]; extern int RightRoundL[][12]; extern int WdownNum; extern int StandNum; extern int ForWalkNum; extern int FirstOneNum; extern int LastOneNum; extern int BackRunNum; extern int BackFirstNum; extern int BackLastNum; extern int LeftRoundFNum; extern int LeftRondLNum; extern int RightRoundFNum; extern int RightRoundLNum; | ||
マイコンのC言語プログラムです。 | ||
#include "3048f.h" #include "nisokusrv.h" unsigned char sci1_rxWait(void); void init_io(void) { /*ITU0、1のクロックを8分週 */ /*3125:1ms、4687.5:1.5ms、6250:2ms、62500:20ms*/ ITU0.TCR.BYTE = 0x03; /* ITU0、TCNTクリア禁止 clock 1/8 */ ITU1.TCR.BYTE = 0x03; /* ITU1、TCNTクリア禁止 clock 1/8 */ ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */ ITU.TSTR.BIT.STR1 = 1; /* ITU1 TCNTカウント開始 */ /* シリアル通信初期化*******************************/ SCI1.SCR.BYTE = 0x00; /* SCI1設定 stop,内部クロック */ SCI1.SMR.BYTE = 0x00; /* data8.stop1,pari non */ SCI1.BRR = 40; /* 19200bps */ ITU0.TCNT = 0; /* ITU0 TCNTカウントを0にクリア */ while(ITU0.TCNT <= 3125);/*1ms待つ*/ SCI1.SCR.BYTE = 0x30; /* Tx,Rx有効 ,割込み無効 */ SCI1.SSR.BYTE &= 0x80; /* エラーフラグのクリア */ return; } /* SCI1から1文字受信する****************************/ char sci1_rx(void){ char data; if((SCI1.SSR.BYTE & 0x78)==0) return 0; /* 受信とエラー以外は0を返す*/ if(SCI1.SSR.BIT.RDRF == 1){ /* データ受信が正常 */ data = SCI1.RDR; /* データを受け取りdataに保存 */ SCI1.SSR.BIT.RDRF = 0; /* 受信フラグのクリア*/ return(data); } else{ /* データ受信にエラー発生 */ SCI1.SSR.BYTE &= 0xc7; /* エラーフラグをクリア */ return(0xff); /* エラー時はFFを返す */ } } /*unsigned int型変数(2バイト)をシリアルポートから入力する*/ /*備考:データが来るまで待つ */ unsigned int sci_read_uintA(void) { unsigned int data; unsigned char data_L, data_H; /*unsigned charのデータ2個を受け取る*/ data_H = sci1_rxWait(); data_L = sci1_rxWait(); /*unsigned intのデータに変換する*/ data = (unsigned int)data_H * 256 + (unsigned int)data_L; return(data); } /* SCI1から1文字受信する データが来るまで待つ*/ unsigned char sci1_rxWait(void) { unsigned char data; while((SCI1.SSR.BYTE & 0x78)==0) ; /* 受信とエラー以外は待つ*/ if(SCI1.SSR.BIT.RDRF == 1){ /* データ受信が正常 */ data = SCI1.RDR; /* データを受け取りdataに保存 */ SCI1.SSR.BIT.RDRF = 0; /* 受信フラグのクリア*/ return(data); } else{ /* データ受信にエラー発生 */ SCI1.SSR.BYTE &= 0xc7; /* エラーフラグをクリア */ return((unsigned char)0xff); /* エラー時はFFを返す */ } } /* SCI1に1文字送信する */ void sci1_tx(char data){ while(SCI1.SSR.BIT.TDRE == 0); /*未送信データが送られるまで待つ*/ SCI1.TDR = data; /*送信データのセット*/ SCI1.SSR.BIT.TDRE = 0; /*送信フラグのクリア*/ return; } /* SCI1に文字列を送信する 文字列は'\0'で締めくくっておく**************/ void sci1_strtx(char *str){ while(*str != '\0'){ /* 文字が\0になるまで繰り返す */ sci1_tx(*str); /* 1文字送信*/ str++; /* 次の文字に移る*/ } return; } /*サーボ番号をピン番号に割り当てる*/ void srvWari(int srvNo, int x) { switch(srvNo){ case 0: P3.DR.BIT.B0 = x;break; case 1: P3.DR.BIT.B1 = x;break; case 2: P3.DR.BIT.B5 = x;break; case 3: P3.DR.BIT.B3 = x;break; case 4: P3.DR.BIT.B4 = x;break; case 5: P3.DR.BIT.B7 = x;break; case 6: P3.DR.BIT.B6 = x;break; case 7: P3.DR.BIT.B7 = x;break; case 8: P1.DR.BIT.B0 = x;break; case 9: P1.DR.BIT.B1 = x;break; case 10:P1.DR.BIT.B2 = x;break; case 11:P1.DR.BIT.B3 = x;break; } } /*プログラムの始まり*/ main() { char signal; int i, j,k,count,Width, pWidth[12],checkState[12],servoNo,switchSignal; int *p, countNum; unsigned char junNo[3]; unsigned int data[4], sData1,sData2,max_num, min_num; unsigned int out_data[4]; unsigned int data0[4][3]; P2.DDR = 0x00; /*port2入力に設定 ディップスイッチ用*/ P2.PCR.BYTE = 0xff; /*port2プルアップon */ P3.DDR = 0xff; /*PORT3を出力に設定*/ P1.DDR = 0xff; /*PORT1を出力に設定*/ ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */ /*H8ハードの初期化を行う*/ init_io(); Width = 4687;/*パルス幅 初期値*/ /*無限ループ開始*/ switchSignal ='1'; while(1){ p = ForWalk[0]; countNum = ForWalkNum; for(count = 1;count<=countNum ;count++){ /*パルスを生成*/ /*IT0のカウント値をゼロにする*/ ITU0.TCNT = 0; srvWari(0,1); srvWari(1,1); for(j=0;j<=1;j++) { out_data[j] = *(p+12*count-12+j); data[j] = 7365 - out_data[j]; } if(out_data[0] >= out_data[1] ) { junNo[0] = 0; junNo[1] = 1; } else { junNo[0] = 1; junNo[1] = 0; } for(j=0;j<=1;j++){ while (ITU0.TCNT < data[junNo[j]]); srvWari(junNo[j] ,0); } /*ITU0のカウント値をゼロにする*/ ITU0.TCNT = 0; srvWari(2,1); srvWari(3,1); for(j=0;j<=1;j++) { out_data[j] = *(p+12*count-12+j+2); data[j] = 7365 - out_data[j]; } if(out_data[0] >= out_data[1] ) { junNo[0] = 0; junNo[1] = 1; } else { junNo[0] = 1; junNo[1] = 0; } for(j=0;j<=1;j++){ while (ITU0.TCNT < data[junNo[j]]); srvWari(2+junNo[j] ,0); } while(ITU0.TCNT < 60000);/*20ms経過するまで待つ*/ ITU0.TCNT = 0; while(ITU0.TCNT < 60000);/*20ms経過するまで待つ*/ }/*for count end*/ }/*while end*/ } |
二足歩行の基礎実験(足 2013年10月〜2014年1月) |
![]() 二足歩行ロボットを歩かせるためのプログラムを考え試しているのですが、なかなか思い通りに 動作してくれません。 「基本的にどうなん…」ということを検証するために、足だけの部分を作りました。 サーボは、以前に使用電力が多すぎてすぐにバッテリーが消耗するものがあったのでそれを 利用しました。 上下2段に分けて、下の段にマイコン基盤とバッテリーを配置しました。 以下に、PC側で操作した通りに足が動作する最も基本的なプログラムを載せます。 この装置を使っていろいろな実験を試みるつもりです。 最初にPCで操作するvisual Basicで組んだプログラム(動作画面とソースプログラム)を、 2番目にマイコン部のC言語プログラムを載せます。 ![]() 左がPC側のVBの実行画面です。 4つのチェックボックスがあります。 どれか一つをチェックして右の矢印をクリックするとチェックした個所の数字が増減します。 送信ボタンをクリックすると該当の足の膝関節が設定した数字の位置まで動きます。 次がそのためのVBのプログラムです。 |
Public Class Form1 '複数のイベントで使用する変数の定義******************** Dim servo_max, servo_min, targetServo, checkState(14) As Integer Dim ar_checkbox(12) As CheckBox Dim i As Integer Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load Dim response As MsgBoxResult 'シリアルポートをオープンする Try SerialPort1.Open() Catch ex As IO.IOException response = MsgBox("エラー!シリアルポートのオープンに失敗しました。 このまま起動しますか?", MsgBoxStyle.YesNo, "起動の確認") End Try 'Noの場合はプログラム終了 If response = MsgBoxResult.No Then End '変数の初期化 servo_max = 5565 'RCサーボ出力最大値 servo_min = 0 'RCサーボ出力最小値 'テキストボックスの配列初期化 ar_checkbox(0) = CheckBox1 ar_checkbox(1) = CheckBox2 ar_checkbox(2) = CheckBox3 ar_checkbox(3) = CheckBox4 ar_checkbox(4) = CheckBox5 ar_checkbox(5) = CheckBox6 ar_checkbox(6) = CheckBox7 ar_checkbox(7) = CheckBox8 ar_checkbox(8) = CheckBox9 ar_checkbox(9) = CheckBox10 ar_checkbox(10) = CheckBox11 ar_checkbox(11) = CheckBox12 End Sub 'ラジオボタンで選択されたRCサーボの出力値を25増やす******************** Private Sub Button4_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button4.Click '変数定義 Dim i, j, k As Integer j = 0 For i = 0 To 11 If ar_checkbox(i).Checked = True Then checkState(i) = 1 j = j + 1 k = i End If Next If j > 1 Then MsgBox("複数のサーボにチェックがあります") Return End If 'サーボの出力値を250減らす ar_checkbox(k).Text = Format(Val(ar_checkbox(k).Text) + 25) End Sub 'ラジオボタンで選択されたRCサーボの出力値を25減らす******************** Private Sub Button5_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button5.Click '変数定義 '変数定義 Dim i, j, k As Integer j = 0 For i = 0 To 11 If ar_checkbox(i).Checked = True Then checkState(i) = 1 j = j + 1 k = i End If Next If j > 1 Then MsgBox("複数のサーボにチェックがあります") Return End If 'サーボの出力値を25減らす ar_checkbox(k).Text = Format(Val(ar_checkbox(k).Text) - 25) End Sub 'ラジオボタンで選択されたRCサーボの出力値を300増やす******************** Private Sub Button6_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button6.Click '変数定義 Dim i, j, k As Integer j = 0 For i = 0 To 11 If ar_checkbox(i).Checked = True Then checkState(i) = 1 j = j + 1 k = i End If Next If j > 1 Then MsgBox("複数のサーボにチェックがあります") Return End If 'サーボの出力値を300増やす ar_checkbox(k).Text = Format(Val(ar_checkbox(k).Text) + 300) End Sub 'ラジオボタンで選択されたRCサーボの出力値を300減らす******************** Private Sub Button7_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button7.Click '変数定義 Dim i, j, k As Integer j = 0 For i = 0 To 11 If ar_checkbox(i).Checked = True Then checkState(i) = 1 j = j + 1 k = i End If Next If j > 1 Then MsgBox("複数のサーボにチェックがあります") Return End If ar_checkbox(k).Text = Format(Val(ar_checkbox(k).Text) - 300) End Sub 'マイコンにチェックされたRCサーボ信号出力値を送信する******************** Private Sub Button8_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button8.Click '変数定義 Dim i, checkState(12) As Integer Dim err_msg As String Dim buff(64) As Byte For i = 0 To 11 If ar_checkbox(i).Checked = True Then checkState(i) = 1 Else checkState(i) = 0 End If Next 'テキストボックスの数値が最大/最小範囲にあるかチェックする i = targetServo If Val(ar_checkbox(i).Text) < servo_min Or Val(ar_checkbox(i).Text) > servo_max Then err_msg = Format(i) + "番サーボの出力値が制限範囲 (" err_msg = err_msg + Format(servo_min) + "〜" + Format(servo_max) + ") を超えています。 出力を見合わせます。" MsgBox(err_msg, MsgBoxStyle.OkOnly, ) Exit Sub End If 'マイコンにRCサーボ制御信号 状態変更コマンドを送信 write_sio_command("B") 'targetサーボに信号出力値を送信する For i = 0 To 11 write_sio_int(ar_checkbox(i).Text) Next 'ターゲットサーボのチェック状態を送る For i = 0 To 11 write_sio_int(Str(checkState(i))) Next End Sub 'ロボット制御コマンドキャラクター送信******************** Private Sub write_sio_command(ByVal command As Char) '変数定義 Dim command_char(64), buff(64) As Byte 'マイコンにコマンドを送信 command_char(0) = Asc(command) SerialPort1.Write(command_char, 0, 1) '受信確認待ちとエラー処理 Try buff(0) = 0 SerialPort1.Read(buff, 0, 1) ' MsgBox(buff(0)) Catch ex As TimeoutException MsgBox("通信エラーです。ロボットが規定時間以内に応答しません") Exit Sub End Try If command_char(0) <> buff(0) Then MsgBox("通信エラーです。ロボットがコマンドに正しく応答しません " ) Exit Sub End If End Sub '2バイトの整数をシリアルポートから出力する(引数は文字列)******************** Private Sub write_sio_int(ByVal input_str As String) '変数定義 Dim buff(64) As Byte Dim output_val As Integer 'テキストボックスの文字列を整数に変換 output_val = Val(input_str) '変換した整数の出力値を上位バイトと下位バイトに分割してバッファにセット buff(0) = Int(output_val / 256) buff(1) = output_val - Int(output_val / 256) * 256 'シリアルポートから出力 SerialPort1.Write(buff, 0, 2) End Sub 'RCサーボの制御信号出力開始******************** Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click '変数定義 Dim command_char(64) As Byte 'マイコンにRCサーボ制御信号 状態変更コマンドを送信 write_sio_command("A") 'RCサーボ制御信号 開始コマンドを送信 command_char(0) = Asc("1") SerialPort1.Write(command_char, 0, 1) End Sub 'RCサーボの制御信号出力停止******************** Private Sub Button3_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button3.Click '変数定義 Dim command_char(64) As Byte 'マイコンにRCサーボ制御信号 状態変更コマンドを送信 write_sio_command("A") 'RCサーボ制御信号 停止コマンドを送信 command_char(0) = Asc("0") SerialPort1.Write(command_char, 0, 1) End Sub Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click End End Sub End Class |
次が足の基盤に設置したマイコン側のC言語プログラムです。 |
#include "3048f.h" /* 待ち時間発生 引数に、必要なミリ秒を指定する***********/ void init_io(void) { /*ITU0のクロックを8分週 3125:1ms、4687.5:1.5ms、6250:2ms、62500:20ms*/ ITU0.TCR.BYTE = 0x03; /* ITU0、TCNTクリア禁止 clock 1/8 */ ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */ /* シリアル通信初期化*******************************/ SCI1.SCR.BYTE = 0x00; /* SCI1設定 stop,内部クロック */ SCI1.SMR.BYTE = 0x00; /* data8.stop1,pari non */ SCI1.BRR = 40; /* 19200bps */ ITU0.TCNT = 0; /* ITU0 TCNTカウントを0にクリア */ while(ITU0.TCNT <= 3125);/*1ms待つ*/ SCI1.SCR.BYTE = 0x30; /* Tx,Rx有効 ,割込み無効 */ SCI1.SSR.BYTE &= 0x80; /* エラーフラグのクリア */ return; } /* SCI1から1文字受信する****************************/ char sci1_rx(void){ char data; while((SCI1.SSR.BYTE & 0x78)==0); /* 受信とエラーのフラグが立つまで待つ*/ if(SCI1.SSR.BIT.RDRF == 1){ /* データ受信が正常 */ data = SCI1.RDR; /* データを受け取りdataに保存 */ SCI1.SSR.BIT.RDRF = 0; /* 受信フラグのクリア*/ return(data); } else{ /* データ受信にエラー発生 */ SCI1.SSR.BYTE &= 0xc7; /* エラーフラグをクリア */ return(0xff); /* エラー時はFFを返す */ } } /*unsigned int型変数(2バイト)をシリアルポートから入力する*/ /*備考:データが来るまで待つ */ unsigned int sci_read_uintA(void) { unsigned int data; unsigned char data_L, data_H; /*unsigned charのデータ2個を受け取る*/ data_H = sci1_rxWait(); data_L = sci1_rxWait(); /*unsigned intのデータに変換する*/ data = (unsigned int)data_H * 256 + (unsigned int)data_L; return(data); } /* SCI1から1文字受信する データが来るまで待つ****************************/ unsigned char sci1_rxWait(void){ unsigned char data; while((SCI1.SSR.BYTE & 0x78)==0) ; /* 受信とエラー以外は待つ*/ if(SCI1.SSR.BIT.RDRF == 1){ /* データ受信が正常 */ data = SCI1.RDR; /* データを受け取りdataに保存 */ SCI1.SSR.BIT.RDRF = 0; /* 受信フラグのクリア*/ return(data); } else{ /* データ受信にエラー発生 */ SCI1.SSR.BYTE &= 0xc7; /* エラーフラグをクリア */ return((unsigned char)0xff); /* エラー時はFFを返す */ } } /* SCI1に1文字送信する */ void sci1_tx(char data){ while(SCI1.SSR.BIT.TDRE == 0); /*未送信データが送られるまで待つ*/ SCI1.TDR = data; /*送信データのセット*/ SCI1.SSR.BIT.TDRE = 0; /*送信フラグのクリア*/ return; } /* SCI1に文字列を送信する 文字列は'\0'で締めくくっておく**************/ void sci1_strtx(char *str){ while(*str != '\0'){ /* 文字が\0になるまで繰り返す */ sci1_tx(*str); /* 1文字送信*/ str++; /* 次の文字に移る*/ } return; } /*サーボ番号をピン番号に割り当てる*/ void srvWari(int srvNo, int x) { switch(srvNo){ case 0: P3.DR.BIT.B0 = x;break; case 1: P3.DR.BIT.B1 = x;break; case 2: P3.DR.BIT.B2 = x;break; case 3: P3.DR.BIT.B3 = x;break; case 4: P3.DR.BIT.B4 = x;break; case 5: P3.DR.BIT.B5 = x;break; case 6: P3.DR.BIT.B6 = x;break; case 7: P3.DR.BIT.B7 = x;break; case 8: P1.DR.BIT.B0 = x;break; case 9: P1.DR.BIT.B1 = x;break; case 10:P1.DR.BIT.B2 = x;break; case 11:P1.DR.BIT.B3 = x;break; } } /*プログラムの始まり*/ /*PCから3つの信号を受け取り該当のサーボを動作させる */ /*1:S(制御コマンド)2:サーボ番号0〜11 3:パルス幅相当数値を順番に受けて動作*/ main() { char signal; int i, j,Width, pWidth[12],checkState[12],servoNo,switchSignal; P3.DDR = 0xff; /*PORT3を出力に設定*/ P1.DDR = 0xff; /*PORT1を出力に設定*/ ITU.TSTR.BIT.STR0 = 1; /* ITU0 TCNTカウント開始 */ /*H8ハードの初期化を行う*/ init_io(); Width = 4687;/*パルス幅 初期値*/ /*無限ループ開始*/ switchSignal ='1'; while(1){ signal = sci1_rx();/*コマンドが受信されているか確認する*/ sci1_tx(signal); if(signal == 'A'){/*RCサーボの制御信号 出力ON/OFF命令受信*/ /*スイッチデータ受信*/ switchSignal = sci1_rx(); } if (switchSignal == '1'){ if( signal =='B') { for(i=0;i<12;i++) pWidth[i] = sci_read_uintA();/*パルス幅12個分*/ for(i=0;i<12;i++) checkState[i] =sci_read_uintA();/*サーボチェック状態*/ for(i=0;i<12;i++) if(checkState[i]==1) { servoNo = i; Width = pWidth[i]; } } for(i=1;i<=20;i++){ ITU0.TCNT = 0; /* ITU0 TCNTカウントを0にクリア */ srvWari(servoNo, 1);/*該当のピン出力を1にする*/ while(ITU0.TCNT< 7375 - Width) ;/* 設定値のパルス幅*/ srvWari(servoNo, 0);/*該当のピン出力を0にする*/ while(ITU0.TCNT < 40000);/*13ms経過するまで待つ*/ } } } } |
腕ロボット(2009年11月〜2010年2月) |
腕ロボットを基盤に組み込んで構想を練る | |
![]() ![]() ![]() サーボモータを組み合わせて作った腕・H8マイコン(3052)・電池・可変抵抗をひとつのボードに組み込んで腕ロボットを作っていきます。 「可変抵抗を回すとサーボモータが回転して腕が動く」というのが腕ロボットの全体構想です。 上図がその概要です。左図がその全体図。真中が下の基盤の拡大図。右が腕をコントロールするボリューム4個を小さい基盤にセットしたものです。 サーボモータの制御のためのPWM(パルス幅変調)に使うポートはPA、PB(CN1)を使います。 PWMに使うポートのピンは次の様に使います。 ![]() これで同時に5個のサーボを制御できますね。しかしこれ以上のサーボの制御はこのままでは出来ませんので、後でゆっくり考えることにします。 可変抵抗を回してその回転量を数値に変換するためのAD返還に使うポートはP7(CN2)を使います。 AD変換の場合は、P7のBit0(AN0)からBit5(AN5)まで同時に6個のAD変換機能が使えます。 そのための回路図はこのようになります。 可変抵抗VR0、VR1、VR2、VR3にH8マイコン(3052)から5Vをとります。サーボモータSERVO1、2、3、4へは外部電源 (単3乾電池4個)から6Vをとります。マイコンからの5Vはレギュレータを用いて作られているものなのでモータの電源用にしてはいけません。 可変抵抗のグラウンドとサーボモータのグラウンドはマイコンのグラウンドと同じにします。 この回路図で基盤にCPUボードを組み込んでC言語でプログラムをCPUに転送しました。 しかし、同時に3つのPWM機能を使うと制御不可能になりました。同時に2つのPWMでの制御はうまくいったのですが・・・。 |
|
やむを得ずソフトウエアでPWM波形を作る |
|
やむをえずITUを使ったPWMでの制御はあきらめてプログラムでPWM波形を生成することに方針変更します。20ms内に
1ms幅から2ms幅のパルス波形を生成出来るように考えます。 この腕ロボットの完成後には足ロボットを作る計画でした。さらにその先には足・腕を両方備えたロボット・・・、という予定です。そうなると サーボの数は5個ぐらいではすみません。H8(3052)に搭載されている5個のITUではまかないきれません。 そうなると何か考える必要がありましたので、そう思えばその時期が早まっただけと思えばいいわけです・・・・・。 ![]() 例えば1.2msのパルスを発生させる場合、サーボの信号線につないだPIOのピンに最初1の値を与えて、1.2msの時間が 来たらそのPIOのピンに0の値を与えますと1.2msのパルスを作ることができます。左図ではポート7のビット1に信号を与える ようになっています。 ![]() 「0.00992ms待つ」と書いてあるのがなぜかは、ITUによる1単位時間を0.00992msにしたからですが、 ITUの設定をそんな風にした理由は以下に説明します。 ボリュームを回してそれに対応する電圧に応じて1msから2msまでのパルスを発生させたいわけです。ボリュームの回転に応じてサーボに円滑な動きを 与えるにはサーボの回転角がどれぐらいの刻みであればいいかを考えます。 1msから2msまでのパルス幅でサーボは左右に135度ずつ、合計270度回転します。この270度を100段階のきざみ、 つまり2.7度ずつサーボが動くと滑らかなサーボの動きが得られると考えました。 すると、1msから2msの間の1msの時間を100で割ると最小限の時間単位を0.01msにすればいいことがわかります。 タイムカウンタの基準として内部クロックの1/8にしていました。なぜそうしてきたかというと参考にしている書物・ネットの 資料がそうなっていたからです。今回もこれで試みます。タイムカウンタをクリアして設定時間が来ましたよ、という知らせのための GRAの値が 3125で1msとなります。0.01msの設定時間だと31.25です。GRAの値は整数値なので31として考えます。 厳密には31の時間間隔は31÷31.25×0.01ms=0.00992msです。つまり0.00992msが基準時間間隔となります。 0.01msより少し小さいのでより細かくサーボの動きを決めることが出来るでしょう。 20ms内でパルスを与えますので20msに対応する値の62500を31で割ると約2016です。基準時間間隔を0.0992msとし ますと2016時間間隔を経過しますと0.00992ms×2016=19.99872msとなりだいたい20msに近い値が得られます のでこれでやることにします。 wait0() という関数で1単位時間である0.00992msの待ち時間を作って、この関数を1回通るたびに0.00992ms経過している ようにします。 サーボに与える信号として、20msのなかで1msから2msの幅のパルスを作ればいいので、このwait0() という関数を101回通れば 約1ms経過している、202回通れば約2ms経過している、と考えました。 さらに可変抵抗を回したとき電圧が0Vから5V変化しますのでこの変化によるパルス幅を1msに加えた時のパルス幅を サーボに与えればいいと考えました。 | |
このための実際のC言語プログラムは次のようになります。 最初、49行目から52行目までの右辺の最初の値を101にしていました。説明した理論値ではcount0()関数を101回通った時に 1ms経過しますので、pwmWidth1 = 101 + data1 としたときにパルス幅が(1ms+ボリュームによる可変幅)となるはずです。 しかし実際にやった時、サーボの回転角が予想以上に小さくなりました。そこでこの101を70に変更しました。 この理由としては、62行目から66行目までの処理時間が0.01msの時間に比べて案外大きいのだろうと推測できます。 またdata1は39・40行目にあるように、ボリュームによる可変電圧(0〜5V)をAD変換して8ビット(0〜255)の数値に直した ものをパルスの可変幅である1msに対応した時間幅100に直すために2.55で割っていました。 これも実際やってみて調整の結果3で割ることにしました。 また20ms周期とするために理論値ではwait0()関数を約2000回通らなければなりません。67行目にIF関数でその回数としての 値をperiodLengthに2000を与えていました。この値も上記の理由で1500にしました(37行目)。 69行目にこの20ms周期のパルスを何回発生させれば新しくボリュームの可変電圧をモニターするかのカウントを5にしていました。 しかし、実際にやってみると1にした時に、ボリュームの変化に合したサーボの動きが円滑になることがわかりました。結局は このカウントの仕組みは不要であるわけです。 ![]() ![]() |