使用 DMA 访问高速串口

Using DMA to access High Speed Serial Port

我在 C# 中使用串口组件,它运行良好!但问题是如何才能更快地处理高速(例如 2 Mbps)数据传输。

正如我对此所做的研究,我发现可以直接访问内存(使用 this link 之类的 DMA)。 谁能告诉我如何在我的应用程序中定义和使用它?

不,[c#] 标签使它遥不可及一百万英里。该网页上的代码片段不是真实的,它只是一个 "pattern"。它可以做 C# 做不到的事情,比如处理中断、获取缓冲区的物理内存地址、直接对设备寄存器进行编程。在那种能执行C#代码的机器上,不算Micro Framework,这只能靠设备驱动了。

这将是一种可以 运行 在微控制器上的代码,这种处理器不 运行 具有保护模式操作系统。即便如此,它仍然是伸展的,它通过未说明的魔法调用 DMA,例如从未真正开始传输。也没有 DMA 控制器的迹象,需要仲裁设备之间的总线访问。这是假代码。

当您使用真正的硬件时,您总是会得到一个设备驱动程序来负责与设备通信。如果设备实际上支持 DMA,非常不寻常,那么设备驱动程序程序员就不会避免使用它。您在 C# 程序中使用的 SerialPort class 使用操作系统 api,该操作系统对任何类型的串行端口设备都是通用的。它将您的 I/O 请求传递给设备驱动程序以完成工作。

操作系统 api 和设备驱动程序之间的接口由 IOCTL 覆盖。 This MSDN page 记录 Windows 的那些。 IOCTL 和 api 之间的匹配非常接近,api 层非常薄。当您仔细观察时,很明显其中 none 个与 DMA 有任何关系。他们不能,这完全是一个驱动程序实现细节。

我相信您不需要使串行访问更快,而是调整您的 c# 应用程序以更快地处理数据传输。 运行 您选择的分析器,并测量花在串口组件方法上的时间百分比。我预测这会很低,这意味着任何使串行端口更快的努力都将白费。

你完全错了。

首先,您所处的环境无法直接访问硬件 (Windows),因此如果不编写内核驱动程序(并且您不我不想,相信我)。

其次,操作系统及其驱动程序已经非常优化,如果需要使用 DMA 传输,它应该已经做到了。

第三,除非您的串行控制器支持,否则您将无法获得这些速度,而它们通常不支持,PC 的 RS232 控制器通常最高可达 115200 波特,但有些控制器最高可达 1Mb。

但还有另一种选择,没有 USB 的 USB :D

根据你的问题,我假设你正在将某种类型的微控制器与 PC 连接,并且你不想为控制器编写 USB 驱动程序(或者它没有 USB 功能),所以一个很好的选择是使用 RS-232 到 USB 电缆,它们通常支持非常快的速度,我个人使用了 FTDI RS-232 3v3,它达到 3Mb (http://www.ftdichip.com/Support/Documents/DataSheets/Cables/DS_TTL-232R_CABLES.pdf)。

最后你将编写一个普通的串行端口代码,但它将使用更扩展的 USB 接口(这是另一个优势,今天并不是所有的 PC 都带有串行端口)。

之后,要真正受益于加速,请记住为端口设置一个非常大的 read/write 缓冲区(至少 1Mb),执行非阻塞接收例程并发送大数据块(必须适合写入缓冲区)。

请记住,您的设备必须与所选速度匹配,因此,如果将其设置为 2-3Mbps,您的设备必须 运行 串行接口的速度完全相同。

这里是我描述的接收部分的例子:

    SerialPort sp;
    Queue<byte[]> buffer = new Queue<byte[]>();
    AutoResetEvent dataAvailable = new AutoResetEvent(false);
    Thread processThread;

    public void Start()
    {
        //Start the processing thread
        processThread = new Thread(ProcessData);
        processThread.Start();

        //Open the serial port at 3Mbps and with buffers of 3Mb
        sp = new SerialPort("COM12", 3145728, Parity.None, 8, StopBits.One);
        sp.ReadBufferSize = 1024 * 1024 * 3;
        sp.WriteBufferSize = 1024 * 1024 * 3;
        sp.DataReceived += sp_DataReceived;
        sp.Open();
    }

    //This thread processes the stored chunks doing the less locking possible
    void ProcessData(object state)
    {

        while (true)
        {

            dataAvailable.WaitOne();

            while (buffer.Count > 0)
            {

                byte[] chunk;

                lock (buffer)
                    chunk = buffer.Dequeue();

                //Process the chunk here as you wish

            }

        }

    }

    //The receiving function only stores data in a list of chunks
    void sp_DataReceived(object sender, SerialDataReceivedEventArgs e)
    {
        while (sp.BytesToRead > 0)
        { 
            byte[] chunk = new byte[sp.BytesToRead];
            sp.Read(chunk, 0, chunk.Length);

            lock (buffer)
                buffer.Enqueue(chunk);

            dataAvailable.Set();
        }
    }