Arduino 101 Serial 实现详解

Arduino 101 连接 PC 是通过一条方口 USB 数据线,并且可以通过这条数据线接收板子输出 debug 调试。
那么这是如何实现的呢?
这部分没有完全看透,只记录下目前收获(也许有误)。
概述
Arduino 中操作串口是通过 Serial1 类。
初始化:
void setup() {
Serial.begin(9600); // 波特率设置为 9600
while(!Serial); // 等待串口初始化完成
}
发送:
void loop() {
Serial.print("Hello World!\n");
}
接收:
void serialEvent(){ // Called when data is available. Use Serial.read() to capture this data.
Serial.print(Serial.read());
}
分析
查看代码时发现了应用程序入口其实在 main.cpp
中,如下:
C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\cores\arduino\main.cpp
int main( void )
{
initVariant();
#if defined(USBCON)
USBDevice.attach();
#endif
setup();
for (;;) /* This infinite loop is intentional and requested by design */
{
loop();
if (serialEventRun) serialEventRun();
}
return 0;
}
从中可以看到 setup
loop
等函数,这就是通用的 Arduino 代码函数块,可以发现 loop
和 serialEvent
其实是顺序轮询方式,并不是真正中断。
初始化
Serial 部分的源码又在哪里呢?查找后发现对象初始化部分:
C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\variants\arduino_101\variant.cpp
uart_init_info info_cdc;
CDCSerialClass Serial(&info_cdc);
RingBuffer rx_buffer_uart;
RingBuffer tx_buffer_uart;
uart_init_info info_uart;
UARTClass Serial1(&info_uart, &rx_buffer_uart, &tx_buffer_uart);
原来 Serial 是全局变量,CDCSerialClass
的构造函数只是赋值,并没有特殊操作。
现在已知如下(和板子定义一致):
- Serial 表示 USB2 串口
- Serial1 表示 UART 串口
接下来的操作在 initVariant()
中:
void initVariant( void ) {
/* Initialise CDC-ACM shared buffers pointers, provided by LMT */
Serial.setSharedData(shared_data->cdc_acm_buffers);
...
}
Serial.setSharedData
只是赋值串口对象指针。
C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\cores\arduino\CDCSerialClass.cpp
void CDCSerialClass::setSharedData(struct cdc_acm_shared_data *cdc_acm_shared_data)
{
this->_shared_data = cdc_acm_shared_data;
this->_rx_buffer = cdc_acm_shared_data->rx_buffer;
this->_tx_buffer = cdc_acm_shared_data->tx_buffer;
}
其中 shared_data
是芯片中的 SRAM3 地址为 0xA8000000,共 80K4。
关于这段内存的作用,我推测是和内核部分进行交互,因为 CDCSerialClass
中并没有对寄存器的操作。
而内核部分也有同样的结构,并且会读取标志位以及设置标志位(内核部分没有详细查看)。
内核相关理解如下:
- 代码入口在
CODK\CODK-A\x86\projects\arduino101\quark\main.c
- 驱动部分代码
CODK\CODK-A\x86\bsp\src\drivers\usb
(usb_driver_intf
没有找到声明地方)
其他
以 Serial.print
实现为例,使用该函数会向串口发送字符串。
CDCSerialClass
中并没有定义 print
的方法,此方法乃是继承 class Print
,其所有继承关系如下:
CDCSerialClass->HardwareSerial->Stream->Print
某个方法的实现如下:
C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\cores\arduino\Print.cpp
size_t Print::print(char c)
{
return write(c);
}
而 write
方法在 CDCSerialClass
类中被重写为向串口发送数据,如此便实现了 Serial.print
的功能。
疑问
CDCSerialClass
中波特率设置似乎没有作用。
void CDCSerialClass::init(const uint32_t dwBaudRate, const uint8_t modeReg)
{
/* Set a per-byte write delay approximately equal to the time it would
* take to clock out a byte on a standard UART at this baud rate */
_writeDelayUsec = 8000000 / dwBaudRate;
/* Make sure both ring buffers are initialized back to empty.
* Empty the Rx buffer but don't touch Tx buffer: it is drained by the
* LMT one way or another */
_rx_buffer->tail = _rx_buffer->head;
_shared_data->device_open = true;
}
以上为初始化部分,从中可以看到 dwBaudRate
只计算了 _writeDelayUsec
,没有传递到 _shared_data
中。
查找代码又会发现 _writeDelayUsec
没有在其他地方被调用过,那么该波特率还有作用吗?
为此我尝试在电脑上随便修改波特率,并和板子通信,发现波特率不匹配也可以通信,不知道是不是因为该口是 USB 虚拟串口的原因?
相对应的 Serial1 UARTClass
的 init
中是包含波特率设置代码的。
修改
添加 printf
方法5。
我使用的是添加 Print::printf
,修改后可在 Serial
中使用 printf
方法了。
C:\Users\XXX\AppData\Local\Arduino15\packages\Intel\hardware\arc32\2.0.2\cores\arduino\Print.cpp
#include <stdarg.h>
#define PRINTF_BUF 80
size_t Print::printf(const char *format, ...)
{
char buf[PRINTF_BUF];
va_list ap;
va_start(ap, format);
vsnprintf(buf, sizeof(buf), format, ap);
for(char *p = &buf[0]; *p; p++) {
if(*p == '\n')
write('\r');
write(*p);
}
va_end(ap);
}
Intel® Quark™ SE Microcontroller C1000 Datasheet, Table 24, Mapping Address Spaces ↩︎