65.9K
CodeProject 正在变化。 阅读更多。
Home

RC 汽车控制编程

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (50投票s)

2010年11月12日

CPOL

6分钟阅读

viewsIcon

252686

downloadIcon

16040

RC 汽车的高层设计, 包括微控制器编程和 PC 上的用户界面

snapshot.JPG

引言

这辆车是一个玩具,上面载有一个摄像头,通过 PC 上的软件控制,可以被驾驶到用户感兴趣的地方。它的运动由两个直流电机(每侧一个)控制,这两个电机的速度通过齿轮箱减速。我在这里将不讨论这辆车的机械和电子部分的细节,当然也包括 PC 上软件的视频显示部分。因此,车子的输入和输出模块仅表示为方框。这是我用 Google SketchUp 绘制的草图。

carPic.JPG

在下图中,整个项目被分解为模块。

Architecture.JPG

背景

正如我在上图所示,该项目主要分为两部分:PC 和车。它们之间有一个 RF 链路用于传输命令,以及一个单独的无线视频链路(此处未显示)用于视频传输。我将 RF 链路设置为单向通信,因为我找到的 RF 组件支持这种操作模式。

在图的左上角,您可以看到一个红外 LED 和一个红外传感器。这些是用于避免与障碍物碰撞的电路。电脉冲会定期发送以打开和关闭红外 LED,并且在单个脉冲周期内会读取两次读数,这样控制器就能“知道”红外传感器的实际读数。这是通过下表中描述的简单逻辑实现的。

输出记录在

推断

开启脉冲时

关闭时间

1

0

有障碍物

1

1

传感器被环境光饱和

0

0

没有障碍物

0

1

此读数不合逻辑(传感器错误)

您可以参考 这里 关于构建简单红外传感器电路。

接下来是 RF 接收器。命令不断地从连接到 PC 串行端口的 RF 发射器以一系列位的形式传输,并由位于车上的 RF 接收器接收。我创建了一个简单的 C# 应用程序用于命令位生成以及视频显示和回放功能。SerialPort 类负责根据用户的输入发送这些字节序列。RF 发射器从串行端口接收这些数据,并通过其天线进行适当的调制进行传输。我使用了 Linx 的 IC 来创建我的车和 PC 之间的 RF 链路。可以在 这里这里 找到发射器和接收器的数据手册及其应用。

为了控制我的 RC 车的直流电机,我使用了两个 H 桥,如前所述。这两个 H 桥由微控制器通过并行端口的某个引脚的输出驱动。可以在 这里 找到关于 H 桥的非常好介绍和应用。

Using the Code

PC 程序

PC 程序有两个主要部分:视频显示和回放,以及一个车控制模块。

视频显示和回放模块使用 DirectShow 库来访问来自无线视频接收器的视频数据,但由于这部分软件不是我的关注点,我暂不讨论。作为参考,您可以查看 Andrew Krillov 在 CodeProject 上一篇非常好的文章(这里)。

车控制模块是使用 SerialPort 对象实现的,七个用于方向控制的按钮,以及一个用于列出可用串行端口的组合框。请记住包含以下命名空间:

using System.IO.Ports;

在窗体加载事件处理程序中,我将所有可用端口提取到组合框中

// create an array for getting available port on my PC
string[] availablePorts;
// fetch them all 
availablePorts = SerialPort.GetPortNames();
// copy them to a comboBox
for (int i = 0; i < availablePorts.Length; i++)
{ 
    cboPort.Items.Add(availablePorts[i]);
}  

每次单击按钮时,都会调用 SendToSerialPort(string data) 方法,并将其各自的 string 参数传递进去,如下所述。

private void SendToSerialPort(string data)
        {
            // create an instance of serial port
            SerialPort ser = new SerialPort();
            byte[] command = new byte[2];
            command[0] = (byte)Convert.ToByte(data, 16);

            // set its properties  
            // i preferred the ff values
            ser.BaudRate = 9600;
            ser.DataBits = 8;
            ser.Parity = Parity.None;
            ser.StopBits = StopBits.One;
            ser.PortName = cboPort.Text;

            // if our command array is not empty then...
            if (command != null)
            {
                // open it if it is closed
                if (!ser.IsOpen)
                    ser.Open();
                // write the byte
                ser.Write(command, 0, 1);    // this sends only a byte to the port
                // then close it
                ser.Close();
            }
        } 

电机驱动器(H 桥)连接到微控制器的 Port1 ,排列如下:

  • Port1 引脚 0 和 1 – 用于右手电机驱动器(H 桥),以及
  • Port1 引脚 4 和 5 – 用于左手电机驱动器(H 桥)

方向控制是通过控制两个直流电机的方向来实现的。例如,要向右转,我们向前驱动左侧电机,并停止右侧电机。单个电机的方向是通过切换四个晶体管的开启和关闭来控制的,这由花括号中的字节值表示。这使我们能够决定要从应用程序发送到串行端口,然后再发送到 RC 车的位序列。因此,前进(“00100010”)、后退(“00010001”)、右转(“00100000”)、左转(“00000010”)、停止(“00000000”)、顺时针旋转(“00100001”)和逆时针旋转(“00010010”)。

微控制器程序

在深入研究代码之前,让我们先看看整个程序的指令流程。

flowChart.JPG

初始化端口后,会读取红外传感器作为红外传感器输出调试的参考,并将其保存为一个变量。然后,红外 LED 会被点亮,红外传感器的点亮状态读数被保存为第二个变量。接下来是一系列对这两个读数的比较,然后采取适当的行动。

如果关闭状态读数是高电平,则要么是传感器被高环境光饱和的问题,要么是传感器本身的问题,这需要通过查看点亮状态的读数来确定(参见上表)。对于每种情况,都会打开相应的调试 LED(高环境光和传感器错误 LED),程序会跳转到下一步。

如果关闭状态读数是低电平,则不存在上述问题。因此,如果第二个(点亮状态)读数是高电平,则传感器前方肯定有障碍物,两个电机都会向后驱动一秒钟。如果点亮状态读数是低电平,则传感器前方没有任何东西,程序会转为接收来自 RF 接收器的命令,并将它们发送到直流电机。

这个过程会无限重复,所以我把代码放在了一个 while(1) 循环中。

在完成每次端口读写操作后,我希望等待一段时间。因此,我在程序中使用了 50ms 的延迟函数 delay_50ms(void),该函数使用微控制器自身的定时器实现。此处假设微控制器频率为 12MHz,振荡周期为 12。要等待的确定时间(50ms 的整数倍)作为参数传递给 wait(int sec) 函数。

    void delay_50ms(void)  
    { 
        // Configure Timer 0 as a 16-bit timer 
        TMOD &= 0xF0;      // Clear all T0 bits (T1 left unchanged) 
        TMOD |= 0x01;      // Set required T0 bits (T1 left unchanged 
        ET0 = 0;           // No interrupts 
                           // Values for 50 ms delay 
        TH0 = 0x3C;        // Timer 0 initial value (High Byte) 
        TL0 = 0xB0;        // Timer 0 initial value (Low Byte) 
        TF0 = 0;           // Clear overflow flag 
        TR0 = 1;           // Start timer 0 
        while (TF0 == 0);  // Loop until Timer 0 overflows (TF0 == 1)
        TR0 = 0;           // Stop Timer 0 
    }

以下是(假定为 9600 波特率、无校验位和 1 个停止位)串行端口(微控制器)的初始化函数:

   // serial port initializing function 
   void serial_init(void)
   {
        TMOD = 0x20;        // T1 in mode 2, 8-bit auto reload
        SCON = 0x50;        // 8-bit data,  none parity bit, 1 stop bit
        TH1  = 0xFD;        //12MHz freq. 12 osc. cycle and 9600 baud rate
        TL1  = 0xFD;
        TR1  = 1;            // Run the timer
   }

从串行端口读取命令的任务由以下方法管理,该方法返回读取到的 char 值。

   // serial port reading function
   unsigned char serial_read(void)
   {
           bit over = 0;
        while(!RI || !over)
        {
            wait(500);
            over = 1;
            RI = 0;
             return SBUF;  
        }//wait some time till received flag is set and read the buffer
   } 

所有其他内容都在程序的 main 函数中处理,这里提供了 main( void ) 函数和 wait(int sec) 函数(负责根据输入参数 sec 设置的延迟时间来延迟程序执行)。

   // some 'sec' milliseconds wait function  
   void wait (int sec) 
   {          
        unsigned int i; 
        for (  i = 0; i < (sec / 50); i++   ) 
        { 
            delay_50ms(); 
        }       
   } 

   //here goes the main function
   void main( void ) 
        {          
        P0 = 0;                        // initialize P0  
        P1 = 0;                        // initialize P1  
        P2 = 0;                        // initialize P2  

        while(1) 
        { 
             unsigned char val = 0x00;  
             unsigned char var1 = 0x00; 
             unsigned char var2 = 0x00; 
             var1 = P2;                  //read IR sensor 
             wait(50);                   // delay 
             P2 = num[1];                //turn IR LED ON 
             wait(200);                  // delay
             var2 = P2;                  //read IR sensor again
             wait(50);                   // delay
             P2 = num[0];                //turn IR LED OFF    
            
             if(var1 == num[2]) 
             { 
                 if(var2 == num[1]) 
                    P0 = num[2];         //Set sensor error flag 
                if(var2 == num[3]) 
                    P0 = num[1];         //Set high ambiet light flag

                serial_init(); 
                val = serial_read();     //Read the serial port
                P1 = val;                //Command motors  
            }               
            
            if(var1 == num[0]) 
             { 
                if(var2 == num[3]) 
                { 
                    P1 = num[4];           //drive motors backward 
                    wait(1000);            //delay for a second 
                    P1 = num[0];     
                } 

                if(var2 == num[1]) 
                { 
                    serial_init(); 
                    val = serial_read();   //Read the serial port
                    P1 = val;              //Command the motors 
                }                              

                P0 = num[0];               //Set the flags to zero 
             } 
        }     
      }

摘要

我使用以下图表编译了上述 C 代码并在 Proteus 仿真程序中进行了模拟:

simulation.JPG

为了模拟目的,我使用了虚拟串行端口驱动程序(它有 14 天的评估期,可以在 这里 找到免费试用下载)来创建一个虚拟端口对,并将我的 PC 软件连接到其中一个 COM 对的端点,以及 Proteus 仿真程序的 COMPIM 串行端口连接到另一个端点。底部的两个 H 桥由 NPN 和 PNP BJT 晶体管制成。

就是这样。如果您需要更多细节,请告诉我!

历史

  • 文章提交 - 2010 年 11 月 12 日
  • 仅进行了一些语法编辑 - 2012 年 1 月 10 日
© . All rights reserved.