文章目录

  • 前言
  • 1、ARM芯片是如何使用串口发送/接收数据的
  • 2、 TTY体系中设备节点的差别
    • 做个小实验
  • 3、TTY驱动程序框架
  • 4、在STM32MP157上做串口实验的准备工作
    • 4.1、使能设备树节点
    • 4.2、通过Pinctrl指定引脚
    • 4.3、指定设备别名
    • 4.4、编译dtb
  • 5、串口回环实验
    • 5.1、程序分析
    • 5.2、函数分析
      • 5.2.1、获取和修改终端属性
      • 5.2.2、终端线速(比特率)
  • 5、串口AT指令读写实验
    • 5.1、串口的阻塞和非阻塞模式
    • 5.2、程序分析
  • 总结
  • 附录
    • 终端特殊字符
  • 参考资料

前言

韦东山嵌入式Linux应用开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容

视频教程地址: https://www.bilibili.com/video/BV1kk4y117Tu

1、ARM芯片是如何使用串口发送/接收数据的

发送数据:CPU > 内存 > FIFO > 发送移位器 > UART单位 发送完成后产生中断提醒CPU传输完成
接收数据:UART单位 > 接收移位器 > FIFO > 内存 > CPU 接收完成后产生中断提醒CPU传输完成

▲串口结构图

2、 TTY体系中设备节点的差别

见韦东山老师的文档,这里只做一些粘贴记录

/dev/ttyS0/dev/ttySAC0/dev/tty/dev/tty0/dev/tty1/dev/console之间的差别

▲各种设备节点之间的差别

TTY/Terminal/Console/UART,之间的差别

▲TTY体系中的相关术语

▲ 各类设备节点的差别

做个小实验

[root@100ask:~]# cat /proc/cmdline
root=/dev/mmcblk2p3 rootwait rw console=ttySTM0,115200

通过上面的命令可以看出开发板的console默认是使用ttySTM0设备的,也就是连接计算机的设别

[root@100ask:~]# echo hello > /dev/console
hello

通过上面的命令可以发现串口终端打印出了hello

systemctl stop myir
clear > /dev/tty0
echo hello > /dev/tty0

通过上面的命令可以使液晶屏幕终端上就显示出了hello
是的,液晶屏幕设备是前台终端

echo hello > /dev/tty1

上面的命令也能起到同样的效果,这也就意味着液晶屏终端设备实际上是tty1,但是它被设定为前台终端

3、TTY驱动程序框架

▲TTY驱动程序框架

关于行规程ling discipline
  大多数用户都会在输入时犯错,所以退格键会很有用。这当然可以由应用程序本身来实现,但是根据UNIX设计“哲学”,应用程序应尽可能保持简单。为了方便起见,操作系统提供了一个编辑缓冲区和一些基本的编辑命令(退格,清除单个单词,清除行,重新打印),这些命令在行规范(line discipline)内默认启用。高级应用程序可以通过将行规范设置为原始模式(raw mode)而不是默认的成熟或准则模式(cooked and canonical)来禁用这些功能。大多数交互程序(编辑器,邮件客户端,shell,及所有依赖curses或readline的程序)均以原始模式运行,并自行处理所有的行编辑命令。行规范还包含字符回显和回车换行(译者注:\r\n 和 \n)间自动转换的选项。如果你喜欢,可以把它看作是一个原始的内核级sed(1)。
  另外,内核提供了几种不同的行规范。一次只能将其中一个连接到给定的串行设备。行规范的默认规则称为N_TTY(drivers/char/n_tty.c,如果你想继续探索的话)。其他的规则被用于其他目的,例如管理数据包交换(ppp,IrDA,串行鼠标),但这不在本文的讨论范围之内。

▲TTY架构

▲TTY架构下的函数调用关系

4、在STM32MP157上做串口实验的准备工作

▲STM32MP157的扩展板

▲UART8 IO和单板接口对应关系

4.1、使能设备树节点

  在STM32MP157的内核设备树文件 arch/arm/boot/dts/stm32mp151.dtsi 中,已经设置了uart8节点:

▲arch/arm/boot/dts/stm32mp151.dtsi

  根据status = "disabled"可以判断该节点未被使能,接下来我们需要使能这个节点,但是并不用在这里使能,可以在其他文件中配置和使能,关于dts和dtsi一些设备树基本概念知识可以戳这里学习

4.2、通过Pinctrl指定引脚

  修改arch/arm/boot/dts/stm32mp15xx-100ask.dtsi,如下:

▲stm32mp15xx-100ask.dtsi 添加项

其中&uart8_pins_mx&uart8_sleep_pins_mx可以根据include文件定位到stm32mp157-m4-srm-pinctrl.dtsi

▲stm32mp15xx-100ask.dtsi include选项

文件路径:arch/arm/boot/dts/stm32mp157-m4-srm-pinctrl.dtsi,如下

▲stm32mp157-m4-srm-pinctrl.dtsi

4.3、指定设备别名

  UART8对应的设备节点是哪个?它的驱动程序需要从"别名"里确定编号。
文件路径:arch/arm/boot/dts/stm32mp157c-100ask-512d-v1.dts

▲stm32mp157c-100ask-512d-v1.dts 添加项

对应#include "stm32mp15xx-100ask.dtsi"中的uart8

▲stm32mp157c-100ask-512d-v1.dts include

4.4、编译dtb

▲编译dtb文件

5、串口回环实验

5.1、程序分析

main()

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>/** ./serial_send_recv <dev>*/
int main(int argc, char **argv)
{int fd;int iRet;char c;/* 1. open *//* 2. setup * 115200,8N1* RAW mode* return data immediately*//* 3. write and read */if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 115200, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}printf("Enter a char: ");while (1){scanf("%c", &c);iRet = write(fd, &c, 1);iRet = read(fd, &c, 1);if (iRet == 1)printf("get: %02x %c\n", c, c);elseprintf("can not get data\n");}return 0;
}

int open_port(char *com)

int open_port(char *com)
{int fd;//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);fd = open(com, O_RDWR|O_NOCTTY);if (-1 == fd){return(-1);}if(fcntl(fd, F_SETFL, 0)<0) /* 设置串口为阻塞状态*/{printf("fcntl failed!\n");return -1;}return fd;
}

int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)

/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio,oldtio;//先获取终端的属性再在其基础上进行设置,这是推荐做法if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1");return -1;}bzero( &newtio, sizeof( newtio ) );/**    CLOCAL:忽略调制解调器的状态行(不检查载波信号)             //一般必设置的标志*    CREAD :允许输入被接收*  CSIZE :字符大小掩码(第 5 到第 8 位:CS5、CS6、CS7、CS8)   //用在设置数据位之前,"&= ~"是清除作用*/newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; /**    ICANON  :规范模式(一行接一行)输入*    ECHO    :回显输入字符* ISIG    :启动信号产生字符(INTR、QUIT、SUSP)**    OPOST   :执行输出后续处理*/newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/newtio.c_oflag  &= ~OPOST;   /*Output*/switch( nBits ){case 7://7位数据位newtio.c_cflag |= CS7;break;case 8://8位数据位newtio.c_cflag |= CS8;break;}/** PARENB:启动奇偶校验*   PARODD:使用奇数奇偶校验;否则使用偶数奇偶校验**  INPCK :开启输入奇偶校验检查*   ISTRIP:从输入字符中去掉最高位(bit 8)*/switch( nEvent ){case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'E': newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;case 'N': newtio.c_cflag &= ~PARENB;break;}/**  设定输入输出线速*/switch( nSpeed ){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}/** CSTOPB:每字符使用 2 个停止位;否则只使用 1 个 */if( nStop == 1 )newtio.c_cflag &= ~CSTOPB;else if ( nStop == 2 )newtio.c_cflag |= CSTOPB;//阻塞条件下有效newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: * 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示: *    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*///TCIFLUSH:刷新输入队列tcflush(fd,TCIFLUSH);//修改立刻生效if((tcsetattr(fd,TCSANOW,&newtio))!=0){perror("com set error");return -1;}//printf("set done!\n");return 0;
}

5.2、函数分析

5.2.1、获取和修改终端属性

int tcgetattr (int fd, struct termios *termios_p);
int tcsetattr (int fd, int optional_actions,const struct termios *termios_p);//Both return 0 on success,or -1 on error

termios_p是一个指向结构体termios的指针,用来记录终端的各项属性

struct termios{//结构体 termios 中的前 4 个字段都是位掩码(数据类型 tcflag_t 是合适大小的整数类型),包含有可控制终端驱动程序各方面操作的标志//这些终端标志见《Linux/UNIX系统编程手册》62.5tcflag_t c_iflag;          /* input mode flags */          //包含控制终端输入的标志tcflag_t c_oflag;          /* output mode flags */     //包含控制终端输出的标志tcflag_t c_cflag;          /* control mode flags */        //包含与终端线速的硬件控制相关的标志tcflag_t c_lflag;            /* local mode flags */          //包含控制终端输入的用户界面的标志cc_t c_line;                      /* line discipline */cc_t c_cc[NCCS];           /* control characters */speed_t c_ispeed;       /* input speed */speed_t c_ospeed;      /* output speed */};

  c_line 字段指定了终端的行规程(line discipline)。为了达到对终端模拟器编程的目的,行规程将一直设为 N_TTY,也就是所谓的新规程。这是内核中处理终端的代码中的一个组件,实现了规范模式下的 I/O 处理。行规程的设定同串口编程有关。
  数组 c_cc 包含着终端的特殊字符(中断、挂起等),以及用来控制非规范模式下输入操作的相关字段。数据类型 cc_t 是无符号整型,适合于保存这些值。常整数 NCCS 指定了数组中的元素个数。在附录或者《Linux/Unix系统编程手册》62.4节可以查看终端特殊字符,可以通过这个数组来修改一些行规程中生效的字符比如将终端字符由ctrl+c改为ctrl+L
  c_ispeedc_ospeed 字段在 Linux 上没有使用到(并且也没有在 SUSv3 中规定)。后面会提到Linux是如何保存终端线速的
  当通过 tcsetattr()来修改终端属性时,参数 optional_actions 用来确定何时这些修改将生效。该参数可以被指定为下列值中的一种。
TCSANOW
  修改立刻得到生效
TCSADRAIN
  当所有当前处于排队中的输出已经传送到终端之后,修改得到生效。通常,该标志应该在修改影响终端的输出时才会指定,这样我们就不会影响到已经处于排队中、但还没有显示出来的输出数据。
TCSAFLUSH
  该标志的产生的效果同 TCSADRAIN,但是除此之外,当标志生效时那些仍然等待处理的输入数据都会被丢弃。这个特性很有用,比如,当读取一个密码时,此时我们希望关闭终端回显功能,并防止用户提前输入

▲关闭终端回显功能

  下面展示了一种stty 命令,stty 命令是以命令行的形式来模拟函数 tcgetattr()和 tcsetattr()的功能,允许我们在 shell 上检视和修改终端属性。当我们监视、调试或者取消程序修改的终端属性时,这个工具非常有用。《Linux/UNIX系统编程手册》62.3

▲stty

▲在终端上查询其他tty设备参数

5.2.2、终端线速(比特率)

   不同的终端之间(以及串行线)传输和接收的速率(位数每秒)是不同的。函数 cfgetispeed()cfsetispeed()用来获取和修改输入的线速。函数 cfgetospeed()cfsetospeed()用来获取和修改输出的线速。

speed_t cfgetospeed (const struct termios *termios_p);
speed_t cfgetispeed (const struct termios *termios_p) ;//Both return a line speed from given termios structure
int cfsetispeed (struct termios *termios_p, speed_t speed) ;
int cfsetospeed (struct termios *termios_p, speed_t speed);//Both return 0 on success,or -1 on error

   这里每一个函数用到的 termios 结构体都必须先通过 tcgetattr()来初始化。
speed:数据类型 speed_t 用来保存线速。这里没有直接以数值形式来设置线速,而是采用了一组符号常量(定义在<termios.h>中)。这些常量定义了一系列离散的值。关于这些常量,有一些例子比如 B300、B2400、B9600 以及 B38400,分别各自对应于线速 300、2400、9600以及 38400 位数每秒。
   尽管函数 cfsetispeed()和 cfsetospeed()可以分开指定输入和输出线速,但是在许多终端上这两个速率必须是一样的。此外,Linux 只用一个单独的字段来保存线速(即,假定这两个速率值总是一样的),这表示所有同输入和输出线速率相关的函数访问的都是相同的 termios 结构体字段。

5、串口AT指令读写实验

5.1、串口的阻塞和非阻塞模式

  在使用串口使用AT指令读写时会遇到发送一条指令受到多条的情况,阻塞模式在这个时候可能很难判断什么时候该结束阅读数据,这个时候可以使用串口的非阻塞模式进行多次阅读判断串口缓冲区是否还有数据来进行多次接收。
   阻塞的定义:
   对于read,block指当串口输入缓冲区没有数据的时候,read函数将会阻塞在这里,移植到串口输入缓冲区中有数据可读取,read读到了需要的字节数之后,返回值为读到的字节数;
   对于write,block指当串口输出缓冲区满,或剩下的空间小于将要写入的字节数,则write将阻塞,一直到串口输出缓冲区中剩下的空间大于等于将要写入的字节数,执行写入操作,返回写入的字节数。
   非阻塞的定义:
  对于read,no block指当串口输入缓冲区没有数据的时候,read函数立即返回,返回值为0。
  对于write,no block指当串口输出缓冲区满,或剩下的空间小于将要写入的字节数,则write将进行写操作,写入当前串口输出缓冲区剩下空间允许的字节数,然后返回写入的字节数。

fcntl(fd,F_SETFL,0)                  //阻塞
fcntl(fd,F_SETFL,FNDELAY)   //非阻塞

5.2、程序分析

功能函数:
void hexdump(const unsigned char *buf, const int num):将buf[]中保存的num长度的字符串转换为16进制数

void hexdump(const unsigned char *buf, const int num)
{int i;printf("hexdump:");for(i = 0; i < num; i++){printf("%02X ", buf[i]);if ((i+1)%8 == 0)printf("\n");}printf("\n");return;
}

void AT_cmd_exc(char *str)//转换成AT命令格式:将str[]字符串的尾巴转换为AT命令的尾巴\r\n

void AT_cmd_exc(char *str)//转换成AT命令格式
{str[strlen(str) - 1] = '\r';    //变后缀'\n' -> '\r'str[strlen(str)] = '\n';     //加后缀'\n'
}

程序功能:实现AT指令配置功能,其中read_lora函数是接收模块返回信息函数

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <termios.h>
#include <stdlib.h>#define MAXWRITE 100/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop);
int open_port(char *com);
int read_lora(int fd, char *str_read);
void hexdump(const unsigned char *buf, const int num);
void AT_cmd_exc(char *str);//转换成AT命令格式/** ./serial_send_recv <dev>*/
int main(int argc, char **argv)
{int fd;int iRet;char str_write[100];char str_read[100];/* 1. open *//* 2. setup * 115200,8N1* RAW mode* return data immediately*//* 3. write and read */if (argc != 2){printf("Usage: \n");printf("%s </dev/ttySAC1 or other>\n", argv[0]);return -1;}fd = open_port(argv[1]);if (fd < 0){printf("open %s err!\n", argv[1]);return -1;}iRet = set_opt(fd, 115200, 8, 'N', 1);if (iRet){printf("set port err!\n");return -1;}while (1){bzero(str_write,sizeof(str_write));bzero(str_read,sizeof(str_read));printf("command: ");fgets(str_write,sizeof(str_write),stdin);AT_cmd_exc(str_write);//printf("echo:%s", str_write);//hexdump(str_write,strlen(str_write));iRet = write(fd, str_write, strlen(str_write));if(iRet != strlen(str_write)){printf("command send failed!\n");}/*iRet = 1 :读完一行iRet = 0 :连续 MAXREADCOUNT 次未读取到数据*///iRet = read_lora(fd,str_read);while(read_lora(fd,str_read)){printf("%s", str_read);bzero(str_read,sizeof(str_read));}}return 0;
}void AT_cmd_exc(char *str)//转换成AT命令格式
{str[strlen(str) - 1] = '\r';    //变后缀'\n' -> '\r'str[strlen(str)] = '\n';     //加后缀'\n'
}#define MAXREADCOUNT 10000
int read_lora(int fd, char *str_read)
{int iRet;char c;int i = 0;int count = 0;int count_readfailed = 0;bzero(str_read,sizeof(str_read));while(1){count++;//printf("read count: %d readfailed count: %d ", count, count_readfailed);iRet = read(fd, &c, 1);//printf(" c:%02X iRet: %d\n", c, iRet);if(iRet == 1){count_readfailed = 0;str_read[i++] = c;if(c == '\n')return 1;}else if(iRet == -1){count_readfailed ++;if(count_readfailed >= MAXREADCOUNT)return 0;}}
}void hexdump(const unsigned char *buf, const int num)
{int i;printf("hexdump:");for(i = 0; i < num; i++){printf("%02X ", buf[i]);if ((i+1)%8 == 0)printf("\n");}printf("\n");return;
}int open_port(char *com)
{int fd;//fd = open(com, O_RDWR|O_NOCTTY|O_NDELAY);fd = open(com, O_RDWR|O_NOCTTY);if (-1 == fd){return(-1);}//fcntl(fd, F_SETFL, 0)<0         /* 设置串口为阻塞状态*/if(fcntl(fd, F_SETFL, FNDELAY)<0) /* 设置串口为非阻塞状态*/{printf("fcntl failed!\n");return -1;}return fd;
}/* set_opt(fd,115200,8,'N',1) */
int set_opt(int fd,int nSpeed, int nBits, char nEvent, int nStop)
{struct termios newtio,oldtio;//先获取终端的属性再在其基础上进行设置,这是推荐做法if ( tcgetattr( fd,&oldtio) != 0) { perror("SetupSerial 1");return -1;}bzero( &newtio, sizeof( newtio ) );/**    CLOCAL:忽略调制解调器的状态行(不检查载波信号)             //一般必设置的标志*    CREAD :允许输入被接收*  CSIZE :字符大小掩码(第 5 到第 8 位:CS5、CS6、CS7、CS8)   //用在设置数据位之前,"&= ~"是清除作用*/newtio.c_cflag |= CLOCAL | CREAD; newtio.c_cflag &= ~CSIZE; /**    ICANON  :规范模式(一行接一行)输入*    ECHO    :回显输入字符* ISIG    :启动信号产生字符(INTR、QUIT、SUSP)**    OPOST   :执行输出后续处理*/newtio.c_lflag  &= ~(ICANON | ECHO | ECHOE | ISIG);  /*Input*/newtio.c_oflag  &= ~OPOST;   /*Output*/switch( nBits ){case 7://7位数据位newtio.c_cflag |= CS7;break;case 8://8位数据位newtio.c_cflag |= CS8;break;}/** PARENB:启动奇偶校验*   PARODD:使用奇数奇偶校验;否则使用偶数奇偶校验**  INPCK :开启输入奇偶校验检查*   ISTRIP:从输入字符中去掉最高位(bit 8)*/switch( nEvent ){case 'O':newtio.c_cflag |= PARENB;newtio.c_cflag |= PARODD;newtio.c_iflag |= (INPCK | ISTRIP);break;case 'E': newtio.c_iflag |= (INPCK | ISTRIP);newtio.c_cflag |= PARENB;newtio.c_cflag &= ~PARODD;break;case 'N': newtio.c_cflag &= ~PARENB;break;}/**  设定输入输出线速*/switch( nSpeed ){case 2400:cfsetispeed(&newtio, B2400);cfsetospeed(&newtio, B2400);break;case 4800:cfsetispeed(&newtio, B4800);cfsetospeed(&newtio, B4800);break;case 9600:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;case 115200:cfsetispeed(&newtio, B115200);cfsetospeed(&newtio, B115200);break;default:cfsetispeed(&newtio, B9600);cfsetospeed(&newtio, B9600);break;}/** CSTOPB:每字符使用 2 个停止位;否则只使用 1 个 */if( nStop == 1 )newtio.c_cflag &= ~CSTOPB;else if ( nStop == 2 )newtio.c_cflag |= CSTOPB;//阻塞条件下有效newtio.c_cc[VMIN]  = 1;  /* 读数据时的最小字节数: 没读到这些数据我就不返回! */newtio.c_cc[VTIME] = 0; /* 等待第1个数据的时间: * 比如VMIN设为10表示至少读到10个数据才返回,* 但是没有数据总不能一直等吧? 可以设置VTIME(单位是10秒)* 假设VTIME=1,表示: *    10秒内一个数据都没有的话就返回*    如果10秒内至少读到了1个字节,那就继续等待,完全读到VMIN个数据再返回*///TCIFLUSH:刷新输入队列tcflush(fd,TCIFLUSH);//修改立刻生效if((tcsetattr(fd,TCSANOW,&newtio))!=0){perror("com set error");return -1;}//printf("set done!\n");return 0;
}

▲测试

总结

▲串口应用编程

附录

终端特殊字符


▲c_cc

参考资料

解密TTY
基于Linux的tty架构及UART驱动详解
Linux dts 设备树详解(一) 基础知识
linux下串口的阻塞和非阻塞操作
linux串口编程参数配置详解

【嵌入式Linux】嵌入式Linux应用开发基础知识之串口应用编程相关推荐

  1. 【嵌入式Linux】嵌入式Linux应用开发基础知识之I2C应用编程和SMBus协议及AP3216C应用编程

    文章目录 前言 1.IIC协议和SMBUS协议 1.1.IIC协议 1.1.1.硬件框架 1.1.2.软件框架 1.1.3.读写数据格式 1.1.4.硬件结构--在硬件上是如何实现双向传输 1.2.S ...

  2. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用

    文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...

  3. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架

    文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...

  4. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树

    文章目录 前言 1.驱动的三种编写方法 2.怎么使用设备树写驱动程序 2.1.设备树节点要与platform_driver能匹配 2.2.修改platform_driver的源码 3.实验和调试技巧 ...

  5. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型

    文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...

  6. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之总线设备驱动模型

    文章目录 前言 1.驱动编写的三种方法 1.1.传统写法 1.2.总线驱动模型 1.3.设备树驱动模型 2.Linux实现分离:Bus/Dev/Drv模型 2.1.Bus/Dev/Drv模型 2.2. ...

  7. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离

    文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...

  8. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想

    文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...

  9. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动

    文章目录 前言 1.Hello驱动 1.1.APP打开的文件在内核中如何表示? 1.2.打开字符设备节点时,内核中也有对应的struct file 1.3.如何编写驱动程序? 1.4.驱动程序代码 1 ...

最新文章

  1. Mysql数据类型(二)
  2. matlab破损皮革定位,皮革下料
  3. Linux shell/makefile/gic/python指令速查-inprocess
  4. QT的QColor 类的使用
  5. linux下隐藏输入密码
  6. win11中gpedit.msc找不到文件怎么办 windows11gepdit.msc找不到文件的解决方法
  7. 轻量级网络模型之EfficientNet
  8. 解决ASP.NET页面回车回发的问题
  9. 诺基亚Nokia的PC套件导出短信乱码问题解决(转)
  10. 电容和电感(自总结)
  11. 机器人动力学方程——拉格朗日法
  12. 从零学爬虫:采集房天下二手房信息
  13. UT2012学习笔记
  14. java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11.XToolkit异常解决
  15. IPV6 邻居发现协议(NDP)
  16. 虚拟空间对新闻媒体的真实挑战
  17. Python量化交易学习笔记(14)——均线交叉策略
  18. 渗透测试实用浏览器插件推荐
  19. 企业使用云计算机的好处,三个问题,让你彻底明白企业为什么上云以及企业上云的好处...
  20. 《23种设计模式之原型模式(2种实现)》

热门文章

  1. KVM虚拟机添加磁盘空间
  2. 中移4G模块-ML302-OpenCpu开发-MCP23017输入/输出
  3. ctf 抓捕赵德汉_2017年网络空间安全技术大赛部分writeup
  4. java 线程死锁简单例子_java 多线程死锁详解及简单实例
  5. 一加7pro保存的录音文件在哪一个文件夹?
  6. .NET 环境中使用RabbitMQ 转发 http://www.cnblogs.com/yangecnu/p/4227535.html
  7. Learning Spark中文版--第三章--RDD编程(1)
  8. java如何声明一个数组用来存储随机生成的字母并且保证不重复
  9. PIC18F26K20
  10. Oracle安装-------实例化EM 配置文件时出错问题 ( 转 )