串口全稱為串行接口,一般指COM接口,是采用串行通信方式的擴展接口。其特點是數(shù)據(jù)位的傳送按位順序進行,最少只需一根傳輸線即可完成,成本低但傳送速度慢。由于串口(COM)不支持熱插拔及傳輸速率較低,目前部分新主板和大部分便攜電腦已取消該接口。現(xiàn)在串口多用于工業(yè)控制和測量設(shè)備以及部分通信設(shè)備中。
根據(jù)美國電子工業(yè)協(xié)會(EIA: Electronic Industry Association)制定的標(biāo)準(zhǔn),串口可以分為RS-232、RS-422以及RS-485等種類,其中以RS-232類型的接口最為典型和常見,如圖 1所示,是RS-232類型9針串口的實物示意圖。RS-232類型9針串口每一個引腳的作用說明如圖 2所示。
當(dāng)需要編程操縱硬件時會遇到過這樣的問題,就是通過串口來接收硬件發(fā)來的數(shù)據(jù),或是通過串口向硬件發(fā)送某種格式的命令。在C#平臺上,可以通過 System.IO.Ports 命名空間下的SerialPort 類來實現(xiàn)。
下面是我做過的一個簡單的示例,首先獲取本機關(guān)聯(lián)的串行端口列表,然后獲取配置文件中配置的COM端口,檢查是否在本機串行端口列表中,若在列表中則進一步實例化串口對象,并為串口對象指定數(shù)據(jù)接收事件來實現(xiàn)監(jiān)聽,示例代碼如下:
using System.IO.Ports;
namespace SerialTest
{
public class SerialTest
{
#region 串口監(jiān)聽
private SerialPort serialPort = null;
/// 《summary》
/// 開啟串口監(jiān)聽
/// 《/summary》
private void StartSerialPortMonitor()
{
List《string》 comList = GetComlist(false); //首先獲取本機關(guān)聯(lián)的串行端口列表
if (comList.Count == 0)
{
DialogForm.Show(“提示信息”, “當(dāng)前設(shè)備不存在串行端口!”);
System.Environment.Exit(0); //徹底退出應(yīng)用程序
}
else
{
string targetCOMPort = ConfigurationManager.AppSettings[“COMPort”].ToString();
//判斷串口列表中是否存在目標(biāo)串行端口
if (!comList.Contains(targetCOMPort))
{
DialogForm.Show(“提示信息”, “當(dāng)前設(shè)備不存在配置的串行端口!”);
System.Environment.Exit(0); //徹底退出應(yīng)用程序
}
serialPort = new SerialPort();
//設(shè)置參數(shù)
serialPort.PortName = ConfigurationManager.AppSettings[“COMPort”].ToString(); //通信端口
serialPort.BaudRate = Int32.Parse(ConfigurationManager.AppSettings[“BaudRate”].ToString()); //串行波特率
serialPort.DataBits = 8; //每個字節(jié)的標(biāo)準(zhǔn)數(shù)據(jù)位長度
serialPort.StopBits = StopBits.One; //設(shè)置每個字節(jié)的標(biāo)準(zhǔn)停止位數(shù)
serialPort.Parity = Parity.None; //設(shè)置奇偶校驗檢查協(xié)議
serialPort.ReadTimeout = 3000; //單位毫秒
serialPort.WriteTimeout = 3000; //單位毫秒
//串口控件成員變量,字面意思為接收字節(jié)閥值,
//串口對象在收到這樣長度的數(shù)據(jù)之后會觸發(fā)事件處理函數(shù)
//一般都設(shè)為1
serialPort.ReceivedBytesThreshold = 1;
serialPort.DataReceived += new SerialDataReceivedEventHandler(CommDataReceived); //設(shè)置數(shù)據(jù)接收事件(監(jiān)聽)
try
{
serialPort.Open(); //打開串口
}
catch (Exception ex)
{
DialogForm.Show(“提示信息”, “串行端口打開失敗!具體原因:” + ex.Message);
System.Environment.Exit(0); //徹底退出應(yīng)用程序
}
}
}
/// 《summary》
/// 串口數(shù)據(jù)處理函數(shù)
/// 《/summary》
/// 《param name=“sender”》《/param》
/// 《param name=“e”》《/param》
public void CommDataReceived(Object sender, SerialDataReceivedEventArgs e)
{
try
{
//Comm.BytesToRead中為要讀入的字節(jié)長度
int len = serialPort.BytesToRead;
Byte[] readBuffer = new Byte[len];
serialPort.Read(readBuffer, 0, len); //將數(shù)據(jù)讀入緩存
//處理readBuffer中的數(shù)據(jù),自定義處理過程
string msg = encoding.GetString(readBuffer, 0, len); //獲取出入庫產(chǎn)品編號
DialogForm.Show(“接收到的信息”, msg);
}
catch(Exception ex)
{
DialogForm.Show(“提示信息”, “接收返回消息異常!具體原因:” + ex.Message);
}
}
/// 《summary》
/// 關(guān)閉串口
/// 《/summary》
private void Stop()
{
serialPort.Close();
}
/// 《summary》
/// 獲取本機串口列表
/// 《/summary》
/// 《param name=“isUseReg”》《/param》
/// 《returns》《/returns》
private List《string》 GetComlist(bool isUseReg)
{
List《string》 list = new List《string》();
try
{
if (isUseReg)
{
RegistryKey RootKey = Registry.LocalMachine;
RegistryKey Comkey = RootKey.OpenSubKey(@“HARDWAREDEVICEMAPSERIALCOMM”);
String[] ComNames = Comkey.GetValueNames();
foreach (String ComNamekey in ComNames)
{
string TemS = Comkey.GetValue(ComNamekey).ToString();
list.Add(TemS);
}
}
else
{
foreach (string com in SerialPort.GetPortNames()) //自動獲取串行口名稱
list.Add(com);
}
}
catch
{
DialogForm.Show(“提示信息”, “串行端口檢查異常!”);
System.Environment.Exit(0); //徹底退出應(yīng)用程序
}
return list;
}
#endregion 串口監(jiān)聽
}
}
從串口讀數(shù)據(jù)
從串口COM11發(fā)送的數(shù)據(jù)最終將到達(dá)與其連通的串口COM21,如果COM21處于可用狀態(tài),則到達(dá)的數(shù)據(jù)將被緩存,等待程序的讀取。從串口讀入數(shù)據(jù)有多種模式,本文將介紹“輪詢模式”和事件監(jiān)聽模式。
“輪詢模式”是指程序(線程)每隔固定的時間就對串口進行一次掃描,如果掃描發(fā)現(xiàn)串口中有可用數(shù)據(jù),則進行讀取。Com21PollingListener類使用“事件監(jiān)聽模式”讀取串口COM21接收到的數(shù)據(jù):
Com21PollingListener.java
package com.serialPort.listener;
import java.io.IOException;
import java.io.InputStream;
import javax.comm.CommPortIdentifier;
import javax.comm.NoSuchPortException;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
/**
* Com21PollingListener類使用“輪訓(xùn)”的方法監(jiān)聽串口COM21,
* 并通過COM21的輸入流對象來獲取該端口接收到的數(shù)據(jù)(在本文中數(shù)據(jù)來自串口COM11)。
*/
public class Com21PollingListener {
public static void main(String[] args){
//1.定義變量
CommPortIdentifier com21 = null;//未打卡的端口
SerialPort serialCom21 = null;//打開的端口
InputStream inputStream = null;//端口輸入流
try{
//2.獲取并打開串口COM21
com21 = CommPortIdentifier.getPortIdentifier(“COM21”);
serialCom21 = (SerialPort) com21.open(“Com21Listener”, 1000);
//3.獲取串口的輸入流對象
inputStream = serialCom21.getInputStream();
//4.從串口讀入數(shù)據(jù)
//定義用于緩存讀入數(shù)據(jù)的數(shù)組
byte[] cache = new byte[1024];
//記錄已經(jīng)到達(dá)串口COM21且未被讀取的數(shù)據(jù)的字節(jié)(Byte)數(shù)。
int availableBytes = 0;
//無限循環(huán),每隔20毫秒對串口COM21進行一次掃描,檢查是否有數(shù)據(jù)到達(dá)
while(true){
//獲取串口COM21收到的可用字節(jié)數(shù)
availableBytes = inputStream.available();
//如果可用字節(jié)數(shù)大于零則開始循環(huán)并獲取數(shù)據(jù)
while(availableBytes 》 0){
//從串口的輸入流對象中讀入數(shù)據(jù)并將數(shù)據(jù)存放到緩存數(shù)組中
inputStream.read(cache);
//將獲取到的數(shù)據(jù)進行轉(zhuǎn)碼并輸出
for(int j = 0;j 《 cache.length && j 《 availableBytes; j++){
//因為COM11口發(fā)送的是使用byte數(shù)組表示的字符串,
//所以在此將接收到的每個字節(jié)的數(shù)據(jù)都強制裝換為char對象即可,
//這是一個簡單的編碼轉(zhuǎn)換,讀者可以根據(jù)需要進行更加復(fù)雜的編碼轉(zhuǎn)換。
System.out.print((char)cache[j]);
}
System.out.println();
//更新循環(huán)條件
availableBytes = inputStream.available();
}
//讓線程睡眠20毫秒
Thread.sleep(20);
}
}catch(InterruptedException e){
e.printStackTrace();
}catch (NoSuchPortException e) {
//找不到串口的情況下拋出該異常
e.printStackTrace();
} catch (PortInUseException e) {
//如果因為端口被占用而導(dǎo)致打開失敗,則拋出該異常
e.printStackTrace();
} catch (IOException e) {
//如果獲取輸出流失敗,則拋出該異常
e.printStackTrace();
}
}
}
“事件監(jiān)聽模式”是為串口注冊一個事件監(jiān)聽類,當(dāng)有數(shù)據(jù)到達(dá)串口的時候就會觸發(fā)事件,在事件的響應(yīng)方法中讀取串口接收到的數(shù)據(jù)。Com21EventListener類使用“事件監(jiān)聽模式”讀取串口COM21接收到的數(shù)據(jù):
Com21EventListener.java
package com.serialPort.listener;
import java.io.IOException;
import java.io.InputStream;
import java.util.TooManyListenersException;
import javax.comm.CommPortIdentifier;
import javax.comm.NoSuchPortException;
import javax.comm.PortInUseException;
import javax.comm.SerialPort;
import javax.comm.SerialPortEvent;
import javax.comm.SerialPortEventListener;
/**
* Com21EventListener類使用“事件監(jiān)聽模式”監(jiān)聽串口COM21,
* 并通過COM21的輸入流對象來獲取該端口接收到的數(shù)據(jù)(在本文中數(shù)據(jù)來自串口COM11)。
* 使用“事件監(jiān)聽模式”監(jiān)聽串口,必須字定義一個事件監(jiān)聽類,該類實現(xiàn)SerialPortEventListener
* 接口并重寫serialEvent方法,在serialEvent方法中編寫監(jiān)聽邏輯。
*/
public class Com21EventListener implements SerialPortEventListener {
//1.定義變量
CommPortIdentifier com21 = null;//未打卡的端口
SerialPort serialCom21 = null;//打開的端口
InputStream inputStream = null;//輸入流
//2.構(gòu)造函數(shù):
//實現(xiàn)初始化動作:獲取串口COM21、打開串口、獲取串口輸入流對象、為串口添加事件監(jiān)聽對象
public Com21EventListener(){
try {
//獲取串口、打開窗串口、獲取串口的輸入流。
com21 = CommPortIdentifier.getPortIdentifier(“COM21”);
serialCom21 = (SerialPort) com21.open(“Com21EventListener”, 1000);
inputStream = serialCom21.getInputStream();
//向串口添加事件監(jiān)聽對象。
serialCom21.addEventListener(this);
//設(shè)置當(dāng)端口有可用數(shù)據(jù)時觸發(fā)事件,此設(shè)置必不可少。
serialCom21.notifyOnDataAvailable(true);
} catch (NoSuchPortException e) {
e.printStackTrace();
} catch (PortInUseException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (TooManyListenersException e) {
e.printStackTrace();
}
}
//重寫繼承的監(jiān)聽器方法
@Override
public void serialEvent(SerialPortEvent event) {
//定義用于緩存讀入數(shù)據(jù)的數(shù)組
byte[] cache = new byte[1024];
//記錄已經(jīng)到達(dá)串口COM21且未被讀取的數(shù)據(jù)的字節(jié)(Byte)數(shù)。
int availableBytes = 0;
//如果是數(shù)據(jù)可用的時間發(fā)送,則進行數(shù)據(jù)的讀寫
if(event.getEventType() == SerialPortEvent.DATA_AVAILABLE){
try {
availableBytes = inputStream.available();
while(availableBytes 》 0){
inputStream.read(cache);
for(int i = 0; i 《 cache.length && i 《 availableBytes; i++){
//解碼并輸出數(shù)據(jù)
System.out.print((char)cache[i]);
}
availableBytes = inputStream.available();
}
System.out.println();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//在main方法中創(chuàng)建類的實例
public static void main(String[] args) {
new Com21EventListener();
}
}
讀寫程序的聯(lián)合運行
串口能接收到數(shù)據(jù)的前提是該串口處于打開(可用)狀態(tài),如果串口處于關(guān)閉狀態(tài),那么發(fā)送到該串口的數(shù)據(jù)就會丟失。所以在實驗的過程中,如果使用銅線連接同一個串口的引腳2和引腳3,一定要注意的是千萬不能在向串口發(fā)送完數(shù)據(jù)之后關(guān)閉該串口,然后再次打開串口去讀取數(shù)據(jù),一定要讓串口始終處于打開狀態(tài)直到程序運行結(jié)束。
評論