一個超酷的開源uHand2.0機(jī)械手掌項目
uHand2.0是深圳樂幻索爾公司開源的一款機(jī)械手掌,它長下面這個樣子:
1、uHand2.0外觀圖
之前在公眾號就分享過視頻:
學(xué)習(xí)嵌入式可以帶娃,不信你們看
2、uHand2.0硬件原理圖
看似整體非常復(fù)雜(主要是結(jié)構(gòu)),但其實硬件(指電路部分)、軟件一點都不復(fù)雜,我們來看下控制機(jī)械手掌的電路原理圖,控制部分的芯片采用的STM32F103RBT6
底板對接的原理圖如下:
可以看到,以上這些都是我們熟悉的硬件接口,包含LED、蜂鳴器、按鍵、SPI FLASH、舵機(jī)、PS2,控制機(jī)械手掌根據(jù)官方提供的文檔主要四種方式:
-
1、通過PC串口連接C#上位機(jī)控制機(jī)械手掌 -
2、通過體感手套藍(lán)牙模塊連接機(jī)械手掌進(jìn)行控制 -
3、通過Android手機(jī)APP控制機(jī)械手掌 -
4、通過PS2手柄控制機(jī)械手掌
不管是通過什么方式去控制手掌運動,能有一套公有的通信協(xié)議那就再好不過了,那么uHand2.0對這一套協(xié)議也是完全開源的,我們來閱讀一些基礎(chǔ)協(xié)議,以便于我們后面入門各個軟件程序。
3、uHand2.0通信協(xié)議
其中,通信分為兩種:
-
1、用戶主動通過C#上位機(jī)、PS2、PC、APP主動給控制板發(fā)送數(shù)據(jù)
-
2、控制板主動給C#上位機(jī)、PS2、PC、APP發(fā)送數(shù)據(jù)
具體協(xié)議內(nèi)容請公眾號后臺回復(fù):uHand
獲取開源機(jī)械手掌資料,這里就不細(xì)說了。
4、uHand2.0底板控制部分
官方給出的有兩種控制方式,一種是基于STM32、還有一種是基于51單片機(jī),不管是什么平臺控制,軟件邏輯其實都是大同小異,我們就拿常用的STM32軟件進(jìn)行分析吧。
先看下代碼的整體框架:
int main(void)
{
SystemInit(); //系統(tǒng)時鐘初始化為72M SYSCLK_FREQ_72MHz
InitDelay(72); //延時初始化
//設(shè)置NVIC中斷分組2:2位搶占優(yōu)先級,2位響應(yīng)優(yōu)先級
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
InitPWM();
InitTimer2();//用于產(chǎn)生100us的定時中斷
InitUart1();//用于與PC端進(jìn)行通信
InitUart3();//外接模塊的串口
InitADC();
InitLED();
InitKey();
InitBuzzer();
InitPS2();//PS2游戲手柄接收器初始化
InitFlash();
InitMemory();
InitBusServoCtrl();
LED = LED_ON;
BusServoCtrl(1,SERVO_MOVE_TIME_WRITE,500,1000);
BusServoCtrl(2,SERVO_MOVE_TIME_WRITE,500,1000);
BusServoCtrl(3,SERVO_MOVE_TIME_WRITE,500,1000);
BusServoCtrl(4,SERVO_MOVE_TIME_WRITE,500,1000);
BusServoCtrl(5,SERVO_MOVE_TIME_WRITE,500,1000);
BusServoCtrl(6,SERVO_MOVE_TIME_WRITE,500,1000);
while(1)
{
TaskRun();
}
}
由于機(jī)械手掌采用是總線舵機(jī),其實它就是數(shù)字舵機(jī),是基于串行總線開發(fā)的,通常采用一根線就可以完成發(fā)送和接收的操作,十分方便,詳情可以參考開源機(jī)械手掌的資料,這里我們看一下BusServoCtrl
這個函數(shù)實現(xiàn)的功能:
void BusServoCtrl(uint8 id,uint8 cmd,uint16 prm1,uint16 prm2)
{
uint32 i;
uint8 tx[20];
uint8 datalLen = 4;
uint32 checkSum = 0;
switch(cmd)
{
case SERVO_MOVE_TIME_WRITE:
datalLen = SERVO_MOVE_TIME_DATA_LEN;
break;
}
tx[0] = 0x55;
tx[1] = 0x55;
tx[2] = id;
tx[3] = datalLen;
tx[4] = cmd;
tx[5] = prm1;
tx[6] = prm1 >> 8;
tx[7] = prm2;
tx[8] = prm2 >> 8;
for(i = 2; i <= datalLen + 1; i++)
{
checkSum += tx[i];
}
tx[datalLen + 2] = ~checkSum;
UART_TX_ENABLE();
USART2SendDataPacket(tx,datalLen + 3);
}
該函數(shù)的第一個參數(shù)為舵機(jī)id,第二個參數(shù)為指令,第三、四個參數(shù)為指令的參數(shù),例如要控制數(shù)字電機(jī)轉(zhuǎn)動,則需要設(shè)置prm1和prm2值,以讓舵機(jī)能夠在具體的時間內(nèi)轉(zhuǎn)動到具體的位置,最終通過串口將協(xié)議數(shù)據(jù)發(fā)送到數(shù)字舵機(jī),這時候舵機(jī)接收到指令則會響應(yīng)具體的操作,這個函數(shù)是貫穿整個機(jī)械手掌運動的核心函數(shù)。
如果通過C#上位機(jī)、APP控制機(jī)械手掌,那么也是一樣的,C#上位機(jī)發(fā)送給控制板的USART1串口,我們重點看下USART1的串口中斷服務(wù)函數(shù)的實現(xiàn):
void USART1_IRQHandler(void)
{
uint8 i;
uint8 rxBuf;
static uint8 startCodeSum = 0;
static bool fFrameStart = FALSE;
static uint8 messageLength = 0;
static uint8 messageLengthSum = 2;
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
rxBuf = USART_ReceiveData(USART1);//(USART1->DR); //讀取接收到的數(shù)據(jù)
if(!fFrameStart)
{
if(rxBuf == 0x55)
{
startCodeSum++;
if(startCodeSum == 2)
{
startCodeSum = 0;
fFrameStart = TRUE;
messageLength = 1;
}
}
else
{
fFrameStart = FALSE;
messageLength = 0;
startCodeSum = 0;
}
}
if(fFrameStart)
{
Uart1RxBuffer[messageLength] = rxBuf;
if(messageLength == 2)
{
messageLengthSum = Uart1RxBuffer[messageLength];
if(messageLengthSum < 2)// || messageLengthSum > 30
{
messageLengthSum = 2;
fFrameStart = FALSE;
}
}
messageLength++;
if(messageLength == messageLengthSum + 2)
{
if(fUartRxComplete == FALSE)
{
fUartRxComplete = TRUE;
for(i = 0; i < messageLength; i++)
{
UartRxBuffer[i] = Uart1RxBuffer[i];
}
}
fFrameStart = FALSE;
}
}
}
}
中斷服務(wù)函數(shù)實現(xiàn)的功能就是將上位機(jī)、APP、PS2所發(fā)送的數(shù)據(jù)根據(jù)第三小節(jié)提到的協(xié)議格式轉(zhuǎn)換成控制串口舵機(jī)的指令,這個過程是在TaskRun
函數(shù)實現(xiàn)的,由于代碼過于冗長,這里就不放出來了,感興趣可以自行下載研究。另外,該代碼的優(yōu)化空間很大,有些部分寫得不是太合理。
5、uHand2.0開源上位機(jī)
上位機(jī)采用的是C# 微軟WPF框架開發(fā),通過PC串口與機(jī)械手掌進(jìn)行通信。
5.1、分析代碼
當(dāng)調(diào)整拖動桿時,調(diào)用anleChangeHandler
方法:
private void angleChangeHandler(Object sender, RoutedEventArgs e)
{
//手動拖動滑竿的時候才觸發(fā),其他情況引起的變化屏蔽
if (needSendAngelChangeFlag)
{
int id = Convert.ToInt32((e.OriginalSource as ServoView).ServoId);
int angle = (e.OriginalSource as ServoView).CurAngle;
sendAngleCmd(id, angle);
}
}
該方法首先會先確定當(dāng)時控制的是哪個ID的拖桿,調(diào)整的數(shù)值是多少,最終調(diào)用sendAngleCmd
方法:
//發(fā)送拖到滑竿引起的角度變化設(shè)置命令
private void sendAngleCmd(int id, int value)
{
UInt16[] dataSend = new UInt16[MAX_ARGS_LENTH];
for (int i = 0; i < MAX_ARGS_LENTH; i++)
{
dataSend[i] = UNDEFINECMD;
}
dataSend[0] = 1;
dataSend[1] = 0;
dataSend[2] = 0;
dataSend[3] = (byte)id;
dataSend[4] = (byte)(value & 0x00ff);
dataSend[5] = (byte)(value >> 8);
makeAndSendCmd(CMD_MULT_SERVO_MOVE, dataSend);
}
UNDEFINECMD
為public const UInt16 UNDEFINECMD = 0xFFFF;
表示命令buffer默認(rèn)參數(shù)。
MAX_ARGS_LENTH
為public const int MAX_ARGS_LENTH = 25;
表示最大的命令長度
最后通過調(diào)用makeAndSendCmd
將指令打包成為標(biāo)準(zhǔn)的通信協(xié)議包,通過串口發(fā)送給控制板,進(jìn)而控制機(jī)械手掌運動。
//處理參數(shù)轉(zhuǎn)換成標(biāo)準(zhǔn)命令協(xié)議格式然后發(fā)送
private void makeAndSendCmd(int cmdType, UInt16[] args)
{
//sendingData = true;
byte[] dataSend = new byte[50];
byte lenth;
dataSend[0] = 0x55;
dataSend[1] = 0x55;
dataSend[3] = (byte)cmdType;
int i = 0;
while (i <= MAX_ARGS_LENTH && args[i] != UNDEFINECMD)
{
dataSend[4 + i] = (byte)args[i];
i++;
}
lenth = (byte)(i + 2);
//填入長度信息
dataSend[2] = lenth;
WriteData(dataSend, lenth + 2);
}
那么其它幾種控制方式也就大同小異了。獲取所有開源資料請公眾號后臺回復(fù):uHand
獲取開源機(jī)械手掌資料。
搞懂了機(jī)械手掌的基本原理,那么后面要實現(xiàn)一些非??岬捻椖烤秃苋菀桌?,比如機(jī)械手掌控制小車等等,敬請期待!
往期精彩
一些實用的C語言小技巧
由static來談?wù)勀K封裝
C語言常用的一些轉(zhuǎn)換工具函數(shù)收集
結(jié)構(gòu)體對齊原則在自定義協(xié)議解析時的妙用之法
覺得本次分享的文章對您有幫助,隨手點[在看]
并轉(zhuǎn)發(fā)分享,也是對我的支持。
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺僅提供信息存儲服務(wù)。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!