資料介紹
描述
概述
讓你的草坪在夏日的陽光下煥然一新對于房主來說通常是一件可怕的家務事。機器人割草機可以消除夏季最令人生畏的家務之一。它看起來類似于機器人吸塵器。你可以看著它在你的草坪上飛來飛去,讓你的草坪保持最佳狀態。為割草機配備支持物聯網的功能,例如藍牙或 Wi-Fi 連接、GPS 導航和靈活的調度功能,可以讓您在任何地方使用移動應用程序控制割草機并跟蹤割草進度。
特征
?
腳步
第 1 步:硬件設計
割草機包括四個硬件部件。
- 無線連接系統:涂鴉的網絡模塊允許在任何地方在移動應用程序上遠程控制和查看割草機狀態。
- 動力系統:12V鋰電池組配合減速電機推動橡膠輪胎車輪前進。
- 割草控制系統:兩個伺服系統可調節切割高度,切割刀片和驅動輪采用無刷直流電機。
- 定位導航系統:集成GNSS模塊和磁傳感器,實現導航定位能力。
無線連接系統
將您的 MCU 與涂鴉的網絡模塊對接,讓您的產品支持物聯網并連接到云端。
涂鴉提供一站式物聯網開發服務,包括典型物聯網產品的三大要素,即網絡模塊、移動應用和云服務。借助涂鴉MCU SDK、一體機APP和控制面板,您可以輕松專注于應用開發,輕松將您的產品接入涂鴉IoT平臺,快速享受云服務。
MCU SDK 是根據您在涂鴉物聯網平臺上定義的產品特性自動生成的。為了方便與協議的接口,MCU SDK 內置了對通信和協議解析的支持。您可以將此 SDK 添加到現有項目中并完成所需的配置以實現 MCU 程序開發。

硬件資源要求
MCU 的 SDK 要求如下。如果你的 MCU 沒有足夠的資源,你可以參考 SDK 中的函數來對接涂鴉的協議。
- 內存:4 KB
- RAM:需要大約 100 字節的 RAM,具體取決于數據點 (DP) 的數據長度。如果啟用 OTA 更新,它必須大于 260 字節。
- 嵌套函數:9 級。
選擇網絡模塊
涂鴉針對物聯網項目的各種需求,提供了一系列不同協議的模塊,如Wi-Fi 模塊、Wi-Fi 和藍牙 LE 模塊。
為了簡化開發過程,您可以選擇涂鴉三明治評估套件。
電源系統
電源
通常,市場上的割草機由氣體或電池供電。
燃氣割草機只要有足夠的燃料并且比電池供電的割草機更強大,就可以跑很遠。然而,它比電池選項產生更多的噪音,并在整個氣體燃燒過程中產生排放。
我們選擇環保鋰電池組為我們的割草機供電,以減輕噪音和污染影響。
電池
建議使用 3S 或 4S 12V 鋰離子電池。鋰離子電池的充放電倍率可以達到15C甚至超過20C,保證了強大的動力推動車輪前進。如果您不選擇鋰離子電池,請選擇使用壽命長且最大輸出電流大于 5A 的電池。

我們需要一個降壓轉換器來為不同的系統提供輸出電壓。
降壓轉換器LM2596提供 3.3V、5V、12V 的固定輸出電壓和可調輸出版本。它具有以下特點。
- 輸入電壓范圍高達 40 V。
- 可調版本輸出電壓范圍:在線路和負載條件下最大為 1.2V 至 37V ±4%。
- 3A 輸出負載電流。
- 高效率。
- 熱關斷和限流保護。

齒輪馬達
減速電機可以定義為直流電機的延伸。它有一個連接到電機的齒輪組件,以增加扭矩并降低速度。
減速電機的額定電壓必須與電池的額定電壓相匹配。12V 電機的電源電壓范圍為 11V 至 16V。
我們選用MG513直流12V減速電機,額定輸出電流0.36A。其減速比為1/60,空載轉速為183 RPM。電機可提供 2 kgf.cm 的扭矩和 6 kg 的最大負載。使用 65 mm 橡膠輪,割草機的速度可以達到 0.5 m/s。
電機具有內置霍爾效應傳感器。基于磁感應,我們可以得到電機的轉速,然后測量所經過的速度和距離。

電機驅動器
東芝的TB6612FNG驅動器是一款基于 MOSFET 的雙通道 H 橋集成電路。它具有以下特點。
TB6612FNG 不需要散熱器,因為它比基于雙極結型晶體管 (BJT) 的驅動器(如 L298N)效率更高。外圍電路簡單,加電源濾波電容即可驅動電機。
TB6612FNG有一對驅動器,可以獨立控制兩個電機。因此,四個電機只需要兩個 TB6612FNG IC。
當STBY
引腳為高電平時,兩個輸入信號IN1
和IN2
可以是順時針(CW)、逆時針(CCW)、短制動和停止模式等四種模式之一。

示意圖如下。

割草控制系統
割草控制系統可以調節割草高度并控制割草操作。
無刷直流電機
無刷電機沒有電刷,因此在有刷電機運行時不會產生電火花,大大降低了電火花對射頻遙控設備的干擾。無刷,運行時摩擦力大大降低,運行平穩,噪音低很多。這一優勢是對穩定性和長使用壽命的巨大支持。

我們使用 900 Kv 的無刷電機。選擇電機時,請考慮 Kv 額定值和扭矩兩個因素。無刷電機的 Kv 額定值是電機的空載轉速與連接到線圈的導線上的峰值電壓之比。一般來說,電機的Kv越大,電機所能產生的扭矩就越小。
尺寸方面,推薦使用2212電機。
電子速度控制器 (ESC)
電子速度控制器 (ESC) 是一種控制和調節電動機速度的電子電路。

我們使用 20A 電調。確保 ESC 的輸出電流與無刷電機的輸出電流匹配。
它具有以下特點。
- 用于多旋翼控制器的特殊核心程序大大提高了油門響應。
- 特別優化的軟件,與盤式電機具有出色的兼容性。
- 除定時外的所有設置都是預設的,使用簡單,高度智能,自適應。
- 油門信號線采用雙絞線設計,有效降低信號傳輸中產生的串擾,使飛行更加平穩。
- 專用驅動芯片的MOSFET驅動效果比由分立元件組成的普通驅動電路要好得多。
- 具有超低電阻的 MOSFET 帶來高性能和大電流耐受性。
伺服
伺服系統包含直流電機、控制電路、減速器、反饋機構和其他電路。它可以根據輸入信號轉到特定位置。其內部的電位器或角度傳感器可以感應輸出軸的位置,從而使控制板據此準確控制輸出軸的轉動。

通常,舵機有三根線:棕色或黑色一根是地線,紅色一根是電源線,橙色或白色一根是信號線。
舵機可以工作在 4V 到 6V 之間,推薦使用 5V 電壓。伺服旋轉的角度是通過調整PWM信號的占空比來實現的。標準 PWM 信號的周期固定為 20 ms (50 Hz)。脈沖寬度在 0.5 ms 到 2.5 ms 之間,對應伺服旋轉角度 0° 到 180°。
兩個舵機和一些金屬圓盤組成一個升降結構來調整切割高度。

定位導航系統
定位導航系統可實現定位和定向。
全球導航衛星系統定位
全球導航衛星系統 (GNSS) 是一個衛星網絡,用于廣播時間和軌道信息,用于導航和定位測量。目前,美國的全球定位系統(GPS)、俄羅斯的全球導航衛星系統(GLONASS)、中國的北斗衛星導航系統(BDS)和歐盟的伽利略系統都是全面運行的全球導航衛星系統。

我們使用涂鴉的GUC300 GNSS 模塊進行定位和導航。
- GUC300由高度集成的GNSS芯片UFirebird-UC6226NIS和外圍電路組成。它具有內置SAW濾波器、低噪聲放大器(LNA)、26 MHz溫度補償晶體振蕩器(TCXO)等元件。它同時支持 GPS 和 BDS 衛星導航系統。
GUC300 具有以下特點。
- 定位引擎:
64個同步跟蹤通道
熱啟動時間:<1.2秒
冷啟動靈敏度:-145 dBm。跟蹤靈敏度:-158 dBm
數據更新率:高達 10 Hz
- 定位引擎:
RF子系統采用寬帶設計。輸入信號的中心頻率約為 1575 MHz。
接收和跟蹤 GPS 的 1575.42 MHz L1 信號
接收和跟蹤北斗衛星導航系統1561.098 MHz B1信號
GUC300默認接收GPS和BDS信號,可定制為GPS+BDS、GPS+GLONASS或單GNSS系統。您可以提交服務單以請求所需的 GNSS 固件。

GUC300 默認每秒輸出$GNRMC
, $GNGGA
, 和$GPGSV
句子。串口波特率為9600。我們可以解析$GNGGA
句子。
$GNGGA,071520.00,3018.12971,N,12003.84423,E,1,20,1.47,60.5,M,,M,,\*61
上述句子的字段描述如下。$GNGGA, <1>, <2>, <3>, <4>, <5>, <6>, <7>, <8>, <9>, M, <10>, M, <11> , <12>*xx
-
\$GNGGA
:句子類型標識符。此示例指示 GGA 協議頭。 -
<1>:UTC時間,格式為
hhmmss.sss
. -
<2>:緯度,格式為
ddmm.mmmm
. 插入前導零。 - <3>:緯度半球,N或S(北緯或南緯)。
-
<4>:經度,格式為
dddmm.mmmm
. 插入前導零。 - <5>:半球經度,E或W(東經或西經)。
-
<6>:GPS定位狀態。
0
: 初始化。1
: 單點定位。2
: 差別。3
: 無效的 PPS。4
: 固定解決方案。5
: 浮點解。6
: 估計。7
:手動輸入的固定值。8
:模擬模式。9
: WAAS different.xx: 從$
to開始的所有 ASCII 字符的 XOR 校驗值\*
。 - <7>:使用的衛星數,范圍從00到12。插入前導零。
- <8>:水平精度稀釋度(HDOP),范圍從0.5到99.9。HDOP越小,衛星分布越好。
- <9>:海平面高度,范圍從-9999.9到9999.9。
- 以米為單位。
- <10>:地球橢球體相對于大地水準面的高度,范圍從-9999.9到9999.9。
- 以米為單位。
- <11>:距離上次接收到差分 GPS 信號的秒數。如果不采用差分定位,則該字段為空。
- <12>:差分參考基站標簽,范圍從0000到1023。插入前導零。
- :回車,結束標記。
- :換行,結束標簽。
電路原理圖
電子羅盤傳感器
我們選擇了 3 軸電子羅盤傳感器QMC5883L 。

電子羅盤由一個 3D 磁阻傳感器、一個 2 軸傾角儀和一個 MCU 組成。3D磁阻傳感器用于監測地磁場的變化,傾角儀可以測量感應方向與地平面之間的角度(相對于重力)。MCU 處理輸入信號并輸出??數據以補償硬鐵和軟鐵失真。磁力計包括 x、y 和 z 方向的三個磁阻傳感器,以相應地測量地磁場強度。傳感器在每個方向上的靈敏度已根據地磁場矢量進行了最佳校準。交叉軸靈敏度已降至最低。來自傳感器的模擬輸出信號將被放大,然后發送到 MCU 進行處理。
我們可以使用下面的公式來計算方向。
方向.x=atan2((double)Mag_data.y, (double)Mag_data.x)*57.3+180;
Mag_data.x
分別是 X 軸和Mag_data.y
Y 軸的數據。atan2
是平面的正 x 軸與其上坐標 (x, y) 給定的點之間的弧度角。將弧度值乘以 57.3 以將弧度轉換為度數。最后,將轉換后的值加上 180 度。
對于計算結果,真南為0或360,西為90,北為180,東為270。
組裝設備
設計一個安裝輪子、伺服系統和其他組件的底板。下載底板結構文件。

整個組件看起來像這樣。
我們需要設計一個PCB布局來放置電機驅動器和電源等組件。確保為舵機、電機和電子羅盤的接口預留空間,以保持項目整潔。您還可以添加一些功能,例如用于警報的蜂鳴器和用于信號的 LED。

電路原理圖
最后,將 GPS、微控制器和電源單元安裝在 PCB 上,PCB 組裝完成。
第 2 步:創建產品
1.登錄涂鴉IoT平臺。
2.點擊創建。

3.向下滾動頁面并單擊找不到類別?在左下角。
4.填寫所需信息并點擊創建。
- Product Name 、Product Description和Product Model是用戶定義的。
- 選擇Wi-Fi-Bluetooth作為協議,然后單擊Create 。

5.在自定義函數部分,單擊添加以創建所需的函數。在函數定義步驟中,您可以根據需要添加標準函數或創建自定義函數。
我們設置了以下功能。
- 標準功能:方向控制
- 自定義功能:刀片位置、刀片轉速、切割長度、切割寬度、鋸齒割草模式。

6. 在設備面板選項卡的第二步中,選擇所需的面板。在設備面板的第二步,可以選擇調試友好的DIY風格面板。
7.在硬件開發的第三步,選擇涂鴉標準模塊SDK 。

8.向下滾動頁面并找到下載文檔。點擊全部下載,下載MCU SDK開發所需的全部文件。

- 平臺上的操作就完成了。您可以使用涂鴉智能 App 使用虛擬設備進行遠程控制。

第 3 步:固件開發
在應用程序開發之前,您需要將 MCU SDK 移植到您的項目中。
然后將通用固件和涂鴉的授權刷入網絡模塊。這樣,割草機就可以與云進行通信。
演示例程還包括 FreeRTOS,以方便您的開發。
以上準備工作完成后,就可以進行應用開發了。
下載示例代碼:tuya-iotos-embeded-mcu-demo-wifi-ble-samrt-lawn-mower
電機、伺服和電調驅動器
將四個電機連接到每個車輪以進行運動。兩個舵機用于控制 ESC 以及連接到 ESC 的刀片的位置。ESC 控制和調節刀片旋轉的速度。
編寫上述組件的初始化程序,在servo_motor.c
.
- 為輸出 GPIO 定義宏。
#define MOTOR_PORT_1 GPIOA
#define MOTOR_PIN_1 GPIO_PIN_15
#define MOTOR_PORT_1_P GPIOD
#define MOTOR_PIN_1_P GPIO_PIN_0
#define MOTOR_PORT_1_N GPIOD
#define MOTOR_PIN_1_N GPIO_PIN_7
#define MOTOR_PORT_2 GPIOB
#define MOTOR_PIN_2 GPIO_PIN_9
#define MOTOR_PORT_2_P GPIOD
#define MOTOR_PIN_2_P GPIO_PIN_1
#define MOTOR_PORT_2_N GPIOD
#define MOTOR_PIN_2_N GPIO_PIN_2
#define MOTOR_PORT_3 GPIOB
#define MOTOR_PIN_3 GPIO_PIN_10
#define MOTOR_PORT_3_P GPIOD
#define MOTOR_PIN_3_P GPIO_PIN_3
#define MOTOR_PORT_3_N GPIOD
#define MOTOR_PIN_3_N GPIO_PIN_4
#define MOTOR_PORT_4 GPIOB
#define MOTOR_PIN_4 GPIO_PIN_11
#define MOTOR_PORT_4_P GPIOD
#define MOTOR_PIN_4_P GPIO_PIN_8
#define MOTOR_PORT_4_N GPIOD
#define MOTOR_PIN_4_N GPIO_PIN_9
#define MOTOR_CHANNEL_1 TIMER_CH_0
#define MOTOR_CHANNEL_2 TIMER_CH_1
#define MOTOR_CHANNEL_3 TIMER_CH_2
#define MOTOR_CHANNEL_4 TIMER_CH_3
#define SERVO_PORT_1 GPIOC
#define SERVO_PIN_1 GPIO_PIN_6
#define SERVO_PORT_2 GPIOC
#define SERVO_PIN_2 GPIO_PIN_7
#define BLADE_MOTOR_PORT GPIOC
#define BLADE_MOTOR_PIN GPIO_PIN_8
void servo_motor_init(void)
{
rcu_periph_clock_enable(RCU_GPIOA);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_GPIOC);
rcu_periph_clock_enable(RCU_GPIOD);
/*Configure PD1~8 Output motor Positive and Negative pin to drive wheels_1~4*/
gpio_mode_set(GPIOD, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, MOTOR_PIN_1_P | MOTOR_PIN_1_N | MOTOR_PIN_2_P | MOTOR_PIN_2_N | MOTOR_PIN_3_P | MOTOR_PIN_3_N | MOTOR_PIN_4_P | MOTOR_PIN_4_N);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_1_P | MOTOR_PIN_1_N | MOTOR_PIN_2_P | MOTOR_PIN_2_N | MOTOR_PIN_3_P | MOTOR_PIN_3_N | MOTOR_PIN_4_P | MOTOR_PIN_4_N);
gpio_bit_reset(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_1_N | MOTOR_PIN_2_P | MOTOR_PIN_2_N | MOTOR_PIN_3_P | MOTOR_PIN_3_N | MOTOR_PIN_4_P | MOTOR_PIN_4_N);
/*Configure PA15(TIMER1_CH0) Output PWM pulse to drive wheels_1*/
gpio_mode_set(MOTOR_PORT_1, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_1);
gpio_output_options_set(MOTOR_PORT_1, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_1);
gpio_af_set(MOTOR_PORT_1, GPIO_AF_1, MOTOR_PIN_1);
/*Configure PB9(TIMER1_CH1) Output PWM pulse to drive wheels_2*/
gpio_mode_set(MOTOR_PORT_2, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_2);
gpio_output_options_set(MOTOR_PORT_2, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_2);
gpio_af_set(MOTOR_PORT_2, GPIO_AF_1, MOTOR_PIN_2);
/*Configure PB10(TIMER1_CH2) Output PWM pulse to drive wheels_3*/
gpio_mode_set(MOTOR_PORT_3, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_3);
gpio_output_options_set(MOTOR_PORT_3, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_3);
gpio_af_set(MOTOR_PORT_3, GPIO_AF_1, MOTOR_PIN_3);
/*Configure PB11(TIMER1_CH3) Output PWM pulse to drive wheels_4*/
gpio_mode_set(MOTOR_PORT_4, GPIO_MODE_AF, GPIO_PUPD_NONE, MOTOR_PIN_4);
gpio_output_options_set(MOTOR_PORT_4, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,MOTOR_PIN_4);
gpio_af_set(MOTOR_PORT_4, GPIO_AF_1, MOTOR_PIN_4);
/*Configure PC6(TIMER2_CH0) Output PWM pulse to drive servo no.1*/
gpio_mode_set(SERVO_PORT_1, GPIO_MODE_AF, GPIO_PUPD_NONE, SERVO_PIN_1);
gpio_output_options_set(SERVO_PORT_1, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,SERVO_PIN_1);
gpio_af_set(SERVO_PORT_1, GPIO_AF_2, SERVO_PIN_1);
/*Configure PC7(TIMER2_CH1) Output PWM pulse to drive servo no.2*/
gpio_mode_set(SERVO_PORT_2, GPIO_MODE_AF, GPIO_PUPD_NONE, SERVO_PIN_2);
gpio_output_options_set(SERVO_PORT_2, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,SERVO_PIN_2);
gpio_af_set(SERVO_PORT_2, GPIO_AF_2, SERVO_PIN_2);
/*Configure PC8(TIMER2_CH2) Output PWM pulse to drive blade motor*/
gpio_mode_set(BLADE_MOTOR_PORT, GPIO_MODE_AF, GPIO_PUPD_NONE, BLADE_MOTOR_PIN);
gpio_output_options_set(BLADE_MOTOR_PORT, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,BLADE_MOTOR_PIN);
gpio_af_set(BLADE_MOTOR_PORT, GPIO_AF_2, BLADE_MOTOR_PIN);
timer_config();
}
void timer_config(void)
{
uint16_t i = 0;
/* TIMER1 configuration: generate PWM signals with different duty cycles:
TIMER1CLK = SystemCoreClock / 120 = 1MHz */
timer_oc_parameter_struct timer_ocintpara;
timer_parameter_struct timer_initpara;
rcu_periph_clock_enable(RCU_TIMER1);
rcu_periph_clock_enable(RCU_TIMER2);
rcu_timer_clock_prescaler_config(RCU_TIMER_PSC_MUL4);
timer_struct_para_init(&timer_initpara);
timer_deinit(TIMER1);
timer_deinit(TIMER2);
/* TIMER1 configuration */
timer_initpara.prescaler = 119;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 500; /* 500*(1/1MHZ) = 500us */
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER1,&timer_initpara);
/* TIMER2 configuration */
timer_initpara.prescaler = 119;
timer_initpara.alignedmode = TIMER_COUNTER_EDGE;
timer_initpara.counterdirection = TIMER_COUNTER_UP;
timer_initpara.period = 20000; /* 20000*(1/1MHZ) = 20ms */
timer_initpara.clockdivision = TIMER_CKDIV_DIV1;
timer_initpara.repetitioncounter = 0;
timer_init(TIMER2,&timer_initpara);
timer_channel_output_struct_para_init(&timer_ocintpara);
timer_ocintpara.ocpolarity = TIMER_OC_POLARITY_HIGH;
timer_ocintpara.outputstate = TIMER_CCX_ENABLE;
timer_ocintpara.ocnpolarity = TIMER_OCN_POLARITY_HIGH;
timer_ocintpara.outputnstate = TIMER_CCXN_DISABLE;
timer_ocintpara.ocidlestate = TIMER_OC_IDLE_STATE_LOW;
timer_ocintpara.ocnidlestate = TIMER_OCN_IDLE_STATE_LOW;
for(i = 0; i < 4; i++) {
timer_channel_output_config(TIMER1,i,&timer_ocintpara);
timer_channel_output_pulse_value_config(TIMER1,i,0);
timer_channel_output_mode_config(TIMER1,i,TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER1,i,TIMER_OC_SHADOW_DISABLE);
}
for(i = 0; i < 3; i++) {
timer_channel_output_config(TIMER2,i,&timer_ocintpara);
timer_channel_output_pulse_value_config(TIMER2,i,0);
timer_channel_output_mode_config(TIMER2,i,TIMER_OC_MODE_PWM0);
timer_channel_output_shadow_config(TIMER2,i,TIMER_OC_SHADOW_DISABLE);
}
/* auto-reload preload enable */
timer_auto_reload_shadow_enable(TIMER1);
timer_auto_reload_shadow_enable(TIMER2);
/* TIMER enable */
timer_enable(TIMER1);
timer_enable(TIMER2);
}
-
調整電機和舵機的PWM占空比,將占空比調整的操作封裝為通用接口。以連接到車輪的電機為例。
car_moving
用于根據輸入方向和速度調整正負電平和占空比,從而控制車輪。
void car_moving(MOVING_DIRECTION direction, uint16_t speed_value)
{
uint8_t i;
switch(direction) {
case forward:
gpio_bit_set(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);
gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);
for(i = 0; i < 4; i++) {
timer_channel_output_pulse_value_config(TIMER1,i,speed_value);
}
break;
case right:
gpio_bit_set(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);
gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);
//Since the two sets of wheels on the right are always faster than the left in the actual commissioning process, 60 is added to compensate
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_1,speed_value + 60);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_3,speed_value + 60);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_2,speed_value);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_4,speed_value);
break;
case left:
gpio_bit_set(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);
gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_1,speed_value);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_3,speed_value);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_2,speed_value + 50);
timer_channel_output_pulse_value_config(TIMER1,MOTOR_CHANNEL_4,speed_value + 50);
break;
case backward:
gpio_bit_reset(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);
gpio_bit_set(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);
for(i = 0; i < 4; i++) {
timer_channel_output_pulse_value_config(TIMER1,i,speed_value);
}
break;
case stop:
gpio_bit_reset(GPIOD, MOTOR_PIN_1_P | MOTOR_PIN_2_P | MOTOR_PIN_3_P | MOTOR_PIN_4_P);
gpio_bit_reset(GPIOD, MOTOR_PIN_1_N | MOTOR_PIN_2_N | MOTOR_PIN_3_N | MOTOR_PIN_4_N);
for(i = 0; i < 4; i++) {
timer_channel_output_pulse_value_config(TIMER1,i,speed_value);
}
break;
default:
break;
}
}
用于線性運動校正的比例積分微分 (PID) 算法
由于四個輪子由獨立的電機控制,即使 PWM 輸出設置為相同的占空比,錯誤和其他因素也會導致割草機偏離線性運動。因此,我們使用PID算法來動態調整每個車輪的實際轉速。直線運動表示四個輪子同時移動相同的距離。由于四個輪子的直徑相同,因此每個輪子的轉速應該相同。因為電機轉速直接決定了電機編碼器輸出的脈沖數,所以我們以電機編碼器單位時間內實時輸出的脈沖數作為PID算法的輸入,占空比增量作為控制對象。我們可以不斷微調脈沖,使四個電機達到相同的值。
-
通過外部中斷對脈沖進行計數,收集電機編碼器輸出脈沖的單位時間增量。中的
movement_system_init()
函數movement.c
包括外部中斷的配置。
void movement_system_init(void)
{
rcu_periph_clock_enable(RCU_SYSCFG);
gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_0);
nvic_irq_enable(EXTI0_IRQn, 2U, 0U);
syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN0);
exti_init(EXTI_0, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_flag_clear(EXTI_0);
gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_1);
nvic_irq_enable(EXTI1_IRQn, 2U, 0U);
syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN1);
exti_init(EXTI_1, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_flag_clear(EXTI_1);
gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_2);
nvic_irq_enable(EXTI2_IRQn, 2U, 0U);
syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN2);
exti_init(EXTI_2, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_flag_clear(EXTI_2);
gpio_mode_set(GPIOC, GPIO_MODE_INPUT, GPIO_PUPD_NONE, GPIO_PIN_3);
nvic_irq_enable(EXTI3_IRQn, 2U, 0U);
syscfg_exti_line_config(EXTI_SOURCE_GPIOC, EXTI_SOURCE_PIN3);
exti_init(EXTI_3, EXTI_INTERRUPT, EXTI_TRIG_RISING);
exti_interrupt_flag_clear(EXTI_3);
}
void EXTI0_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(EXTI_0)){
move_state.encoder_pulse[0]++;
}
exti_interrupt_flag_clear(EXTI_0);
}
void EXTI1_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(EXTI_1)){
move_state.encoder_pulse[1]++;
}
exti_interrupt_flag_clear(EXTI_1);
}
void EXTI2_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(EXTI_2)){
move_state.encoder_pulse[2]++;
}
exti_interrupt_flag_clear(EXTI_2);
}
void EXTI3_IRQHandler(void)
{
if(RESET != exti_interrupt_flag_get(EXTI_3)){
move_state.encoder_pulse[3]++;
}
exti_interrupt_flag_clear(EXTI_3);
}
-
實現
forward_correction()
接口,將收集到的脈沖pulse_num
、最后一個脈沖pulse_num_old
和所需的脈沖增量standard_increment
傳遞給 PID 控制函數。調用single_motor_speed_set()
根據計算結果調整各電機的PWM占空比。
int e[4]={0},e1[4]={0},e2[4]={0}; // PID offset
float uk[4]={0.0},uk1[4]={0.0},duk[4]={0.0}; // PID output
float Kp=0.8,Ki=0.3,Kd=0.9; // PID control factors.
int out[4] = {0};
static void forward_correction(uint32_t *pulse_num, uint32_t *pulse_num_old, uint32_t standard_increment)
{
uint8_t i = 0;
for(i = 0;i < 4;i++) {
e[i] = standard_increment - (pulse_num[i] - pulse_num_old[i]);
duk[i] = (Kp*(e[i]-e1[i])+Ki*e[i])/100;
uk[i] = uk1[i] + duk[i];
out[i] = (int)uk[i];
uk1[i] = uk[i];
e2[i] = e1[i];
e1[i] = e[i];
single_motor_speed_set(i, (uint16_t)(200+(out[i]*5)));
}
return;
}
-
當運動輪詢處理程序進入
switch
直線運動的分支時,您可以調用forward_correction()
來調整電機速度,然后將 分配encoder_pulse
給encoder_pulse_old
。
void movement_logic_handle(void)
{
MOVE_STATE_T *p = NULL;
p = &move_state;
uint8_t i = 0;
MOVING_DIRECTION turning_state;
switch(p->todo_type) {
......
case on_the_move:
if(forward_sampling_time_flag == 20) { //20*20ms = 400ms
for(i = 0;i < 4;i++) {
pulse_test[i] = p->encoder_pulse[i]-p->encoder_pulse_old[i];
}
forward_correction(p->encoder_pulse,p->encoder_pulse_old,390);
for(i = 0;i < 4;i++) {
p->encoder_pulse_old[i] = p->encoder_pulse[i];
}
forward_sampling_time_flag = 0;
}
forward_sampling_time_flag++;
todo_judge();
break;
......
default:
break;
}
}
讓割草機旋轉 90 度
以之字形割草模式為例。當割草機到達直線運動的終點時,它必須轉動 90 度。該todo_judge
功能用于確定旋轉運動,該運動也基于來自電機編碼器的脈沖數。將長度轉換為脈沖數。當脈沖達到規定值時,割草機將轉動 90 度。割草機需要確定從長邊或寬邊轉向以及向左或向右轉向。todo_judge()
函數有判斷switch
。分支指示割草機是case
從長邊轉向還是從寬邊轉向。case
用于判斷直線運動中的割草機是否應轉彎。如果是,則轉彎狀態將更改(change_to_do(turning);
),并且案例分支p->run_step_flag = width_right;
將在割草機在下一個行駛階段進入此功能時更改。如果寬度達到規定值,則表明曲折割草作業完成。
static void todo_judge(void)
{
MOVE_STATE_T *p = NULL;
p = &move_state;
switch(p->run_step_flag) {
case length_right:
if(pulse_to_distance(p->encoder_pulse[0]) >= (p->range_length_mm - 10)) {
if((p->current_angle + 900) > 3600) {
p->target_angle = p->current_angle + 900 - 3600;
}else{
p->target_angle = p->current_angle + 900;
}
change_to_do(turning);
p->run_step_flag = width_right;
}
break;
case width_right:
if(pulse_to_distance(p->encoder_pulse[0]) >= 100) {
if((p->current_angle + 900) > 3600) {
p->target_angle = p->current_angle + 900 - 3600;
}else{
p->target_angle = p->current_angle + 900;
}
change_to_do(turning);
p->run_step_flag = length_left;
width_remain_mm -= 100;
}
break;
case length_left:
if(pulse_to_distance(p->encoder_pulse[0]) >= (p->range_length_mm - 10)) {
if(p->current_angle < 900) {
p->target_angle = 3600 - (900 - p->current_angle);
}else{
p->target_angle = p->current_angle - 900;
}
change_to_do(turning);
p->run_step_flag = width_left;
}
break;
case width_left:
if(pulse_to_distance(p->encoder_pulse[0]) >= 100) {
if(p->current_angle < 900) {
p->target_angle = 3600 - (900 - p->current_angle);
}else{
p->target_angle = p->current_angle - 900;
}
change_to_do(turning);
p->run_step_flag = length_right;
width_remain_mm -= 100;
}
break;
default:
break;
}
if(width_remain_mm <= 0) {
change_to_do(to_stop);
move_state.bow_mode_switch = pdFALSE;
}
}
-
上述例程中,
p->target_angle
是目標方位角,p->current_angle
是當前方位角,用于控制割草機的轉向角。目標方位角是割草機即將轉彎時的方位角加減90度得到的。并且當前方位角是根據地磁傳感器的數據計算得出的。該QMC5883L.c
文件包含 I2C QMC5883L 的驅動程序代碼。在這里,我們使用QMC5883L_GetAngle()
來獲取當前方位角。
void move_control_task(void *pvParameters)
{
float_xyz Mag_angle;
uint8_t test_flag = 50;
vTaskDelay(200);
QMC_Init();
QMC5883L_GetAngle(&Mag_angle);
move_state.current_angle = (int16_t)Mag_angle.x;
vTaskDelay(500);
while(1) {
if(test_flag){
vTaskDelay(20);
test_flag--;
}else if(test_flag == 0) {
vTaskDelay(20);
movement_logic_handle();
}
QMC5883L_GetAngle(&Mag_angle);
move_state.current_angle = (int16_t)Mag_angle.x;
}
}
-
方位角可用于確定轉彎操作。該
angle_correction()
界面可以確定割草機應該左轉還是右轉或直行。該angle_correction()
接口在移動輪詢處理程序turning
的case
分支中調用。
static MOVING_DIRECTION angle_correction(void)
{
int16_t target,current = 0;
target = move_state.target_angle;
current = move_state.current_angle;
if(target < current) {
if(current - target <= 20) {
return forward;
}
if(current - target <= 1800) {
return left;
}else{
return right;
}
}else if(target > current) {
if(target - current <= 20) {
return forward;
}
if(target - current <= 1800) {
return right;
}else{
return left;
}
}else if(current == target) {
return forward;
}
}
void movement_logic_handle(void)
{
MOVE_STATE_T *p = NULL;
p = &move_state;
uint8_t i = 0;
MOVING_DIRECTION turning_state;
switch(p->todo_type) {
......
case on_the_move:
if(forward_sampling_time_flag == 20) { //20*20ms = 400ms
for(i = 0;i < 4;i++) {
pulse_test[i] = p->encoder_pulse[i]-p->encoder_pulse_old[i];
}
forward_correction(p->encoder_pulse,p->encoder_pulse_old,390);
for(i = 0;i < 4;i++) {
p->encoder_pulse_old[i] = p->encoder_pulse[i];
}
forward_sampling_time_flag = 0;
}
forward_sampling_time_flag++;
todo_judge();
break;
case turning:
turning_state = angle_correction();
if(turning_state == right) {
car_full_turn(right,150);
turning_sampling_time_flag = 0;
}else if(turning_state == left) {
car_full_turn(left,150);
turning_sampling_time_flag = 0;
}else if(turning_state == forward) {
car_moving(stop,0);
turning_sampling_time_flag++;
}
if(turning_sampling_time_flag >= 25) {
p->todo_type = on_the_move;
car_moving(forward,200);
forward_sampling_time_flag = 0;
turning_sampling_time_flag = 0;
for(i = 0;i < 4;i++) {
p->encoder_pulse[i] = 0;
p->encoder_pulse_old[i] = 0;
}
}
break;
......
default:
break;
}
}
設置刀片位置和速度
刀片位置由兩個伺服系統控制。如果要在刀片上下移動時保持刀片的水平位置不變,則兩個舵機必須在相反的方向上改變相同的角度。兩個舵機的相反方向的角度變化必須相同。封裝一個用于設置刀片位置的接口,以位置枚舉值作為輸入。
void blade_position_set(BLADE_POSITION value)
{
switch(value) {
case low:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,3100);
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,3000);
break;
case medium:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,2800);
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,2700);
break;
case high:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_0,2600);
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_1,2500);
break;
default:
break;
}
}
葉片速度由 ESC 控制并封裝在blade_speed_set();
.
void blade_speed_set(BLADE_SPEED speed)
{
switch(speed) {
case init:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,1500);
break;
case off:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,0);
break;
case low_speed:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,1800);
break;
case medium_speed:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,1900);
break;
case high_speed:
timer_channel_output_pulse_value_config(TIMER2,TIMER_CH_2,2000);
break;
default:
break;
}
}
5.接收GNSS數據
涂鴉 GNSS 模塊 GUC300 模塊通過串口通信向 MCU 發送 GNSS 數據。我們使用USART1
MCU的來接收數據,因為USART0
已經用于與Wi-Fi模塊的串行通信。
- 初始化串口:
void uart_init(void)
{
/* USART interrupt configuration */
nvic_irq_enable(USART0_IRQn, 0, 0);
nvic_irq_enable(USART1_IRQn, 1, 1);
/* enable USART clock */
rcu_periph_clock_enable(RCU_USART0);
/* connect port to USART0_Tx */
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_9);
/* connect port to USART0_Rx */
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_10);
/* configure USART Tx as alternate function push-pull */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_9);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_9);
/* configure USART Rx as alternate function push-pull */
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_10);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_10);
/* USART configure */
usart_deinit(USART0);
usart_baudrate_set(USART0,115200U);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
/* enable USART clock */
rcu_periph_clock_enable(RCU_USART1);
/* connect port to USART0_Tx */
gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_5);
/* connect port to USART0_Rx */
gpio_af_set(GPIOD, GPIO_AF_7, GPIO_PIN_6);
/* configure USART Tx as alternate function push-pull */
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_5);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_5);
/* configure USART Rx as alternate function push-pull */
gpio_mode_set(GPIOD, GPIO_MODE_AF, GPIO_PUPD_PULLUP,GPIO_PIN_6);
gpio_output_options_set(GPIOD, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ,GPIO_PIN_6);
/* USART configure */
usart_deinit(USART1);
usart_baudrate_set(USART1,9600U);
usart_receive_config(USART1, USART_RECEIVE_ENABLE);
usart_transmit_config(USART1, USART_TRANSMIT_ENABLE);
usart_enable(USART1);
/* enable USART0 receive interrupt */
usart_interrupt_enable(USART0, USART_INT_RBNE);
/* enable USART1 receive interrupt */
usart_interrupt_enable(USART1, USART_INT_RBNE);
}
-
初始化 USART 中斷處理函數。GUC300 默認每秒輸出
$GNRMC
,$GNGGA
,$GPGSV
, 和$GPGGA
句子。我們可以解析$GPGGA
句子。 -
$GPGGA
句子包含17個字段:標題,位置的UTC時間狀態,緯度,緯度方向,經度,經度方向,定位質量,正在使用的衛星數量,精度水平稀釋,天線高度高于/低于平均海平面,天線高度單位、起伏(大地水準面和WGS84橢球的關系)、起伏的單位、校正數據的年齡、差分基站ID、校驗和、句子終止符。 -
例子:
$GPGGA,014434.70,3817.13334637,N,12139.72994196,E,4,07,1.5,6.571,M,8.942,M,0.7,0016*79
- 按照上述格式處理串行數據。
#define USART_FH_len 6 // The length of the header.
char USART_FH[USART_FH_len]={'$','G','N','G','G','A'}; // Header
#define USART_FT_len 2 // The length of the trailer.
uint8_t USART_FT[USART_FT_len]={0X0D,0X0A}; // Frame trailer
uint8_t data_buf[128] = {0};
uint8_t rx_buf[128] = {0};
uint8_t buff_len = 0;
void USART1_IRQHandler(void)
{
if((RESET != usart_interrupt_flag_get(USART1, USART_INT_FLAG_RBNE)) &&
(RESET != usart_flag_get(USART1, USART_FLAG_RBNE))){
rx_buf[buff_len++] = (uint8_t)usart_data_receive(USART1);
if(rx_buf[0] != USART_FH[0]) {
rx_buf[0] = 0;
buff_len = 0;
}
if(buff_len >= USART_FH_len) {
if(strncmp((char*)rx_buf,USART_FH,USART_FH_len) == 0) {
if(strncmp(&rx_buf[buff_len-2],(char*)USART_FT,USART_FT_len) == 0) {
memcpy(data_buf,rx_buf,buff_len);
memset(rx_buf,0,buff_len);
buff_len = 0;
}
}else {
memset(rx_buf,0,USART_FH_len);
buff_len = 0;
}
}
}
}
void gps_data_task(void *pvParameters)
{
MAP_POINT_t *p;
p = &map_point[point_1];
uint8_t data_len = 0;
while(1) {
vTaskDelay(100);
data_len = strlen((char*)data_buf);
if(data_len != 0){
gps_data_pick(point_1, data_buf, data_len);
memset(data_buf,0,data_len);
}
}
}
概括
恭喜!您已經成功地制作了機器人割草機的原型。您可以添加更多酷炫和有用的功能來制作功能齊全的割草機。涂鴉物聯網平臺提供便捷的物聯網開發工具和服務,旨在讓您的物聯網項目更輕松、更高效。查看并發現更多很棒的想法
?
- 割草機器人利用超聲波感應自動檢測地形類型和障礙物
- 適用于割草機器人,商用服務型機器人的高精度陀螺儀模組GGPM61
- 檢測機器人開源分享
- 坦克機器人開源分享
- 掃地機器人開源資料 44次下載
- 無刷電機驅動器直流無刷綠籬機園林工具割草機方案
- 機器人守衛開源分享
- 自動割草機v1開源項目
- 基于Arduino Mega 2560的自主機器人割草機 5次下載
- 英雄機器人開源
- 機器人開源案例
- 刷機機器人開源
- 基于DSP和PC的農業機器人控制系統 18次下載
- 基于DSP芯片的足球機器人實現與設計 27次下載
- 自動割草機器人的設計
- 字節發布機器人領域首個開源視覺-語言操作大模型,激發開源VLMs更大潛能 562次閱讀
- 機器人技術中常用的路徑規劃算法的開源庫 1188次閱讀
- 用Arduino做一個自動割草機 2020次閱讀
- GNSS技術助力自動割草機轉型升級 692次閱讀
- 基于ES32F0101的鋰電割草機應用 1087次閱讀
- 什么是EPSON機器人與上位機TCP通信 3436次閱讀
- 采用STC12C5410AD處理器實現自動割草機器人主控系統的設計 3007次閱讀
- 面對疫情 醫療機器人能幫上什么忙? 2236次閱讀
- dfrobotSparki機器人套裝簡介 2207次閱讀
- 醫用機器人的定義_醫用機器人發展 3257次閱讀
- 協作機器人的起源_為什么需要協作機器人 8322次閱讀
- 怎樣給割草機裝上太陽能 4420次閱讀
- 如何區分機器人、協作機器人和移動機器人? 7029次閱讀
- 軟體機器人 前所未見的機器人 3770次閱讀
- 機器人的最佳編程語言是什么?機器人十大流行編程語言匯總 3.5w次閱讀
下載排行
本周
- 1山景DSP芯片AP8248A2數據手冊
- 1.06 MB | 532次下載 | 免費
- 2RK3399完整板原理圖(支持平板,盒子VR)
- 3.28 MB | 339次下載 | 免費
- 3TC358743XBG評估板參考手冊
- 1.36 MB | 330次下載 | 免費
- 4DFM軟件使用教程
- 0.84 MB | 295次下載 | 免費
- 5元宇宙深度解析—未來的未來-風口還是泡沫
- 6.40 MB | 227次下載 | 免費
- 6迪文DGUS開發指南
- 31.67 MB | 194次下載 | 免費
- 7元宇宙底層硬件系列報告
- 13.42 MB | 182次下載 | 免費
- 8FP5207XR-G1中文應用手冊
- 1.09 MB | 178次下載 | 免費
本月
- 1OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費
- 2555集成電路應用800例(新編版)
- 0.00 MB | 33566次下載 | 免費
- 3接口電路圖大全
- 未知 | 30323次下載 | 免費
- 4開關電源設計實例指南
- 未知 | 21549次下載 | 免費
- 5電氣工程師手冊免費下載(新編第二版pdf電子書)
- 0.00 MB | 15349次下載 | 免費
- 6數字電路基礎pdf(下載)
- 未知 | 13750次下載 | 免費
- 7電子制作實例集錦 下載
- 未知 | 8113次下載 | 免費
- 8《LED驅動電路設計》 溫德爾著
- 0.00 MB | 6656次下載 | 免費
總榜
- 1matlab軟件下載入口
- 未知 | 935054次下載 | 免費
- 2protel99se軟件下載(可英文版轉中文版)
- 78.1 MB | 537798次下載 | 免費
- 3MATLAB 7.1 下載 (含軟件介紹)
- 未知 | 420027次下載 | 免費
- 4OrCAD10.5下載OrCAD10.5中文版軟件
- 0.00 MB | 234315次下載 | 免費
- 5Altium DXP2002下載入口
- 未知 | 233046次下載 | 免費
- 6電路仿真軟件multisim 10.0免費下載
- 340992 | 191187次下載 | 免費
- 7十天學會AVR單片機與C語言視頻教程 下載
- 158M | 183279次下載 | 免費
- 8proe5.0野火版下載(中文版免費下載)
- 未知 | 138040次下載 | 免費
評論