通過完成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 }
將編譯好的代碼上傳到嵌入式Linux芯片中並執行,使用串口工具即可測試通訊,如下所示: