1. 程式人生 > >Linux下讀取RFID卡號(C串口編程)

Linux下讀取RFID卡號(C串口編程)

start itl star tor 卡號 字節數 同時 描述符 lag

由於項目需要用到RFID、GPRS、攝像頭等模塊所以便看了一下,整理了一下學習思路,本篇先是整理一下串口讀取RFID卡號的程序思路,後面還會更其他的技術分享技術分享

RFID模塊:

本次采用的是125K的RFID讀卡器和標簽,很容易理解的,其實就是一張卡片裏面存了一串數字(這個問題有點像你問一個藝術家洛必達法則是啥技術分享咦洛必達是啥),然後有個讀卡器,當你把卡片放到讀卡器上時,讀卡器會將卡裏面存的卡號讀取出來,然後放到串口發送緩沖區,等待我們去讀取,那麽問題就是怎麽讀取。

串口讀寫:

大家都知道。linux下面一切皆文件,設備也不例外,上面提到的串口就是個設備文件,linux設備文件一般存放在“/dev/”下,當你ls的時候會發現一大堆什麽ttyS0、sda、video....現在筆記本串口設備文件一般都是ttyUSBx(x=0,1,2...)。既然是文件,那就能打開嘍,不過它不是被“右鍵->打開”,而是被“系統調用open()”。當然不只是把它打開就完了,操作串口有一系列的系統調用。說到系統調用,其實就是系統底層給在上層編寫程序的你提供的一些系統級函數。

一些需要的頭文件:

[cpp] view plain copy
  1. #include <unistd.h> /*linux系統調用*/
  2. #include <stdlib.h>
  3. #include <stdio.h>
  4. #include <string.h>
  5. #include <fcntl.h> /*文件控制*/
  6. #include <sys/stat.h> /*文件狀態*/
  7. #include <sys/types.h> /*定義系統類型,像size_t等*/
  8. #include <errno.h> /*出錯碼*/
  9. #include <termios.h> /*終端參數*/



1.打開串口

這裏把open()這個系統調用封裝成一個com_open()函數,可以方便判斷是否打開成功並打印錯誤信息。

參數*DEV是文件路徑(上面提到的/dev/ttyUSBx),第二個參數告訴它是以什麽方式打開,常見的有:

O_RDONLY 只讀

O_WRONLY 只寫

O_RDWR 讀寫

註:上面的三個不能同時出現,即不能這樣寫O_RDONLY | O_RDWR,像下面這些是可選的:

O_NOCTTY 如果路徑名指向終端設備,不要把這個設備用作控制終端。

O_NONBLOCK 阻塞模式(詳見配置串口處)

open()返回值是int型fd文件描述符,對於內核而言,所有打開的文件都通過文件描述符引用。文件描述符是一個非負整數。當打開一個現有文件或創建一個新文件時,內核向進程返回一個文件描述符(0~255,不過有些是系統已經占用的),這個文件描述符用來告訴我們要對哪個文件進行操作。

[cpp] view plain copy
  1. int com_open(const char *DEV)
  2. {
  3. int fd = -1;
  4. open(DEV, O_RDWR);
  5. if(fd == -1)
  6. {
  7. perror("open error");
  8. exit(0);
  9. }
  10. return fd;
  11. }

2.配置串口

設置串口屬性(類似於約定好雙方通信協議),即上面提到的配置串口,其實主要就是設置termios.h中的termios結構體參數:

[cpp] view plain copy
  1. typedef struct com_attr /*我自己定義的串口屬性結構*/
  2. {
  3. unsigned int baudrate; /*波特率*/
  4. unsigned char databits; /*數據位*/
  5. unsigned char stopbits; /*停止位*/
  6. unsigned char parity; /*校驗位*/
  7. }com_attr;

[cpp] view plain copy
  1. struct termios /*termios結構,其實終極目的就是把我們自己定義的結構屬性設置到這裏面去*/
  2. {
  3. tcflag_t c_iflag; //輸入模式標誌
  4. tcflag_t c_oflag; //輸出模式標誌
  5. tcflag_t c_cflag; //控制模式標誌
  6. tcflag_t c_lflag; //本地模式標誌
  7. cc_t c_line; //line discipline
  8. cc_t c_cc[NCC]; //control characters
  9. }

可以看到兩個參數,第一個文件描述符,告訴它你想在個文件操作,第二個是我定義的串口屬性結構體:

註:由於項目需要,可能有些不必要的參數我就沒有去設置和解釋,詳細可以google一下配置串口屬性結構體詳細介紹!

[cpp] view plain copy
  1. int set_com_attr(int fd, com_attr *attr)
  2. {
  3. struct termios opt;
  4. memset(&opt, 0, sizeof(struct termios));
  5. tcgetattr(fd, &opt);
  6. cfmakeraw(&opt);
  7. /*******************波特率********************/
  8. printf("set baudrate %d\n", attr->baudrate);
  9. switch (attr->baudrate)
  10. {
  11. case 50:
  12. cfsetispeed(&opt, B50);
  13. cfsetospeed(&opt, B50);
  14. break;
  15. case 75:
  16. cfsetispeed(&opt, B75);
  17. cfsetospeed(&opt, B75);
  18. break;
  19. case 110:
  20. cfsetispeed(&opt, B110);
  21. cfsetospeed(&opt, B110);
  22. break;
  23. case 134:
  24. cfsetispeed(&opt, B134);
  25. cfsetospeed(&opt, B134);
  26. break;
  27. case 150:
  28. cfsetispeed(&opt, B150);
  29. cfsetospeed(&opt, B150);
  30. break;
  31. case 200:
  32. cfsetispeed(&opt, B200);
  33. cfsetospeed(&opt, B200);
  34. break;
  35. case 300:
  36. cfsetispeed(&opt, B300);
  37. cfsetospeed(&opt, B300);
  38. break;
  39. case 600:
  40. cfsetispeed(&opt, B600);
  41. cfsetospeed(&opt, B600);
  42. break;
  43. case 1200:
  44. cfsetispeed(&opt, B1200);
  45. cfsetospeed(&opt, B1200);
  46. break;
  47. case 1800:
  48. cfsetispeed(&opt, B1800);
  49. cfsetospeed(&opt, B1800);
  50. break;
  51. case 2400:
  52. cfsetispeed(&opt, B2400);
  53. cfsetospeed(&opt, B2400);
  54. break;
  55. case 4800:
  56. cfsetispeed(&opt, B4800);
  57. cfsetospeed(&opt, B4800);
  58. break;
  59. case 9600:
  60. cfsetispeed(&opt, B9600);
  61. cfsetospeed(&opt, B9600);
  62. break;
  63. case 19200:
  64. cfsetispeed(&opt, B19200);
  65. cfsetospeed(&opt, B19200);
  66. break;
  67. case 38400:
  68. cfsetispeed(&opt, B38400);
  69. cfsetospeed(&opt, B38400);
  70. break;
  71. case 57600:
  72. cfsetispeed(&opt, B57600);
  73. cfsetospeed(&opt, B57600);
  74. break;
  75. case 115200:
  76. cfsetispeed(&opt, B115200);
  77. cfsetospeed(&opt, B115200);
  78. break;
  79. case 230400:
  80. cfsetispeed(&opt, B230400);
  81. cfsetospeed(&opt, B230400);
  82. break;
  83. case 460800:
  84. cfsetispeed(&opt, B460800);
  85. cfsetospeed(&opt, B460800);
  86. break;
  87. case 500000:
  88. cfsetispeed(&opt, B500000);
  89. cfsetospeed(&opt, B500000);
  90. break;
  91. case 576000:
  92. cfsetispeed(&opt, B576000);
  93. cfsetospeed(&opt, B576000);
  94. break;
  95. case 921600:
  96. cfsetispeed(&opt, B921600);
  97. cfsetospeed(&opt, B921600);
  98. break;
  99. case 1000000:
  100. cfsetispeed(&opt, B1000000);
  101. cfsetospeed(&opt, B1000000);
  102. break;
  103. case 1152000:
  104. cfsetispeed(&opt, B1152000);
  105. cfsetospeed(&opt, B1152000);
  106. break;
  107. case 1500000:
  108. cfsetispeed(&opt, B1500000);
  109. cfsetospeed(&opt, B1500000);
  110. break;
  111. case 2000000:
  112. cfsetispeed(&opt, B2000000);
  113. cfsetospeed(&opt, B2000000);
  114. break;
  115. case 2500000:
  116. cfsetispeed(&opt, B2500000);
  117. cfsetospeed(&opt, B2500000);
  118. break;
  119. case 3000000:
  120. cfsetispeed(&opt, B3000000);
  121. cfsetospeed(&opt, B3000000);
  122. break;
  123. case 3500000:
  124. cfsetispeed(&opt, B3500000);
  125. cfsetospeed(&opt, B3500000);
  126. break;
  127. case 4000000:
  128. cfsetispeed(&opt, B4000000);
  129. cfsetospeed(&opt, B4000000);
  130. break;
  131. default:
  132. printf("unsupported baudrate %d\n", attr->baudrate);
  133. return FALSE;
  134. break;
  135. }
  136. /************************校驗位************************/
  137. switch (attr->parity)
  138. {
  139. case COMM_NOPARITY:
  140. opt.c_cflag &= ~PARENB;
  141. opt.c_iflag &= ~INPCK;
  142. break;
  143. case COMM_ODDPARITY:
  144. opt.c_cflag |= PARENB;
  145. opt.c_cflag |= PARODD;
  146. opt.c_iflag |= INPCK;
  147. break;
  148. case COMM_EVENPARITY:
  149. opt.c_cflag |= PARENB;
  150. opt.c_cflag &= ~PARODD;
  151. opt.c_iflag |= INPCK;
  152. default:
  153. printf("unsupported parity %d\n", attr->parity);
  154. return FALSE;
  155. break;
  156. }
  157. opt.c_cflag &= ~CSIZE; /*無論設置多少校驗位都需要的*/
  158. /*******************數據位*****************/
  159. switch (attr->databits)
  160. {
  161. case 5:
  162. opt.c_cflag |= CS5;
  163. break;
  164. case 6:
  165. opt.c_cflag |= CS6;
  166. break;
  167. case 7:
  168. opt.c_cflag |= CS7;
  169. break;
  170. case 8:
  171. opt.c_cflag |= CS8;
  172. break;
  173. default:
  174. printf("unsupported data bits %d\n", attr->databits);
  175. return FALSE;
  176. break;
  177. }
  178. opt.c_cflag &= ~CSTOPB;
  179. /*******************停止位***************/
  180. switch (attr->stopbits)
  181. {
  182. case COMM_ONESTOPBIT:
  183. opt.c_cflag &= ~CSTOPB;
  184. break;
  185. case COMM_TWOSTOPBITS:
  186. opt.c_cflag |= CSTOPB;
  187. break;
  188. default:
  189. printf("unsupported stop bits %d\n", attr->stopbits);
  190. return FALSE;
  191. break;
  192. }
  193. /*等待時間,阻塞模式下設置的*/
  194. //opt.c_cc[VTIME] = 0; /*設置超時時間*/
  195. //opt.c_cc[VMIN] = 1;
  196. opt.c_iflag &= ~(ICRNL | INLCR);
  197. opt.c_iflag &= ~(IXON | IXOFF | IXANY);/*關閉軟件流控(一般都是關閉軟硬流控,我也不知道為啥)*/
  198. tcflush(fd, TCIOFLUSH); //刷清緩沖區
  199. if (tcsetattr(fd, TCSANOW, &opt) < 0)
  200. {
  201. printf("tcsetattr faild\n");
  202. return FALSE;
  203. }
  204. return TRUE;
  205. }

當以阻塞模式打開時也可以通過修改結構體termios來改變位非阻塞模式或者通過函數fcntl()函數:

阻塞:fcntl(fd, F_SETFL, 0)

對於read,阻塞指當串口輸入緩沖區沒有數據的時候,read函數將會阻塞在這裏,直到串口輸入緩沖區中有數據可讀取時read讀到了需要的字節數之後,返回值為讀到的字節數;對於write,指當串口輸出緩沖區滿或剩下的空間小於將要寫入的字節數,write函數將阻塞在這裏,一直到串口輸出緩沖區中剩下的空間大於等於將要寫入的字節數,執行寫入操作,返回寫入的字節數。

註:控制符VTIME定義要等待的時間t(百毫秒),VMIN定義了要等待的最小字節數n,以下幾種情況:
VTIME=0,VMIN=n,read必須在讀取了VMIN個字節的數據或者收到一個信號才會返回。
VTIME=t,VMIN=0,不管能否讀取到數據,read也要等待VTIME的時間量。
VTIME=t,VMIN=n,那麽將從read讀取第一個字節的數據時開始計時,並會在讀取到VMIN個字節或者VTIME時間後返回。
VTIME=0,VMIN=0,不管能否讀取到數據,read都會立即返回。

非阻塞的定義:fcntl(fd, F_SETFL,FNDELAY)

當串口輸入緩沖區沒有數據的時候,read函數立即返回,返回值為0。

[cpp] view plain copy
  1. void get_com_attr(int fd)
  2. {
  3. struct termios opt;
  4. if(fd < 0)
  5. {
  6. printf("get_com_attr error");
  7. exit(0);
  8. }
  9. memset(&opt, 0, sizeof(struct termios));
  10. tcgetattr(fd, &opt);
  11. cfmakeraw(&opt);
  12. }

有必要說一下int tcsetattr(int fd, int optional_actions, const struct termios *termios_p)

int tcgetattr(int fd, struct termios *termios_p)

tcgetattr函數用於獲取與終端相關的參數。參數fd為終端的文件描述符,結果保存在termios結構體中。

tcsetattr函數用於設置終端的相關參數。參數optional_actions用於控制修改起作用的時間,而結構體termios_p中保存了要修改的參數;

optional_actions可以取如下的值:
TCSANOW:不等數據傳輸完畢就立即改變屬性。
TCSADRAIN:等待所有數據傳輸結束才改變屬性。
TCSAFLUSH:清空輸入輸出緩沖區才改變屬性。

3.讀取串口

這裏也是將read()系統調用封裝成com_read(),當我們設置好通訊協議了(串口屬性),就可以對串口進行讀寫了。

參數一fd就不用說了,第二個參數read_buff從名字看出就是要把數據讀到這個緩沖區中,第三個參數是你想要讀多少字節,註意是”你想要“,而返回值則是讀到的真正字節數,當你讀到末尾(假如緩沖區有10個字節,而你想要讀20個)或者出現異常中斷了讀操作,就會出現返回值ret(return) != nbytes。

[cpp] view plain copy
  1. int com_read(int fd, unsigned char *read_buff, unsigned int nbytes)
  2. {
  3. int ret;
  4. if(fd < 0)
  5. {
  6. printf("com_read error");
  7. exit(0);
  8. }
  9. ret = read(fd, read_buff, nbytes);
  10. return ret;
  11. }

4.寫入串口

道理和寫差不多

[cpp] view plain copy
  1. int com_write(int fd, BYTE *write_buff, DWORD nbytes)
  2. {
  3. int ret;
  4. if(fd < 0)
  5. {
  6. printf("com_write error");
  7. exit(0);
  8. }
  9. ret = write(fd, write_buff, nbytes);
  10. return ret;
  11. }

5.關閉串口

記得每次操作完串口要關閉串口(當然了,當你操作多個文件時可別操作錯了文件描述符,那就gg了)

[cpp] view plain copy
  1. void com_close(int fd)
  2. {
  3. if(fd < 0)
  4. {
  5. printf("com_close error");
  6. exit(0);
  7. }
  8. close(fd);
  9. }

好了,萬事具備,下面就可以插上設備刷卡讀卡號啦(註意看清你的設備ttyUSBx中的x是多少啊),具體讀卡號函數就看大家的具體需求啦。

由於博主的項目需求是要將卡號變成一個字符串然後再填充到另一個字符串,然後再巴拉巴拉,可是這個卡號讀出來是一串16進制數據,所以想了半天決定用類型轉換(不過聽說可以用fprintf)。

Linux下讀取RFID卡號(C串口編程)