嵌入式Linux学习笔记(四) 设备树和UART驱动开发

摘要:
目录。参考材料。启动硬件配置。设备树的描述和修改测试代码用于完成LED驱动器,我们熟悉驱动器的一般结构框架。然而,在实际开发中,嵌入式Linux与普通SCM之间的最大区别是提供大量代码以满足大多数应用程序需求。例如,在本节中,我们使用的UART驱动程序已集成到内核中。Linux启动后,编译的dtb文件将加载到内核中,以满足内核模块对硬件和外围设备的操作要求。
目录

(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硬件配置

嵌入式Linux学习笔记(四) 设备树和UART驱动开发第1张

 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学习笔记(四) 设备树和UART驱动开发第2张

  然后到linux文件夹下,使用

make dtbs

  编译生成需要的dtb文件如下:

 嵌入式Linux学习笔记(四) 设备树和UART驱动开发第3张

  将生成的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修改,测试代码如下:

嵌入式Linux学习笔记(四) 设备树和UART驱动开发第4张嵌入式Linux学习笔记(四) 设备树和UART驱动开发第5张
  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
", pDevice);
141                 return ;
142         }
143         else{
144                 set_opt(com_fd, 115200, 8, 'N', 1);
145                 USR_DEBUG("open %s success!	
", pDevice);
146         }
147 
148         USR_DEBUG("Uart Main Task Start
");
149         write(com_fd, "Uart Start OK!
", strlen("Uart Start OK!
"));
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
", 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
");
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
");
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!
");
218                     return RT_OK;
219                 }
220                 else{
221                         proto_info.rx_size = 0;
222                     USR_DEBUG("CRC Check ERROR!
");
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
");
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!

");
339         return 0;
340 }
View Code

  将编译好的代码上传到嵌入式Linux芯片中并执行,使用串口工具即可测试通讯,如下所示:

嵌入式Linux学习笔记(四) 设备树和UART驱动开发第6张

免责声明:文章转载自《嵌入式Linux学习笔记(四) 设备树和UART驱动开发》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇android使用POI读写word doc文件phpstorm 终端设置下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Linux访问控制列表(Access Control List,简称ACL)

Linux访问控制列表(Access Control List,简称ACL) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任。 一.ACL概述 ACL:Access Control List,实现灵活的权限管理   除了文件的所有者,所属组和其它人,可以对更多的用户设置权限   CentOS7 默认创建的xfs和ext4文件系统具有AC...

Linux Python3 的一些坑

在使用 python3 过度的过程中总是会出现很多问题,这里慢慢收集记录,如有错误欢迎指正。 安装问题 Lunix 系统一般默认都是 python2.7.5 升级到 Python3.x 版本一般都需要通过编译安装。这里主要记录下编译安装需要依赖的包,我们需要先安装。 yum groupinstall 'Development Tools' yum i...

KVM虚拟机管理——虚拟机创建和操作系统安装

1. 概述2. 交互式安装2.1 图形化-本地安装2.1.1 图形化本地CDROM安装2.2.2 图形化本地镜像安装2.2 命令行-本地安装2.2.1 命令行CDROM安装2.3 图形化-网络安装2.3.1 图形化HTTP镜像2.3.2 图形化FTP镜像2.3.3 图形化NFS镜像2.4 命令行-网络安装2.4.1 命令行HTTP镜像2.4.2 命令...

(转)VC++的Unicode编程

一、什么是Unicode  先从ASCII说起,ASCII是用来表示英文字符的一种编码规范。每个ASCII字符占用1个字节,因此,ASCII编码可以表示的最大字符数是255(00H—FFH)。其实,英文字符并没有那么多,一般只用前128个(00H—7FH,最高位为0),其中包括了控制字符、数字、大小写字母和其它一些符号。而最高位为1的另128个字符(80H...

wParam与lParam的区别

wParam与lParam的区别 lParam 和 wParam 是宏定义,一般在消息函数中带这两个类型的参数,通常用来存储窗口消息的参数。 LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);wParam 通常用来存储小段信息,如,标志lParam...

puTTY与SecureCRT的比较

从网上看到别人对这两个工具的比较:从windows访问linux,除了samba之外,日常操作用得最多的大概就是PuTTY和SecureCRTPutty是免费的,SecureCRT是收费的(当然,有破解版)。 Putty缺省配置就很好看很好用,SecureCRT的缺省配置不是为linux准备的而且很难看。Putty拿来就可以立刻使用,SecureCRT需要...