UART(Universal Asynchronous Receiver Transmitter)
UART是通用异步收发器(Universal Asynchronous Receiver Transmitter)的缩写。复杂的外设比如GPS模块,LCD显示器以及收音机通常使用UART端口来通信。
UART是用于与一个设备交换原始数据的通用接口。说它通用是因为它的数据传输速率和数据字节格式都可以配置。说它异步是因为没有时钟信号用于同步两个设备之间的数据传输。设备硬件会用先进先出(FIFO)缓冲区来收集所有传入的数据集直到你的app读取。
UART数据传输是全双工的,这意味着数据可以在同一时刻被发送或者接受。它显然要比 I2C 快得多,但是缺少共享钟意味着两个设备必须有统一的数据传输速率,这样每个设备可以以最小的时序误差最为独立的宿主机。
UART外设通常有以下两种:
- 3线缆端口:包含数据接收(RX),数据发射(TX)以及地线(GND)
- 5线缆端口:相比3线端口添加了请求发送(RTS)和清除发射(CTS)信号用于硬件流控制。流控制允许接收设备去表明它的FIFO缓冲区暂时满了,而此时发射设备应该在发送更多数据之前等待。
不像 SPI 和 I2C ,UART仅仅支持两个设备间的点到点(point-to-point)之间的通信。
管理连接
为了打开一个到特定UART设备的连接,你需要知道唯一的端口名。在开发的前期阶段,后者移植一个app到一个新设备时,使用PeripheralManagerService 的 getUartDeviceList() 方法获取所用可用的设备名是很有帮助的。
PeripheralManagerService manager = new PeripheralManagerService();
List<String> deviceList = manager.getUartDeviceList();
if (deviceList.isEmpty()) {
Log.i(TAG, "No UART port available on this device.");
} else {
Log.i(TAG, "List of available devices: " + deviceList);
}
一旦你知道了目标名称,使用 PeripheralManagerService 来连接到该设备。当你完成与外设的通信后,要断开连接释放资源。另外,你不能在同一个设备连接关闭之前再次打开一个新的连接。使用 close() 方法来关闭连接:
public class HomeActivity extends Activity {
// UART Device Name
private static final String UART_DEVICE_NAME = ...;
private UartDevice mDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Attempt to access the UART device
try {
PeripheralManagerService manager = new PeripheralManagerService();
mDevice = manager.openUartDevice(UART_DEVICE_NAME);
} catch (IOException e) {
Log.w(TAG, "Unable to access UART device", e);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
if (mDevice != null) {
try {
mDevice.close();
mDevice = null;
} catch (IOException e) {
Log.w(TAG, "Unable to close UART device", e);
}
}
}
}
配置端口参数
当一个连接建立后,配置数据传输速率和帧格式来匹配连接上的外部设备。
数据帧
每个通过UART发送的字符都被一个数据帧包裹,数据帧包括以下组成部分:
- 开始位(Start Bit)–在发送数据之前,该行保持1位持续时间的固定时间间隔,用来指示新字符的开始。
- 数据位(Data Bits)–表示数据字符的各个位。UART会被配置发送5到9位数据来表示一个字符。位数越少表示的数据的范围越小,但是可以提高数据传输的效率。
- 校验位(Parity Bit)–可选错误检查值。如果UART被配置为奇校验或者偶校验,额外的一位数据会被添加到帧中用于表明数据位的内容总和为偶数还是奇数值。设置它为none来从数据帧中移除这一位。
- 停止位(Stop Bit)–在所有数据都传输完成之后,该行被重置为可配置的时间间隔以指示该字符的结尾。这可以配置为在1或2位持续时间内保持空闲。
注意:大多数UART设备默认配置8位数据位,不需要检验,1位停止位(8N1)。
UART上的数据传输速率被称为波特率(Baud rate)。它代表了接收和发送的速度(单位为:位/秒)。
由于通过UART连接的两个设备之间没有共享时钟,所以必须提前配置,以便使用相同的波特率来正确的解码数据。
通用的波特率包括9600,19200,38400,57600,115200和921600。这些速率包括数据帧(开始,停止,奇偶校验为)的开销,所以有效的数据传输速率将会略低并且根据你配置的帧位数量而有所不同。
下面的代码配置UART连接的波特率为115200,8位数据位,没有校验,1个停止位(8N1):
public void configureUartFrame(UartDevice uart) throws IOException {
// Configure the UART port
uart.setBaudrate(115200);
uart.setDataSize(8);
uart.setParity(UartDevice.PARITY_NONE);
uart.setStopBits(1);
}
注意:选择不通用的波特率在传输过程中可能造成较高的错误率。你应该总是确认你选择的波特率是否是你的设备硬件支持的。
硬件流控制(Hardware Flow Control)
如果你的设备支持 5线缆 UART端口,激活硬件流控制可以增加数据传输的可靠性。这也意味着你可以安全的使用高波特率传输,并且丢失数据的机会更少。
当你激活了硬件流控制,当设备上的接收缓冲区满并且不再接收更多数据时UART会发送请求发送(RTS)信号。一旦缓冲区被清空,该信号会被清除。同样,UART会监听清除发送(CTS)信号,and will pause transmitting data if it sees that line asserted by the peripheral device.(并且如果得知外围设备断言的线路它会停止发射数据。【不知assert如何翻译才好,求各位指教。】)
要激活硬件流控制,使用 setHardwareFlowControl() 方法并且传递参数 HW_FLOW_CONTROL_AUTO_RTSCTS 。默认值是 HW_FLOW_CONTROL_NONE ,也就是说硬件流控制在默认情况下是不可用的:
public void setFlowControlEnabled(UartDevice uart, boolean enable) throws IOException {
if (enable) {
// Enable hardware flow control
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_AUTO_RTSCTS);
} else {
// Disable flow control
uart.setHardwareFlowControl(UartDevice.HW_FLOW_CONTROL_NONE);
}
}
发送输出数据
要通过UART将数据缓冲区传输到外设,使用write() 方法:
public void writeUartData(UartDevice uart) throws IOException {
byte[] buffer = {...};
int count = uart.write(buffer, buffer.length);
Log.d(TAG, "Wrote " + count + " bytes to peripheral");
}
注意:一个Java字节占8位,如果你使用setDataSize() 方法配置一个较小的数据,那么每个字节的高位会被截断。
监听输入数据
使用read() 方法从UART的FIFO缓冲区中拉取数据到应用程序。这个方法接收一个空的缓冲用于填充输入的数据以及一个最大能够读取的字节数作为参数。UART读取是非阻塞的,如果FIFO缓冲区中没有可用数据,它将立即返回。
UartDevice 在读取的时候将会返回FIFO缓冲区可用的数据字节数。为了确保所有的数据都接收到,在UART上循环直到当前没有更多可读数据:
public void readUartBuffer(UartDevice uart) throws IOException {
// Maximum amount of data to read at one time
final int maxCount = ...;
byte[] buffer = new byte[maxCount];
int count;
while ((count = uart.read(buffer, buffer.length)) > 0) {
Log.d(TAG, "Read " + count + " bytes from peripheral");
}
}
为了在缓冲区空时避免没有必要的轮训UART,使用 UartDevice 注册一个UartDeviceCallback 。这个回调在存在可读数据时将会调用onUartDeviceDataAvailable() 方法。在你的应用程序不再监听输入数据时你应该取消回调函数的注册。
public class HomeActivity extends Activity {
private UartDevice mDevice;
...
@Override
protected void onStart() {
super.onStart();
// Begin listening for interrupt events
mDevice.registerUartDeviceCallback(mUartCallback);
}
@Override
protected void onStop() {
super.onStop();
// Interrupt events no longer necessary
mDevice.unregisterUartDeviceCallback(mUartCallback);
}
private UartDeviceCallback mUartCallback = new UartDeviceCallback() {
@Override
public boolean onUartDeviceDataAvailable(UartDevice uart) {
// Read available data from the UART device
try {
readUartBuffer(uart);
} catch (IOException e) {
Log.w(TAG, "Unable to access UART device", e);
}
// Continue listening for more interrupts
return true;
}
@Override
public void onUartDeviceError(UartDevice uart, int error) {
Log.w(TAG, uart + ": Error event " + error);
}
};
}
onUartDeviceDataAvailable() 方法返回一个boolean值用于表明回调是否应该从接受未来的中断事件中自动取消注册。返回true表明在UART的FIFO缓冲区中每次出现数据时继续接收事件。