980 likes | 1.27k Views
第 11 章 应用程序开发. 本章主要内容. 串口应用程序的编写方法。 TCP/IP 协议以及 Socket 的编写方法。 基于 uClinux 音频接口的应用程序的编写 方法。 键盘和 LCD 的应用程序的编写方法。 汉字音乐点播程序的编写实例。. 第十一章 目录. 3 音频设备应用 常用音频文件格式 播放 WAV 文件举例 4 键盘及 LCD 显示应用 LCD 介绍 键盘实现 5 汉字音乐点播应用. 1. 串口应用程序 串口主要函数介绍 串口举例
E N D
本章主要内容 • 串口应用程序的编写方法。 • TCP/IP协议以及Socket的编写方法。 • 基于uClinux音频接口的应用程序的编写 方法。 • 键盘和LCD的应用程序的编写方法。 • 汉字音乐点播程序的编写实例。
第十一章 目录 • 3 音频设备应用 • 常用音频文件格式 • 播放WAV文件举例 • 4 键盘及LCD显示应用 • LCD介绍 • 键盘实现 • 5 汉字音乐点播应用 • 1.串口应用程序 • 串口主要函数介绍 • 串口举例 • 2.网络应用 • TCP/IP网络应用 • Web服务器应用
第十一章 目录 • 3 音频设备应用 • 常用音频文件格式 • 播放WAV文件举例 • 4 键盘及LCD显示应用 • LCD介绍 • 键盘实现 • 3 音频设备应用 • 1.串口应用程序 • 串口主要函数介绍 • 串口举例 • 2.网络应用 • TCP/IP网络应用 • Web服务器应用
本章从一个针对运行在S3C44B0X上的uClinux操作系统进行应用程序的开发入手,给出Windows操作系统平台上使用Hitool for uClinux等工具开发的多种应用程序。
本章主要介绍了: • 串口应用程序的编写方法。 • TCP/IP协议以及Socket的编写方法。 • 基于uClinux音频接口的应用程序的编写 方法。 • 键盘和LCD的应用程序的编写方法。 • 汉字音乐点播程序的编写实例。
11.1 串口应用程序 • S3C44B0X提供2个UART收发器,对它们可以操作在中断方式或DMA方式。它们内置波特率发生器,波特率发生器的时钟源为S3C44B0X的系统使用,所以最高速率可达115.2K bps。二个串口有单独的波特率发生器,接收、发送和控制单元,支持红外方式的传送和接收。 • 同时,在S3C44B0X串口的接收器和发送器中都有16字节的FIFO,FIFO可以有效的降低接收器和发送器对CPU的中断频率,提高发送和接收的效率。 • 串口设备的可配置参数包括波特率,起始位数量,数据位数量,停止位数量和流量控制协议。 • 在Linux操作系统中,设备驱动是以主设备号为主,每个设备都有唯一的主设备号和从设备号。
11.1.1 串行口主要函数介绍 1. 打开串口 在Linux下串口文件是位于/dev下,串口0为/dev/ttyS0,串口1为/dev/ttyS1,O_RDWR是以读写方式打开串口,O_NOCTTY表示该程序不会成为控制终端,避免了当在键盘输入类似ctrl+c的命令后,终止程序的运行。 打开串口是通过使用标准的文件打开函数操作: int fd; fd = open( "/dev/ttyS0", O_RDWR); if (-1 == fd) { perror(" 提示错误!"); }
11.1.1 串行口主要函数介绍 2. 设置串口 最基本的设置串口包括波特率设置,效验位和停止位设置。串口的 设置主要是设置如下struct termios结构体的各成员值: struct termios { unsigned short c_iflag; // 输入模式标志unsigned short c_oflag; // 输出模式标志 unsigned short c_cflag; // 控制模式标志unsigned short c_lflag; // local mode flags unsigned char c_line; // line discipline unsigned char c_cc[NCC]; // control characters };
11.1.1 串行口主要函数介绍 • 通过对c_cflag的赋值,设置波特率,字符大小,使能本地连接,使能串行口驱动读取输入数据。 • 通过设置c_iflag ,控制端口对字符的输入处理过程,IGNPAR符号常量表示忽略奇偶性错误的字节,不对输入数据进行任何校验,ICRNL将回车符映射为换行符。 这里就只考虑常见的一些设置: (1) 波特率设置: 下面是修改波特率的代码: struct termios Opt; tcgetattr(fd, &Opt); cfsetispeed(&Opt,B19200); // 设置为19200Bps cfsetospeed(&Opt,B19200); tcsetattr(fd,TCANOW,&Opt);
(2) 校验位和停止位的设置: ① 无效验8 位 Option.c_cflag &= ~PARENB; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS8; ③ 偶效验(Even) 7 位 Option.c_cflag &= ~PARENB; Option.c_cflag |= ~PARODD; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS7; ② 奇效验(Odd) 7 位 Option.c_cflag |= ~PARENB; Option.c_cflag &= ~PARODD; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= ~CSIZE; Option.c_cflag |= ~CS7; ④ Space效验7 位 Option.c_cflag &= ~PARENB; Option.c_cflag &= ~CSTOPB; Option.c_cflag &= &~CSIZE; Option.c_cflag |= CS8; 11.1.1 串行口主要函数介绍
11.1.1 串行口主要函数介绍 ⑤ 设置停止位 1 位:options.c_cflag &= ~CSTOPB; 2 位:options.c_cflag |= CSTOPB; 需要注意的是: 如果不是开发终端之类的,只是串口传输数据,而不需要串口来处理,那么使用原始模式(Raw Mode)方式来通讯。 设置方式如下: options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);//Input options.c_oflag &= ~OPOST;// Output 3. 读写串口 设置好串口之后,读写串口就很容易,把串口当作文件读写就可以了。 (1) 发送数据 char buffer[1024]; int Length=1024;
11.1.1 串行口主要函数介绍 int nByte; nByte = write(fd, buffer ,Length) (2) 读取串口数据 使用文件操作read函数读取,如果设置为原始模式(Raw Mode)传输数据,那么read函数返回的字符数是实际串口收到的字符数。可以使用操作文件的函数来实现异步读取,如fcntl,或者select等来操作。 char buff[1024]; int Len=1024; int readByte = read(fd, buff, Len); 4. 关闭串口 关闭串口就是关闭文件。 close(fd);
11.1.2 串口举例 假设接收程序readtest.c运行在装有标准Linux的PC机上,发送程序writetest.c运行在目标板S3C44B0X上,两台设备的串口通过交叉线连接在一起。 接收程序readtest.c的源码如下: #include <stdio.h> #include <string.h> #include <malloc.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> #include <errno.h> #include < math.h>
int spfd; int main() { char fname[16],hd[16],*rbuf; int retv,i,ncount=0; struct termios oldtio; int realdata=0; spfd=open("/dev/ttyS1",O_RDWR|O_NOCTTY); if(spfd<0) return -1; } tcgetattr(spfd,&oldtio);//保存串口的当前设置 cfmakeraw(&oldtio); cfsetispeed(&oldtio,B19200); cfsetospeed(&oldtio,B19200); tcsetattr(spfd,TCSANOW,&oldtio);//选择新设置,TCSANOW表示设 置立即生效rbuf=hd; printf("ready for receiving data...\n"); retv=read(spfd,rbuf,1);
if(retv==-1) { perror("read"); reture -1; { while(*rbuf!='\0') { ncount+=1; rbuf++; retv=read(spfd,rbuf,1); printf("the number received is %d\n",retv); if(retv==-1) perror("read"); } for(i=0;i<ncount;i++) { realdata+=(hd[ i ]-48)*pow(10,ncount-i-1); } printf("complete receiving the data %d\n",realdata); close(spfd); return 0; }
11.1.2 串口举例 发送程序writetest.c的源码如下: #include <stdio.h> #include <string.h> #include <malloc.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <termios.h> int spfd; int main(int argc, char *argv[]) { char fname[16],*sbuf; int sfd,retv,i; struct termios oldtio; spfd=open("/dev/ttyS0",O_RDWR|O_NOCTTY); perror("open /dev/ttyS0");
if(spfd<0) return -1; printf("ready for sending data...\n"); tcgetattr(spfd,&oldtio); cfmakeraw(&oldtio); cfsetispeed(&oldtio,B19200); cfsetospeed(&oldtio,B19200); tcsetattr(spfd,TCSANOW,&oldtio); fname[0]='1'; fname[1]='2'; fname[2]='3'; fname[3]='\0'; sbuf=(char *)malloc(4); strncpy(sbuf,fname,4); retv=write(spfd,sbuf,4); if(retv==-1) perror("write"); printf("the number of char sent is %d\n",retv); close(spfd); return 0; }
11.1.2 串口举例 本例程实现: 在发送端发送数字123,在接收端接收并显示接收到的数据。 这里请同学们注意的是: 发送方按字符发送数据,接收方将接收的字符相应的ascii值与字符0所对应的ascii值相减,最终得到实际的十进制数值。
11.2 网络应用11.2.1 TCP/IP网络应用 以太网技术作为当前局域网的主流技术,可以提供从10Mbit/s,100Mbit/s到1000Mbit/s的物理带宽,以及比较好的抗干扰性、比较大的网络半径和比较低系统维护费用;同时在不同速率以太网之间保持比较好的前向兼容性,所以在系统升级时很方便。 图11-1 以太网电路结构图
11.2.1 TCP/IP网络应用 1. 网络基础 (1) TCP/IP协议分层模型 在实际操作中接触到的通常只是网络系统的最高层——应用层的用户界面。实际上要进行网际的数据传送,需要经过如下的步骤: ① 需要发送的数据如E-mail、web页等,通过用户界面由应用程序传送到应用程序的数据发送缓冲区,并设置好与下一层连接的参数等待发送。 ② 数据做好传输前的准备工作,进入传输层。传输层主要负责为两台主机上的应用程序提供端口到端口的通信。 ③ 然后进入网络层的范畴。这里主要处理数据分组在网络中的活动,例如分组的选路。 ④ 当然最终数据还是要靠物理层的电磁波或光导纤维来传输。 ⑤ 在接收的一方是相反的过程,数据从最底层一直到应用层还原为 用户可以识别的信息,这一切都是由上面的协议来规范的。
11.2.1 TCP/IP网络应用 (2) 数据的封装与分用 ① 数据的封装 用户数据从应用层逐级传送到链路层,每经过一层都要被该层的协议进行一定的封装、标识和改造,就是给这个数据增加一些头部信息(或尾部信息)。 数据封装过程如图11-3所示。
11.2.1 TCP/IP网络应用 ② 数据的分用(解包) 在接收端接收这些数据的时候,经过拆分的数据要重新组合,并且去掉各层加上的头部信息,把数据还原。 (3) 客户—服务器模型 目前大多数网络应用程序在编写时都采用客户——服务器模型,假设—端是客户,另一端是服务器,让服务器提供给客户一定的服务内容。 ①并发型交互 在并发型交互模式下,程度的主要运作步骤如下: ◆ 等待一个客户请求的到来; ◆ 生成一个新的进程或者任务来处理这个客户请求,同时这里还可以 接收其他客户的请求。处理结束后,终止这个进程。 ◆ 反馈客户端; ◆ 等待新的客户请求的到来并进行下一次服务,如此循环运作。
11.2.1 TCP/IP网络应用 ②重复型交互 重复型交互摸式下,程序的的主要运作步骤如下: ◆ 等待一个客户请求的到来; ◆ 处理客户的请求,对客户进行服务; ◆ 给客户反馈信息,服务结束; ◆ 等待下一个请求到来,如此循环。
11.2.1 TCP/IP网络应用 2. TCP套接字 Linux系统的套接字是一个通用的网络编程接口。TCP协议就是通过套接字来实现连接的建立的,这里将就这个过程具体化并对其内部的函数进行必要的说明。 (1) 套接字地址结构 在头文件<Linux/socket.h>中定义了以下结构来保持套接字函数调用参数的一致性。 struct sockaddr { unsigned short sa_family;// 地址类型,格式为AF_xxx char sa_data[14];// 14字节的协议地址};
11.2.1 TCP/IP网络应用 其中的sa_family为套接字的协议簇地址类型,TCP/IP的协议对于IPv4地址类型为AF_INET。sa_data中存储具体的协议地址,不同的协议簇有不同的地址格式。但一般编程中并不直接针对此数据结构操作,而是使用另一个与sockaddr等价的数据结构sockaddr_in(在netinet/in.h中定义): struct sockaddr_in { unsigned short int sin_len;/*IPv4地址长度*/ short int sin_family;/*地址类型*/ unsigned short int sin_port;/*存储端口号*/ struct in_addr sin_addr;/*存储IP地址*/ unsigned char sin_zero[8]; /*空字节*/ };
11.2.1 TCP/IP网络应用 在编程中大多数是使用sockaddr_in这个结构来设置获取地址信息。 sin_family指协议族,在TCP套接字编程中只能是AF_INET;sin_port存储端口号(使用网络字节顺序),数据类型是一个16位的无符号整数类型; sin_addr存储IP地址,IP地址使用in_addr这个数据结构: struct in_addr { unsigned long s_addr; }; s_addr按照网络字节顺序存储IP地址;sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。
11.2.1 TCP/IP网络应用 设置地址信息实例(IPv4) struct sockaddr_in mysock; /*设置sockaddr_in的结构体变量 mysock*/ mysock.sin_family=AF_INET; /*TCP地址结构*/ mysock.sin_port=htons(3490); /*short,NBO*/ mysock.sin_addr.s_addr=inet_addr(“166.111.160.10”); /*设置地址为166.111.160.10*/ bzero(&(mysock.sin_zero),8); /*设置sin_zero为8位保留字节*/ 注意:如果mysock.sin_addr.s_addr= INADDR_ANY,则不指定IP地址(用于Server程序)。
11.2.1 TCP/IP网络应用 (2) TCP客户-服务器通信模型 TCP客户-服务器通信过程如图11-4所示。
11.2.1 TCP/IP网络应用 (3) socket主要函数 ① 强制类型转换函数的调用: 将指向于特定协议的套接口地址结构的指针类型-> 指向通用套接口地址结构的指针。 int connect( int, struct sockaddr *, socklen_t) struct sockaddr-in servaddr; connect(sockfd,(sturct sockaddr *) &servaddr, sizeof(servaddr));
11.2.1 TCP/IP网络应用 ② 主机字节序和网络字节序的转换函数: #include <netinet/in.h> unit16_t htons(uint16_t host16bitvalue); unit32_t htons(uint32_t host32bitvalue); unit16_t ntohs(uint16_t net16bitvalue); unit32_t ntohs(uint32_t net32bitvalue); h : host n : network s : short (16 bits) l : long (32 bits)
11.2.1 TCP/IP网络应用 ③int socket(int domain, int type, int protocol) domain:说明我们网络程序所在的主机采用的通讯协族(AF_UNIX和AF_INET等)。 type: 我们网络程序所采用的通讯协议(SOCK_STREAM,SOCK_DGRAM等) SOCK_STREAM表明我们用的是TCP协议,这样会提供按顺序的,可靠,双向,面向连接的比特流。SOCK_DGRAM 表明我们用的是UDP协议,这样只会提供定长的,不可靠,无连接的通信。 protocol:由于我们指定了type, 所以这个地方我们一般只要用0来代替就可以了。 socket为网络通讯做基本的准备。成功时返回文件描述符,失败时返回-1,看errno可知道出错的详细情况。
11.2.1 TCP/IP网络应用 ④int bind(int sockfd, struct sockaddr *my_addr, int addrlen) sockfd:是由socket调用返回的文件描述符。 addrlen:是sockaddr结构的长度。 my_addr:是一个指向sockaddr的指针。 ⑤int listen(int sockfd, int backlog) sockfd:是bind后的文件描述符。 backlog:设置请求排队的最大长度。当有多个客户端程序和服务端相连时, 使用这个表示可以介入的排队长度。 listen函数将bind的文件描述符变为监听套接字,返回的情况和bind一样。 ⑥int accept(int sockfd, struct sockaddr *addr, int *addrlen) sockfd:是listen后的文件描述符。 addr,addrlen是用来给客户端的程序填写的, 服务器端只要传递指针就可以了。bind, listen和accept是服务器端用的函数,accept调用时, 服务器端的程序会一直阻塞到有一个客户程序发出了连接。 accept成功时返回最后的服务器端的文件描述符, 这个时候服务器端可以向该描述符写信息了,失败时返回-1。
11.2.1 TCP/IP网络应用 ⑦int connect(int sockfd, struct sockaddr * serv_addr,int addrlen) sockfd是socket返回的文件描述符。 serv_addr:储存了服务器端的连接信息,其中sin_add是服务端的地址。 addrlen: serv_addr的长度 。 connect函数是客户端用来同服务端连接的。成功时返回0,sockfd是同服务端通讯的文件描述符,失败时返回-1。 ⑧ ssize_t write(int fd, const void *buf, size_t nbytes) write函数将buf中的nbytes字节内容写入文件描述符fd。 成功时返回写的字节数, 失败时返回-1。并设置errno变量,在网络程序中,当我们向套接字文件描述符写时有两种可能。 write的返回值大于0,表示写了部分或者是全部的数据。 返回的值小于0,此时出现了错误.我们要根据错误类型来处理。
11.2.1 TCP/IP网络应用 ⑨ ssize_t read(int fd, void *buf, size_t nbyte) read函数是从fd中读取内容。当读成功时,read返回实际所读的字节数,如果返回的值是0 表示已经读到文件的结束了,小于0表示出现了错误。 ⑩recv和send函数提供了和read和write差不多的功能,不过提供了第四个参数来控制读写操作。 int recv(int sockfd, void *buf,int len, int flags) int send(int sockfd, void *buf, int len,int flags) 前面的三个参数和read, write一样,第四个参数可以是0或者是以下的组合: ◆MSG_DONTROUTE:不查找路由表 ◆ MSG_OOB:接受或者发送带外数据 ◆MSG_PEEK:查看数据,并不从系统缓冲区移走数据 ◆MSG_WAITALL :等待所有数据
11.2.1 TCP/IP网络应用 3.举例 我们将使用TCP协议提供的服务,组成一个简单的重复型的网络时间服务器。在一台EV44B0II系统中启动服务程序并指定服务端口。在另外一台EV44B0II系统中启动客户端程序并指定服务器IP地址和服务端口。服务器将接收该服务,并返回服务器本地的系统时间。 本程序使用TCP协议,可以工作在服务器或客户端状态。使用的默认端口号为9988。 程序流程图如图11-5所示:
11.2.1 TCP/IP网络应用 附程序清单: /*TCP/IP nettime service*/ #include <sys/param.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <sys/time.h> #include <sys/file.h> #include <netinet/in.h> #include <netinet/ip.h> #include <netinet/tcp.h> #include <arpa/inet.h> #include <stdio.h>
#include <signal.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <errno.h> #include <netdb.h> #include <pwd.h> #include <stdarg.h> extern char *optarg; /* getopt */ #define COM_SERVER 1 #define COM_CLIENT 2 int ComStatus; #define PORT_NUMBER 0x1000 short ComPort ; int main(int argc,char * argv[ ]) { int fd_listen,fd_client,fd_service; char server_ip[64];
int port; struct sockaddr_in sn = { AF_INET }; int sa_len; char * buffer; int start,packet_len,c,counter; int debug; ComStatus = COM_SERVER; ComPort = PORT_NUMBER; while((c = getopt(argc,argv,"sc:o:")) != -1) { switch(c) { case 'c':/* get server ip address */ memcpy(server_ip,optarg,(strlen(optarg)+1)); ComStatus = COM_CLIENT; break; case 's': /* open debug flag */ ComStatus = COM_SERVER;
break; case 'o': ComPort = atoi(optarg); break; default: /* print usage */ fprintf(stderr, "Usage: %s [ [-c <server ip> ] | -s ] [-p <port>]\n", argv[0]); exit(1); } } /* setup address and port */ sn.sin_port = __constant_htons(ComPort); sn.sin_addr.s_addr = 0; if(argc < 2) { fprintf(stderr,"\n argv too less \n"); exit(1); }
/* alloc mem for data buffer */ packet_len = 256; buffer = malloc(packet_len); if(buffer < 0) { fprintf(stderr,"\n malloc buffer error \n"); exit(1); } if(ComStatus == COM_SERVER) {/* server process */ if((fd_listen = socket(AF_INET,SOCK_STREAM,0)) < 0) { fprintf(stderr,"\ncan not open server socket ,exit\n"); exit(1); } if(bind(fd_listen,(struct sockaddr *)&sn, sizeof(sn)) < 0)
{ fprintf(stderr,"\ncan not bin server,socket,exit\n"); close(fd_listen); exit(1); } if (listen(fd_listen, 1) < 0) { fprintf(stderr,"listen failed,exit"); close(fd_listen); exit(1); } sa_len = sizeof(sn); { printf("\nget service request from %s\n",inet_ntoa(sn.sin_addr)); } start = time(0); *(int *)buffer = start; while(1)
{ /* loop service*/ fd_service = accept(fd_listen, (struct sockaddr *)&sn, &sa_len); if (fd_service < 0) { perror("accept failed"); exit(1); } else if(write(fd_service,buffer,packet_len) < 0) { perror("server write"); close(fd_listen); close(fd_service); exit(1); } printf("\ncurrent time %d s\n",start); close(fd_service);
} close(fd_listen); } else { /* client process */ if((fd_client = socket(AF_INET,SOCK_STREAM,0)) < 0) { perror("client socket"); exit(1); } sn.sin_addr.s_addr = inet_addr(server_ip); sa_len = sizeof(sn); if(connect(fd_client,(struct sockaddr *)&sn,sa_len) < 0) { perror("client connect"); close(fd_client); exit(1); }
if((counter = read(fd_client,buffer,packet_len)) <= 0) { perror("receive failed"); close(fd_client); exit(1); } start = *(int *)(buffer); printf("\nnet server time %d s\n",start); close(fd_client); } free(buffer); exit(0); }
11.2.1 TCP/IP网络应用 • 首先配置开发板IP地址(ifconfig eth0 10.10.16.220 netmask255.255.255.0),配置Hitools的调试协议为MDB,启动主机的Target Server程序运行,选择Hitools的load image then fork task窗口的Command Line中填写参数(–s –o 8888),然后带参下载运行。 • 其次启动客户端程序,在超级终端中,进入目录/var/tmp ,入 ./nettime–c 10.10.16.220 –o 8888回车。 • 超级终端显示客户端程序输出了服务器端的以秒为单位的时间:“net server time 333s”。
11.2.2 web服务器应用 1.Web服务器的原理 • 从原理上来说,Web服务器与其他在socket端口进行监听的应用程序差别不大,都是监听并接收用户请求,遵循HTTP协议,根据请求内容和类型,使用串行和并行方式提供相应的服务。 • 使用Web服务的好处是:在提供服务时,客户端的软件是标准的,不需要考虑客户端软件的开发问题,使用者容易上手;服务器端的开发,也是有很多现成的资源可以利用。