古人智慧

Just Do it!
上士聞道,勤而行之;中士聞道,若存若亡;下士聞道,大笑之。不笑,不足以爲道。
~ 道德經 41

「實現夢想不是追逐成功,而是在於賦予生命意義,人生中的每個決定與聲音都有其重要含義。」"The key to realizing a dream is to focus not on success but on significance — and then even the small steps and little victories along your path will take on greater meaning."
電視名人-歐普拉·溫芙蕾(OPRAH WINFREY)

搜尋此網誌

Translation

2018年4月29日 星期日

[Robot] 全3D打印 履帶機器人DIY

坦克車履帶傳動系統是個很棒的設計,可以在複雜與惡劣的地形上快速行進,用在robot上應該是適用的。
最近在Thingiverse看到一個神人作品SMARS作品是可以全3D打印的履帶與機身, 而且使用的小型馬達N20剛好手上有,真是太棒了,可以解解這種傳動系統的渴。。。

作品背景說明

要啟動一個項目,電控板是核心,有幾點必須考慮:
1.PCB尺寸
2.有多少GPIO可以控制外接模塊或馬達
3.operation電壓
再來就是周邊模塊的選擇,例如直流DC馬達要搭配馬達驅動板。
遙控器是相當麻煩的部分,我是選擇用jjRobots公司搭配OSC協議透過Wifi UDP傳送,OSC主要訴求是即時傳送與接收,這對遙控車是非常必要的選擇。
之前做4足Spider用HTML來控制,就非常不順暢卡卡的,甚至會突然HTML package會漏掉。
最後,電源系統設計,從電池電壓、尺寸與重量,DC-DC降壓板的電流輸出,這些都足以讓項目卡關,難以持續。
從上述的分析,很容易瞭解這次為何選擇TinyPlan。這片TinyPlan是以ESP8266為核心,並搭載兩顆2.4v 750F電容式快充鋰電池,串聯起來可以提供4.8v,1C放電。並提供8個GPIO,4根pin接馬達驅動板,2根接UltraSonic sensor綽綽有餘,是個非常好的選擇。

作品展示

零件表與設備工具

BOM list:

設備:

3D 打印機
3D 結構

設計階段

3D 結構列印與組裝


《用2mm鑽頭通一下比較好安裝》

《TinyPlan+L9113S》

Arduino軟件設計

參考jjRobots的OSC來開發(https://github.com/jjrobots/B-ROBOT_EVO2/tree/master/Arduino)
主程式如下:
/*
   RegisHsu 2018-04-28
   TinyPlan ESP8266 module + L9110S + UltraSound
   Controller jjrobots - www.jjrobots.com

   v01:
      initial version
   v02:
      add UltraDonic sensor
*/

#include <ESP8266WiFi.h>
#include "RHROBOTS_OSC.h"
#include "RHROBOTS_BROBOT.h"

#define BAUDRATE 250000

// TinyPlan Port define
#define PIN_D1 14
#define PIN_D2 12
#define PIN_D3 13
#define PIN_D4 15
#define PIN_D5 16
#define PIN_D6 5
#define PIN_D7 4
#define PIN_D8 2

//N20 Motor pin define
#define MOTOR_M1_S1 PIN_D1
#define MOTOR_M1_S2 PIN_D2
#define MOTOR_M2_S1 PIN_D5
#define MOTOR_M2_S2 PIN_D6

//Ultrasonic sensor pin define
#define ULTRA_TRIG PIN_D3
#define ULTRA_ECHO PIN_D7

// NORMAL MODE PARAMETERS (MAXIMUN SETTINGS)
#define MAX_THROTTLE 2400
#define MAX_STEERING 2400
#define MAX_CONTROL_OUTPUT 1024

uint8_t loop_counter;       // To generate a medium loop 40Hz
uint8_t slow_loop_counter;  // slow loop 2Hz
uint8_t sendBattery_counter; // To send battery status

long timer_old;
long timer_value;
int debug_counter;
float debugVariable;
float dt;

int16_t motor1;
int16_t motor2;

bool newControlParameters = false;
bool modifing_control_parameters = false;

uint8_t mode;  // mode = 0 Normal mode, mode = 1 Pro mode (More agressive)

float throttle;
float steering;
float max_throttle = MAX_THROTTLE;
float max_steering = MAX_STEERING;
float control_output;

// Ultrasonic
volatile long duration;
volatile int distance;
volatile int echo_interr_flag, echo_obstacle;
volatile long t_echo_s, t_echo_e;
long t_auto_last, t_auto_curr;
int echo_trig_sw;

void echo_interr(void)
{
  echo_interr_flag = 1 - echo_interr_flag;
  if (echo_interr_flag)
    t_echo_s = micros();
  else
  {
    t_echo_e = micros();
    duration = t_echo_e - t_echo_s;
    distance = duration * 0.034 / 2;
    if (distance < 12) //12cm
      echo_obstacle = 1;
    else
      echo_obstacle = 0;
  }
}

void echo_trigger(void)
{
  echo_trig_sw = 1;
  // Clears the trigPin
  digitalWrite(ULTRA_TRIG, LOW);
  delayMicroseconds(2);

  // Sets the trigPin on HIGH state for 10 micro seconds
  digitalWrite(ULTRA_TRIG, HIGH);
  delayMicroseconds(10);
  digitalWrite(ULTRA_TRIG, LOW);
  echo_interr_flag = 0;
  echo_obstacle = 0;
}

void auto_pilot(void)
{
  t_auto_curr = millis();
  if ((t_auto_curr - t_auto_last) > 80)
  {
    t_auto_last = t_auto_curr;
    //Serial.println("Trig");
    echo_trigger();
  }

  if (echo_obstacle)
  {
    echo_obstacle = 0;
    //Serial.println("obstacle");
    //stop
    setMotorSpeedM1(0);
    setMotorSpeedM2(0);
    delay(800);
    //back
    setMotorSpeedM1(-motor1);
    setMotorSpeedM2(-motor2);
    delay(600);
    //turn left
    setMotorSpeedM1(motor1);
    setMotorSpeedM2(-motor2);
    delay(600);
    //stop
    setMotorSpeedM1(0);
    setMotorSpeedM2(0);
    delay(500);
  }
  setMotorSpeedM1(motor1);
  setMotorSpeedM2(motor2);
}

// Set speed of Stepper Motor1
// tspeed could be positive or negative (reverse)
void setMotorSpeedM1(int16_t tspeed)
{
  if (tspeed >= 0)
  {
    analogWrite(MOTOR_M1_S1, 0);
    analogWrite(MOTOR_M1_S2, tspeed);
  }
  else
  {
    analogWrite(MOTOR_M1_S1, -tspeed);
    analogWrite(MOTOR_M1_S2, 0);
  }
}

// Set speed of Stepper Motor2
// tspeed could be positive or negative (reverse)
void setMotorSpeedM2(int16_t tspeed)
{
  if (tspeed >= 0)
  {
    analogWrite(MOTOR_M2_S1, 0);
    analogWrite(MOTOR_M2_S2, tspeed);
  }
  else
  {
    analogWrite(MOTOR_M2_S1, -tspeed);
    analogWrite(MOTOR_M2_S2, 0);
  }
}

void setup() {
  // put your setup code here, to run once:
  // init motors
  pinMode(MOTOR_M1_S1, OUTPUT);
  pinMode(MOTOR_M1_S2, OUTPUT);
  pinMode(MOTOR_M2_S1, OUTPUT);
  pinMode(MOTOR_M2_S2, OUTPUT);

  //init Ultrasonic sensor
  echo_interr_flag = 0;
  t_auto_last = 0;
  echo_obstacle = 0;
  pinMode(ULTRA_TRIG, OUTPUT); // Sets the trigPin as an Output
  pinMode(ULTRA_ECHO, INPUT_PULLUP); // Sets the echoPin as an Input
  attachInterrupt(digitalPinToInterrupt(ULTRA_ECHO), echo_interr, CHANGE);
  Serial.begin(BAUDRATE); // Serial output to console
  //Regis
  OSC.UDP_Init();

  Serial.println("BROBOT by JJROBOTS v2.2");

  // STEPPER MOTORS INITIALIZATION
  Serial.println("DC motors initialization...");
  // pre-action
  echo_trigger();
  // Little motor vibration to indicate that robot is ready
  setMotorSpeedM1(0);
  setMotorSpeedM2(0);
  for (uint8_t k = 0; k < 3; k++)
  {
    setMotorSpeedM1(1000);
    setMotorSpeedM2(1000);
    //BROBOT.moveServo1(SERVO_AUX_NEUTRO + 5);
    delay(100);
    setMotorSpeedM1(-1000);
    setMotorSpeedM2(-1000);
    //BROBOT.moveServo1(SERVO_AUX_NEUTRO - 5);
    delay(100);
  }
  setMotorSpeedM1(0);
  setMotorSpeedM2(0);

  for (int i = 0; i < 3; i++)
  {
    // pre-action
    echo_trigger();
    delay(200);
  }
  // OSC initialization
  OSC.fadder1 = 0.5;
  OSC.fadder2 = 0.5;

  Serial.println("Let's start...");
  mode = 0;
}

void loop() {

  //Regis
  OSC.MsgRead();  // Read UDP OSC messages
  if (OSC.toggle1 == 1)
  {
    motor1 = -780;
    motor2 = -780;
    auto_pilot();
  }

  if (OSC.newMessage)
  {
    OSC.newMessage = 0;
    //Regis
    if (OSC.page == 1) // Get commands from user (PAGE1 are user commands: throttle, steering...)
    {
      //OSC.newMessage = 0;
      throttle = (OSC.fadder1 - 0.5) * max_throttle;
      // We add some exponential on steering to smooth the center band
      steering = OSC.fadder2 - 0.5;
      if (steering > 0)
        steering = (steering * steering + 0.5 * steering) * max_steering;
      else
        steering = (-steering * steering + 0.5 * steering) * max_steering;

      motor1 = throttle - steering;
      motor2 = throttle + steering;
      motor1 = constrain(motor1, -MAX_CONTROL_OUTPUT, MAX_CONTROL_OUTPUT);
      motor2 = constrain(motor2, -MAX_CONTROL_OUTPUT, MAX_CONTROL_OUTPUT);
      setMotorSpeedM1(motor1);
      setMotorSpeedM2(motor2);
    }
  } // End new OSC message
}

測試


《使用jjRobots的遙控App》

《加上UltraSonic sensor》

2018年4月22日 星期日

[NodeMCU] L293D Motor shield board 電機驅動擴展板 應用

NodeMCU來控制馬達電機做個履帶機器人是最近想要做的項目,雖然是很普通的項目,但是加上攝像顯示屏還有一些sensors應該是個很好的運動平台。

《把NodeMCU直接插上》

《插上後狀態》
看上這片L293D擴展板是因為可以直接用NodeMCU,而且把全部的I/O都外接與VccGround規劃在一起,是個不錯的多功能的擴展板。
但是~~收到這板子,要命了。。。。這擴展板的footprint尺寸與手上的NodeMCU不適配,手上的NodeMCU比較寬將近2mm。也因為這樣,這片擴展板就被擺在桌角。
今天剛好瞄到,拿到手上把玩了一下,突然靈光一現,用排針座!!!
是的,90°排針座的尺寸剛剛好,太棒了~~~
立馬用烙鐵焊上,大小通吃!!

《腳跑出床外。。。》

《90°排針座剛好》

《呵呵,大小通吃》
既然接上了,那就繼續把這擴展板徹底研究清楚。
找遍x寶都找不到這片擴展板的原理圖,只好用萬用電錶一條條量測,還好沒很多條。
下圖就是示意圖。
《L293D接腳與擴展版圖》

《正面解說圖》

《尺寸圖》

測試工具與設備

NodeMCU x1
L293D Motor Shield board 電機擴展板 x1
5-12v電機馬達
萬用電錶 x1
電源供應器 x1

測試code

/*
 * RegisHsu 2018-04-22
 * The L293D motor shield board for NodeMCU
 * Enable 1-2: nodemcu pin 1
 * Enable 3-4: pin 2
 * 1A: pin 3
 * 2A: internal control
 * 3A: pin 4
 * 4A: internal xontrol
 */

#define __NODEMCU__

#ifdef __NODEMCU__
// NodeMCU與ESP8266 pin對照表
// These are the pins for all ESP8266 boards
//      Name   GPIO    Function
#define PIN_D0  16  // WAKE
#define PIN_D1   5  // User purpose
#define PIN_D2   4  // User purpose
#define PIN_D3   0  // FLASH mode at boot time
#define PIN_D4   2  // TXD1 (Note: low on boot means go to FLASH mode)
#define PIN_D5  14  // HSCLK
#define PIN_D6  12  // HMISO
#define PIN_D7  13  // HMOSI  RXD2
#define PIN_D8  15  // HCS    TXD0
#define PIN_D9   3  // RXD0
#define PIN_D10  1  // TXD0

#define E1 PIN_D1  // Enable Pin for motor 1, every pin can be PWM in ESP8266
#define E2 PIN_D2  // Enable Pin for motor 2

#define I1 PIN_D3  // Control pin 1 for motor 1
#define I3 PIN_D4  // Control pin 1 for motor 2

#else
//      Name   GPIO    Function
#define PIN_D0   0
#define PIN_D1   1
#define PIN_D2   2
#define PIN_D3   3
#define PIN_D4   4
#define PIN_D5   5
#define PIN_D6   6
#define PIN_D7   7
#define PIN_D8   8
#define PIN_D9   9
#define PIN_D10  10

#define E1 PIN_D8  // Enable Pin for motor 1
#define E2 PIN_D9  // Enable Pin for motor 2

#define I1 PIN_D3  // Control pin 1 for motor 1
#define I3 PIN_D4  // Control pin 1 for motor 2

#endif

void setup()
{
  Serial.begin(115200);

  pinMode(E1, OUTPUT);
  pinMode(E2, OUTPUT);

  pinMode(I1, OUTPUT);
  pinMode(I3, OUTPUT);
  Serial.println("Setup already...");
}

void L_Wheel(int sp, bool s1, bool s2)
{
  analogWrite(E2, sp); // Enable as speed
  digitalWrite(I3, s1);
}

void R_Wheel(int sp, bool s1, bool s2)
{
  analogWrite(E1, sp); // Enable as speed
  digitalWrite(I1, s1);
}

int spd = 1020;
void loop()
{
  Serial.print("SPD=");
  Serial.println(spd);
  //FF
  Serial.println("FF...");
  L_Wheel(spd, HIGH, LOW);
  R_Wheel(spd, HIGH, LOW);
  delay(2000);
  L_Wheel(0, LOW, LOW);
  R_Wheel(0, LOW, LOW);
  delay(2000);

  //RR
  Serial.println("RR...");
  L_Wheel(spd, LOW, HIGH);
  R_Wheel(spd, LOW, HIGH);
  delay(2000);
  L_Wheel(0, LOW, LOW);
  R_Wheel(0, LOW, LOW);
  delay(2000);

  //FR
  Serial.println("FR...");
  L_Wheel(spd, HIGH, LOW);
  R_Wheel(spd, LOW, HIGH);
  delay(2000);
  L_Wheel(0, LOW, LOW);
  R_Wheel(0, LOW, LOW);
  delay(2000);

  //RF
  Serial.println("RF...");
  L_Wheel(spd, LOW, HIGH);
  R_Wheel(spd, HIGH, LOW);
  delay(2000);
  L_Wheel(0, LOW, LOW);
  R_Wheel(0, LOW, LOW);
  delay(2000);
  spd = spd - 100;
  if (spd < 200)
    spd = 1020;
}

視頻分享

2018年4月8日 星期日

[ESP8266] MicroPython + TFT-LCD

想找個小屏來做個有趣的玩具,前些時間買一套樹莓派3B包含攝像頭3.5“TFT-LCD
也用3D打印機做個漂亮的外殼。

好不容易把camera和屏的驅動都設定好,準備開始拿來做有攝像與顯示功能的Robot,結果樹莓派3B不給力,用沒多久就開始開機不順然後直接掛點,還好camera與3.5“屏都可用。
既然樹莓派3B掛點,但對這個3.5”屏非常有興趣想知道底層driver如何控制電阻式touch與TFT-LCD屏。手上有幾片ArduinoESP8266,心中在想是否可以用來學習點亮這個屏? 花了好幾天睡覺時間google很多相關這個屏的pinout電路原理圖,看來全球還真不少強人已經跑在前面好多,車尾燈都不見。。。
這屏的TFT driver是ILI9486,touch driver是XPT2046。在搜尋過程中,這個神人寫的driver驅動最厲害TFT_eSPI,幾乎把可接的LCD屏都寫好並優化加速。
雖然很完整,但是我是想要學習如何透過SPI控制LCD與Touch,能一步步下command觀察屏driver的反應,Python應該是最佳選擇。

前言 MicroPython

MicroPython真是不錯的project,用ESP8266跑起來還算順,但是memory的空間非常有限,我的pyhton code只要稍微多寫一些,就很容易就出現“MemoryError: memory allocation failed,xxxxxxxxx”。所以,MicroPython在ESP8266只能當做實驗作用,無法做成有用的產品。

材料設備

步驟一,NoteMCU與LCD接線

這個屏都是透過SPI配合幾根control pin如chip selectCommand-Datainterrupt來控制,接腳不多但是軟件開發似乎難度不低。。。
NoteMCU pinout%20pinout.pdf)
TFT-LCD pinout
NoteMCU     TFT-LCD
=======     ========
D8              TFT-CS(24)
D3              Command/Data(18)
D4              Reset(22)
D1              Touch-CS(26)
D7              MOSI(19)
D6              MISO(21)
D5              CLK(23)
+5              +5(2)
GND           GND(6)

TFT_CS = 15   # TFT Chip select control pin D8
TFT_DC = 0    # Data Command control pin D3
TFT_RST = 2   # Reset pin pin D4
TOUCH_CS = 5  # Touch Chip select control pin D1

NodeMCU與ESP8266 pin對照表

// These are the pins for all ESP8266 boards
//      Name   GPIO    Function
#define PIN_D0  16  // WAKE
#define PIN_D1   5  // User purpose
#define PIN_D2   4  // User purpose
#define PIN_D3   0  // FLASH mode at boot time
#define PIN_D4   2  // TXD1 (Note: low on boot means go to FLASH mode)
#define PIN_D5  14  // HSCLK
#define PIN_D6  12  // HMISO
#define PIN_D7  13  // HMOSI  RXD2
#define PIN_D8  15  // HCS    TXD0
#define PIN_D9   3  // RXD0
#define PIN_D10  1  // TXD0

#define PIN_MOSI 8  // SD1
#define PIN_MISO 7  // SD0
#define PIN_SCLK 6  // CLK
#define PIN_HWCS 0  // CMD

#define PIN_D11  9  // SD2
#define PIN_D12 10  // SD4

步驟二,下載MicroPython並燒錄到NoteMCU

下載Firmware,我選擇esp8266-20171101-v1.9.3.bin
依照以下指令,安裝ESP8266燒錄工具後,先將NoteMCU的flash清乾淨,然後燒錄MicroPython Firmware。
pip install esptool
esptool.py --port /dev/cu.usbserial erase_flash
esptool.py --port /dev/cu.usbserial --baud 460800 write_flash --flash_size=detect -fm dio 0 esp8266-20171101-v1.9.3.bin
如果沒發生任何錯誤,這MicroPython應該已經完整上傳ESP8266。按下NoteMCU板子上的reset後,MicroPython就開始作動。

步驟三,透過terminal進入MicroPython的對話模式

在MacOS環境,打開Terminal,輸入以下指令
screen /dev/cu.usbserial 115200
這時候應該進入MicroPython的對話模式,按幾下Enter會出現>>>的符號,表示NoteMCU準備接收下指令。輸入簡單指令測試
>>> print('hello esp8266!')
hello esp8266!
>>> import esp
esp.check_fw()
NoteMCU應該回應剛剛燒錄的Firmware version。
重要: 要退出screen,按下CTRL-A+CTRL-K然後回答yes即可退出

步驟四,上傳TFT-LCD的測試code

這是我寫的測試code,分享在GitHub。
那,如何從GitHub下載後如何上傳到NoteMCU?

步驟五,設定NoteMCU上網與File Transfer

輸入以下的code,讓NoteMCU連上家中Wifi AP。
import network
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect('<your ESSID>', '<your password>')
sta_if.ifconfig()
會回應類似的IP address
('192.168.0.2', '255.255.255.0', '192.168.0.1', '8.8.8.8')
這樣NoteMCU就應該連上Wifi AP。
再來就是上傳Python code,不然自己慢慢“摳”到死。。。
參考WebREPL設定

方法一

這個方法簡單,但是會影響PC/Mac上internet。

方法二(我喜歡的方法)

在MicroPython對話模式下這個指令,把WebREPL啟動(Enable)
import webrepl_setup
E啟動,再輸入簡單密碼。這個密碼是運用REPL上傳Python code都需要輸入。
下載REPL,然後解開zip file後在folder內下運行以下的指令就可以上傳Python code。
./webrepl_cli.py raspi35.py 192.168.0.2:/raspi35.py
輸入密碼後就上傳。

步驟六,執行上傳的Python code

最重要的部分出現啦。。。
輸入指令就可以執行剛上傳的code
exec(open("raspi35.py").read())
接下來就可以看到類似這樣的圖形在LCD上,就大功告成!!!

參考:

我的開發桌面~~