這兒說的串口包括兩種,232和485。其實,二者沒有本質(zhì)的區(qū)別,驅(qū)動都是一樣的,只是232是雙工,而485是半雙工。
所以,485在正常情況下出于接收狀態(tài),一旦需要發(fā)送數(shù)據(jù)時,需要設(shè)置對于的IO的狀態(tài),使其出于發(fā)送狀態(tài)。
除此,沒有太多區(qū)別。下面已485為例具體總結(jié)。
2 打開串口
fd = open("/dev/s3c2410_serial0", O_RDWR|O_NOCTTY|O_NDELAY);
if (fd < 0)
{
perror("rs485: open 485 device");
return -1;
}
? ?就像打開普通的文件一樣操作open。? ?"/dev/s3c2410_serial0":是串口設(shè)備名
? ?O_RDWR:以可讀可寫的方式打開
? ?O_NOCTTY:不是控制終端控制的程序,否則,任何輸入都會影響該程序。具體沒有測試過,讓加就加上唄。
? ?O_NDELAY:不關(guān)心DCD信號線狀態(tài),即其他端口是否允許。否則,程序會在DCD信號線為低電平時停止。
? ? 目前,還不是很明白什么意思。暫時理解為設(shè)置為非阻塞狀態(tài)吧。
3 參數(shù)設(shè)置
串口的參數(shù)放在了結(jié)構(gòu)體struct termios中。
首先,需要獲取參數(shù)信息。
struct termios options;
if(tcgetattr( fd,&options) != 0)
{
perror("tcgetattr");
return -1;
}
然后,具體設(shè)置參數(shù)。這塊是下面重點說的。我們以9600,8,N,1為例。因為,這是經(jīng)典的配置。最后,保存新設(shè)置的參數(shù)。
tcflush(fd,TCIFLUSH);
if (tcsetattr(fd,TCSANOW,&options) != 0)
{
perror("tcsetattr");
return -1;
}
至此,參數(shù)設(shè)置完畢。3.1 波特率
/*波特率9600*/
? ? cfsetispeed(&options, B9600);
? ? cfsetospeed(&options, B9600);
3.2 數(shù)據(jù)位
? /*數(shù)據(jù)位8*/? ? options.c_cflag &= ~CSIZE;
? ? options.c_cflag |= CS8;
? ?設(shè)置數(shù)據(jù)位之前,必須先設(shè)置options.c_cflag &= ~CSIZE;
? ?作用是先屏蔽其他標(biāo)志,然后修改數(shù)據(jù)位。
3.3 奇偶校驗
? ??/*奇偶校驗,無*/
? ? options.c_cflag &= ~PARENB;
? ? options.c_iflag &= ~INPCK;
3.4 停止位
? ??/*停止位,1*/
? ? options.c_cflag &= ~CSTOPB;
3.5 控制設(shè)置
? /*保證程序不會占用串口,并且可以能夠從串口中讀取輸入數(shù)據(jù)*/
? ? options.c_cflag |= (CLOCAL|CREAD);
? ? 這兩個標(biāo)記必須加上。
3.6 輸入輸出
? options.c_oflag &= ~OPOST;
? 當(dāng)OPOST不被使能,c_oflag的其他位也被忽略,效果相當(dāng)于c_oflag=0
? options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
3.7 等待時間
? ??/*設(shè)置等待時間*/? ? options.c_cc[VTIME] = 0;?
? ? options.c_cc[VMIN] = 0;
? ? 關(guān)于這兩項設(shè)置,內(nèi)容挺多的,首先VTIME是設(shè)置等待時間,最小單位是0.1s;VMIN是設(shè)置等待字符數(shù)。
? ? 有四種情況,具體如下:
? ? VTIME=0,VMIN=0:read時,有數(shù)據(jù)就讀,沒有就立馬返回。
? ? VTIME=1,VMIN=0:read時,有數(shù)據(jù)時就讀,讀的過程中沒有延時。沒有數(shù)據(jù)時,會阻塞0.1s,然后返回。
? ? VTIME=0,VMIN=5:read時,有數(shù)據(jù)時,讀到5個字節(jié),立馬返回。沒有數(shù)據(jù)時,會一直阻塞著,非得等到那個字節(jié)的到來。
? ? VTIME=1,VMIN=5:read時,有數(shù)據(jù)是,讀到一個字節(jié)后,才開始計時,然后,如果時間到或已經(jīng)讀取了5個字節(jié),則返回。
? ? 沒有數(shù)據(jù)時,會一直阻塞著等待字符的到來,此時時間不起作用,因為還沒有計時。
? ? 注意:只有在打開設(shè)備時,設(shè)置為阻塞,即沒有設(shè)置O_NONBLOCK或O_NDELAY標(biāo)志,上面的情況才會有效。
上面的1和5只是為了方便說明而已??傮w來說,只要設(shè)置了VMIN,在沒有數(shù)據(jù)可讀時,就會一直阻塞。 ? ? ?
4 讀串口
關(guān)于讀串口,網(wǎng)上說了很多方式,有select查詢,軟中斷等。當(dāng)然,最終實際去讀的都是用read函數(shù)。
我采取的方式是先用select查詢是否有數(shù)據(jù)需要讀,然后,每次讀取一個字節(jié),再去用狀態(tài)機(jī)檢查該字節(jié)。這樣一來,不用考慮
每次讀取多少,以及是否讀全等問題。代碼如下:
timeout.tv_sec = 0;
timeout.tv_usec = 10000;
FD_ZERO(&readfd);
FD_SET(fd,&readfd);
ret=select(fd+1,&readfd,NULL,NULL,&timeout);
if(ret > 0)
{
if(FD_ISSET(fd,&readfd))
{
ret = read(fd, &cmd, 1);
if(ret > 0)
{
/*有信息,則去處理信息*/
}
}
}
? ? 處理信息函數(shù)根據(jù)當(dāng)前狀態(tài)處理該字節(jié),并進(jìn)入下一狀態(tài)。? ? 目前,不太確定加上select和直接用來read哪個效率高,有沒有必要加select。
5 寫串口
按說串口的寫只是用一個write函數(shù)即可實現(xiàn)的。但是,現(xiàn)在是485串口,寫串口時,需首先更改串口狀態(tài)。
下面重點說明一下如何更改串口狀態(tài)。
5.1 /sys/class/gpio
它是gpio到文件系統(tǒng)的映射,通過操作里面的文件,可以直接操作gpio。
首先,查看系統(tǒng)中有沒有/sys/class/gpio目錄。
如果沒有,需要在配置內(nèi)核時加上Device Drivers —> GPIO Support —> /sys/class/gpio/。
5.2 文件介紹
該目錄下有export和unexport兩個可寫文件和其他的類似gpiochip146的軟連接。
export:通知系統(tǒng)需要導(dǎo)出控制的GPIO引腳編號。
unexport:通知系統(tǒng)取消導(dǎo)出。
gpiochip146:保存系統(tǒng)中GPIO寄存器的信息。該目錄在/sys/devices/virtual/gpio/gpiochip146
目錄下有l(wèi)abel、base、ngpio、subsystem、uevent。代表的意義如下:
label:設(shè)備信息,如cat label,得到的是GPH2
base:設(shè)備所管理的gpio初始編號,如cat base,得到的是146
ngpio:設(shè)備所管理的gpio總是,如cat ngpio,得到的是8,
即該gpiochip146管理從146到153這8個gpio接口
subsystem:符號鏈接,指向父目錄
uevent:內(nèi)核與udev(自動設(shè)備發(fā)現(xiàn)程序)之間的通信接口
5.3 導(dǎo)出GPIO接口
首先,需要計算出引腳編號。
引腳編號 = 控制引腳的寄存器基數(shù) + 控制引腳寄存器位數(shù)
該引腳即是控制485狀態(tài)轉(zhuǎn)換的引腳。根據(jù)具體的硬件連接來定,我的是146。
fd = open("/sys/class/gpio/export", O_WRONLY);
write(fd, "146", 3);
導(dǎo)出成功后,會在當(dāng)前目錄下生成新的目錄gpio146。
5.4 gpio146
該目錄下會有幾個個文件,其中有value、direction和active_low
direction:具有讀寫屬性,控制GPIO接口的輸入輸出方向。
如果將"out"寫入該文件,該GPIO接口為輸出狀態(tài);
如果將"in"寫入該文件,該GPIO接口為輸入狀態(tài);
如果將"high"寫入該文件,那么在將GPIO接口置為輸出狀態(tài)的同時,也將value的值置為"1";
如果將"low"寫入value
當(dāng)GPIO的方向為輸入時,可以通過v文件,那么在將GPIO接口置為輸出狀態(tài)的同時,將"0"寫入value文件。
通過對direction文件的讀操作還可以判斷當(dāng)前GPIO接口的輸入/輸出狀態(tài)("in"/"out")。
value:具有讀寫屬性,表示當(dāng)前GPIO接口的電平狀態(tài)。
當(dāng)GPIO的方向為輸入時,可以通過value讀出當(dāng)前GPIO接口的電平狀態(tài)高低("1"/"0",均以ASCII碼表示);
當(dāng)GPIO方向為輸出時,可以向該文件寫入"1"/"0",控制當(dāng)前GPIO接口的高/低電平。
active_low:具有讀寫屬性,值為"0"或"1",用于決定value中的值是否進(jìn)行翻轉(zhuǎn)。
當(dāng)值為"0"時,value中的"0"表示低電平,"1"表示高電平;
當(dāng)值為"1"時,value中的"1"表示低電平,"0"表示高電平。
5.5 輸出
設(shè)置引腳為輸出方向。
fd = open("/sys/class/gpio/gpio146/direction", O_WRONLY); ?
write(fd, "out", 3);
5.6 發(fā)送模式
fd = open("/sys/class/gpio/gpio146/value", O_WRONLY); ?
write(fd, "1", 1);
此處高低根據(jù)硬件情況來。
5.7 取消導(dǎo)出
fd = open("/sys/class/gpio/unexport", O_WRONLY);
write(fd, "146", 3);
5.8 接收模式
發(fā)送完之后,需等待一會,然后再設(shè)置成接收模式。
這個時間我暫時設(shè)置成usleep(1000*len);
len:發(fā)送數(shù)據(jù)的長度。
fd = open("/sys/class/gpio/gpio146/value", O_WRONLY);
write(fd, "0", 1);
6 總結(jié)
232的操作比這簡單,寫時不需要再通過GPIO來更改狀態(tài)了。