気の向くままに辿るIT/ICT/IoT
IoT・電子工作

Raspberry Pi/WebIOPi/RPI.GPIO/28BYJ-48でパンチルト

ホーム前へ次へ
Raspberry Piって?

Raspberry Pi/WebIOPi/RPI.GPIO/28BYJ-48でパンチルト

Raspberry Pi/WebIOPi/RPI.GPIO/28BYJ-48でパンチルト

2022/05/04

 Raspberry Piとステッピングモータ28BYJ-48 5V/ステッピングモータドライバULN2003でブラウザからUSBカメラのパンチルト(縦横首振り)のうち、パン(左右首振り)できる機能を作ってみようかとPython界隈を散策していたら、Raspberry Piフォーラム内の回答に何を変更することもなく、そのままプログラム中のGPIOピンにつなげば、パンチルトともに実現できるピッタリなプログラム一式を見つけ、動作確認してみた話。

 使用したラズパイは、余った3B+、現時点のRaspberry Pi OSのバージョンは11.3、開発コードはbullseye。

 ただし、当該プログラムでは、ラズパイGPIO操作には、RPI.GPIOを使っている一方、Webサーバ機能として既に3系に完全移行したPythonにおいて2系に依存したまま、数年前に開発停止したWebIOPiを使っているので他に移植するつもり。

 結果、Raspberry Pi/pywebview/RPI.GPIO/28BYJ-48に寄り道しつつ、Raspberry Pi/Flask/WebSocket/RPI.GPIO/28BYJ-48にすることにしました。

 が、動画のソースは、IPアドレスやボタンの文字など変更しているものの、リンク先がなくなったら寂しいのでリンク先と全く同一ですが、そのままコピペさせてもらったのが以下。

Pythonスクリプト

USER@raspberrypi:~ $ cat stepper.py
import threading
import time
import sys
import webiopi
import RPi.GPIO as GPIO
 
GPIO.setmode(GPIO.BCM)
 
 
class Stepper:
 
 GPIO= None
 
 def __init__(self, Id=0,IO=[17 , 18 , 27 ,22], PulseDelay = 0.03):
  #set variable value
  self.StepperId=Id
  self.StepperIO= IO
  self.PulseDelay = PulseDelay
  self.CurrentPosition = 0
  self.TargetPosition = 0
  self.ThreadRunning = False
  self.StopThread= True
  self.StepperTable= [[1,0,0,1] , [1,1,0,0] , [0,1,1,0] , [0,0,1,1]]
 
 
 
 def begin(self):
  #set GPIO OUTPUT
  for i in self.StepperIO:
   GPIO.setup(i,GPIO.OUT)
   GPIO.output(i,0)
  #start thread
  self.StopThread=False
  t = threading.Thread(target=self.stepperThread)
  t.start()
 
 
 def setCoil(self):
   StepperIdx = self.CurrentPosition & 3
   for i in range(4):
    GPIO.output(self.StepperIO[i], self.StepperTable[i][StepperIdx])
 
 
 def stepperThread(self):
  self.ThreadRunning=True
  while not self.StopThread:
   if self.CurrentPosition != self.TargetPosition:
    if self.CurrentPosition > self.TargetPosition:
     self.CurrentPosition -= 1
    else:
     self.CurrentPosition += 1
    self.setCoil()
    time.sleep(self.PulseDelay)
   else:
    time.sleep(0.01)
  self.ThreadRunning=False
 
 def moveTo(self, Target):
  self.TargetPosition = Target
 
 def move(self,StepCount):
  Target= self.TargetPosition + StepCount
  self.moveTo(Target)
 
 def stop(self):
  TargetPosition= CurrentPosition
 
 
stepper0 = Stepper(Id=0,IO=[17 , 18 , 27 ,22])
stepper1 = Stepper(Id=1,IO=[23 , 24 , 25 , 4])
 
stepper0.begin()
 
#stepper1 is not activate right now
#remove the rem on the next line to activate it
#stepper1.begin()
 
@webiopi.macro
def StepperMove(_StepperId,_Step):
 StepperId= int(_StepperId)
 Step = int(_Step)
 if StepperId == 0 :
  stepper0.move(Step)
 else:
  stepper1.move(Step)
 
 
@webiopi.macro
def StepperMoveTo(_StepperId,_Step):
 StepperId= int(_StepperId)
 Step = int(_Step)
 if StepperId == 0 :
  stepper0.moveTo(Step)
 else:
  stepper1.moveTo(Step)
 
@webiopi.macro
def StepperStop(_StepperId):
 StepperId= int(_StepperId)
 if StepperId == 0:
  stepper0.stop()
 else:
  stepper1.stop()
 
@webiopi.macro
def GetStepperPosition(_StepperId):
 StepperId= int(_StepperId)
 if StepperId == 0:
  return str(stepper0.CurrentPosition)
 else:
  return str(stepper1.CurrentPosition)
 
@webiopi.macro
def GetStepperTarget(_StepperId):
 StepperId= int(_StepperId)
 if StepperId == 0:
  return str(stepper0.TargetPosition)
 else:
  return str(stepper1.TargetPosition)
USER@raspberrypi:~ $

 stepper.py(リンク先newの方)は、こんな感じ。

HTML/CSS/JavaScriptソース

USER@raspberrypi:~ $ cat Stepper2.html
<html>
<head>
<title>Stepper</title>
 
<script type="text/javascript" src="/webiopi.js"></script>
<script type="text/javascript">
 
  // read CurrentPosition On start
  webiopi().ready(function(){
  setInterval(ReadPosition,500);
  ReadTarget(0);
  ReadTarget(1);
  });
 
 function ReadPosition()
  {
  webiopi().callMacro("GetStepperPosition",[0], readPositioncallback);
  webiopi().callMacro("GetStepperPosition",[1], readPositioncallback);
  }
 
 
 function SetTarget(StepperId,value)
 {
  var nStep = parseInt(value);
  webiopi().callMacro("StepperMoveTo",[StepperId , nStep]);
  }
 
 
 function ReadTarget(StepperId)
 {
  webiopi().callMacro("GetStepperTarget",[StepperId], readTargetcallback);
 }
 
 
  var readTargetcallback = function(macro,args,response){
        var StepperId = parseInt(args[0]);
        var TargetP = "TargetP" + StepperId.toString();
        // let's put the Target value
        document.getElementById(TargetP).value=response;
        }
 
  var readPositioncallback = function(macro,args,response){
        var StepperId = parseInt(args[0]);
        var CurrentP = "CurrentP" + StepperId.toString();
        // let's put the position
        document.getElementById(CurrentP).value=response;
        }
 
 
 function Move(StepperId , StepCount) {
  webiopi().callMacro("StepperMove",[StepperId , StepCount]);
  var TargetP = "TargetP" + StepperId.toString();
  var newTarget = parseInt(document.getElementById(TargetP).value);
  newTarget = newTarget + StepCount;
  document.getElementById(TargetP).value = newTarget.toString();
 }
 
 function Forward(StepperId)
 {
  var nStep = "nStep"+StepperId.toString();
  var StepValue = parseInt(document.getElementById(nStep).value)
  Move(StepperId,StepValue);
 }
 
 function Backward(StepperId)
 {
  var nStep = "nStep"+StepperId.toString();
  var StepValue = parseInt(document.getElementById(nStep).value)
  Move(StepperId,-StepValue);
 }
 
 
 
</script>
 
 
<style type="text/css">
 
.myButton {
 -moz-box-shadow:inset 0px 1px 0px 0px #ffffff;
 -webkit-box-shadow:inset 0px 1px 0px 0px #ffffff;
 box-shadow:inset 0px 1px 0px 0px #ffffff;
 background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #ffffff), color-stop(1, #f6f6f6));
 background:-moz-linear-gradient(top, #ffffff 5%, #f6f6f6 100%);
 background:-webkit-linear-gradient(top, #ffffff 5%, #f6f6f6 100%);
 background:-o-linear-gradient(top, #ffffff 5%, #f6f6f6 100%);
 background:-ms-linear-gradient(top, #ffffff 5%, #f6f6f6 100%);
 background:linear-gradient(to bottom, #ffffff 5%, #f6f6f6 100%);
 background-color:#ffffff;
 -moz-border-radius:6px;
 -webkit-border-radius:6px;
 border-radius:6px;
 border:1px solid #dcdcdc;
 display:inline-block;
 cursor:pointer;
 color:#666666;
 font-family:Arial;
 font-size:15px;
 font-weight:bold;
 padding:6px 24px;
 text-decoration:none;
 text-shadow:0px 1px 0px #ffffff;
 width: 130px;
 height:30px;
 margin:1px;
}
.myButton:hover {
 background:-webkit-gradient(linear, left top, left bottom, color-stop(0.05, #f6f6f6), color-stop(1, #ffffff));
 background:-moz-linear-gradient(top, #f6f6f6 5%, #ffffff 100%);
 background:-webkit-linear-gradient(top, #f6f6f6 5%, #ffffff 100%);
 background:-o-linear-gradient(top, #f6f6f6 5%, #ffffff 100%);
 background:-ms-linear-gradient(top, #f6f6f6 5%, #ffffff 100%);
 background:linear-gradient(to bottom, #f6f6f6 5%, #ffffff 100%);
 background-color:#f6f6f6;
 padding:6px 24px;
}
.myButton:active {
 color:#ff0000;
 position:relative;
 top:1px;
 padding:6px 24px;
}
 
</style>
 
</head>
<body>
<center><h3> My Stepper</h3><br>
<table border="2">
<td>
<table style="margin-left:auto;margin-right:auto">
<tr><td colspan="2" align="center">Stepper 0</td></tr>
<tr><td>Current Position:</td><td><input type="text" id="CurrentP0" name="CurrentP0" value="0" readonly></td></tr>
<tr><td>Target Position:</td><td><input type="text" id="TargetP0" name="TargetP0" value="0" onchange="SetTarget(0,this.value)"></td></tr>
<tr><td>Number of Step:</td><td><input type="text" id="nStep0" name="nStep0" value="100"></td></tr>
<tr><td colspan="2" align="center"><button class="myButton" type="button" onclick="Backward(0)">Backward</button>
<button class="myButton" type="button" onclick="Forward(0)">Forward</button></td></tr>
</table>
</td>
<td>
<table border="0">
<tr><td colspan="2" align="center">Stepper 1</td></tr>
<tr><td>Current Position:</td><td><input type="text" id="CurrentP1" name="CurrentP1" value="0" readonly></td></tr>
<tr><td>Target Position:</td><td><input type="text" id="TargetP1" name="TargetP1" value="0" onchange="SetTarget(0,this.value)"></td></tr>
<tr><td>Number of Step:</td><td><input type="text" id="nStep1" name="nStep1" value="100"></td></tr>
<tr><td colspan="2" align="center"><button class="myButton" type="button" onclick="Backward(1)">Backward</button>
<button class="myButton" type="button" onclick="Forward(1)">Forward</button></td></tr>
</table>
</td>
</table>
</center>
</body>
</html>
USER@raspberrypi:~ $

 Stepper2.htmlは、こんな感じ。

操作例

 ソース通りなら、ラズパイのGPIO(BCM)ピン17/18/27/22を、それぞれULN2003のIN1/IN2/IN3/IN4に接続し、電源投入、端末でstepper.pyを実行(python stepper.py)、当該マシンや他マシンのブラウザでラズパイのIPアドレスやmDNS(*.local)にポート8000(*.*.*.*:8000や*.local:8000)にアクセスし、Stepper0側のボタンを操作。

 Forward/Backwardクリックすると、Number of Step分だけ、Target Positionが増減、Current Positonには、回転中の位置が反映されるようになっているという芸の細かい秀逸なものとなっています。

 これの視覚的に不要な部分を非表示にした上、例えば、ustreamerやmjpg-streamerなどの映像を加えるだけで動作中のカメラのパンチルト状態をリアルタイムで見て取れるようになるということで自身のニーズにピッタリ、しっくりだったわけです。

ホーム前へ次へ