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






4.64/5 (9投票s)
串行通信
引言
第一部分 在这里。
要开始处理 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文件夹的源代码。