嵌入式Linux學習筆記(四) 設備樹和UART驅動開發


目錄

(1).參考資料

(2).Uart硬件配置

(3).設備樹的說明和修改

(4).測試代碼

 

  通過完成LED的驅動,我們熟悉了驅動編寫的大致結構框架,然而在實際開發中,嵌入式Linux和普通單片機最大的不同就是提供大量的代碼,滿足大部分的應用需求,如本節中,我們使用的UART驅動已經被集成到內核。不過通過對底層驅動更高級的抽象,使用設備樹實現了底層驅動的復用,是目前主推的驅動的開發模式,還是必須重視和掌握(后面涉及到驅動的部分都會以設備樹開發),下面就開始本小節的修改和開發。

 

參考資料

1. 開發板原理圖 《IMX6UL_ALPHA_V2.0(底板原理圖)》 《IMX6ULL_CORE_V1.4(核心板原理圖)》 

2. 正點原子《Linux驅動開發指南說明V1.0》 第四十三章 Linux設備樹

3. 正點原子《Linux驅動開發指南說明V1.0》 第四十五章 pinctrl和gpio子程序

4. 正點原子《Linux驅動開發指南說明V1.0》 第六十三章 Linux RS232/RS485/GPS驅動程序

5. 宋寶華 《Linux設備驅動開發詳解:基於最新的Linux 4.0內核》 第18章ARM Linux設備樹

 

UART硬件配置

 RS232對應的位USART3串口。

 

設備樹的說明和修改

  在早期不支持設備樹的Linux版本,描述板級信息的代碼被集成在內核中,這就讓內核充斥着大量的冗余代碼以應對不同的開發板和產品的外設需求,如在arch/arm/mach-xxx下的板級目錄,代碼量在數萬行,為了解決這種情況,采用設備樹(Flattned Device Tree),將硬件信息從Linux系統中剝離出來,從而不用在內核中進行大量的冗余編碼。

  設備樹由一系列被命名的節點(Node)和屬性(Property)組成,其中節點本身可包含子節點,而屬性就是成對出現的名與值,在設備樹中,可描述的信息包含:

  1. CPU的數量和類型

  2.內存基地址和大小

  3.總線和橋

  4.外設連接

  5.中斷控制器和中斷使用情況

  6.GPIO控制器和GPIO使用情況

  7.時間控制器和時鍾使用情況

  對於設備樹的開發,如果沒有原型(如新開發的一款芯片),對於設備樹的設計流程如下:根據硬件設計的板級信息,結合DTS語法知識,完成.dts或.dtsi文件的編寫,在通過scripts/dtc目錄下的DTC工具進行編譯,生成.dtb文件。Linux啟動后,會加載編譯完成的dtb文件到內核中,從而滿足內核模塊對於硬件和外設的操作要求。不過大多數情況下(非IC設計原廠的軟件的工程師), 都會提供好支持官方開發板的相同芯片的DTS文件,而我們的主要工作就是根據硬件設計的變動,修改這個DTS的信息以適配目前應用的需求,那么工作就簡單多了,具體分為以下幾個步驟

  1.結合現有設備樹的Node情況,修改成符合需求Node的dts文件,編譯完成后,生成dtb文件

  2.結合上節內容,實現字符驅動設備的添加,硬件部分操作替換成基於設備樹操作的版本

  3.下載,編寫測試模塊並進行測試

  按照需求,目前使用的dts文件為arch/arm/boot/dts/imx6ull-14x14-nand-4.3-480*272-c.dts, 通過內部的include代碼,我們可以找到

  imx6ull-14x14-evk-gpmi-weim.dts ->imx6ull-14x14-evk.dts->imx6ull.dtsi

  在imx6ull.dtsi中搜索硬件用到的uart3,通過全局檢索如下:

uart3: serial@021ec000 {
    compatible = "fsl,imx6ul-uart",
                "fsl,imx6q-uart", "fsl,imx21-uart";
    reg = <0x021ec000 0x4000>;
    interrupts = <GIC_SPI 28 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_UART3_IPG>,
            <&clks IMX6UL_CLK_UART3_SERIAL>;
    clock-names = "ipg", "per";
    dmas = <&sdma 29 4 0>, <&sdma 30 4 0>;
    dma-names = "rx", "tx";
    status = "disabled";
};

  其中compatible定義了整個系統(設備級別)的名稱,通過檢索Linux代碼,在drivers/tty/serial/imx.c中可以找到imx6q-uart的位置,定義在imx_uart_devtype中,仔細觀察該文件的實現,串口驅動本質上是platform驅動,包含

module_init(imx_serial_init);
module_exit(imx_serial_exit);
 
MODULE_AUTHOR("Sascha Hauer");
MODULE_DESCRIPTION("IMX generic serial port driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx-uart");

  並通過platform_driver_register注冊到虛擬總線上,這部分對於內核都已經實現了,我們不要做造輪子了(在上一節為了解驅動開發,所以對驅動進行了比較深入的講解), 對於嵌入式Linux開發,分清楚何時使用官方提供的驅動,何時使用自己編寫,這是需要養成的習慣,也是與單片機開發中是重要不同點。

  在imx6ull-14x14-evk.dts聲明了關於板級信息,在這里我們要添加GPIO對應的寄存器的信息以及在總線上添加UART3接口,因為默認配置的接口uart1和uart2,所以我們按照原本的格式添加(這部分的了解是可以隨着深入慢慢掌握的,先學會如何寫更重要),首先我們確認位RS232接口,不需要RTS或CTS功能,搜索&uart1,檢索到數據如下:

&uart1 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart1>;
    status = "okay";
};

  參考這個結構,在后面添加uart3的節點:

&uart3 {
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_uart3>;
    status = "okay";
};

  從上面可以看出,我們還需要添加pinctrl_uart3對應的GPIO子系統的信息,那么很簡單,檢索全局,找到如下代碼:

pinctrl_uart1: uart1grp {
fsl,pins = <
        MX6UL_PAD_UART1_TX_DATA__UART1_DCE_TX 0x1b0b1
        MX6UL_PAD_UART1_RX_DATA__UART1_DCE_RX 0x1b0b1
        >;
};

  參考上述結構,在后面添加UART3對應的接口:

pinctrl_uart3: uart3grp {
    fsl,pins = <
        MX6UL_PAD_UART3_TX_DATA__UART3_DCE_TX 0x1b0b1
        MX6UL_PAD_UART3_RX_DATA__UART3_DCE_RX 0x1b0b1
    >;
};

  當然,此時還要檢索全局,判斷UART3的接口是否被用到其它模塊,如果被占用,將該模塊注釋掉。這十分重要,因為被其它模塊占用,會導致引腳的配置被覆蓋或者改變,從而驅動的注冊無效。完成上述上述流程后,將imx6ull-14x14-nand-4.3-480*272-c.dts添加到/arch/arm/boot/dts/Makefile中的dtb-$(CONFIG_SOC_IMX6ULL)下,添加后如下:

 

  然后到linux文件夾下,使用

make dtbs

  編譯生成需要的dtb文件如下:

 

  將生成的dtb文件,參考之前Linux下載更新的流程,通過mgtool下載到芯片中,重新啟動,使用

ls /dev/ttymxc*

  即可查看支持的串口,如Uart3對應的位ttymxc2,ttymxc可通過drivers/tty/serial/imx.c中的Device_Name確認,如下圖所示:

 

測試代碼

  RS232的測試代碼和LED類似,有open/write/read/close接口實現,不過和LED不同,RS232作為通用串口,需要上層傳遞波特率,數據位,停止位,奇偶檢驗位,這是通關set_attr修改,測試代碼如下:

  1 /*
  2  * File      : uart_proto.c
  3  * This file is uart protocolw work
  4  * COPYRIGHT (C) 2020, zc
  5  *
  6  * Change Logs:
  7  * Date           Author       Notes
  8  * 2020-5-4      zc           the first version
  9  */
 10 
 11 /**
 12  * @addtogroup IMX6ULL
 13  */
 14 /*@{*/
 15 #include <stdio.h>
 16 #include <string.h>
 17 #include <unistd.h>
 18 #include <fcntl.h>
 19 #include <termios.h>
 20 
 21 /**************************************************************************
 22 * Local Macro Definition
 23 ***************************************************************************/
 24 /*協議數據格式*/
 25 #define __DEBUG                         0
 26 
 27 /*調試接口*/
 28 #if __DEBUG     == 1
 29 #define USR_DEBUG               printf
 30 #else
 31 void USR_DEBUG(char *format, ...){
 32 }
 33 #endif
 34 
 35 #define FRAME_HEAD_SIZE         5
 36 
 37 #define PROTO_REQ_HEAD          0x5A    /*協議數據頭*/
 38 #define PROTO_ID                0x01    /*設備ID*/
 39 
 40 /*設備操作指令*/
 41 #define TYPE_CMD                0x01
 42 #define TYPE_DATA                       0x02
 43 #define TYPE_RST                0x03
 44 //other reserved            0x04~0xff
 45 
 46 #define UART_DEV_LED        0x00
 47 #define UART_DEV_OTHERUART  0x01 //目前使用printf打印到系統遠程管理口
 48 #define BUFFER_SIZE             1200
 49 
 50 /*返回命令狀態*/
 51 #define RT_OK               0
 52 #define RT_FAIL             1
 53 #define RT_EMPTY            2
 54 
 55 /**************************************************************************
 56 * Local Type Definition
 57 ***************************************************************************/
 58 typedef signed char int8_t;
 59 typedef signed short int16_t;
 60 typedef signed int int32_t;
 61 typedef unsigned char uint8_t;
 62 typedef unsigned short uint16_t;
 63 typedef unsigned int uint32_t;
 64 
 65 /*協議包結構*/
 66 #pragma pack(push, 1)
 67 struct req_frame
 68 {
 69         uint8_t head;
 70         uint8_t id;
 71         uint8_t type;
 72         uint16_t length;
 73 };
 74 #pragma pack(pop)
 75 struct extra_frame
 76 {
 77         uint8_t *rx_ptr;
 78         uint8_t *tx_ptr;
 79         uint8_t *rx_data_ptr;
 80         uint16_t rx_size;
 81         uint16_t tx_size;
 82         uint16_t crc;
 83 };
 84 
 85 /**************************************************************************
 86 * Local static Variable Declaration
 87 ***************************************************************************/
 88 static uint8_t  rx_buffer[BUFFER_SIZE];
 89 static uint8_t  tx_buffer[BUFFER_SIZE];
 90 static const char DeviceList[][20] = {
 91         "/dev/ttymxc2",
 92 };
 93 struct req_frame *frame_ptr;
 94 struct extra_frame proto_info = {
 95         .rx_ptr = rx_buffer,
 96         .tx_ptr = tx_buffer,
 97         .rx_data_ptr =  &rx_buffer[FRAME_HEAD_SIZE],
 98         .rx_size = 0,
 99         .tx_size = 0,
100         .crc = 0,
101 };
102 
103 /**************************************************************************
104 * Global Variable Declaration
105 ***************************************************************************/
106 
107 /**************************************************************************
108 * Function
109 ***************************************************************************/
110 int ReceiveCheckData(int);
111 void protocol_process(int);
112 static int set_opt(int, int, int, char, int);
113 static uint16_t  CrcCalculate(char *, int);
114 
115 /**
116  * uart執行入口函數
117  * 
118  * @param argc
119  * @param argv
120  *  
121  * @return the error code, 0 on initialization successfully.
122  */
123 int main(int argc, char* argv[])
124 {
125         const char *pDevice = DeviceList[0];
126         int result = 0;
127         int com_fd;
128 
129         result = daemon(1, 1);
130         if(result < 0){
131                 perror("daemon");
132                 return result;
133         }
134 
135         if(argc > 2){
136                 pDevice = argv[1];
137         }
138 
139         if((com_fd = open(pDevice, O_RDWR|O_NOCTTY|O_NDELAY))<0){
140                 USR_DEBUG("open %s is failed\n", pDevice);
141                 return ;
142         }
143         else{
144                 set_opt(com_fd, 115200, 8, 'N', 1);
145                 USR_DEBUG("open %s success!\t\n", pDevice);
146         }
147 
148         USR_DEBUG("Uart Main Task Start\n");
149         write(com_fd, "Uart Start OK!\n", strlen("Uart Start OK!\n"));
150         protocol_process(com_fd);
151 
152         return result;
153 }
154 
155 /**
156  * 協議執行主執行流程
157  * 
158  * @param fd
159  *  
160  * @return NULL
161  */
162 void protocol_process(int fd)
163 {
164         int flag;
165 
166         frame_ptr = (struct req_frame *)rx_buffer;
167 
168         for(;;){
169                 flag = ReceiveCheckData(fd);
170                 if(flag == RT_OK){
171                         printf("data:%s, len:%d\n", proto_info.rx_ptr, proto_info.rx_size);
172                         write(fd, proto_info.rx_ptr, proto_info.rx_size);
173                         proto_info.rx_size = 0;
174                 }
175         }
176 }
177 
178 /**
179  * 協議執行主執行流程
180  * 
181  * @param fd
182  *  
183  * @return NULL
184  */
185 int ReceiveCheckData(int fd)
186 {
187     int nread;
188     int nLen;
189     int CrcRecv, CrcCacl;
190 
191     nread = read(fd, &rx_buffer[proto_info.rx_size], (BUFFER_SIZE-proto_info.rx_size));
192     if(nread > 0)
193     {
194        proto_info.rx_size += nread;
195 
196            /*接收到頭不符合預期*/
197        if(frame_ptr->head != PROTO_REQ_HEAD) {
198             USR_DEBUG("No Valid Head\n");
199             proto_info.rx_size = 0;
200             return RT_FAIL;
201        }
202        else if(proto_info.rx_size > 3){
203                     /*設備ID檢測*/
204             if(frame_ptr->id != PROTO_ID && frame_ptr->id != 0xff)
205             {
206                 proto_info.rx_size = 0;
207                 USR_DEBUG("Valid ID\n");
208                 return RT_FAIL;
209             }
210                         /*crc檢測*/
211             nLen = frame_ptr->length+7;
212             if(proto_info.rx_size >= nLen)
213             {
214                 CrcRecv = (rx_buffer[nLen-2]<<8) + rx_buffer[nLen-1];
215                 CrcCacl = CrcCalculate(&rx_buffer[1], nLen-3);
216                 if(CrcRecv == CrcCacl){
217                     USR_DEBUG("CRC Check OK!\n");
218                     return RT_OK;
219                 }
220                 else{
221                         proto_info.rx_size = 0;
222                     USR_DEBUG("CRC Check ERROR!\n");
223                     return RT_FAIL;
224                 }
225             }
226        }
227     }
228     return RT_EMPTY;
229 }
230 
231 /**
232  * CRC16計算實現
233  * 
234  * @param ptr
235  * @param len
236  *  
237  * @return NULL
238  */
239 static uint16_t CrcCalculate(char *ptr, int len)
240 {
241     return 0xffff;
242 }
243 
244 /**
245  * 設置uart的信息
246  * 
247  * @param ptr
248  * @param len
249  *  
250  * @return NULL
251  */
252 static int set_opt(int fd, int nSpeed, int nBits, char nEvent, int nStop)
253 {
254         struct termios newtio;
255         struct termios oldtio;
256 
257         if  ( tcgetattr( fd,&oldtio)  !=  0) {
258                 perror("SetupSerial 1");
259                 return -1;
260         }
261         bzero( &newtio, sizeof( newtio ) );
262         newtio.c_cflag  |=  CLOCAL | CREAD;
263         newtio.c_cflag &= ~CSIZE;
264 
265         switch( nBits )
266         {
267                 case 7:
268                         newtio.c_cflag |= CS7;
269                         break;
270                 case 8:
271                         newtio.c_cflag |= CS8;
272                         break;
273                 default:
274                         break;
275         }
276 
277         switch( nEvent )
278         {
279         case 'O':
280                 newtio.c_cflag |= PARENB;
281                 newtio.c_cflag |= PARODD;
282                 newtio.c_iflag |= (INPCK | ISTRIP);
283                 break;
284         case 'E':
285                 newtio.c_iflag |= (INPCK | ISTRIP);
286                 newtio.c_cflag |= PARENB;
287                 newtio.c_cflag &= ~PARODD;
288                 break;
289         case 'N':
290                 newtio.c_cflag &= ~PARENB;
291                 break;
292         }
293 
294         switch( nSpeed )
295         {
296         case 2400:
297                 cfsetispeed(&newtio, B2400);
298                 cfsetospeed(&newtio, B2400);
299                 break;
300         case 4800:
301                 cfsetispeed(&newtio, B4800);
302                 cfsetospeed(&newtio, B4800);
303                 break;
304         case 9600:
305                 cfsetispeed(&newtio, B9600);
306                 cfsetospeed(&newtio, B9600);
307                 break;
308         case 115200:
309                 cfsetispeed(&newtio, B115200);
310                 cfsetospeed(&newtio, B115200);
311                 break;
312         case 460800:
313                 cfsetispeed(&newtio, B460800);
314                 cfsetospeed(&newtio, B460800);
315                 break;
316         case 921600:
317                 printf("B921600\n");
318                 cfsetispeed(&newtio, B921600);
319                 cfsetospeed(&newtio, B921600);
320                 break;
321         default:
322                 cfsetispeed(&newtio, B9600);
323                 cfsetospeed(&newtio, B9600);
324                 break;
325         }
326         if( nStop == 1 )
327                 newtio.c_cflag &=  ~CSTOPB;
328         else if ( nStop == 2 )
329         newtio.c_cflag |=  CSTOPB;
330         newtio.c_cc[VTIME]  = 0;
331         newtio.c_cc[VMIN] = 0;
332         tcflush(fd,TCIFLUSH);
333         if((tcsetattr(fd,TCSANOW,&newtio))!=0)
334         {
335                 perror("com set error");
336                 return -1;
337         }
338 //      printf("set done!\n\r");
339         return 0;
340 }
View Code

  將編譯好的代碼上傳到嵌入式Linux芯片中並執行,使用串口工具即可測試通訊,如下所示:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM