列舉 Mac 下可用的串列埠(Serial Port)

列舉 Mac 下可用的串列埠(Serial Port)

I/O 瀏覽工具

Mac下使用Objective-C來了解硬體資訊必需要依靠 IOKit Framework ,如果在還未寫程式時也可以利用 IORegistry Explorer 看一下硬體資訊,要取得該工具程式的方法很多,如果您是開發者更可以在Downloads for Apple Developer取得工具 Hardwre Tools for Xcode ,其中內建許多硬體有關的資訊,包含了 IORegistry Explorer

尋找可用的Serial Port

還未寫程式之前,使用IORegistry Explorer 尋找可用的Serial Port,Mac上規範如果你的驅動是Serial Port的話,它的IOClass必需為IOSerilaBSDClient,利用此特性將尋找的關鍵字定為IOSerialBSDClient

上圖中能看到符合的條件的數量有3,如此一來就取得可用的Serial Port,接下來就要使用IOKit Framework來完成。

使用IOKit

IOKit Framework 必需要先引用標頭檔:

//-----------start-----------
#import <IOKit/IOKitLib.h>
#import <IOKit/serial/IOSerialKeys.h>
//------------end------------

依照先前工具操作方式,尋找IOSerialBSDClient的程式如下:

//-----------start-----------
    CFMutableDictionaryRef keywordDict = IOServiceMatching(kIOSerialBSDServiceValue);
    //
    io_object_t port;

    io_iterator_t portIterator = 0;
    kern_return_t result = IOServiceGetMatchingServices(kIOMasterPortDefault, keywordDict, &portIterator);

    if (result){
        NSLog(@"Error getting for serial port");
        return;
    }
//------------end------------

  • IOServiceMatching 使用它來產生要被尋找的關鍵字,它以字典的型式,所以可以輸入多個條件
  • io_iterator_t portIterator 存放列舉資訊的變數
  • IOServiceGetMatchingServices 當尋找到成立的條件時會將資料紀錄並加入portIteratorportIterator這是一個Iterator結構,必需要使用它提供的Function將內容列舉。

上述程式最後確認result沒問題的話就不會有Error getting for serial port訊息,之後使用portIterator將符合的條件內容顯示:

//-----------start-----------
    while ((port = IOIteratorNext(portIterator)))
    {
        CFStringRef stringIOTTYBaseNameKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                        (__bridge CFStringRef)(NSString*)CFSTR(kIOTTYBaseNameKey),
                                                                                        kCFAllocatorDefault,
                                                                                        0);

        CFStringRef stringIOSerialBSDTypeKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                        (__bridge CFStringRef)(NSString*)CFSTR(kIOSerialBSDTypeKey),
                                                                                        kCFAllocatorDefault,
                                                                                        0);

        CFStringRef stringIOCalloutDeviceKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                            (__bridge CFStringRef)(NSString*)CFSTR(kIOCalloutDeviceKey),
                                                                                            kCFAllocatorDefault,
                                                                                            0);

        CFStringRef stringIODialinDeviceKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                            (__bridge CFStringRef)(NSString*)CFSTR(kIODialinDeviceKey),
                                                                                            kCFAllocatorDefault,
                                                                                            0);

        NSLog(@"\r Name:%@ \r SerialBSDTypeKey:%@ \r CalloutDevice:%@ \r DialinDevice:%@",stringIOTTYBaseNameKey,stringIOSerialBSDTypeKey,stringIOCalloutDeviceKey,stringIODialinDeviceKey);
        //
        CFRelease(stringIOTTYBaseNameKey);
        CFRelease(stringIOSerialBSDTypeKey);
        CFRelease(stringIOCalloutDeviceKey);
        CFRelease(stringIODialinDeviceKey);
        IOObjectRelease(port);
    }
//------------end------------

  • IOIteratorNext 檢查並取得符合條件的物件,在這裡因為取得的都是,還需要利用一些相關的Function取得資訊
  • IORegistryEntryCreateCFProperty 取得的物件後,在其中取得所指定的資訊並會轉換成Objective-C的NSString,上面例子取用四個欄位的資訊

依照<IOKit/serial/IOSerialKeys.h>所提供能取得的資訊欄位如下:

/* Service Matching That is the 'IOProviderClass' */
#define kIOSerialBSDServiceValue    "IOSerialBSDClient"

/* Matching keys */
#define kIOSerialBSDTypeKey     "IOSerialBSDClientType"

/* Currently possible kIOSerialBSDTypeKey values. */
#define kIOSerialBSDAllTypes        "IOSerialStream"
#define kIOSerialBSDModemType       "IOModemSerialStream"
#define kIOSerialBSDRS232Type       "IORS232SerialStream"

// Properties that resolve to a /dev device node to open for
// a particular service
#define kIOTTYDeviceKey         "IOTTYDevice"
#define kIOTTYBaseNameKey       "IOTTYBaseName"
#define kIOTTYSuffixKey         "IOTTYSuffix"

#define kIOCalloutDeviceKey     "IOCalloutDevice"
#define kIODialinDeviceKey      "IODialinDevice"

// Property 'ioctl' wait for the tty device to go idle.
#define kIOTTYWaitForIdleKey        "IOTTYWaitForIdle"

最後程式執行的結果:

2014-05-05 17:13:35.924 GetSerialBSDService[29208:303]
 Name:Bluetooth-Incoming-Port
 SerialBSDTypeKey:IORS232SerialStream
 CalloutDevice:/dev/cu.Bluetooth-Incoming-Port
 DialinDevice:/dev/tty.Bluetooth-Incoming-Port

2014-05-05 17:13:35.925 GetSerialBSDService[29208:303]
 Name:Bluetooth-Modem
 SerialBSDTypeKey:IOModemSerialStream
 CalloutDevice:/dev/cu.Bluetooth-Modem
 DialinDevice:/dev/tty.Bluetooth-Modem

2014-05-05 17:13:35.925 GetSerialBSDService[29208:303]
 Name:HOLUX_M-241-SPPSlave
 SerialBSDTypeKey:IORS232SerialStream
 CalloutDevice:/dev/cu.HOLUX_M-241-SPPSlave
 DialinDevice:/dev/tty.HOLUX_M-241-SPPSlave

2014-05-05 17:13:35.926 GetSerialBSDService[29208:303]
 Name:usbserial
 SerialBSDTypeKey:IORS232SerialStream
 CalloutDevice:/dev/cu.usbserial
 DialinDevice:/dev/tty.usbserial

以測試環境中能看到執行結果列舉了4個Serial Port,其中CalloutDeviceDialinDevice與Linux類似結構,它就是Serial週邊存取的裝置名稱,我們執行命令查看一下:

ls -l /dev/cu* tty.*

命令結果:

crw-rw-rw-  1 root  wheel   17,   1  5  5 09:52 /dev/cu.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel   17,   3  5  5 09:52 /dev/cu.Bluetooth-Modem
crw-rw-rw-  1 root  wheel   17,   5  5  5 09:52 /dev/cu.HOLUX_M-241-SPPSlave
crw-rw-rw-  1 root  wheel   17,  13  5  5 16:30 /dev/cu.usbserial
crw-rw-rw-  1 root  wheel   17,   0  5  5 09:52 /dev/tty.Bluetooth-Incoming-Port
crw-rw-rw-  1 root  wheel   17,   2  5  5 09:52 /dev/tty.Bluetooth-Modem
crw-rw-rw-  1 root  wheel   17,   4  5  5 09:52 /dev/tty.HOLUX_M-241-SPPSlave
crw-rw-rw-  1 root  wheel   17,  12  5  5 16:30 /dev/tty.usbserial

如果你要使用Serial Port就能在CalloutDeviceDialinDevice取得之後,使用Open/Read/Write的檔案存取命令來對Serial Port操作。

程式內容:

//-----------start-----------
- (void)runTest1
{
    CFMutableDictionaryRef keywordDict = IOServiceMatching(kIOSerialBSDServiceValue);
    //
    io_object_t port;

    io_iterator_t portIterator = 0;
    kern_return_t result = IOServiceGetMatchingServices(kIOMasterPortDefault, keywordDict, &portIterator);
    NSLog(@"%i",result);
    if (result){
        NSLog(@"Error getting for serial port");
        return;
    }

    while ((port = IOIteratorNext(portIterator)))
    {
        CFStringRef stringIOTTYBaseNameKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                        (__bridge CFStringRef)(NSString*)CFSTR(kIOTTYBaseNameKey),
                                                                                        kCFAllocatorDefault,
                                                                                        0);

        CFStringRef stringIOSerialBSDTypeKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                        (__bridge CFStringRef)(NSString*)CFSTR(kIOSerialBSDTypeKey),
                                                                                        kCFAllocatorDefault,
                                                                                        0);

        CFStringRef stringIOCalloutDeviceKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                            (__bridge CFStringRef)(NSString*)CFSTR(kIOCalloutDeviceKey),
                                                                                            kCFAllocatorDefault,
                                                                                            0);

        CFStringRef stringIODialinDeviceKey = (CFStringRef)IORegistryEntryCreateCFProperty(port,
                                                                                            (__bridge CFStringRef)(NSString*)CFSTR(kIODialinDeviceKey),
                                                                                            kCFAllocatorDefault,
                                                                                            0);

        NSLog(@"\r Name:%@ \r SerialBSDTypeKey:%@ \r CalloutDevice:%@ \r DialinDevice:%@",stringIOTTYBaseNameKey,stringIOSerialBSDTypeKey,stringIOCalloutDeviceKey,stringIODialinDeviceKey);
        //
        CFRelease(stringIOTTYBaseNameKey);
        CFRelease(stringIOSerialBSDTypeKey);
        CFRelease(stringIOCalloutDeviceKey);
        CFRelease(stringIODialinDeviceKey);
        IOObjectRelease(port);
    }
    //CFRelease(keywordDict);
}
//------------end------------

參考資料

Device Access and the I/O Kit

I/O Kit Fundamentals