自制摄像头云台,并通过计算机LPT并口直接控制(二)

硬件准备

1,待监控对象若干(只、个、位),呵呵开个玩笑。

2,普通摄像头一个,电脑市场到处都是卖的,¥30~40左右一个,我这个买的时候号称1400万像素,要是真的话,我还是回去把我的佳能7D扔掉好了。

3,RC模型舵机两个,淘宝有的卖,10~20一个,因为我们只是控制摄像头转动,需要的力矩很小,普通3KG的足够了。我们需要控制摄像头左右、上下移动,所以需要2个舵机,一个转轴水平,一个转轴垂直。如果你只想左右移动,一个也可以凑合。

4,302胶水,¥2。

5,USB电缆一根,从坏掉的USB鼠标键盘弄来一根不要钱的。

6,带有LPT接口的电脑一台,大部分台式机都有,如果你是笔记本可以需要10块钱左右买一个USB To LPT的转接口了。

硬件连接

把你的两个舵机和摄像头固定起来,你可能会选择502胶水其实302效果更好虽然干得慢一些。首先把1号舵机立起来的使转轴垂直向上,然后在转轴上用胶水固定第2号舵机,2号舵机的转轴应该水平,最后把摄像头用胶水固定到2号舵机的转轴,最终效果就像这样。

你可能会觉得这个底座不错,其实是摄像头自带的,想要的话,你买这种摄像头就行了。40块钱,这个摄像头还带麦克风呢。

舵机控制线与电源线连接

1,你把两个舵机的地线(通常是黑色)连在一起合并成一根,然后弄一根线延长一些插入上图蓝色线的插口内。

2,把两个舵机的信号线(通常是白色)分别延长插入黄色和白色线的插口。

3,把你准备的USB线剪开,会发现四根线,其中红色连接到两个舵机的火线(通常也是红色),黑色连接到舵机的地线(通常也是黑色),确定连接没有问题,就把这个USB线插入电脑,这样舵机的电源供应就解决了。

4,固定LPT接口上不稳定的插线连接很必要,要不信号会不稳定,舵机会乱抖,这时你可以选择插入三个小钉子、牙签、或者像我一样使用剪断后的吉他D弦,用力插入后固定效果还是不错的,不过要注意不要搞短路了。

5,最后插入摄像头的USB线,这一步很简单。

最后我们看一下整体情况:

舵机地线(黑色)合并在一起,连接到LPT(第二排从左起1~8随便选),也同时连接到USB电源线的地线(黑色)。

舵机火线(红色)合并在一起,连接到USB电源线的火线。

舵机信号线(白色),分别单独连接到对应的LPT数据口。

插入USB电源线到主机,插入摄像头USB线到主机,连接完成。

准备编写控制软件

首先回顾一下我们的控制理论:

1,舵机信号线接收周期为20ms的电平信号,根据高电平的持续时间,决定舵机的转向角度。

2,LPT接口一次可以发送一个字节,一个字节=8位,所以LPT接口有8个数据端口,每个端口代表1位。如果这一位是0,该端口就是低电平0V。如果这一位是1,该端口就是高电平5V。其实LPT端口可以控制8个舵机,只是我们仅需要两个而已。

3,我们先给LPT端口发送0xff(1 1 1 1 1 1 1 1),然后1.5ms后发送0x00(0 0 0 0 0 0 0 0),如此往复。这样每个端口所连接的舵机就会接收到1.5ms的高电平信号,根据舵机角度与高电平时间的关系,所有连接的舵机都会指向90度的位置。

4,我们先给LPT端口发送0xff,然后1ms后发送0x01,在经过1ms后发送0x00,那么1号舵机接收到2ms高电平,2号舵机接收到1ms高电平,两个舵机就会指向不同的位置了。

5,最后我们得出发送字节与所控制的舵机的对应关系。

0x01:1通道舵机有信号。
0x02:2通道舵机有信号。
0x04:3通道舵机有信号。
0x08:4通道舵机有信号。
0x10:5通道舵机有信号。
0x20:6通道舵机有信号。
0x40:7通道舵机有信号。
0x80:8通道舵机有信号。

如果你想让1/2通道有信号,而其他通道没有信号,很简单发送0x01 OR 0x02 = 0x03就行了。

好了,控制理论我们已经很清楚了,接下来我们遇到了两个问题。

1,由于0.5ms~2.5ms这短短的2ms就代表了0~180度的角度变化,我们的定时器必须很精确才行,要不1ms的偏差就会导致90度的角度偏差,这是不能容忍的。

2,我们需要直接向IO端口写入字节,而Win32的保护机制是不允许你这么干的,你要是用DOS可能会容易点。

不过不要灰心,第一个问题可以使用WindowsAPI QueryPerformanceFrequency/QueryPerformanceCounter高精度计时器来解决,这个已CPU频率为基准的计时器的精确度足以满足我们的需要。简单来说QueryPerformanceFrequency返回你的硬件每秒钟震荡频率是多少次,在我的机器是2208064,函数QueryPerformanceCounter返回自开机以来你的硬件晶体已经震荡了多少次。具体可以查阅MSDN。

第二个问题有WinIO可以帮助我们,这是一个兼容所有Windows平台的让你可以进入RING0来直接读写IO或物理内存的免费函数库。WinIO官方网站(www.internals.com

舵机控制部分源代码

unit ThreadServo;

interface

uses SysUtils, Windows, Classes, SyncObjs, WinIo;

const
  MAX_CHANNELS=8;

type
  EServoThread = class(Exception);

  TServoThread = class(TThread)
  private
    FBase:Int64;
    FCurrent:Int64;
    FInterval:Int64;
    FFrequency:Int64;
    FMaxPulse:Int64;
    FMinPulse:Int64;
    FLastSignal:Byte;
    FSleeped:Boolean;
    FAllowSleep:Boolean;
    FLock:TCriticalSection;
    FChannels:array [1..MAX_CHANNELS] of Integer;
    FChannelsTime:array [1..MAX_CHANNELS] of Int64;
    procedure SetChannel(Index: Integer; const Value: Integer);
    procedure SetSignal(CH1,CH2,CH3,CH4,CH5,CH6,CH7,CH8:Boolean);
    procedure SetInterval(const Value: Integer);
    procedure SetMaxPulse(const Value: Integer);
    procedure SetMinPulse(const Value: Integer);
  public
    constructor Create;
    destructor Destroy; override;
    procedure Execute; override;
    property Channels[Index:Integer]:Integer write SetChannel;
    property Interval:Integer write SetInterval;
    property MaxPulse:Integer write SetMaxPulse;
    property MinPulse:Integer write SetMinPulse;
    property AllowSleep:Boolean write FAllowSleep;
  end;

implementation

{ TServoThread }

constructor TServoThread.Create;
begin
  if not InitializeWinIo then //初始化WinIO,一边可以直接访问硬件IO
    raise EServoThread.Create('Initialize error.');
  FLock:=TCriticalSection.Create;
  QueryPerformanceFrequency(FFrequency);  //取得硬件晶体震荡频率
  SetInterval(20000);  //设置信号周期 单位是微妙,1毫秒=1000微妙 所以信号周期为20ms  
  SetMaxPulse(2500);  //最长脉冲2.5ms
  SetMinPulse(500);  //最短脉冲0.5ms
  FAllowSleep:=True;  //允许低电平的时候Sleep,节省CPU占用
  inherited Create(False);
end;

destructor TServoThread.Destroy;
begin
  Terminate;
  ShutdownWinIo;
  FLock.Free;
  inherited;
end;

procedure TServoThread.Execute;
begin
  Priority:=tpTimeCritical;  //提升线程优先级
  QueryPerformanceCounter(FBase);
  while not Terminated do begin
    QueryPerformanceCounter(FCurrent);
    if FCurrent-FBase>FInterval then begin  //每周期进入一次,初始化一些东西
      FBase:=FCurrent;
      FSleeped:=False;
    end;
    //设置LPT每一个通道的电平,8个参数对应,8个通道,True为高电平,False为第电平。
    SetSignal(FCurrent-FBase<FChannelsTime[1],  //根据本周期以进行的时间算出应该高电平还是低电平。下同
              FCurrent-FBase<FChannelsTime[2],
              FCurrent-FBase<FChannelsTime[3],
              FCurrent-FBase<FChannelsTime[4],
              FCurrent-FBase<FChannelsTime[5],
              FCurrent-FBase<FChannelsTime[6],
              FCurrent-FBase<FChannelsTime[7],
              FCurrent-FBase<FChannelsTime[8]);
  end;
end;

procedure TServoThread.SetChannel(Index: Integer; const Value: Integer);
begin
  //设置每个通道的位置1024个级别,0=0度 1024=180度
  if (Index<1) or (Index>MAX_CHANNELS) then
    raise EServoThread.Create(Format('Index %d out of Channels.',[Index]));
  FLock.Enter;
  FChannels[Index]:=Value;
  //根据通道位置级别算出对应的高电平脉冲时间,微秒。
  FChannelsTime[Index]:=FMinPulse+Round((FMaxPulse-FMinPulse)*(Value/1024));
  FLock.Leave;
end;

procedure TServoThread.SetInterval(const Value: Integer);
begin
  FInterval:=Round(FFrequency*(Value/1000/1000));
end;

procedure TServoThread.SetMaxPulse(const Value: Integer);
begin
  FMaxPulse:=Round(FFrequency*(Value/1000/1000));
end;

procedure TServoThread.SetMinPulse(const Value: Integer);
begin
  FMinPulse:=Round(FFrequency*(Value/1000/1000));
end;

procedure TServoThread.SetSignal(CH1,CH2,CH3,CH4,CH5,CH6,CH7,CH8:Boolean);
var
  S:Integer;
  Signal:Byte;
begin
  Signal:=0;
  if CH1 then Signal:=Signal or $01;
  if CH2 then Signal:=Signal or $02;
  if CH3 then Signal:=Signal or $04;
  if CH4 then Signal:=Signal or $08;
  if CH5 then Signal:=Signal or $10;
  if CH6 then Signal:=Signal or $20;
  if CH7 then Signal:=Signal or $40;
  if CH8 then Signal:=Signal or $80;
  //因为IO操作很费时还可能导致时间片切换,所以不重复写入相同的IO值
  if FLastSignal<>Signal then begin
    FLastSignal:=Signal;
//注意这个$378,这代表第一个LPT端口的数据位IO地址。第二个LPT数据位IO地址为$278 if not SetPortVal($378,Signal,1) then raise EServoThread.Create('SetPortVal Error.'); end;
  //低电平时间允许通过Sleep把CPU还给操作系统,以免CPU占用100%的尴尬。
  if (Signal=0) and FAllowSleep and not FSleeped then begin
    S:=Trunc((FInterval-(FCurrent-FBase))/FFrequency*1000);
    Sleep(S-1);
    FSleeped:=True;
  end;
end;

end.

总结

本章描述了从如何连接硬件、接线,以及通过WinIo直接控制IO输入,并通过高精度计时器生成规律的脉冲信号控制舵机。

由于时间关系还没写完的内容包括:

1,编写一个小型HTTP服务器,用WEB页面控制舵机,使得远程控制成为可能。

2,抓取摄像头视频并通过HTTP服务传送给客户端。

3,编写一些JS代码,把视频传送和舵机控制结合在一个页面。

4,制定一个密码机制,不能让所有人都可以访问你的摄像头。

5,动态IP不可知怎么不办?花生壳

6,内网还需要在路由做端口映射

不过其实软件已经全部完成,由于本章已经把硬件连接的方法给出,我先把编译后的完整程序发出来,有兴趣的朋友接上舵机就可以先用了。

软件下载地址:https://files.cnblogs.com/manors/ServoControlV03.rar

我还会在几天内更新第三部分,敬请关注。

更新1:运行软件后钩上Remote Port和Camera后面的Enable,然后再浏览器打入 http://localhost:81/ 输入admin密码fakepeter就可以看了,如果需要远程请设置防火墙或路由端口映射。

更新2:刚从手机里翻出来一段测试时候的控制视频,有兴趣可以看看

posted @ 2011-03-27 20:53  庄园  阅读(10792)  评论(21编辑  收藏  举报