MacOS上的串口访问,有2个用的比较多的框架,这两个框架功能都比较强大,但也都很大,很复杂
AMSerialPort、ORSSerialPort
参考这两个框架及其它 资料,经过详细了解,发现在MacOS上串口编程的核心部分就是对
1.定义fileDescriptor
int fileDescriptor = 0
2.通过设备名字全称打开串口
假设串口名字为comDeviceName = @"/dev/cu.usbserial-ABCDEFG"
fileDescriptor = open([comDeviceName UTF8String], O_RDWR | O_NONBLOCK);
fileDescriptor <= 0, 表示有串口打开失败,每次打开串口前,最好先检查fileDescriptor的状态
open-only flags 每种flags的含义及取值如下
#define O_RDONLY 0x0000 // open for reading only 只读
#define O_WRONLY 0x0001 // open for writing only 只写
#define O_RDWR 0x0002 // open for reading and writing 读写
#define O_ACCMODE 0x0003 // mask for above modes 综合以上模式
#define FREAD 0x0001
#define FWRITE 0x0002
#define O_NONBLOCK 0x0004 // no delay 无延迟
#define O_APPEND 0x0008 // set append mode
3.设置端口初始化默认参数
struct termios term; //声明结构体
tcgetattr(fileDescriptor, &term); //获取端口的配置参数
cfmakeraw(&term);//设置为默认参数
term.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); //设置c_lflag local flag
tcsetattr(fileDescriptor, TCSANOW, &term); //TCSANOW将新的配置立即应用于端口,立即生效
term结构体的具体含义定义如下
struct termios {
tcflag_t c_iflag; // input flags
tcflag_t c_oflag; // output flags
tcflag_t c_cflag; // control flags
tcflag_t c_lflag; // local flags
cc_t c_cc[NCCS]; // control chars
speed_t c_ispeed; // input speed
speed_t c_ospeed; // output speed
};
int tcgetattr(fd, struct termios *);
将当前fileDescriptor的配置读取到结构体term中
void cfmakeraw(struct termios *);
将终端设置为原始模式,该模式下所有的输入数据以字节为单位被处理
端口模式区分为Row mode或者line mode,row mode就是以字节为单位处理数据
在原始模式下,终端是不可回显的,而且所有特定的终端输入/输出模式不可用。
termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);
termios_p->c_oflag &= ~OPOST;
termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);
termios_p->c_cflag &= ~(CSIZE|PARENB);
termios_p->c_cflag |= CS8;
term.c_lflag中各种flag的定义
* 每种配置的具体含义定义如下:
* "Local" flags - dumping ground for other state
* Warning: some flags in this structure begin with
* the letter "I" and look like they belong in the
* input flag.
*
#define ECHOKE 0x00000001 // visual erase for line kill
#define ECHOE 0x00000002 // visually erase chars
#define ECHOK 0x00000004 // echo NL after line kill
#define ECHO 0x00000008 // enable echoing
#define ECHONL 0x00000010 // echo NL even if ECHO is off
#define ECHOPRT 0x00000020 // visual erase mode for hardcopy
#define ECHOCTL 0x00000040 // echo control chars as ^(Char)
#define ISIG 0x00000080 // enable signals INTR, QUIT, [D]SUSP
#define ICANON 0x00000100 // canonicalize input lines
#define ALTWERASE 0x00000200 // use alternate WERASE algorithm
#define IEXTEN 0x00000400 // enable DISCARD and LNEXT
#define EXTPROC 0x00000800 // external processing
#define TOSTOP 0x00400000 // stop background jobs from output
#define FLUSHO 0x00800000 // output being flushed (state)
#define NOKERNINFO 0x02000000 // no kernel output from VSTATUS
#define PENDIN 0x20000000 // XXX retype pending input (state)
#define NOFLSH 0x80000000 // don't flush after interrupt
int tcsetattr(fd, int, const struct termios *); 将新的配置应用于端口
int值的定义:Commands passed to tcsetattr() for setting the termios structure.
#define TCSANOW 0 // make change immediate 立即生效
#define TCSADRAIN 1 // drain output, then change 输出完毕后改变
#define TCSAFLUSH 2 // drain output, flush input 输入/输出完毕
#define TCSASOFT 0x10 // flag - don't alter h.w. state
4.波特率baudRate设置
常用的函数形式如下
speed_t cfgetispeed(const struct termios *);
speed_t cfgetospeed(const struct termios *);
int cfsetispeed(struct termios *, speed_t);
int cfsetospeed(struct termios *, speed_t);
int tcgetattr(int, struct termios *);
int tcsetattr(int, int, const struct termios *);
int tcdrain(int) __DARWIN_ALIAS_C(tcdrain);
int tcflow(int, int);
int tcflush(int, int);
int tcsendbreak(int, int);void cfmakeraw(struct termios *);
int cfsetspeed(struct termios *, speed_t);
int ioctl(int, unsigned long, ...);
函数返回值如果!=0表示设置参数失败
_IOW()的宏定义如下
#define _IOC(inout, group, num, len) \
(inout | ((len & IOCPARM_MASK) << 16) | ((group) << 8) | (num))
#define _IO(g, n) _IOC(IOC_VOID, (g), (n), 0)
#define _IOR(g, n, t) _IOC(IOC_OUT, (g), (n), sizeof(t))
#define _IOW(g, n, t) _IOC(IOC_IN, (g), (n), sizeof(t))
// this should be _IORW, but stdio got there first
#define _IOWR(g, n, t) _IOC(IOC_INOUT, (g), (n), sizeof(t))
//网上看了些例子,对于baudRate<115200的时候,可以直接按以下方式设置
tcgetattr(fileDescriptor, &term);
cfsetspeed(&term, baudRate); //设置波特率
tcsetattr(fileDescriptor, TCSANOW, &term);
//但是对于更高的波特率设置,他们采用的是ioctl()方式
ioctl(fileDescriptor, _IOW('T', 2, speed_t), &baudRate);
5.使用GCD技术后台读取数据
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT , 0), ^(){
@autoreleasepool {//后台读取串口数据
[self readDataInBackground];
}
});
6.后台读取数据
-(void) readDataInBackground
{
void *recvData = malloc(1024); //1024的容量
//memory allocation,向系统申请分配指定size个字节的内存空间
memset(recvData, 0, 1024); // 初始化recvData
ssize_t length = 0; //实际读取到的字符字节数
while (YES)
{
@autoreleasepool
{
if(fileDescriptor>0) //检查串口是否已经打开)
{
memset(recvData, 0, 1024); //每次读取数据前先清空
length = read(fileDescriptor, recvData, 1024);//读取数据
//read函数是阻塞型的,只有读取到数据了才运行到下边
NSData *data = [NSData dataWithBytes: recvData length:length];
//bytes转为data
//读取到数据后可做进一步处理
//参考AMSerialPort的做法,可使用delegate调用其它方法具体处理读取到的数据
}
}
}
}
7.发送数据
发送数据主要使用
ssize_t write(int __fd, const void * __buf, size_t __nbyte)
函数返回值是真实发送了多少个byte的数据
fd:file descriptor
buf:发送数据的buf 通常为Byte或char型数组
nbyte:表示number of byte,即表示几个字节
row mode下是一次发送1个字节数据,line mode下是一次性将所有数据发送出去
//假设要发送的数据定义为NSData *data
//line mode下发送数据为:
const char *dataBytes = (const char*)[data bytes];
write(fileDescriptor, dataBytes, [data length]);//发送所有数据
//row模式下发送数据为:
write(fileDescriptor, dataBytes++, 1); //一次发送1个byte的数据