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

在 Arduino Due 上获取 USB 网络摄像头视频流 - 第二部分:UART

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.64/5 (9投票s)

2015年3月6日

CPOL

6分钟阅读

viewsIcon

25843

downloadIcon

463

串行通信

引言

第一部分 在这里

要开始处理 USB,还需要两件事:与计算机进行串行通信以及某种监视器来查看流视频。在本文中,我将展示如何初始化串行通信组件,创建打印字符串、十六进制、十进制和二进制数字的函数。然后在下一篇文章中,我将简要讨论 TFT 显示器和看门狗定时器。

串行通信

我将使用 UART(通用异步收发器) [1, p.755] 与我的笔记本电脑进行串行通信。计算机过去曾使用 COM 端口(RS-232)进行串行通信,UART 与此类端口完全兼容,只是信号电压级别不同。但不用担心,在 Arduino Due 中,这一切都通过 USB 线缆和 Arduino 软件创建的虚拟 COM 端口(请参阅上一篇文章)完成。

UART 初始化

UART 初始化包含 3 个阶段:RX / TX 引脚配置;UART 速度和参数配置以及为 UART 启用时钟。我将把所有与 UART 相关的代码放在两个单独的文件中:UART.h 和 UART.c。因此,让我们将它们添加到项目中,然后在 UART.c 的顶部添加以下行

#include "UART.h"

所有函数声明都将放在 UART.h 文件中。我将把 UART 初始化函数命名为 UART_Init 并将其添加到 UART.h 文件中

#ifndef UART_H_
#define UART_H_

void UART_Init(void);

#endif

实现将放入 UART.c 文件中。RX 和 TX 引脚属于 PIOA,引脚号为 8 和 9。首先,我将创建引脚掩码

uint32_t ul_UART_pins_mask = PIO_PA8A_URXD | PIO_PA9A_UTXD;

在 ARM 处理器中,引脚可以扮演普通输入/输出的角色,也可以被外围设备使用。例如,PIOA 的引脚 8 可以是普通的输入/输出,也可以是 UART 的输入引脚 (RX)。每个引脚最多可以分配给两个外围设备,但一次只能选择其中一个。选择由 PIO_ABSR 寄存器控制

PIOA->PIO_ABSR &= ~ul_UART_pins_mask;       //Peripheral A selected for both lines (0 is A, 1 is B)

UART 是这些引脚的外围设备 A。一旦选择了正确的外围设备,接下来要做的就是将这些引脚的控制权从 PIO 控制器切换到 UART

PIOA->PIO_PDR = ul_UART_pins_mask;          //Moves pins control from PIO controller to Peripheral

从这一刻起,RX 和 TX 引脚由 UART 控制。接下来,我将指定 UART 参数,速度将是 115200 b/s

UART->UART_BRGR =84000000/16/115200;                     //Clock Divisor value
UART->UART_MR = UART_MR_CHMODE_NORMAL | UART_MR_PAR_NO;  //Normal mode, no parity
UART->UART_CR = UART_CR_TXEN;                            //Enable transmitter
UART->UART_IDR = 0xFFFFu;                                //Disable all interrupts   

最后一步是为 UART 启用时钟,否则它将无法工作

PMC->PMC_PCER0 |= (1<< ID_UART);

UART 初始化结束,从那时起,UART 就可以向连接的计算机传输数据了。

通过 UART 发送字符串

我将编写一个发送字符串的函数,该函数接受指向以 null 结尾的字符串的指针。UART 逐字节发送数据,因此发送字符串需要迭代字符串中的每个字节,直到遇到 **null**。在迭代过程中,每个字节必须在传输保持寄存器 (THR) 为空时复制到其中。让我们称此函数为 **PrintStr**,将其声明放在 UART.h 中,并将其实现放在 UART.c 文件中

void PrintStr(char *Text)
{
    int i = 0;
    do
    {
        while(0 == (UART->UART_SR & UART_SR_TXRDY)) {}   //wait until previous character is processed
        UART->UART_THR = Text[i];                        //send next character
        i++;
    }
    while (Text[i] != 0);                                //loop until string termination symbol (null)
}

为了测试它,我将在切换 LED 的相同位置添加对此函数的调用(请参阅上一篇文章)。因此,每次切换 LED 时,都会将消息发送到计算机。主文件现在看起来像这样

#include "sam.h"
#include "UART.h"

uint32_t SysTickCounter;

void SysTick_Handler(void)
{    
    uint32_t Pin_LEDRX;
    uint32_t Pin_LEDTX;
    
    SysTickCounter ++;
    if(1000 == SysTickCounter)
    {
        SysTickCounter = 0;
        
        Pin_LEDRX = 1 << (PIO_PC30_IDX & 0x1Fu);
        Pin_LEDTX = 1 << (PIO_PA21_IDX & 0x1Fu);
        if(PIOC->PIO_PDSR & Pin_LEDRX)
        {
            PIOC->PIO_CODR = Pin_LEDRX;    //Lights on                                
            PIOA->PIO_CODR = Pin_LEDTX;
        }
        else
        {
            PIOC->PIO_SODR = Pin_LEDRX;    //Lights off                                
            PIOA->PIO_SODR = Pin_LEDTX;
        }
        
        PrintStr("Hello World\r\n");       //Testing call is here!!!
    }
}

int main(void)
{
    SystemInit();
    UART_Init();
    while(1);
}

请注意,我在主文件顶部添加了 **#include UART.h** 来链接我们的 UART 代码,并在主函数中调用了 **UART_Init()**。

构建它并上传到 Arduino Due 板,然后启动 Docklight,输出应如下所示

现在您可以看到通过 UART 发送数据确实很简单。将来我们将打印 USB 设备描述符,为此需要更多功能,而不仅仅是打印字符串。我们还需要能够以十六进制、二进制和十进制格式打印数字。因此,让我们编写这样的函数。

以十六进制格式打印

以十六进制格式打印很简单。如您所知(难道不是吗?),一个字节由两个十六进制数字表示,每个数字 4 位。例如,200 是 0xC8。前缀 0x 表示数字以十六进制表示。因此,以十六进制打印的算法如下:先打印 0x,然后提取半字节,然后将该半字节用作以下数组中的索引

const char arHex[16] = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};

通过索引,我们可以得到可读的表示(字符),可以直接馈送到 UART。

我将只解释一个十六进制打印函数,即打印字节缓冲区的功能。将有另外 2 个函数:一个用于打印半字(2 字节),一个用于打印字(4 字节)。我将不解释它们,因为它们与第一个函数基本相似,我是通过复制重复的代码块生成的。让我们开始吧,将以下声明添加到 UART.h 文件中

void PrintHEX(uint8_t* ptrBuffer, uint32_t Length);

然后在 UART.c 文件中

void PrintHEX(uint8_t* ptrBuffer, uint32_t Length)
{
    uint8_t ByteToSend;
    for (uint32_t i = 0; i < Length; i ++)
    {
        PrintStr("0x");                                //Printing prefix
        
        ByteToSend = ptrBuffer[i] >> 4;                //Extracting higher half-byte     
        ByteToSend = arHex[ByteToSend];                //Getting readable char
        while(0 == (UART->UART_SR & UART_SR_TXRDY)) {} //Waiting until UART is ready
        UART->UART_THR = ByteToSend;                   //Sending
        
        ByteToSend = ptrBuffer[i] & 0x0F;              //Extraction lower half-byte
        ByteToSend = arHex[ByteToSend];
        while(0 == (UART->UART_SR & UART_SR_TXRDY)) {}
        UART->UART_THR = ByteToSend;
        
        if(i != (Length - 1))
        {
            PrintStr(" ");                             //Separating bytes for readability
        }
    }
}

为了提取高半字节,我使用右移 4 位操作,它将高半字节移到低半字节,并自动将高半字节置零。为了提取低半字节,我使用按位 AND 操作,高半字节为 0,这样我不需要移位,因为位已经处于正确的位置,可以将其用作数组索引。

有关其余十六进制打印函数的更多信息,请参阅本文末尾的源代码。

以二进制格式打印

要以二进制格式打印,必须测试每个位是 0 还是 1。我将使用左移操作,一旦测试并打印了一个位,我就会将其移出,将下一个位移入正确的位置。移位操作的数量取决于要打印的数字的大小,如果是一个字节的数字,则移位 8 次,两个字节的数字,则移位 16 次,依此类推。

为了提高可读性,所有半字节之间都用一个空格分隔,所有字节之间都用两个空格分隔。

将声明添加到 UART.h 文件中

void PrintBIN8(uint8_t Number);

然后在 UART.c 文件中

void PrintBIN8(uint8_t Number)
{
    uint32_t Result;                                    //Working variable
    for(int i = 0; i < 8; i ++)                         //There are 8 bits in a byte
    {
        Result = (Number << i);                         //Getting needed bit into position
        Result = (Result & (1u << 7));                  //Extracting it
        
        while(0 == (UART->UART_SR & UART_SR_TXRDY)) {}  //Waiting till UART is ready
        if(Result == 0)                                 //Sending 0 or 1 depending on bit value
            UART->UART_THR = '0';                       //Sending '0'
        else
            UART->UART_THR = '1';                       //Sending '1'
        
        if((i + 1) % 4 == 0)                            //Every half-byte is separated
        {                                               //by white space for better readability
            while(0 == (UART->UART_SR & UART_SR_TXRDY)) {}
            UART->UART_THR = ' ';
        }
    }
}

有关其余二进制打印函数的更多信息,请参阅本文末尾的源代码,它们与此非常相似。

打印十进制数

这个比其他的要复杂一些。算法如下:一个数字根据其数量级除以 10、100 等。例如:要显示 243,需要除以 100,结果是 2,然后从 243 中减去 2*100,结果是 43。接下来,需要将 43 除以 10,结果是 4,然后减去 4*10,结果是 3。这就是所有三个单独数字的提取方式。让我们在 C 语言中实现。

将声明添加到 UART.h 文件中

void PrintDEC(uint32_t Number);

然后在 UART.h 文件中

void PrintDEC(uint32_t Number)
{
    uint32_t Temp, Divider, Result;
    
    Temp = Number;
    Divider = 10;
    
    while(Temp / Divider)                        //Finding the divider        
        Divider = Divider * 10;
    
    Divider = Divider / 10;                      //Don't need last one as division by it makes zero
    while(Divider)
    {        
        Result = Temp / Divider;                 //Extracting a digit (decimals are discarded)
        Temp = Temp - Result * Divider;          //Decreasing by 1 order of magnitude    
        Divider = Divider / 10;                  //Readjusting divider    
        
        while(0 == (UART->UART_SR & UART_SR_TXRDY)) {}
        UART->UART_THR = Result + 48;            //Sending out the digit
    }
}

请注意单个数字是如何发送的。添加了 48。在 ANSII 代码中,48 是 '0',例如,将 48 添加到 1 得到 49,即 '1'。

测试所有打印函数

编写完所有必需的打印函数后,最好对其进行测试。此过程包括指定一个参数(数字)给函数,格式与我们希望打印的格式相同。例如,要测试 PrintDEC,我将指定数字 240,并且应该在 RS232 监视器程序中看到“240”被打印出来。以下代码测试 PrintDEC 函数

int main(void)
{
    SystemInit();
    UART_Init();
    
    PrintStr("\r\n");
    PrintStr("Testing PrintDEC function: \r\n");
    PrintDEC(0);
    PrintStr(" ");    
    PrintDEC(3);
    PrintStr(" ");    
    PrintDEC(25);
    PrintStr(" ");    
    PrintDEC(128);
    PrintStr(" ");    
    PrintDEC(250);
    PrintStr(" ");
    PrintDEC(1285);
    PrintStr(" ");
    PrintDEC(25001);
    PrintStr(" ");
    PrintDEC(560022);
    PrintStr(" ");
    PrintDEC(1000234);
    PrintStr("\r\n");
    
    while(1);
}

构建它并上传到 Arduino Due。输出将是

您可以看到输出与输入完全一致。请参阅源代码以获取更多测试代码。

结论

在本文中,我创建了调试和读取 USB 描述符所需的打印方法。在我开始处理 USB 之前,还将有一篇“准备”文章——关于看门狗定时器和 TFT 显示器。

源代码 在此处

第三部分 在此处

更新 2015-07-10:重新上传了不包含Debug文件夹的源代码。

© . All rights reserved.