介绍Linux下时间处理的相关操作(RTC、延时、闹钟、转换)

一、系统时间设置这篇文章主要介绍Linux下时间处理的相关函数与操作。比如: 系统时间设置,读取、RTC时间设置,读取、时间单位转换、延时函数、闹钟信号等等。Linux下存在两种时间: 1. 系统时间,2. RTC时间系统时间是每次操作系统启动之后,从RTC驱动里读取进行设置的,一般只会在系统上电启动的时候自动(启动脚本)同步一次,后续用户也可以通过特定的命令再次同步;在系统界面上看到的时间就是系统时间;系统时间每次系统关机之后就会丢失,需要每次上电从RTC驱动里获取。系统时间设置的方法如下:需要有管理员权限[wbyq@wbyq linux_c]$ date -s "2020-10-12 9:28:20" date: 无法设置日期: 不允许的操作 2020年 10月 12日 星期一 09:28:20 CST [wbyq@wbyq linux_c]$ sudo date -s "2020-10-12 9:28:20" [sudo] password for wbyq: 2020年 10月 12日 星期一 09:28:20 CST [wbyq@wbyq linux_c]$ RTC时间掉电不会停止运行,电源是后备电源单独供给的;可以一直运行,方便给系统提供准确的时间。RTC时间读取与设置方法:需要有管理员权限hwclock -r 显示RTC时间 (读取RTC时间显示) hwclock -w 设置RTC时间 (将系统时间传递给RTC驱动,设置RTC的驱动时间) hwclock -s 设置系统时间(将RTC时间读取出来设置给系统时间) 也可以通过代码实现:#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/rtc.h> RTC_SET_TIME RTC_RD_TIME struct rtc_time time; int main(int argc,char **argv) if(argc!=2) printf("./app /dev/rtcX\n"); return 0; //1.打开设备文件 int fd=open(argv[1],2); if(fd<2) printf("%s 设备文件打开失败.\n",argv[1]); return 0; //2.获取RTC驱动的时间 ioctl(fd,RTC_RD_TIME,&time); printf("应用层读取的时间: %d-%d-%d %d:%d:%d\n", time.tm_year+1900, time.tm_mon+1, time.tm_mday, time.tm_hour, time.tm_min, time.tm_sec); //3.设置RTC驱动的时间 time.tm_year=2021-1900; time.tm_mon=10-1; time.tm_mday=1; time.tm_hour=11; time.tm_min=10; time.tm_sec=20; ioctl(fd,RTC_SET_TIME,&time); //4. 关闭驱动 close(fd); return 0; 二、时间处理相关函数介绍(time.h)#include <time.h> struct tm { int tm_sec; /* seconds */ int tm_min; /* minutes */ int tm_hour; /* hours */ int tm_mday; /* day of the month */ int tm_mon; /* month */ int tm_year; /* year */ int tm_wday; /* day of the week */ int tm_yday; /* day in the year */ int tm_isdst; /* daylight saving time */ char *asctime(const struct tm *tm); //内部有一个全局空间存放转换的时间 char *asctime_r(const struct tm *tm, char *buf); //用户可以指定自己的空间 函数功能: 将tm时间结构体里的时间转为字符串格式返回(指针返回). char *ctime(const time_t *timep); char *ctime_r(const time_t *timep, char *buf); 函数功能: 将秒单位的时间转为字符串格式返回. struct tm *gmtime(const time_t *timep); struct tm *gmtime_r(const time_t *timep, struct tm *result); 函数功能: 将秒单位的时间转为格林威治时间返回---使用tm结构体。 struct tm *localtime(const time_t *timep); struct tm *localtime_r(const time_t *timep, struct tm *result); 函数功能: 将秒单位的时间转为本地时间返回.---使用tm结构体 time_t mktime(struct tm *tm); 函数功能: 将tm结构体时间转为秒单位返回. time_t time(time_t *t); 函数功能:如果形参填NULL就表示获取当期系统的秒单位时间. size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); 函数功能: 将tm结构体的时间按照指定的格式转成字符串返回. const char *format 格式有以下格式可以填: %H 小时(以 00-23 来表示) %M 分钟(以 00-59 来表示) %S 秒(以本地的惯用法来表示) %Y 年份(以四位数来表示) %m 月份(以 01-12 来表示) %d 日期(以 01-31 来表示)。 时间获取与转换示例:#include <stdio.h> #include <stdlib.h> #include <time.h> int main(int argc,char **argv) /*1.获取本地的秒单位时间*/ time_t sec_time=time(NULL); printf("当前系统的总秒数:%d\n",sec_time); /*2. 将秒单位时间转为字符串返回*/ char time_buff[100]; ctime_r(&sec_time,time_buff); printf("字符串格式时间(系统默认):%s\n",time_buff); /*3. 将秒单位时间转为tm结构体返回*/ struct tm tm_time; gmtime_r(&sec_time,&tm_time); printf("国际时间: %d-%d-%d %d:%d:%d\n",tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); localtime_r(&sec_time,&tm_time); printf("本地时间: %d-%d-%d %d:%d:%d\n",tm_time.tm_year+1900, tm_time.tm_mon+1, tm_time.tm_mday, tm_time.tm_hour, tm_time.tm_min, tm_time.tm_sec); /*4. 将tm结构体时间转为秒单位返回.*/ printf("总秒数:%d\n",mktime(&tm_time)); /*5. 将tm结构体时间格式按照指定格式转为字符串*/ strftime(time_buff,sizeof(time_buff),"%Y_%m_%d_%H_%M_%S.mp4",&tm_time); printf("time_buff=%s\n",time_buff); return 0; 三、常用的一些延时函数#include <unistd.h> unsigned int sleep(unsigned int seconds); 函数功能: 秒单位的延时函数. int usleep(useconds_t usec); 函数功能: 微秒单位的延时函数. #include <time.h> int nanosleep(const struct timespec *req, struct timespec *rem); 函数功能: 秒+纳秒的延时函数. struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ 以上的函数都是可中断的延时函数。 比如: 延时10秒,有可能10秒钟还没有到达,它可以被其他信号终止. 示例代码:#include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> int main(int argc,char **argv) printf("1234\n"); //sleep(5); //usleep(1000*1000); struct timespec req={5,1000}; //将要延时的时间 struct timespec rem; //保存是延时结束剩余的时间 nanosleep(&req,&rem); printf("5678\n"); return 0; 四、系统定时器信号: 闹钟信号函数原型介绍:#include <unistd.h> unsigned int alarm(unsigned int seconds); 闹钟超时之后会产生SIGALRM闹钟信号。 #include <signal.h> typedef void (*sighandler_t)(int); sighandler_t signal(int signum, sighandler_t handler); 函数功能: 捕获进程收到的信号. 函数参数: int signum 要捕获的信号 sighandler_t handler 捕获信号之后调用的处理函数 示例代码:例子代码: #include <stdio.h> #include <stdlib.h> #include <time.h> #include <unistd.h> #include <signal.h> void sighandler_func(int sig) printf("闹钟时间到达.\n"); //定义一个闹钟 alarm(1); //重复定时 int main(int argc,char **argv) //声明要捕获的信号 signal(SIGALRM,sighandler_func); //定义一个闹钟 alarm(1); while(1) return 0; 运行效果:[wbyq@wbyq linux_c]$ gcc app.c [wbyq@wbyq linux_c]$ ./a.out 闹钟时间到达. 闹钟时间到达. 闹钟时间到达. 闹钟时间到达. 闹钟时间到达.

4.5 51单片机-DS18B20温度传感器

4.5 DS18B20温度传感器4.5.1 原理图介绍实验板上的DS18B20模块接在单片机的P3.5 IO口上,在插入DS18B20芯片时,圆弧朝上插入,具体效果可以看上面图片。4.5.2 DS18B20温度传感器介绍DS18B20是常用的数字温度传感器,其输出的是数字信号,它的温度检测与数字数据输出全集成于一个芯片之上,从而抗干扰力更强。DS18B20的主要特征如下:(1)、全数字温度转换及输出。(2)、先进的单总线数据通信。(一根线即可采集温度)(3)、最高 12 位分辨率,精度可达土 0.5 摄氏度。(4)、12 位分辨率时的最大工作周期为 750 毫秒。(5)、可选择寄生工作方式。(6)、检测温度范围为–55° C ~+125° C (–67° F ~+257° F)(7)、内置 EEPROM,限温报警功能。(8)、64位光刻 ROM,内置产品序列号,方便多机挂接。(支持一根线操作多个芯片)(9)、多样封装形式,适应不同硬件系统,封装可以看下面的图片。图片里芯片的引脚功能:GND电压地 、DQ单数据总线、VDD电源电压、NC 空引脚4.5.3  DS18B20的工作原理介绍DS18B20的温度检测与数字数据输出全集成于一个芯片之上,从而抗干扰力更强。它的一个工作周期可分为两个部分,温度检测和数据处理。DS18B20内部有三种形态的存储器: (1) ROM只读存储器:用于存放 DS18B20ID 编码,其前 8 位是单线系列编码(DS18B20 的编码是19H),后面 48 位是芯片唯一的序列号,最后8位是以上56的位的 CRC 码(冗余校验),数据在芯片出厂时设置不可由用户更改。DS18B20 共 64 位 ROM(8+48+8)。(2) RAM数据暂存器:用于内部计算和数据存取,数据在掉电后丢失,DS18B20 共 9 个字节 RAM,每个字节为 8 位。第 1、 2 个字节是温度转换后的数据值信息,第 3、 4 个字节是用户 EEPROM(常用于温度报警值储存)的镜像。在上电复位时其值将被刷新。第 5 个字节则是用户第 3 个 EEPROM的镜像。第 6、 7、 8 个字节为计数寄存器,是为了让用户得到更高的温度分辨率而设计的,同样也是内部温度转换、计算的暂存单元。第 9 个字节为前 8 个字节的 CRC 码。(3) EEPROM非易失性记忆体:用于存放长期需要保存的数据。比如: 上下限温度报警值和校验数据,DS18B20共有3个字节的EEPROM,并在 RAM 都存在镜像,以方便用户操作。DS18B20默认工作在12位分辨率模式,转换后得到的12位数据,存储在DS18B20的两个8比特的RAM中(最前面的两个字节),二进制中的前面5位是符号位,如果测得的温度大于0,这5位为0,只要将测到的数值乘于0.0625即可得到实际温度;如果温度小于0,这5位为1,测到的数值需要取反加1再乘于0.0625即可得到实际温度。数据提取也可以使用位运算,读取出来的数据是2个字节一共16位(H和L),最低4位是小数位,剩下的是整数位。如果读取的数据是负数,需要-1再取反即可得到真实数据。例如:int temp=0; temp=DS18B20_ReadTemp(); //读取一次DS18B20采集的温度(返回H+L位) if(temp<0) //如果温度是负数 temp=temp-1; temp=~temp; printf("DS18b20=-%d.%d\r\n",temp>>4,temp&0xF); printf("DS18b20=%d.%d\r\n",temp>>4,temp&0xF); }4.5.4  DS18B20操作ROM/RAM的常用指令介绍1.读取64位ROM编码指令:0x33这个命令允许总线控制器读到DS18B20的64位ROM。只有当总线上只存在一个 DS18B20的时候才可以使用此指令,如果挂接不只一个,当通信时将会发生数据冲突。2.匹配芯片指令:0x55这个指令后面紧跟着由控制器发出的64 位序列号,当总线上有多只 DS18B20 时,只有与控制发出的序列号相同的芯片才可以做出反应,其它芯片将等待下一次复位。这条指令适应单芯片和多芯片挂接。3.跳过ROM编码匹配:0xCC这条指令使芯片不对 ROM 编码做出反应,在单总线的情况之下,为了节省时间则可以选用此指令。如果在多芯片挂接时使用此指令将会出现数据冲突,导致错误出现。4.启动温度转换:0x44收到此指令后芯片将进行一次温度转换,将转换的温度值放入 RAM 的第 1、 2 地址。此后由于芯片忙于温度转换处理,当控制器发一个读时间隙时,总线上输出“0”,当储存工作完成时,总线将输出“1”。在寄生工作方式时必须在发出此指令后立刻超用强上拉并至少保持 500MS,来维持芯片工作。5.从RAM中读数据指令:0xBE此指令将从 RAM中读数据,读地址从地址0开始,一直可以读到地址 9,完成整个 RAM 数据的读出。芯片允许在读过程中用复位信号中止读取,即可以不读后面不需要的字节以减少读取时间。4.5.5 DS18B20时序图(1).  DS18B20复位与应答时序图每一次与DS18B20通信之前都必须进行复位,复位的时间、等待时间、回应时间应严格按时序编程。(2) 写数据时序上面的时序图是向DS18B20写数据的时序图,该图分为两部分,左边部分是发送数据0的时序图,右边部分是发送数据1的时序图。根据时序图提示(右边部分),每次发送数据之前(不管是数据0还是数据1)都需要先将总线拉低至少1us。如果接下来发送的数据是0,那么需要将数据线拉低至少60us的时间,DS18B20对总线的采样时间在15~60us内;在采样时间内,如果总线为低电平,则表示写0,如果总线为高电平,则表示写1。如果接下来发送的数据是1,那么也需要将数据线拉低至少60us的时间,DS18B20对总线的采样时间在15~60us内;在采样时间内,如果总线为低电平,则表示写0,如果总线为高电平,则表示写1。注意: 在通信时,是以一个字节为单位向DS18B20进行传输,字节的读或者写是从低位开始的。(3) 读数据时序上面的时序图是从DS18B20读取数据的时序图,该图分为两部分展示,左边部分是读取数据0时,总线电平变化过程,右边部分是读取数据1时,总线电平的变化过程。根据时序图提示(右边部分),每次读取数据之前(不管是数据0还是数据1),总线都需要由主机拉低至少1us,然后再释放总线(拉高); 随后需要等待15us的时间,才可以读取总线数据。DS18B20会在随后的45us内维持总线的电平,这段时间内读取总线的数据都是有效数据。注意: 再总线拉低1us之后,必须等待15us之后才可以读取总线数据,这样才能保证总线数据是准确的。在通信过程中,DS18B20输出的数据是从低位开始传输的。4.5.6 读取温度的步骤总线上只有单只DS18B20的情景(读取一次DS18B20的温度):1.  向总线发送复位脉冲并检测DS18B20的响应信号(可以确保DS18B20硬件没有问题)2.  发送指令跳过ROM编号检查 (指令0xCC)3.  发送温度转换命令(指令0x44)(DS18B20在转换温度的过程中,总线会一直保持高电平状态,不会响应总线命令)4.  向总线发送复位脉冲并检测DS18B20的响应信号5.  发送指令跳过ROM编号检查 (指令0xCC)6.  发送读取温度的指令(指令 0xBE)7.  接着读取温度数据低8位8.  接着读取温度数据高8位4.5.7 读取DS18B20温度示例代码(单只DS18B20情景)下面代码演示了循环读取DS18B20温度的过程,在主函数里1秒的间隔读取一次温度。在编写DS18B20时序代码时,要注意时间的把控。当前实验板的环境:采用STC90C516RD单片机,晶振是12MHZ,工作在12T模式下,代码中执行一条i++语句大概消耗的时间是12us。 程序中的延时时间,都是通过该时间推算的,如果程序要移植到其他单片机上,要注意时间的问题。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> /*DS18B20硬件接口: P3.5*/ sbit DS18B20_GPIO=P3^7; int DS18B20_ReadTemp(void); 说明: 在12MHZ晶振下,12T模式下,i++消耗的时间差不多是12us 函数名称:u8 DS18B20_Init(void) 函数功能:向DS18B20发送复位脉冲,并检测应答信号 返 回 值:1表示失败,0表示成功 说明: 51单片机IO口默认输出高电平 u8 DS18B20_ResetSignal(void) u8 i=0; //1. 发送复位信号 DS18B20_GPIO=0;//将总线拉低480us i=50; while(i--){} //延时600us ,最少480us i=0; DS18B20_GPIO=1;//然后释放(拉高)总线,如果DS18B20做出反应会将在15us~60us后总线拉低 //2. 等待DS18B20拉低总线 while(DS18B20_GPIO) i++; if(i>10)return 1;//失败 ,大概120us //3. 等待DS18B20释放总线 i=0; while(DS18B20_GPIO==0) //60us~240us i++; if(i>20)return 1;//失败,大概240us return 0;//初始化成功 函数名称:u8 DS18B20_WriteByte(void) 函数功能:向DS18B20写入一个字节的数据 函数形参:写入的字节数据 void DS18B20_WriteByte(u8 byte) u16 i=0,j=0; for(j=0;j<8;j++) DS18B20_GPIO=0;//每写入一位数据之前先把总线拉低1us i++; //+1消耗的时间是12us DS18B20_GPIO=byte&0x01;//然后写入一个数据,从最低位开始 i=6; while(i--){}//持续时间最少60us,这里大概72us DS18B20_GPIO=1;//然后释放总线 byte>>=1;//继续发送 函数名称:u8 DS18B20_ReadByte(void) 函数功能:从DS18B20读取一个字节的数据 返 回 值:读到的数据 u8 DS18B20_ReadByte(void) u8 byte=0; u16 i=0,j=0; for(j=0;j<8;j++) DS18B20_GPIO=0;//先将总线拉低1us i++;//+1消耗的时间是12us DS18B20_GPIO=1;//然后释放总线 i++; i++;//至少等待15us的时间,在读取数据 byte>>=1; //先从低位开始接收数据 if(DS18B20_GPIO)byte|=0x80; i=4; //读取完之后等待48us再接着读取下一个数据 while(i--){} return byte; 函数名称:u16 DS18B20_ReadTemp(void) 函数功能:读取一次DS18B20的温度数据 返 回 值:读取的温度值 注意: 返回值要使用有符号的数据类型,因为温度可以返回负数。 int DS18B20_ReadTemp(void) int temp=0;//存放温度数据 u8 TH,TL; //第一步: 启动温度转换 DS18B20_ResetSignal(); //发送复位脉冲并检测应答信号 DS18B20_WriteByte(0xcc);//跳过ROM操作命令 DS18B20_WriteByte(0x44);//温度转换命令 //第二步: 读取温度 DS18B20_ResetSignal();//发送复位脉冲并检测应答信号 DS18B20_WriteByte(0xcc);//跳过ROM操作命令 DS18B20_WriteByte(0xbe);//发送读取温度命令 TL=DS18B20_ReadByte();//读取温度值共16位,先读低字节 TH=DS18B20_ReadByte();//再读高字节 temp=TH<<8|TL; //合并成16位 return temp; int main() int temp=0; UART_Init(); //初始化串口波特率为4800 while(1) temp=DS18B20_ReadTemp(); if(temp<0) //如果温度是负数 temp=temp-1; temp=~temp; printf("DS18b20=-%d.%d\r\n",temp>>4,temp&0xF); printf("DS18b20=%d.%d\r\n",temp>>4,temp&0xF); DelayMs(1000);

4.3 51单片机-串口通信

4.3 串口通信4.3.1 通信的概念通信一词按照传统的理解就是信息的传输与交换。对于单片机来说,通信则与传感器、存储芯片、外围控制芯片等技术紧密结合,成为整个单片机系统的“神经中枢”;没有通信,单片机所实现的功能仅仅局限于单片机本身,就无法通过其它设备获得有用信息,也无法将自己产生的信息告诉其它设备。如果单片机通信没处理好的话,它和外围器件的合作程度就受到限制,最终整个系统也无法完成强大的功能,由此可见单片机通信技术的重要性。 UART(Universal Asynchronous Receiver/Transmitter,即通用异步收发器)串行通信是单片机最常用的一种通信技术,通常用于单片机和电脑之间以及单片机和单片机之间的通信。4.3.2 串口通信介绍串口通信是按照位(bit)发送和接收,串口可以在使用一根线发送数据的同时用另一根线接收数据;这种通信方式使用的数据线少,在远距离通信中可以节约通信成本,但其传输速度比并行传输低。串口是计算机上一种非常通用的设备通信协议,大多数计算机(不包括笔记本电脑,主要是台式主机)包含两个基于RS-232的串口。串口同时也是仪器仪表设备通用的通信协议。上面图中的串行接口叫做 RS232 接口,由于现在笔记本电脑都不带这种 9 针串口了,所以和单片机通信越来越趋向于使用USB协议虚拟的串口(就是使用USB转串口协议芯片,实现串口与USB协议互转,比如:CH340)。 标准的RS232接口里,2号引脚是接收数据口RXD,3号引脚是发送数据TXD,对于 RS232 标准来说,它的TXD 和 RXD 的电压, -3V~-15V 电压代表是 1, +3~+15V 电压代表是 0。 因此电脑的 9 针 RS232串口是不能和单片机直接连接的,需要用一个电平转换芯片 MAX232 来完成,单片机上的电压是TTL标准,TTL电平信号规定,+5V等价于逻辑“1”,0V等价于逻辑“0”。STC90C51RC/RD+系列单片机串口通信对应的专用管脚是P3.0/RxD和P3.1/TxD,由它们组成的通信接口就叫做串行接口,简称串口。图中, GND 表示单片机系统电源的参考地, TXD 是串行发送引脚, RXD 是串行接收引脚。两个单片机之间要通信,首先电源基准得一样,所以要把两个单片机的 GND 相互连接起来,然后单片机1的TXD引脚接到单片机2 的 RXD 引脚上,即此路为单片机 1 发送而单片机 2 接收的通道,单片机 1 的 RXD 引脚接到单片机 2 的 TXD 引脚上,即此路为单片机 2 发送而单片机 1 接收的通道。这个示意图就体现了两个单片机相互收发信息的过程。当单片机 1 想给单片机 2 发送数据时,比如发送一个 0xE4 这个数据,用二进制形式表示就是 0b11100100,在 UART 通信过程中,是低位先发,高位后发的原则,那么就让 TXD首先拉低电平,持续一段时间,发送一位 0,然后继续拉低,再持续一段时间,又发送了一位 0,然后拉高电平,持续一段时间,发了一位 1……一直到把 8 位二进制数字 0b11100100全部发送完毕。这里就涉及到了一个问题,就是持续的这“一段时间”到底是多久?由此便引入了通信中的一个重要概念——波特率,也叫做比特率。波特率就是发送二进制数据位的速率,习惯上用 baud 表示,即发送一位二进制数据的持续时间=1/baud。在通信之前,单片机 1 和单片机 2 首先都要明确的约定好它们之间的通信波特率,必须保持一致,收发双方才能正常实现通信。约定好速度后,还要考虑第二个问题,数据什么时候是起始,什么时候是结束?不管是提前接收还是延迟接收,数据都会接收错误。在 UART 通信的时候,一个字节是 8 位,规定当没有通信信号发生时,通信线路保持高电平,当要发送数据之前,先发一位 0 表示起始位,然后发送 8 位数据位,数据位是先低后高的顺序,数据位发完后再发一位 1 表示停止位。这样本来要发送一个字节的 8 位数据,而实际上一共发送了 10 位,多出来的两位其中一位起始位,一位停止位。而接收方,原本一直保持的高电平,一旦检测到了一位低平,那就知道了要开始准备接收数据了,接收到 8 位数据位后,然后检测到停止位,再准备下一个数据的接收。下面图片展示了一个完整的串口数据发送接收过程:4.3.3 51单片机的串口寄存器介绍STC90C51RC/RD+系列单片机内部集成有一个功能很强的全双工串行通信口(P3.0/RxD和P3.1/TxD),与传统8051单片机的串口完全兼容。设有2个互相独立的接收、发送缓冲器,可以同时发送和接收数据。串行通信设有4种工作方式,其中两种方式的波特率是可变的,另两种是固定的,波特率由内部定时器/计数器产生。4种工作模式,可通过软件编程对SCON中的SM0、 SM1的设置进行选择。其中模式1、模式2和模式3为异步通信,每个发送和接收的字符都带有1个起始位和1个停止位。发送缓冲器只能写入而不能读出,接收缓冲器只能读出而不能写入,因而两个缓冲器可以共用一个地址码(99H)。两个缓冲器统称串行通信特殊功能寄存器SBUF。串口相关的配置寄存器如下:完成基本串口通信主要使用的寄存器有4个:SCON: 串行控制寄存器 (可位寻址)PCON: 电源控制寄存器 (不可位寻址)IE : 中断允许寄存器 (可位寻址)SBUF: 发送/接收缓冲区(双向的)4.3.3 串行控制寄存器SCON串行控制寄存器SCON用于选择串行通信的工作方式和某些控制功能,其格式如下:TI: 数据发送完成中断请求标志位,由内部硬件自动置位(TI=1),必须用软件复位(TI=0)。RI: 数据接收完成中断请求志位,由内部硬件置位,即RI=1,必须由软件复位,即RI=0。SCON的所有位在复位之后全部为"0"。REN:允许/禁止串行接收控制位。 由软件置位REN,即REN=1为允许串行接收状态,可启动串行接收器RxD,开始接收信息。软件复位REN,即REN=0,则禁止接收。SM0/FE:当PCON寄存器中的SMOD0/PCON.6位为0时,该位和SM1一起指定串行通信的工作方式,如下表所示。SCON寄存器主要配置串口的工作方式,启动串口的接收功能,常用的工作模式是方式1(8位UART)。串口工作在方式1时,波特率是可变的,可变的波特由定时器/计数器1或独立波特率发生器产生。示例:SCON=0x50; //设置为工作方式1,允许串口接收4.3.3 电源控制寄存器PCON电源控制寄存器PCON格式如下:SMOD: 波特率选择位。当SMOD=1,则使串行通信方式1、 2、 3的波特率加倍;复位时SMOD=0。SMOD0: 帧错误检测有效控制位,当SMOD0=0时,与SCON寄存器中的SM0/FE位一起指定串行口的工作方式。复位时SMOD0=0。配置示例:PCON=0x80; //波特率加倍4.3.4 串行口数据缓冲寄存器SBUFSTC90C51RC/RD+系列单片机的串行口缓冲寄存器(SBUF)的地址是99H,实际是2个缓冲器,写SBUF的操作完成待发送数据的加载,读SBUF的操作可获得已接收到的数据。两个操作分别对应两个不同的寄存器,1个是只写寄存器,1个是只读寄存器。示例:u8 Rx_Byte; Rx_Byte = SBUF; //接收到的数据保存到变量中 SBUF = Rx_Byte; //将变量保存的数据发送出去4.3.5 波特率设置4.3.6 配置串口实现数据收发示例(波特率不加倍)下面代码配置串口的波特率为9600,波特率不加倍,当前运行代码的单片机晶振是: 11.059200MHZ。单片机工作在12T模式下(在12T架构下一个机器周期是12个时钟周期,也就是 12/11059200 秒)主函数里1秒钟向串口发送一个字符串,串口开启了接收中断,如果收到数据就原样将数据再发送出去。波特率的配置方法,在STC芯片参考手册的串口章节有示例代码(P199),可以参考修改。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> /*串口初始化函数*/ void UART_Init(void) PCON=0x00; //波特率不加倍 SCON=0x50; //配置串口工作在模式1(8位数据模式) EA=1; //打开总中断 ES=1; //打开接收中断 TMOD&=0x0F; //清零T1的控制位 TMOD|=0x20; //配置T1为模式2 (8位自动重装载) TL1=TH1=256-(11059200/12/32/9600); //计算 T1 重载值 28800 //TH1= TL1=256-(FOSC/12/32/BAUD); //计算公式 FOSC表示晶振频率 BAUD表示波特率 TR1=1; //启动 T1 /*串口接收中断*/ void UART_IRQHandler(void) interrupt 4 u8 Rx_Byte; if(RI) //接收到字节 RI=0;//手动清零接收中断标志位 Rx_Byte=SBUF; //接收到的数据保存到变量中 UART_SendOneByte(Rx_Byte); //再发回给电脑端 /*发送一个字符*/ void UART_SendOneByte(u8 c) SBUF = c; while(TI==0){} TI = 0; /*发送字符串*/ void UART_SendString(u8 *p) while(*p++!='\0') UART_SendOneByte(*p); int main() UART_Init(); while(1) UART_SendString("12345欢迎学习51单片机开发.\r\n"); DelayMs(1000); }4.3.7 配置51单片机的串口支持printf函数下面代码中重写了putchar函数支持了标准的printf函数,因为printf函数底层会调用putchar函数进行字节发送。Keil软件上不需要做任何其他设置。putchar函数原型在stdio.h文件中有原型声明。如果要支持scanf函数,重写getchar函数即可。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> /*串口初始化函数*/ void UART_Init(void) PCON=0x00; //波特率不加倍 SCON=0x50; //配置串口工作在模式1(8位数据模式) //EA=1; //打开总中断 //ES=1; //打开接收中断 TMOD&=0x0F; //清零T1的控制位 TMOD|=0x20; //配置T1为模式2 (8位自动重装载) TL1=TH1=256-(11059200/12/32/9600); //计算 T1 重载值 11956000 //TH1= TL1=256-(FOSC/12/32/BAUD); //计算公式 FOSC表示晶振频率 BAUD表示波特率 TR1=1; //启动 T1 /*发送一个字符*/ void UART_SendOneByte(u8 c) SBUF = c; while(TI==0){} TI = 0; /*重写putchar函数为了支持printf函数*/ char putchar(char c) UART_SendOneByte(c); return c; int main() u8 str[]="我是字符串"; u32 data1=123456; float data2=123.356; int data3=0x12345; UART_Init(); while(1) printf("字符串:%s\r\n",str); printf("data1:%ld\r\n",data1); //这里的u32是typedef unsigned long u32; printf("data2:%f\r\n",data2); printf("十六进制:%#x\r\n",(int)data3); DelayMs(1000); }4.3.8 配置串口实现数据收发(12M晶振、波特率加倍)下面代码配置串口的波特率为4800,单片机晶振的频率为12MHZ。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> int main() u8 key; UART_Init(); while(1) key=Array_Scan(); if(key) UART_SendString("12345欢迎学习51单片机开发.\r\n"); 串口初始化函数 单片机采用了12M的晶振 void UART_Init(void) PCON=0x80; //波特率加倍 SCON=0x50; //配置串口工作在模式1(8位数据模式) EA=1; //打开总中断 ES=1; //打开接收中断 TMOD&=0x0F; //清零T1的控制位 TMOD|=0x20; //配置T1为模式2 (8位自动重装载) TL1=TH1=0xF3; //TH1=TL1=256-(FOSC/12/32/BAUD); //计算公式 FOSC表示晶振频率 BAUD表示波特率 TR1=1; //启动 T1 串口接收中断 void UART_IRQHandler(void) interrupt 4 u8 Rx_Byte; if(RI) //接收到字节 RI=0;//手动清零接收中断标志位 Rx_Byte=SBUF; //接收到的数据保存到变量中 UART_SendOneByte(Rx_Byte); //再发回给电脑端 发送一个字符 void UART_SendOneByte(u8 c) SBUF = c; while(TI==0){} TI = 0; 发送字符串 void UART_SendString(u8 *p) while(*p++!='\0') UART_SendOneByte(*p); 重写putchar函数为了支持printf函数 char putchar(char c) UART_SendOneByte(c); return c;

Linux下基于TCP协议的群聊系统设计(多线程+select)

一、功能介绍这是基于Linux下命令行设计的一个简单的群聊天程序。这个例子可以学习、巩固Linux下网络编程相关知识点练习Linux下socket、TCP编程练习Linux下pthread、线程编程练习Linux下多路IO检测、select函数使用练习C语言链表使用练习线程间同步与互斥、互斥锁mutex的使用群聊程序分为客户端和服务器两个程序服务器端: 运行整个例子要先运行服务器, 服务器主要用于接收客户端的消息,再转发给其他在线的客户端。服务器里采用多线程的形式,每连接上一个客户端就创建一个子线程单独处理;用了一个全局链表存放已经连接上来的客户端,当一个客户端发来消息后,就逐个转发给其他客户端,客户端断开连接下线后,就删除对应的节点;链表添加节点、删除节点采用互斥锁保护。客户端: 客户端相当于一个用户,客户端代码可以同时运行多个,连接到服务器之后,互相发送消息进行聊天。发送的消息采用一个结构体封装,里面包含了 用户名、状态、消息本身。功能总结: 支持好友上线提醒、好友下线提醒、当前在线总人数提示、聊天消息文本转发。好友上线通知、正常聊天效果:好友下线提示:二、select函数功能、参数介绍在linux命令行可以直接man查看select函数的原型、头文件、帮助、例子 相关信息。select函数可以同时监听多个文件描述符的状态,在socket编程里,可以用来监听客户端或者服务器有没有发来消息。Linux下监听文件描述符状态的函数有3个:select、poll、epoll,这3个函数都可以用在socket网络编程里监听客户端、服务器的状态。 这篇文章的例子里使用的是select,后面文章会继续介绍poll、epoll函数的使用例子。select函数原型、参数介绍#include <sys/select.h> #include <sys/time.h> #include <sys/types.h> #include <unistd.h> int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout); 函数功能: 监听指定数量的文件描述符的状态。 函数参数: int nfds : 监听最大的文件描述符+1的值 fd_set *readfds :监听读事件的文件描述符集合,不想监听读事件这里可以填NULL fd_set *writefds :监听写事件的文件描述符集合,不想监听事件这里可以填NULL fd_set *exceptfds :监听其他事件的文件描述符集合,不想监听事件这里可以填NULL struct timeval *timeout : 指定等待的时间。 如果填NULL表示永久等待,直到任意一个文件描述符产生事件再返回。 如果填正常时间,如果在等待的时间内没有事件产生,也会返回。 struct timeval { long tv_sec; /* seconds */ long tv_usec; /* microseconds */ 返回值: 表示产生事件文件描述符数量。 ==0表示没有事件产生。 >0表示事件数量 <0表示错误。 void FD_CLR(int fd, fd_set *set); //清除某个集合里的指定文件描述符 int FD_ISSET(int fd, fd_set *set); //判断指定集合里的指定文件描述符是否产生了某个事件。 为真就表示产生了事件 void FD_SET(int fd, fd_set *set); //将指定的文件描述符添加到指定的集合 void FD_ZERO(fd_set *set); //清空整个集合。 三、聊天程序代码3.1 client.c 客户端代码#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <pthread.h> #include <sys/select.h> #include <sys/time.h> //消息结构体 struct MSG_DATA char type; //消息类型. 0表示有聊天的消息数据 1表示好友上线 2表示好友下线 char name[50]; //好友名称 int number; //在线人数的数量 unsigned char buff[100]; //发送的聊天数据消息 struct MSG_DATA msg_data; //文件接收端 int main(int argc,char **argv) if(argc!=4) printf("./app <IP地址> <端口号> <名称>\n"); return 0; int sockfd; //忽略 SIGPIPE 信号--方式服务器向无效的套接字写数据导致进程退出 signal(SIGPIPE,SIG_IGN); /*1. 创建socket套接字*/ sockfd=socket(AF_INET,SOCK_STREAM,0); /*2. 连接服务器*/ struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_port=htons(atoi(argv[2])); // 端口号0~65535 addr.sin_addr.s_addr=inet_addr(argv[1]); //IP地址 if(connect(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr_in))!=0) printf("客户端:服务器连接失败.\n"); return 0; /*3. 发送消息表示上线*/ msg_data.type=1; strcpy(msg_data.name,argv[3]); write(sockfd,&msg_data,sizeof(struct MSG_DATA)); int cnt; fd_set readfds; while(1) FD_ZERO(&readfds); //清空集合 FD_SET(sockfd,&readfds); //添加要监听的文件描述符---可以多次调用 FD_SET(0,&readfds); //添加要监听的文件描述符---可以多次调用 // 0表示读 1写 2错误 //监听读事件 cnt=select(sockfd+1,&readfds,NULL,NULL,NULL); if(cnt) if(FD_ISSET(sockfd,&readfds)) //判断收到服务器的消息 cnt=read(sockfd,&msg_data,sizeof(struct MSG_DATA)); if(cnt<=0) //判断服务器是否断开了连接 printf("服务器已经退出.\n"); break; else if(cnt>0) if(msg_data.type==0) printf("%s:%s 在线人数:%d\n",msg_data.name,msg_data.buff,msg_data.number); else if(msg_data.type==1) printf("%s 好友上线. 在线人数:%d\n",msg_data.name,msg_data.number); else if(msg_data.type==2) printf("%s 好友下线. 在线人数:%d\n",msg_data.name,msg_data.number); if(FD_ISSET(0,&readfds)) //判断键盘上有输入 gets(msg_data.buff); //读取键盘上的消息 msg_data.type=0; //表示正常消息 strcpy(msg_data.name,argv[3]); //名称 write(sockfd,&msg_data,sizeof(struct MSG_DATA)); close(sockfd); return 0; 3.2 select.c 服务器端代码#include <stdio.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> #include <signal.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <pthread.h> #include <sys/select.h> #include <sys/time.h> pthread_mutex_t mutex; //定义互斥锁 int sockfd; //消息结构体 struct MSG_DATA char type; //消息类型. 0表示有聊天的消息数据 1表示好友上线 2表示好友下线 char name[50]; //好友名称 int number; //在线人数的数量 unsigned char buff[100]; //发送的聊天数据消息 //存放当前服务器连接的客户端套接字 struct CLIENT_FD int fd; struct CLIENT_FD *next; //定义链表头 struct CLIENT_FD *list_head=NULL; struct CLIENT_FD *List_CreateHead(struct CLIENT_FD *list_head); void List_AddNode(struct CLIENT_FD *list_head,int fd); void List_DelNode(struct CLIENT_FD *list_head,int fd); int List_GetNodeCnt(struct CLIENT_FD *list_head); void Server_SendMsgData(struct CLIENT_FD *list_head,struct MSG_DATA *msg_data,int client_fd); 线程工作函数 void *thread_work_func(void *argv) int client_fd=*(int*)argv; free(argv); struct MSG_DATA msg_data; //1. 将新的客户端套接字添加到链表中 List_AddNode(list_head,client_fd); //2. 接收客户端信息 fd_set readfds; int cnt; while(1) FD_ZERO(&readfds); //清空整个集合。 FD_SET(client_fd,&readfds); //添加要监听的描述符 cnt=select(client_fd+1,&readfds,NULL,NULL,NULL); if(cnt>0) //读取客户端发送的消息 cnt=read(client_fd,&msg_data,sizeof(struct MSG_DATA)); if(cnt<=0) //表示当前客户端断开了连接 List_DelNode(list_head,client_fd); //删除节点 msg_data.type=2; //转发消息给其他好友 msg_data.number=List_GetNodeCnt(list_head); //当前在线好友人数 Server_SendMsgData(list_head,&msg_data,client_fd); if(msg_data.type==2)break; close(client_fd); 信号工作函数 void signal_work_func(int sig) //销毁互斥锁 pthread_mutex_destroy(&mutex); close(sockfd); exit(0); //结束进程 int main(int argc,char **argv) if(argc!=2) printf("./app <端口号>\n"); return 0; //初始化互斥锁 pthread_mutex_init(&mutex,NULL); signal(SIGPIPE,SIG_IGN); //忽略 SIGPIPE 信号--防止服务器异常退出 signal(SIGINT,signal_work_func); //创建链表头 list_head=List_CreateHead(list_head); /*1. 创建socket套接字*/ sockfd=socket(AF_INET,SOCK_STREAM,0); //设置端口号的复用功能 int on = 1; setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)); /*2. 绑定端口号与IP地址*/ struct sockaddr_in addr; addr.sin_family=AF_INET; addr.sin_port=htons(atoi(argv[1])); // 端口号0~65535 addr.sin_addr.s_addr=INADDR_ANY; //inet_addr("0.0.0.0"); //IP地址 if(bind(sockfd,(const struct sockaddr *)&addr,sizeof(struct sockaddr))!=0) printf("服务器:端口号绑定失败.\n"); /*3. 设置监听的数量*/ listen(sockfd,20); /*4. 等待客户端连接*/ int *client_fd; struct sockaddr_in client_addr; socklen_t addrlen; pthread_t thread_id; while(1) addrlen=sizeof(struct sockaddr_in); client_fd=malloc(sizeof(int)); *client_fd=accept(sockfd,(struct sockaddr *)&client_addr,&addrlen); if(*client_fd<0) printf("客户端连接失败.\n"); return 0; printf("连接的客户端IP地址:%s\n",inet_ntoa(client_addr.sin_addr)); printf("连接的客户端端口号:%d\n",ntohs(client_addr.sin_port)); /*创建线程*/ if(pthread_create(&thread_id,NULL,thread_work_func,client_fd)) printf("线程创建失败.\n"); break; /*设置线程的分离属性*/ pthread_detach(thread_id); //退出进程 signal_work_func(0); return 0; 函数功能: 创建链表头 struct CLIENT_FD *List_CreateHead(struct CLIENT_FD *list_head) if(list_head==NULL) list_head=malloc(sizeof(struct CLIENT_FD)); list_head->next=NULL; return list_head; 函数功能: 添加节点 void List_AddNode(struct CLIENT_FD *list_head,int fd) struct CLIENT_FD *p=list_head; struct CLIENT_FD *new_p; pthread_mutex_lock(&mutex); while(p->next!=NULL) p=p->next; new_p=malloc(sizeof(struct CLIENT_FD)); new_p->next=NULL; new_p->fd=fd; p->next=new_p; pthread_mutex_unlock(&mutex); 函数功能: 删除节点 void List_DelNode(struct CLIENT_FD *list_head,int fd) struct CLIENT_FD *p=list_head; struct CLIENT_FD *tmp; pthread_mutex_lock(&mutex); while(p->next!=NULL) tmp=p; p=p->next; if(p->fd==fd) //找到了要删除的节点 tmp->next=p->next; free(p); break; pthread_mutex_unlock(&mutex); 函数功能: 获取当前链表中有多少个节点 int List_GetNodeCnt(struct CLIENT_FD *list_head) int cnt=0; struct CLIENT_FD *p=list_head; pthread_mutex_lock(&mutex); while(p->next!=NULL) p=p->next; cnt++; pthread_mutex_unlock(&mutex); return cnt; 函数功能: 转发消息 void Server_SendMsgData(struct CLIENT_FD *list_head,struct MSG_DATA *msg_data,int client_fd) struct CLIENT_FD *p=list_head; pthread_mutex_lock(&mutex); while(p->next!=NULL) p=p->next; if(p->fd!=client_fd) write(p->fd,msg_data,sizeof(struct MSG_DATA)); pthread_mutex_unlock(&mutex);

QTextEdit实现自定义关键字着色(代码块着色)

一、功能介绍在使用QTextEdit 编辑或者显示文本的过程中,经常需要实现关键字、或者指定的一些文本着色,显示高亮颜色,突出显示。 比如: 我们经常编写代码的IDE软件,界面上就可以根据不同的语言、不同的关键字完成各种颜色的高亮,这个功能QT的QTextEdit 完全也可以实现,并且QT官方也给出了例子代码。这篇文章参考官方提供的例子代码思路,继承QSyntaxHighlighter 类,重写highlightBlock函数,设置自己的关键字。设置的着色文本支持正则表达式语句,核心代码示例如下:void Highlighter::highlightBlock(const QString &text) if(word_text.isEmpty())return; QTextCharFormat myClassFormat; myClassFormat.setFontWeight(QFont::Bold); myClassFormat.setForeground(Qt::darkMagenta); //支持正则表达式 QString pattern = word_text; QRegExp expression(pattern); int index = text.indexOf(expression); while (index >= 0) { int length = expression.matchedLength(); setFormat(index, length, myClassFormat); index = text.indexOf(expression, index + length); 二、实现方法2.1 Highlighter.cpp这是CPP源文件,继承QSyntaxHighlighter类。 #include "highlighter.h" //! [0] Highlighter::Highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) void Highlighter::highlightBlock(const QString &text) if(word_text.isEmpty())return; QTextCharFormat myClassFormat; myClassFormat.setFontWeight(QFont::Bold); myClassFormat.setForeground(Qt::darkMagenta); QString pattern = word_text; QRegExp expression(pattern); int index = text.indexOf(expression); while (index >= 0) { int length = expression.matchedLength(); setFormat(index, length, myClassFormat); index = text.indexOf(expression, index + length); 工程: QTextEdit_Highlight_test 日期: 2021-10-30 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 高亮的文本设置 void Highlighter::SetText(QString text) word_text=text; 2.2 Highlighter.h #ifndef HIGHLIGHTER_H #define HIGHLIGHTER_H #include <QSyntaxHighlighter> #include <QTextCharFormat> QT_BEGIN_NAMESPACE class QTextDocument; QT_END_NAMESPACE //! [0] class Highlighter : public QSyntaxHighlighter Q_OBJECT public: Highlighter(QTextDocument *parent = 0); void SetText(QString text); protected: void highlightBlock(const QString &text) Q_DECL_OVERRIDE; QString word_text; //! [0] #endif // HIGHLIGHTER_H 2.3 widget.cpp#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); highlighter = new Highlighter(ui->textEdit->document()); Widget::~Widget() delete ui; 工程: QTextEdit_Highlight_test 日期: 2021-10-30 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 设置高亮的文本 void Widget::on_pushButton_clicked() highlighter->SetText(ui->lineEdit->text()); 2.4 widget.h#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include "highlighter.h" QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); Highlighter *highlighter; private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; #endif // WIDGET_H 2.5 UI设计界面

采用QWebEngineView引擎设计web浏览器

一、介绍QWebEngineView 是QT5.4版本加入的新浏览器引擎,用于编辑、查看web内容。在windows系统下 QWebEngineView支持MSVC编译器编译、不支持mingw编译。使用QWebEngineView时,需要在工程文件里增加webenginewidgets模块的引用,并加上#include <QWebEngineView> 头文件。Header: #include <QWebEngineView> qmake: QT += webenginewidgets Since: Qt 5.4 Inherits: QWidget 下面是来至官方文档翻译:QWebEngineView简单使用的示例代码:QWebEngineView *view = new QWebEngineView(parent); view->load(QUrl("http://qt-project.org/")); view->show(); QWebEngineView类常用的几个接口介绍:公共的函数: 1. 网页上查找文本 void findText(const QString &subString, QWebEnginePage::FindFlags options = ..., const QWebEngineCallback<bool> &resultCallback = ...) 返回当前选定的文本 QString selectedText() const 2. 此属性保存此页面是否包含选定内容。 bool hasSelection() const 3. 返回指向已导航网页的视图历史记录的指针。 QWebEngineHistory *history() const 4. 返回当前网页图标 QIcon icon() const 5. 返回当前网页图标地址 QUrl iconUrl() const 6. 加载新的网页地址 void load(const QUrl &url) 7. 加载新的请求 void load(const QWebEngineHttpRequest &request) 8. 返回指向当前网页的指针。 QWebEnginePage *page() const 9. 返回指向封装指定web操作的QAction的指针。 QAction *pageAction(QWebEnginePage::WebAction action) const 10. 将web视图的内容设置为数据 void setContent(const QByteArray &data, const QString &mimeType = QString(), const QUrl &baseUrl = QUrl()) 11. 设置HTML内容 void setHtml(const QString &html, const QUrl &baseUrl = QUrl()) 12. 设置页: 使网页成为web视图的新网页。 void setPage(QWebEnginePage *page) 13. 设置新的地址 void setUrl(const QUrl &url) QUrl url() const 14. 设置缩放属性 void setZoomFactor(qreal factor) qreal zoomFactor() const 15.返回指向视图或页面特定设置对象的指针。 QWebEngineSettings *settings() const 16. 当前网页标题 QString title() const 17. 触发指定的操作。 void triggerPageAction(QWebEnginePage::WebAction action, bool checked = false) 方便的槽函数: void back() 返回上一步页面-没有就没反应 void forward() 返回下一步页面-没有就没反应 void reload() 重新加载当前网页-刷新网页 void stop() 停止网页加载 可以关联的信号: 1. 图标发生改变 void iconChanged(const QIcon &icon) void iconUrlChanged(const QUrl &url) 2. 加载完成 void loadFinished(bool ok) 3. 加载进度 0~100 void loadProgress(int progress) 4. 该信号在页面的新加载开始时发出。 void loadStarted() 5. 当渲染过程以非零退出状态终止时,将发出此信号。terminationStatus是进程的终止状态,exitCode是进程终止时使用的状态代码。 void renderProcessTerminated(QWebEnginePage::RenderProcessTerminationStatus terminationStatus, int exitCode) 6. 只要选择发生变化,就会发出该信号。 注意:当使用鼠标通过左键单击和拖动选择文本时,将为每个选定的新字符发出信号,而不是释放鼠标左键。 void selectionChanged() 7. 标题改变 void titleChanged(const QString &title) 8. url改变 void urlChanged(const QUrl &url) 二、设计程序、完成网页浏览当前的环境:windows10 64 系统、QT5.12.6 + VS2017运行效果:2.1 新建工程(1) 在创建工程时,选择VS编译器。(2) 创建完毕之后,在pro工程文件里添加webenginewidgets模块。QT += webenginewidgets 2.2 设计UI界面​​2.3 widget.cpp#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); //new 一个QWebEngineView m_webView = new QWebEngineView(this); //添加到布局器 ui->verticalLayout_web->addWidget(m_webView); //关联信号 connect(m_webView, SIGNAL(iconChanged(const QIcon &)), this, SLOT(slot_iconChanged(const QIcon &))); connect(m_webView, SIGNAL(loadProgress(int)), this, SLOT(slot_loadProgress(int))); connect(m_webView, SIGNAL(titleChanged(const QString &)), this, SLOT(slot_titleChanged(const QString &))); ui->progressBar->setMaximum(100); ui->progressBar->setMinimum(0); Widget::~Widget() delete ui; //刷新网页 void Widget::on_pushButton_new_clicked() ui->progressBar->setValue(0); m_webView->reload(); //加载新页面 void Widget::on_pushButton_load_clicked() QString url=ui->lineEdit->text(); if(!url.isEmpty()) ui->progressBar->setValue(0); m_webView->load(QUrl(url)); void Widget::on_pushButton_stop_clicked() m_webView->stop(); //上一页 void Widget::on_pushButton_up_clicked() m_webView->back(); //下一页 void Widget::on_pushButton_dn_clicked() m_webView->forward(); //图标改变 void Widget::slot_iconChanged(const QIcon &icon) this->setWindowIcon(icon); //加载进度 void Widget::slot_loadProgress(int progress) ui->progressBar->setValue(progress); //标题改变 void Widget::slot_titleChanged(const QString &title) this->setWindowTitle(title); 2.4 widget.h#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QWebEngineView> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void slot_iconChanged(const QIcon &icon); void slot_loadProgress(int progress); void slot_titleChanged(const QString &title); void on_pushButton_new_clicked(); void on_pushButton_load_clicked(); void on_pushButton_stop_clicked(); void on_pushButton_up_clicked(); void on_pushButton_dn_clicked(); private: Ui::Widget *ui; QWebEngineView *m_webView; #endif // WIDGET_H 2.5 xxx.proQT += core gui QT += webenginewidgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ widget.cpp HEADERS += \ widget.h FORMS += \ widget.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target

QT5.14.2使用webkit引擎完成网页浏览

一、webkit 框架介绍WebKit是一个跨平台的 Web 浏览器引擎,据说苹果的Safari、谷歌的 Chrome 浏览器都是基于webkit框架来开发的,而且WebKit还支持移动设备和手机,包括 iPhone 和 Android 手机都是使用WebKit做为浏览器的核心。之前QT里直接包含了webkit引擎,但是在5.6之后的版本中就去掉了webkit,替换成 QWebEngineView引擎了;但是QWebEngineView只支持MSVC编译器,不支持MinGW编译器,导致很多使用MinGW的项目里无法使用。 现在在QT5.6之后的版本中,使用浏览器访问网页可以采用IE浏览器的COM插件、或者自己编译安装webkit。自己编译webkit还是挺麻烦的,好在已经有编译好的库可以直接使用,不用自己编译。在GitHub上可以直接下载对应编译器使用的库,下载下来拷贝到QT按照目录下就可以使用。webkit库下载地址: https://github.com/qtwebkit/qtwebkit/releases/tag/qtwebkit-5.212.0-alpha4这个页面上是截止目前最新的webkit的库,MinGW版本是采用MinGW730编译的,支持QT的版本是QT5.14,下面截图里可以看到对应几个编译器使用的库。我当前使用的QT版本是QT5.14.2,使用的MinGW730_32位的编译器,使用的win10 64位系统,对应该下载 qtwebkit-Windows-Windows_7-Mingw73-Windows-Windows_7-X86.7z 这个库,这个库测试使用成功。本来按道理应该使用qtwebkit-Windows-Windows_10-Mingw73-Windows-Windows_10-X86_64.7z 这个库的,但是下载下来使用编译报错,链接不了。如果要下载低版本QT、编译器 对应的库,可以在这个页面里找https://github.com/qtwebkit/qtwebkit/releases可以找到之前的旧版本。二、安装webkit库将下载下来的压缩包解压,然后把解压出来目录里的整个子目录全部拷贝到 QT 编译器的目录下即可。我当前使用的是编译器是MinGW730_32,将webkit的所有文件拷贝过来就行。三、编写代码测试webkit要使用webkit,需要在pro工程文件里引用 webkitwidgets3.1 xxx.pro工程文件QT += core gui webkitwidgets greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ widget.cpp HEADERS += \ widget.h FORMS += \ widget.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target 3.2 widget.cpp 源码#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); view=new QWebView(this); ui->verticalLayout_2->addWidget(view); Widget::~Widget() delete ui; void Widget::on_pushButton_clicked() view->load(QUrl(ui->lineEdit->text())); 3.3 widget.h源码#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QtWebKitWidgets/QWebView> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_pushButton_clicked(); private: Ui::Widget *ui; QWebView *view; #endif // WIDGET_H 3.4 UI设计界面3.5 运行效果

QT调用IE浏览器COM插件完成网页浏览

一、可用的嵌入式浏览器方案QT在5.6之前可以webkit浏览器框架访问网页,在之后就去掉了webkit,加入了QWebEngineView框架,但是QWebEngineView只能支持VS编译器,mingw编译器不支持。在后面的高版本QT里,mingw编译器如果要加载网页可以使用两种方式。(1). 编译webkit源码,使用webkit。 也可以不用自己编译,GitHub上可以下载编译好的库,直接下载使用即可。下载地址: https://github.com/qtwebkit/qtwebkit/releases/tag/qtwebkit-5.212.0-alpha4(2). 使用IE浏览器的COM插件,这个比较简单,也比较方便,就是IE浏览器目前不维护了。本篇文章就介绍如何使用IE的COM插件完成网页浏览。分别都支持VS和MinGW编译器。二、实现方法2.1 加载IE浏览器的COM组件打开UI设计界面,拖入一个axWidget控件,加载IE浏览器插件(Internet Explorer)。右键选择转到槽,弹出菜单,可用选择需要使用的信号。我这里就关联了两个信号,一个标题加载完成,一个是加载进度。void axWidget_TitleChange(const QString &Text); void axWidget_ProgressChange(int Progress, int ProgressMax); 最终实现的效果是,调用百度搜索指定的内容:如果打开网页报错–脚本错误-JS加载错误之类的,需要设置IE浏览器的Intel安全设置,把活动脚本禁用即可。(win10)按下win键,弹出左边的选项栏,找到windows附件,打开IE浏览器。2.2 widget.cpp#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); //导出支持调用的函数接口 QString DOC = ui->axWidget->generateDocumentation(); QFile outFile("com_function.html"); outFile.open(QIODevice ::ReadWrite|QIODevice ::Text); QTextStream TS(&outFile); TS<<DOC<<endl; this->setWindowTitle("单词翻译"); Widget::~Widget() delete ui; 工程: COM_InternetExplorer_Test 日期: 2021-10-29 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 加载完成 void Widget::on_axWidget_TitleChange(const QString &Text) qDebug()<<"Text:"<<Text; 工程: COM_InternetExplorer_Test 日期: 2021-10-29 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 加载进度改变 void Widget::on_axWidget_ProgressChange(int Progress, int ProgressMax) qDebug()<<QString("%1:%2").arg(ProgressMax).arg(Progress); void Widget::on_pushButton_clicked() QString url; url="https://www.baidu.com/s?ie=UTF-8&wd="+ui->lineEdit->text(); QVariantList params ={url,0,"","",""}; ui->axWidget->dynamicCall("Navigate2(QString, QVariant&, QVariant&, QVariant&, QVariant&)", params); 2.3 widget.h#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QAxWidget> #include <QDebug> #include <QFile> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); private slots: void on_axWidget_TitleChange(const QString &Text); void on_axWidget_ProgressChange(int Progress, int ProgressMax); void on_pushButton_clicked(); private: Ui::Widget *ui; #endif // WIDGET_H 2.4 xxx.pro文件QT += core gui QT += axcontainer greaterThan(QT_MAJOR_VERSION, 4): QT += widgets CONFIG += c++11 # The following define makes your compiler emit warnings if you use # any Qt feature that has been marked deprecated (the exact warnings # depend on your compiler). Please consult the documentation of the # deprecated API in order to know how to port your code away from it. DEFINES += QT_DEPRECATED_WARNINGS # You can also make your code fail to compile if it uses deprecated APIs. # In order to do so, uncomment the following line. # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 SOURCES += \ main.cpp \ widget.cpp HEADERS += \ widget.h FORMS += \ widget.ui # Default rules for deployment. qnx: target.path = /tmp/$${TARGET}/bin else: unix:!android: target.path = /opt/$${TARGET}/bin !isEmpty(target.path): INSTALLS += target

4.1 51单片机-定时器使用

4.1 定时器4.1.1 51时钟周期介绍时钟周期:时钟周期T是时序中最小的时间单位,具体计算的方法就是 1/时钟源频率,89C51单片机开发板上常用的晶振是11.0592M,对于这个单片机系统来说,时钟周期=1/11059200 秒。机器周期:是单片机完成一个操作的最短时间。机器周期主要针对汇编语言而言,在汇编语言下程序的每一条语句执行所使用的时间都是机器周期的整数倍,而且语句占用的时间是可以计算出来的,而 C 语言一条语句的时间是不确定的,受到诸多因素的影响。51单片机系列,在其标准架构下一个机器周期是 12 个时钟周期,也就是 12/11059200 秒。现在很多的增强型51单片机,其速度都比较块,有的1个机器周期等于 4 个时钟周期,有的1个机器周期就等于1个时钟周期,也就是说大体上其速度可以达到标准 51 架构的 3 倍或 12倍。定时器和计数器是单片机内部的同一个模块,通过配置 SFR(特殊功能寄存器)可以实现两种不同的功能大多数情况下是使用定时器功能。顾名思义,定时器就是用来进行定时的,定时器内部有一个寄存器,让它开始计数后,这个寄存器的值每经过一个机器周期就会自动加1,可以把机器周期理解为定时器的计数周期。就像钟表,每经过一秒,数字自动加 1,而这个定时器就是每过一个机器周期的时间,也就是 12/11059200 秒,数字自动加 1。4.1.2 定时器功能介绍标准的51单片机内部有T0和T1两个定时器, T 就是 Timer 的缩写。STC90C51RC/RD+系列单片机的定时器0和定时器1,与传统8051的定时器完全兼容,当在定时器1做波特率发生器时,定时器0可当两个8位定时器使用。STC90C51RC/RD+系列单片机内部设置的两个16位定时器/计数器T0和T1都具有计数方式和定时方式两种工作方式。 对每个定时器/计数器(T0和T1),在特殊功能寄存器TMOD中都有一个控制位— C/T来选择T0或T1为定时器还是计数器。定时器/计数器的核心部件是一个加法(也有减法)的计数器,其本质是对脉冲进行计数。只是计数脉冲来源不同:如果计数脉冲来自系统时钟,则为定时方式,此时定时器/计数器每12个时钟或每6个时钟得到一个计数脉冲,计数值加1;如果计数脉冲来自单片机外部引脚(T0为P3.3,T1为P3.3),则为计数方式,每来一个脉冲加1。定时器/计数器0有4种工作模式:模式0(13位定时器/计数器)模式1(16位定时器/计数器)模式2(8位自动重装模式)模式3(两个8位定时器/计数器)定时器/计数器1除模式3外,其他工作模式与定时器/计数器0相同,T1在模式3时无效,停止计数。定时器的相关寄存器介绍,在STC单片机官方手册的137页有详细讲解。手册下载地址: http://www.stcmcu.com上面图片里就是使用定时器时需要配置的寄存器,其中TL0、TL1、TH0、TH1寄存器用于存放定时器的重装载值,分为高低位两个寄存器。4.1.3 TCON寄存器TCON寄存器用于控制定时器,比如: 定时器启动位、定时器溢出标志位等等。TCON寄存器支持位寻址,在<reg51.h>头文件里也定义了每个位的功能,可以直接操作位进行赋值。TF1和TF0:是定时器1和定时器0的溢出标志位,当定时器计数溢出时(就是定时器超时),该标志位置1。TR1和TR0:是定时器1和定时器0的控制位。当赋值为1时,就启动定时器,开始计数。其他的位是外部中断相关的控制,目前学习定时器时没有用到,先暂时不管。4.1.4 TMOD寄存器TMOD寄存器用于配置定时器的模式。4.1.5 定时器重装值计算方法51单片机标准架构下一个机器周期是12个时钟周期,如果晶振频率是11.059200MHZ,那一个机器周期的时间就是12/11.059200 微秒。也就是说定时器的计数器+1的时间就是12/11.059200=1.085069us。如果定时器工作在16位模式下,最大值可以存放: 0~65535范围的值,那么最大的定时时间就可以得知:65535*1.085069=71109.996915us=71.109996915ms  ,大约是71毫秒的时间。如果需要定时1000us,那么公式就是:x*1.085069=1000,得出x的值就是:  1000/1.085069=921 。计算出定时器需要+921次刚好得到1000us,但是单片机工作在16位模式情况下,需要加满65535定时器才会溢出,所以需要给定时器赋初值。65535-921=64614,这样定时器就可以从64614开始计数,当计数到65535时,定时器就会溢出,TF0就会置1,这时刚好经过1000us时间。那么定时器的重装值寄存器就可以这样赋值:u16 t0_data=64614;TH0=t0_data>>8;  //高位TL0=t0_data;     //低位4.1.6 配置定时器0工作在16位定时器模式下面代码里配置51单片机的定时器0工作在16位定时器模式。程序封装了计算重装值的函数,方便调用,程序没有使用中断,采用轮询方式检测定时器是否超时,在主函数里使用了一个计数变量,记录定时器超时的次数,方便记录更长的时间。程序里的功能是,时间到达1秒钟,就是改变一次LED灯的状态。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> #define LED P0 //定义LED引脚 u16 T0_Update_data;//定时器0的初始值 void Timer0_16bit_Init(u16 us) //当前实验板上的晶振实际频率为: 11.956MHZ u16 val=us/(12/11.956); //得到计数的时间,只要整数部分 T0_Update_data=65535-val; //得到重装载值 TMOD&=0xF0; //清除配置 TMOD|=0x01; //配置定时器0工作在16位定时器模式 TH0=T0_Update_data>>8; //定时器0高位重装值 TL0=T0_Update_data; //定时器0低位重装值 TR0=1; //启动定时器0 //定时器0的重装值更新函数 void Timer0_Update(void) TH0=T0_Update_data>>8; //定时器0高位重装值 TL0=T0_Update_data; //定时器0低位重装值 int main() u32 cnt=0; u8 i=0; Timer0_16bit_Init(1000); //配置定时器超时的时间为1000us LED=0x00; //关闭所有灯 while(1) if(TF0) //判断定时器0定时时间是否到达 cnt++;//记录超时次数 if(cnt==1000) cnt=0; LED=~LED; //取反LED灯 TF0=0; //清除标志位 Timer0_Update(); //重新给定时器的计数器赋值 }4.1.7 配置定时器1工作在16位定时器模式下面代码里配置51单片机的定时器1工作在16位定时器模式。程序封装了计算重装值的函数,方便调用,程序没有使用中断,采用轮询方式检测定时器是否超时,在主函数里使用了一个计数变量,记录定时器超时的次数,方便记录更长的时间。程序里的功能是,时间到达1秒钟,就是改变一次LED灯的状态。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> #include "delay.h" #define LED P0 //定义LED引脚 u16 T1_Update_data;//定时器1的初始值 void Timer1_16bit_Init(u16 us) //当前实验板上的晶振实际频率为: 11.956MHZ u16 val=us/(12/11.956); //得到计数的时间,只要整数部分 T1_Update_data=65535-val; //得到重装载值 TMOD&=0x0F; //清除配置 TMOD|=0x10; //配置定时器1工作在16位定时器模式 TH1=T1_Update_data>>8; //定时器1高位重装值 TL1=T1_Update_data; //定时器1低位重装值 TR1=1; //启动定时器1 //定时器1的重装值更新函数 void Timer1_Update(void) TH1=T1_Update_data>>8; //定时器1高位重装值 TL1=T1_Update_data; //定时器1低位重装值 int main() u32 cnt=0; u8 i=0; Timer1_16bit_Init(1000); //配置定时器超时的时间为1000us LED=0x00; //关闭所有灯 while(1) if(TF1) //判断定时器0定时时间是否到达 cnt++;//记录超时次数 if(cnt==1000) cnt=0; LED=~LED; TF1=0; //清除标志位 Timer1_Update(); //重新给定时器的计数器赋值 }4.1.8 配置定时器0工作在8位自动重装载模式下面代码里配置51单片机的定时器0工作在8位定时器自动重装载模式,在自动重装载模式下,每次定时器超时之后,就省去了手动赋重装值的过程,比较方便,但是定时器的每次最大定时时间变短了,计数器到达255就会溢出。程序里封装了计算重装值的函数,方便调用,程序没有使用中断,采用轮询方式检测定时器是否超时,在主函数里使用了一个计数变量,记录定时器超时的次数,方便记录更长的时间。程序里的功能是,时间到达500毫秒,就改变一次LED灯的状态。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:  #include <reg51.h> 配置定时器0工作在8位自动重装载模式 注意,时间不能超过定时器最大时间 255*(12/11.059200)=276us void Timer0_8bit_Init(u16 us) //当前实验板上的晶振实际频率为: 11.956MHZ u16 val=us/(12/11.956); //得到计数的时间,只要整数部分 TMOD&=0xF0; //清除配置 TMOD|=0x02; //配置定时器0工作在8位自动重载模式 TL0=TH0=255-val;//得到重装载值; TR0=1; //启动定时器0 #define LED P0 //定义LED引脚 int main() u32 cnt=0; u8 i=0; Timer0_8bit_Init(100); //配置定时器超时的时间为100us LED=0x00; //关闭所有灯 while(1) if(TF0) //判断定时器0定时时间是否到达 cnt++;//记录超时次数 if(cnt==10*500) //500ms cnt=0; LED=~LED; TF0=0; //清除标志位

3.8 51单片机-M74HC138译码器驱动8位数码管

3.8 采用38译码器驱动8位数码管3.8.1 原理图开发板连线:JP10(P0)接J12、J21跳线帽接左边、A.P22、B.P23、C.P243.8.2 74HC138译码器M74HC138是一款高速COMS器件,引脚兼容低功耗肖基特TTL(LSTTL)系列。TM74HC138有三个地址数据输入端(A0、A1、A2)和八个有效译码为低的输出端(Y0 - Y7);TM74HC138有三个使能控制端(E1 、 E2 、E 3),当E1 、E2为低电平且E3为高电平时,八个译码输出端才有译码输出,否则八个译码输出端将全为高。TM74HC138通常应用于单个三地址数据输入八译码输出的3-6译码器,也可根据使能信号特点用两个TM74HC138实现四地址数据输入和16 译码输出的 4-16 译码器, 应用中未使用的使能端要处在译码有效输出使能电平状态。3.8.3 示例代码: 8位数码管静态轮流显示数字下面代码使用38译码器控制数码管的位选,单片机的P0口控制段选,程序实现在每个数码管上轮流显示数字0~9。示例代码:#include <reg51.h> //共阴极数码管编码(要显示的段就输出1) //数字0~9 code u8 LED2_Coding[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; //定义38译码器的引脚 sbit HC138_A0=P2^2; //A sbit HC138_A1=P2^3; //B sbit HC138_A2=P2^4; //C #define LED P0 //定义LED引脚 静态数码管显示,共阴极数码管 void LED2_StaticDisplay(u8 number,u8 val) switch(number) //位选,选择点亮的数码管, case 0: HC138_A0=0;HC138_A1=0;HC138_A2=0; break;//显示第0位 case 1: HC138_A0=1;HC138_A1=0;HC138_A2=0; break;//显示第1位 case 2: HC138_A0=0;HC138_A1=1;HC138_A2=0; break;//显示第2位 case 3: HC138_A0=1;HC138_A1=1;HC138_A2=0; break;//显示第3位 case 4: HC138_A0=0;HC138_A1=0;HC138_A2=1; break;//显示第4位 case 5: HC138_A0=1;HC138_A1=0;HC138_A2=1; break;//显示第5位 case 6: HC138_A0=0;HC138_A1=1;HC138_A2=1; break;//显示第6位 case 7: HC138_A0=1;HC138_A1=1;HC138_A2=1; break;//显示第7位 LED=LED2_Coding[val]; //显示数字: 0~9 //动态数码管一共有8个数码管 //数码的控制端接P0端口 //数码管的片选端接138译码器 int main() u8 i,j; while(1) for(i=0;i<8;i++) for(j=0;j<10;j++) LED2_StaticDisplay(i,j); DelayMs(500); }3.8.4 示例代码: 显示指定的整数下面代码使用38译码器控制数码管的位选,51单片机的P0口控制段选,程序实现在8位数码管上显示一个指定的整数。#include <reg51.h> //共阴极数码管编码(要显示的段就输出1) //数字0~9 code u8 LED2_Coding[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; //定义38译码器的引脚 sbit HC138_A0=P2^2; //A sbit HC138_A1=P2^3; //B sbit HC138_A2=P2^4; //C #define LED P0 //定义LED引脚 //设置数码管显示指定的数字 void LED_DisplayNumber(unsigned long number) u16 i,j; u8 display_data[8];//存放当前数码管显示的数据 //以下代码将number按十进制位从低到高依次提取并转为数码管显示字符 display_data[0] = LED2_Coding[number/10000000%10]; display_data[1] = LED2_Coding[number/1000000%10]; display_data[2] = LED2_Coding[number/100000%10]; display_data[3] = LED2_Coding[number/10000%10]; display_data[4] = LED2_Coding[number/1000%10]; display_data[5] = LED2_Coding[number/100%10]; display_data[6] = LED2_Coding[number/10%10]; display_data[7] = LED2_Coding[number/1%10]; for(i=0;i<8;i++) switch(i) //位选,选择点亮的数码管, case 0: HC138_A0=0;HC138_A1=0;HC138_A2=0; break;//显示第0位 case 1: HC138_A0=1;HC138_A1=0;HC138_A2=0; break;//显示第1位 case 2: HC138_A0=0;HC138_A1=1;HC138_A2=0; break;//显示第2位 case 3: HC138_A0=1;HC138_A1=1;HC138_A2=0; break;//显示第3位 case 4: HC138_A0=0;HC138_A1=0;HC138_A2=1; break;//显示第4位 case 5: HC138_A0=1;HC138_A1=0;HC138_A2=1; break;//显示第5位 case 6: HC138_A0=0;HC138_A1=1;HC138_A2=1; break;//显示第6位 case 7: HC138_A0=1;HC138_A1=1;HC138_A2=1; break;//显示第7位 LED=display_data[i]; //控制数码管的显示数据值 j = 100; //扫描间隔时间设定 while(j--){} LED=0x00; //消隐,所有数码管都不显示 //动态数码管一共有8个数码管 //数码的控制端接P0端口 //数码管的片选端接138译码器 int main() while(1) LED_DisplayNumber(4579);

QT5.14.2编译部署QTAV播放器环境

一、编译环境与QTAV介绍QTAV是开源的跨平台的播放器框架,框架是基于QT开发的,可以在Android、IOS、WINDOWS、Linux各个平台编译部署运行。QTAV官网介绍: http://www.qtav.org/QTAV源码下载地址GitHub: https://github.com/wang-bin/QtAV直接在GitHub上下载最新的源码就行。我当前使用的QT环境是QT5.14.2,编译器是minGW和VS2017 都采用的是32位的编译器。QT的下载地址:https://download.qt.io/archive/qt/5.14/5.14.2/当前编译时,我使用的ffmpeg的版本是4.2.2 ,也可以使用最新的版本,QTAV的说明里就介绍,最好使用最新的ffmpeg版本。ffmpeg4.2.2的库地址:https://download.csdn.net/download/xiaolong1126626497/13328939也可以直接去ffmpeg的官方下载:http://www.ffmpeg.org/download.html二、编译QTAV源码在GitHub上作者也介绍过,如何编译部署QTAV。地址: https://github.com/wang-bin/QtAV/wiki/Build-QtAV在windows下编译,作者推荐了2种方法:第一种: 直接把ffmpeg的头文件和库文件拷贝到QT安装目录下的编译器目录里,这样做简单粗暴。第二种: 打开QTAV源码工程,修改qmake.conf文件,指定ffmpeg的头文件和库文件路径让QT编译器能找到ffmpeg库和头文件在哪里。我这里就是采用的第二种方法,直接指定路径,不污染QT安装目录下的文件。INCLUDEPATH += C:/FFMPEG/ffmpeg_x86_4.2.2/include LIBS += -LC:/FFMPEG/ffmpeg_x86_4.2.2/lib 设置好路径之后,直接点击构建即可。构建如果没有问题的话,在生成的目录下就会有一个自动安装脚本,双击运行即可自动拷贝文件到QT的安装目录下。可以分别使用VS2017编译器、minGW编译器构建一遍QTAV源码,这样两个编译器都可以引用QTAV进行开发播放器。编译完成之后,接下来就可以使用QTAV了。在使用QTAV框架时,如果使用的是VS2017编译器,编译可能会报错,提示max函数不能识别。解决办法如下:在QT的pro工程文件里需要引用QTAV的框架库: #LIBS += -L$$quote(C:\Qt\Qt5.14.2\5.14.2\mingw73_32\lib) -lQtAV1 -lQtAVWidgets1 LIBS += -L$$quote(C:\Qt\Qt5.14.2\5.14.2\msvc2017\lib) -lQtAV1 -lQtAVWidgets1 LIBS += -lopengl32 -lglu32 三、使用QTAV开发播放器3.1 mainwindow.cpp 源码#include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) ui->setupUi(this); Widgets::registerRenderers(); VideoOutput *m_vo; AVPlayer *m_player; m_player = new AVPlayer(this); m_vo = new VideoOutput(this); m_player->setRenderer(m_vo); setCentralWidget(m_vo->widget()); m_player->play("D:/test1080.flv"); MainWindow::~MainWindow() delete ui; 3.2 mainwindow.h 源码#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QtAV> #include <QtAVWidgets> using namespace QtAV; namespace Ui { class MainWindow; class MainWindow : public QMainWindow Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow(); private: Ui::MainWindow *ui; #endif // MAINWINDOW_H 3.3 运行效果

4.9 51单片机-(HC-SR04)超声波测距模块

4.9 (HC-SR04)超声波测距模块4.9.1 超声波模块实物图实验板上没有超声波测距模块,这里采用外接模块的形式使用。超声波模块GPIO口功能介绍:(1)、VCC供5V 电源(2)、GND 为地线(3)、TRIG 触发控制信号输入(4)、ECHO 回响信号输出4.9.2 超声波模块功能与工作原理介绍HC-SR04 超声波测距模块可提供 2cm-400cm 的非接触式距离感测功能,测距精度可达高到 3mm;模块包括超声波发射器、 接收器与控制电路。基本工作原理:(1)、单片机控制超声波的TRIG 口至少给10us的高电平信号,触发测距;(2)、模块会自动发送8个 40khz 的方波, 并自动检测是否有信号返回;(3)、有信号返回, 模块会通过 ECHO 口输出一个高电平, 高电平持续的时间就是超声波从发射到返回的时间。 公式:uS/58=厘米  或者 uS/148=英寸 或是 距离=高电平时间*声速(340M/S)以上时序图表明,单片机只需要提供一个 10uS 以上脉冲触发信号, 该模块内部将发出 8 个 40kHz 周期电平并检测回波,模块一旦检测到有回波信号则输出回响信号,回响信号的脉冲宽度与所测的距离成正比。由此通过发射信号到收到的回响信号时间间隔可以计算得到距离。公式: uS/58=厘米或者 uS/148=英寸; 或  距离=高电平时间*声速(340M/S) /2;建议测量周期为 60ms 以上, 以防止发射信号对回响信号的影响。4.9.3 超声波测距示例代码当前使用的实验板上没有超声波模块,当前采用外接模块的形式与实验板进行连接。超声波模块型号是:HC-SR04。由于当前51单片机(STC90C51)的中断没法配置成上升沿触发,主程序里采用阻塞判断的方式等待测距结束,使用定时器0记录经过的时间,定时器0开启了溢出中断,在中断里使用变量记录中断溢出的次数。当测距结束时,通过定时器的溢出次数和当前定时器的值得到记录的时间,计算测量的距离,最终将测量的距离通过串口打印到电脑终端查看。(当前使用的测距模块最大测量的距离是4米,16位定时器完全足够计数,可以不用开启定时器溢出中断,下面程序设计的思路比较通用,如果其他测距模块测量的距离更加远,也可以使用)(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> sbit ECHO=P1^0; //超声波的回响信号输出脚 sbit TRIG=P1^1; //触发超声波测距的引脚 u32 timt0_cnt=0; //记录定时器0溢出的次数 u16 time_val=0; float distance=0.0; //保存测量的距离 void Timer0_16bit_Init(u16 us); void DelayMs(u32 ms); void delay20us(void); int main() ECHO=0; TRIG=0; UART_Init(); //初始化串口波特率为4800 while(1) TRIG=1;//触发测距 delay20us(); //延时20us TRIG=0; //停止触发 while(ECHO==0){} //等待回响信号返回 Timer0_16bit_Init(65535); //初始化定时器0,并开始计数 while(ECHO==1){} //等待回响信号结束 TR0=0; //关闭定时器0 time_val=(TH0<<8|TL0)+timt0_cnt*65535; //计算时间 distance=time_val/58.0; //得到距离。单位是厘米 printf("distance=%f CM\r\n",distance); timt0_cnt=0; //溢出次数清零 DelayMs(1000); //延时1秒 u16 T0_Update_data;//定时器0的初始值 void Timer0_16bit_Init(u16 us) //当前实验板上的晶振实际频率为: 12MHZ u16 val=us/(12/12); //得到计数的时间,只要整数部分 T0_Update_data=65535-val; //得到重装载值 TMOD&=0xF0; //清除配置 TMOD|=0x01; //配置定时器0工作在16位定时器模式 TH0=T0_Update_data>>8; //定时器0高位重装值 TL0=T0_Update_data; //定时器0低位重装值 TR0=1; //开启定时器0 extern timt0_cnt; //记录定时器0的溢出次数 //定时器0的重装值更新函数 void Timer0_Update(void) TH0=T0_Update_data>>8; //定时器0高位重装值 TL0=T0_Update_data; //定时器0低位重装值 timt0_cnt++; //记录定时器0的溢出次数 void DelayMs(u32 ms) u32 i; u8 a,b; for(i=0;i<ms;i++) for(b=199;b>0;b--) for(a=1;a>0;a--); void delay20us(void) //误差 0us unsigned char a,b; for(b=1;b>0;b--) for(a=7;a>0;a--);

4.4 51单片机-NEC红外线遥控器解码

4.4  NEC红外线遥控器解码4.4.1 接收头原理图介绍实验板上的红外线接收头是接在单片机的P3.2 IO口上,要使用红外线接收功能,需要将红外线接收头的跳线帽接上。4.4.2 NEC红外线协议介绍红外线遥控是目前使用最广泛的一种通信和遥控手段。由于红外线遥控装置具有体积小、功耗低、功能强、成本低等特点,因而,继彩电、录像机之后,在录音机、音响设备、空凋机以及玩具等其它小型电器装置上也纷纷采用红外线遥控。工业设备中,在高压、辐射、有毒气体、粉尘等环境下,采用红外线遥控不仅完全可靠而且能有效地隔离电气干扰。红外线是波长介于微波和可见光之间的电磁波,波长在 760 纳米到 1 毫米之间,是波形比红光长的非可见光。家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地。遥控器的基带通信协议很多,大概有几十种,常用的就有 ITT 协议、 NEC 协议、 Sharp 协议、 Philips RC-5 协议、 Sony SIRC 协议等。用的最多的就是 NEC 协议了,本小节以 NEC协议标准进行讲解,实验板自带的遥控器也是NEC协议的。NEC协议,通常是使用 38K左右的载波进行调制,再发送的;这里的调制就是用待传送信号去控制某个高频信号的幅度、相位、频率等参量变化的过程,即用一个信号去装载另一个信号。比如:我们的红外遥控信号要发送的时候,先经过 38K 调制。原始信号就是要发送的一个数据“0”位或者一位数据“1”位,而38K 载波就是频率为 38K 的方波信号,调制后信号就是最终我们发射出去的波形。使用原始信号来控制 38K 载波,当信号是数据“0”的时候, 就发送38K载波,当信号是数据“1”的时候,就不发送任何载波信号,那么数据接收方只需要检测是否收到38KHZ的载波就行判断是否收到了数据。实验板上带了一个一体化接收头HS0038B,可以识别38KHZ的载波信号,当HS0038B收到38KHZ载波就输出低电平,没有收到38KHZ载波就输出高电平;程序就可以判断HS3008的引脚电平配合指定的协议格式来分析收到的数据。NEC协议的数据格式包括了引导码、用户码、用户码反码、按键码、按键反码。其中数据编码总共是4个字节 32 位(除了引导码外就是数据位),数据格式中的反码用于数据校验,可以判断数据在传输过程中有没有丢包。NEC协议解码总结:引导码:9ms的低电平+4.5ms的高空闲。逻辑 1:  560us 低+1680us 高电平逻辑 0:  应该是 560us 低+560us 高电平。遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平。红外线遥控器向红外线接收头按下一个按键之后,红外线接收头先收到的是9ms的高电平脉冲,接着是4.5ms的低电平,之后就是接收8bit的用户码、8bit的用户码的反码,8bit 的按键码,8bit 的按键码的反码。如果一直按着1个键,这样遥控器发送的是以110ms为周期的重复码,就是说,发了一次按键码之后,不会再发送按键码,而是每隔110ms时间,发送一段重复码,重复码由9ms高电平和2.25ms的低电平以及560us的高电平组成。4.4.3 采集NEC协议的信号为了更加直观的理解NEC协议格式,可以使用逻辑分析仪或者示波器采集HS0038红外线接收头收到的数据,再通过软件分析收到的数据。逻辑分析仪软件下载地址: Logic analyzer software from Saleae4.4.4 NEC红外线协议解码示例下面代码采用定时器+外部中断的方式解码红外线数据,外部中断采用外部中断0,接在P3.2口上,配置外部中断0的触发方式为下降沿触发。解码思路: 红外线接收头没有收到38KHZ方波时,默认输出高电平,当收到38KHZ方波时输出低电平,这时就触发了下降沿,接着就进入到外部中断0 的中断服务函数;解码的过程就在中断服务函数里实现,高低电平的持续时间通过定时器0进行计数,当前实验板的晶振是12MHZ,刚好定时器的计数器+1的时间就是1us,配置定时器0的工作方式为16位模式,最大计数可以到65535,NEC协议最长的一段时间也就9000us,完全满足需求;解码完成之后设置标志位,在主函数里将解码的数据通过串口打印出去。(硬件平台说明:CPU是STC90C516RD 、晶振频率12MHZ 、工作在12T模式下、一个机器周期为1us时间)示例代码:#include <reg51.h> u8 Infrared_RX_Flag=0; //红外接收标志,收到一帧正确数据后置1 u8 Infrared_RX_Buff[4];//红外代码接收缓冲区 sbit Infrared_GPIO=P3^2;//红外接收引脚--外部中断0 函数功能: 开始红外线解码之前的相关初始化 实验板的晶振频率是12MHZ 51单片机标准架构下一个机器周期是12个时钟周期,如果晶振频率是12MHZ,那一个机器周期的时间就是12/12微秒。 也就是说定时器的计数器+1的时间就是12/12=1us。 void Infrared_Init(void) Infrared_GPIO=1;//红外接收引脚默认保持高电平输出 TMOD&=0xF0; //清除配置 TMOD|=0x01; //配置定时器0,工作在16位计数模式 TR0=0; //停止定时器0计数 ET0=0; //禁止定时器0中断 IT0=1; //开启外部中断0,下降沿触发 EX0=1; //允许外部中断0中断 函数功能: 检测高电平持续的时间 u16 Infrared_GetTimeH(void) TH0=0; //定时器0重装值为0 TL0=0; //定时器0重装值为0 TR0=1; //启动定时器0开始计数 while(Infrared_GPIO)//等待高电平结束 if(TH0>0x40)//防止超时 break; TR0=0;//停止定时器0计数 return TH0<<8|TL0;//T0计数值合成为16位整数返回 检测低电平持续的时间 u16 Infrared_GetTimeL(void) TH0=0;//定时器0的高8位重装值 TL0=0;//定时器0的低8位重装值 TR0=1;//开启定时器0 while(Infrared_GPIO==0)//等待低电平结束 if(TH0>0x40)//防止超时 break; TR0=0;//停止定时器0计数 return TH0<<8|TL0;//T0计数值合成为16位整数返回 外部中断0中断服务函数 void EXTI0_IRQHandler() interrupt 0 u8 i, j; u16 time; u8 byte; time=Infrared_GetTimeL(); //获取出现低电平的时间 if((time<7800)||(time>9300))//判断低电平时间是否符合9ms范围 { //超过此范围则说明为误码,直接退出 IE0=0; //清除外部中断0中断标志 return; time=Infrared_GetTimeH(); //获取出现高电平的时间 if((time<3500)||(time>4700))//高电平是否符合4.5ms范围 { //超过此范围则说明为误码,直接退出 IE0=0; //清除外部中断0中断标志 return; //接收32位数据位 for(i=0;i<4;i++) for(j=0;j<8;j++) time=Infrared_GetTimeL(); //获取低电平持续时间,标准的间隔时间为560us范围 if((time<300)||(time>700)) //判断范围是否合理 IE0=0;//清除外部中断0中断标志 return; //1和0是靠高电平持续的长短来区分的 time=Infrared_GetTimeH(); //获取高电平持续时间 if(time>300&&time<700) //0的标准时间为560us byte>>=1; else if(time>1400&&time<1800) //1的标准时间是1680us byte>>=1; byte|=0x80; else //不在上面的判断范围内说明是错误码,直接退出 IE0=0;//清除外部中断0中标 return; Infrared_RX_Buff[i]=byte;//接收完一个字节后保存到缓冲区 Infrared_RX_Flag=1;//接收完毕后设置标志 IE0=0;//退出前清除外部中断0中断标志 int main() UART_Init(); //初始化串口波特率为4800 Infrared_Init(); //初始化红外功能 while(1) if(Infrared_RX_Flag) //接收到红外数据 Infrared_RX_Flag=0; //清楚标志 printf("user1:%d,user2:%d\r\n",(int)Infrared_RX_Buff[0],(int)((u8)(~Infrared_RX_Buff[1]))); printf("key1:%d,key2:%d\r\n",(int)Infrared_RX_Buff[2],(int)((u8)(~Infrared_RX_Buff[3])));

重写C语言string.c的部分函数(1)

一、string.c里相关函数介绍string.h里主要包含了C语言对字符串操作的相关函数,这篇文章就介绍几个比较常用的函数重新自己实现。并且每个函数给出了2种以上的不同写法,全部采用指针方式;在学习C语言过程中,重写这些字符串处理函数可以快速提升、磨练自己的指针、数组、函数相关知识,对学习是非常有帮助的;在单片机、嵌入式开发中,也会经常需要自己实现这些函数,可能有些功能系统函数不能满足的情况下,都需要自己重新实现。二、重写函数介绍2.1 strcmp : 字符串比较字符串比较函数,用来比较两个字符串是否相等,下面给出了4种写法,其中一个是strncmp函数。int strcmp(const char *str1, const char *str2) const unsigned char *s1 = (const unsigned char *)str1; const unsigned char *s2 = (const unsigned char *)str2; int delta = 0; while (*s1 || *s2) { delta = *s2 - *s1; if (delta) return delta; s1++; s2++; return 0; int strncmp(const char *cs, const char *ct, size_t count) unsigned char c1, c2; while (count) { c1 = *cs++; c2 = *ct++; if (c1 != c2) return c1 < c2 ? -1 : 1; if (!c1) break; count--; return 0; int strcmp(const char *a, const char *b) while (*a || *b) { if (*a != *b) return 1; if (*a == 0 || *b == 0) return 1; a++; b++; return 0; int strcmp(const char *cs, const char *ct) unsigned char c1, c2; int res = 0; c1 = *cs++; c2 = *ct++; res = c1 - c2; if (res) break; } while (c1); return res; 2.2 strlen: 字符串长度介绍strlen是计算字符串长度的函数,比较常用,代码也最简单,下面写了两种实现方法。size_t strlen(const char *s) const char *sc = s; while (*sc != '\0') sc++; return sc - s; size_t strlen(const char *s) const char *sc; for (sc = s; *sc != '\0'; ++sc) return sc - s; size_t strnlen(const char *s, size_t count) const char *sc; for (sc = s; count-- && *sc != '\0'; ++sc) return sc - s; 2.3 strstr: 字符串查找strstr字符串查找函数,用来查找指定的字符串在源字符串里是否存在,存在就返回地址。这个就给出了一种写法。char *strstr(const char *s1, const char *s2) size_t l1, l2; l2 = strlen(s2); if (!l2) return (char *)s1; l1 = strlen(s1); while (l1 >= l2) { l1--; if (!memcmp(s1, s2, l2)) return (char *)s1; s1++; return NULL; 2.4 memcmp : 内存比较memcmp内存比较函数,用来比较两个内存地址里的数据是否相等,不局限于字符串,只要是合法内存都可以按字节比较。int memcmp(const void *cs, const void *ct, size_t count) const unsigned char *su1 = cs, *su2 = ct, *end = su1 + count; int res = 0; while (su1 < end) { res = *su1++ - *su2++; if (res) break; return res; int memcmp(const void *cs, const void *ct, size_t count) const unsigned char *su1, *su2; int res = 0; for (su1 = cs, su2 = ct; 0 < count; ++su1, ++su2, count--) if ((res = *su1 - *su2) != 0) break; return res; 2.5 strcpy :字符串拷贝strcpy字符串拷贝函数,用来将目标字符串拷贝到指定的地址中。目标字符串必须是‘\0’结束。 这里写了2种函数,一个strcpy、一个strncpy。char *strcpy(char *dest, const char *src) char *tmp = dest; while ((*dest++ = *src++) != '\0') return tmp; char *strncpy(char *dest, const char *src, size_t count) char *tmp = dest; while (count) { if ((*tmp = *src) != 0) src++; tmp++; count--; return dest; size_t strlcpy(char *dest, const char *src, size_t size) size_t ret = strlen(src); if (size) { size_t len = (ret >= size) ? size - 1 : ret; memcpy(dest, src, len); dest[len] = '\0'; return ret; 2.6 内存拷贝: memcpy内存拷贝函数,将目标内存数据拷贝导致指定内存位置,不局限于字符串。void *memcpy(void *__dest, __const void *__src, size_t __n) int i = 0; unsigned char *d = (unsigned char *)__dest, *s = (unsigned char *)__src; for (i = __n >> 3; i > 0; i--) { *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; if (__n & 1 << 2) { *d++ = *s++; *d++ = *s++; *d++ = *s++; *d++ = *s++; if (__n & 1 << 1) { *d++ = *s++; *d++ = *s++; if (__n & 1) *d++ = *s++; return __dest; void *memcpy(void *__dest, __const void *__src, size_t __n) int i; char *d = (char *)__dest, *s = (char *)__src; for (i = 0; i < __n; i++) d[i] = s[i]; return __dest; static inline void *memcpy(void *__dest, const void *__src, size_t __n) int i; const char *s = __src; char *d = __dest; for (i = 0; i < __n; i++) d[i] = s[i]; return __dest;

3.7 51单片机-LED 16*16点阵

3.7 LED 16*16点阵3.7.1 点阵原理图LED点阵使用连线说明:P595_A接J17、P595_B接J18、JP595跳线帽需要接上、JP1302断开、J11(P3.3)断开从上面LED点阵原理图中看出,点阵里的每个LED的阴端接在行上面,阳端接在列上面。点亮一个LED二极管只需要根据正负极给对应的电源即可发光。根据实验板的16*16点阵原理图来看,只要给横向(行)16个IO输出低电平,纵向(列)16个IO口输出高电平,即可点亮点阵上面的所有LED。3.7.2 点阵原理介绍实验板上的16*16点阵是由4个8*8LED点阵组成的,一个 8*8 的点阵是由 64个LED小灯组成。16*16点阵分别由4片74HC595控制,4片74HC595采用级联的方式连接。3.7.3 74HC595缓存器74HC595是一个8位串行输入、平行输出的位移缓存器:平行输出为三态输出。在SCK的上升沿,单行数据由SDL输入到内部的8位位移缓存器,并由Q7‘输出,而平行输出则是在LCK的上升沿将在8位位移缓存器的数据存人到8位平行输出缓存器。当串行数据输人端OE的控制信号为低使能时,平行输出端的输出值等于平行输出缓存器所存储的值。而当OE为高电位,也就是输出关闭时,平行输出端会维持在高阻抗状态。74HC595支持级联,当多个595级联到一起时,通过数据线发送的一个数据最终会移位给最后的一个595。因为级联数据是被挤出到下一级的,所以先发送的数据最后是到最后一级的595。每次向74HC595发送一个字节,74HC595最先收到的一位数据是高位,也就是最先收到的数据就到达Q7脚,如果我们先输出数据高位的话,最高位在8个脉冲后就会跑到Q7脚(数据脚最高位)。这就像我们排队一样,一个寄存器里面有8个位置,每次给一个脉冲就好比一次呼叫:“大家可以往前移一位了!”就这样,队伍不断得往前移。这个过程就像在向前挤一样:通过真值表得知:发送一个数据0的时序代码如下:HC595_DATA=0;HC595_SCK=0;HC595_SCK=1;发送一个数据1的时序代码如下:HC595_DATA=1;HC595_SCK=0;HC595_SCK=1;3.7.4 示例代码: LED点阵单点扫描显示下面代码实现,点阵依次从左上角点亮一个点,等待一段时间,再关闭所有点,再点亮第二个点,等待一段时间,再关闭所有点,从左到右,从上到下,依次把所有点亮都点亮一遍。1.#include <reg51.h> //定义HC595使用的IO口 sbit HC595_DATA=P3^4; //串行数据输出 sbit HC595_RCK=P3^5; //存储寄存器时钟 sbit HC595_SCK=P3^6; //移位寄存器时钟 通过HC595发送一个字节数据 先发送高位,在HC959上对应的是低位(也就是LED点阵的最左边) void HC595_Send_Byte(u8 byte) u8 i; for(i=0;i<8;i++) if(byte&0x80)HC595_DATA=1; else HC595_DATA=0; HC595_SCK=0; HC595_SCK=1; byte<<=1; //依次发送高位 将HC959存储器里的数据输出到总线上 void HC595_DataOut(void) //将移位存储器的输出并行输出到总线上 HC595_RCK=0; HC595_RCK=1; HC595_RCK=0; HC595的3、4级是控制点阵的列 (低电平) HC595的1、2级是控制点阵的行 (高电平) 给HC595第1级发送的数据会移动到最后一个HC595。 int main() u8 i,j; u16 Row; //行 u16 col; //列 while(1) for(i=0;i<16;i++) Row=0x0000; Row|=1<<i; //行 for(j=0;j<16;j++) col=0xFFFF; col&=~(1<<j); //列 HC595_Send_Byte(col>>8); HC595_Send_Byte(col); HC595_Send_Byte(Row>>8); HC595_Send_Byte(Row); HC595_DataOut(); DelayMs(100); //延时一段时间,再关闭所有显示 HC595_Send_Byte(0xFF); HC595_Send_Byte(0xFF); HC595_Send_Byte(0x00); HC595_Send_Byte(0x00); HC595_DataOut(); }3.7.5 示例代码: 控制LED点阵显示指定效果(1). 实现点阵按照指定时间间隔全亮和全灭#include <reg51.h> int main() u8 i,j; while(1) //下面4行代码,开启点阵 HC595_Send_Byte(0x00);//4列: 低位对应右边第8列开始,8~15 HC595_Send_Byte(0x00);//3列: 低位对应左边第0列开始,0~7 HC595_Send_Byte(0xFF);//2行: 低位对应上面第8行,依次8~15 HC595_Send_Byte(0xFF);//1行: 低位对应上面第0行,依次0~7 HC595_DataOut(); DelayMs(500); //下面4行代码,关闭点阵 HC595_Send_Byte(0xFF);//4列: 低位对应右边第8列开始,8~15 HC595_Send_Byte(0xFF);//3列: 低位对应左边第0列开始,0~7 HC595_Send_Byte(0x00);//2行: 低位对应上面第8行,依次8~15 HC595_Send_Byte(0x00);//1行: 低位对应上面第0行,依次0~7 HC595_DataOut(); DelayMs(500); }(2). 实现点阵旋转效果,按照8*8为单位,点亮4个小方块点亮的顺序,看下面代码的注释。int main() while(1) //点亮左上角8*8方块 HC595_Send_Byte(0xFF); HC595_Send_Byte(0x00); HC595_Send_Byte(0x00); HC595_Send_Byte(0xFF); HC595_DataOut(); DelayMs(100); //点亮右上角8*8方块 HC595_Send_Byte(0x00); HC595_Send_Byte(0xFF); HC595_Send_Byte(0x00); HC595_Send_Byte(0xFF); HC595_DataOut(); DelayMs(100); //点亮右下角8*8方块 HC595_Send_Byte(0x00); HC595_Send_Byte(0xFF); HC595_Send_Byte(0xFF); HC595_Send_Byte(0x00); HC595_DataOut(); DelayMs(100); //点亮左下角8*8方块 HC595_Send_Byte(0xFF); HC595_Send_Byte(0x00); HC595_Send_Byte(0xFF); HC595_Send_Byte(0x00); HC595_DataOut(); DelayMs(100); }3.7.6 示例代码: 控制LED点阵显示汉字使用LED点阵显示汉字,需要用到汉字取模软件,本章节使用的取模软件“PCtoLCD2002”,软件的取模方式配置如下。示例代码:  在LED点阵上显示一个中文#include <reg51.h> //定义HC595使用的IO口 sbit HC595_DATA=P3^4; //串行数据输出 sbit HC595_RCK=P3^5; //存储寄存器时钟 sbit HC595_SCK=P3^6; //移位寄存器时钟 通过HC595发送一个字节数据 先发送高位,在HC959上对应的是低位(也就是LED点阵的最左边) void HC595_Send_Byte(u8 byte) u8 i; for(i=0;i<8;i++) if(byte&0x80)HC595_DATA=1; else HC595_DATA=0; HC595_SCK=0; HC595_SCK=1; byte<<=1; //依次发送高位 将HC959存储器里的数据输出到总线上 void HC595_DataOut(void) //将移位存储器的输出并行输出到总线上 HC595_RCK=0; HC595_RCK=1; HC595_RCK=0; 在LED点阵上显示一个16*16数据 void LED_DisplayData(u8 *p) u8 i; u16 Row; //控制行的状态,每次只点亮1行 for(i=0;i<16;i++) Row=0x0000; Row|=1<<i; HC595_Send_Byte(*(p+1)); //控制列右8个IO HC595_Send_Byte(*p); //控制列左8个IO p+=2; HC595_Send_Byte(Row>>8); //控制行下8个IO HC595_Send_Byte(Row); //控制行上8个IO HC595_DataOut(); u8 code font[] = {0x7F,0xFF,0x7F,0xFF,0x7F,0xFF,0x7F,0xFF,0x03,0xE0,0x7B,0xEF,0x7B,0xEF,0x7B,0xEF,0x7B,0xEF,0x7B,0xEF,0x03,0xE0,0x7B,0xEF,0x7F,0xFF,0x7F,0xFF,0x7F,0xFF,0x7F,0xFF};/*"中",0*/ int main() while(1) LED_DisplayData(font); //显示中文 }3.7.7 示例代码: 控制LED点阵显示多个中文下面代码实现,在LED点阵上循环显示多个文字或者图案,所有的功能都使用子函数封装,调用逻辑清晰。#include <reg51.h> //定义HC595使用的IO口 sbit HC595_DATA=P3^4; //串行数据输出 sbit HC595_RCK=P3^5; //存储寄存器时钟 sbit HC595_SCK=P3^6; //移位寄存器时钟 通过HC595发送一个字节数据 先发送高位,在HC959上对应的是低位(也就是LED点阵的最左边) void HC595_Send_Byte(u8 byte) u8 i; for(i=0;i<8;i++) if(byte&0x80)HC595_DATA=1; else HC595_DATA=0; HC595_SCK=0; HC595_SCK=1; byte<<=1; //依次发送高位 将HC959存储器里的数据输出到总线上 void HC595_DataOut(void) //将移位存储器的输出并行输出到总线上 HC595_RCK=0; HC595_RCK=1; HC595_RCK=0; 在LED点阵上显示一个16*16数据 void LED_DisplayData(u8 *p) u8 i; u16 Row; //控制行的状态,每次只点亮1行 for(i=0;i<16;i++) Row=0x0000; Row|=1<<i; HC595_Send_Byte(*(p+1)); //控制列右8个IO HC595_Send_Byte(*p); //控制列左8个IO p+=2; HC595_Send_Byte(Row>>8); //控制行下8个IO HC595_Send_Byte(Row); //控制行上8个IO HC595_DataOut(); 关闭点阵所有点 void LED_Clear(void) HC595_Send_Byte(0xFF);//4列: 低位对应右边第8列开始,8~15 HC595_Send_Byte(0xFF);//3列: 低位对应左边第0列开始,0~7 HC595_Send_Byte(0x00);//2行: 低位对应上面第8行,依次8~15 HC595_Send_Byte(0x00);//1行: 低位对应上面第0行,依次0~7 HC595_DataOut(); code u8 buff[][32]= {0xFF,0xFF,0xFF,0xFF,0x00,0x80,0xDF,0xFF,0xDF,0xFF,0xDF,0xFF,0x1F,0xF0,0xDF,0xF7,0xDF,0xF7,0xEF,0xF7,0xEF,0xF7,0xF7,0xF7,0xF7,0xF7,0xFB,0xF7,0xFD,0xFA,0xFE,0xFD},/*"万",0*/ {0xEF,0xFF,0xEF,0xC1,0xEF,0xDD,0x00,0xED,0xEF,0xED,0xEF,0xF5,0x81,0xED,0xEF,0xED,0xEF,0xDD,0x00,0xDD,0xEF,0xDD,0xF7,0xE9,0xF7,0xF5,0xFB,0xFD,0xFD,0xFD,0xFE,0xFD},/*"邦",1*/ {0x0F,0xF0,0xEF,0xF7,0xEF,0xF7,0x0F,0xF0,0xEF,0xF7,0xEF,0xF7,0x0F,0xF0,0xDF,0xFF,0xEF,0xFF,0x07,0xC0,0xBB,0xDD,0xBD,0xDD,0xDF,0xDE,0xEF,0xDE,0x77,0xEB,0xBF,0xF7},/*"易",2*/ {0x7F,0xFF,0x7B,0xEF,0x7B,0xEF,0x03,0xE0,0xFF,0xFB,0xBB,0xFB,0xBB,0x83,0x00,0xBD,0xBB,0xD6,0xBB,0xF7,0x83,0xF7,0xBB,0xF7,0xBB,0xEB,0x83,0xEB,0xBB,0xDD,0xFF,0xBE},/*"嵌",3*/ #define TIME 100 int main() u8 i,j; while(1) for(i=0;i<sizeof(buff)/sizeof(buff[0]);i++) for(j=0;j<TIME;j++) //停留的时间 LED_DisplayData(buff[i]); //显示中文,需要循环调用 LED_Clear(); //清除显示

3.2 51单片机-蜂鸣器模块

3.2 蜂鸣器模块3.2.1 蜂鸣器原理图实验板上的蜂鸣器没有直接与单片机的IO口相连接,需要使用一个杜邦线手动连接(图中黄色的线)。3.2.2 无源蜂鸣器与有源蜂鸣器介绍有源和无源这里的“源”不是指电源,而是指震荡源。也就是说,有源蜂鸣器内部带震荡源,所以只要一通电就会叫。而无源内部不带震荡源,所以如果用直流信号无法令其鸣叫。必须用2K~5K的方波去驱动它,有源蜂鸣器往往比无源的贵,就是因为里面多个震荡电路。有源蜂鸣器:内部有振荡、驱动电路,加电源就可以响,用起来省事,缺点是频率固定,就只一个单音(高、低电平直接驱动,发出滴答的声音)。无源蜂鸣器: 价格便宜(少了个振荡电路),声音频率可控,可以做出“多来米发索拉西”的效果,缺点是驱动比较麻烦一点。当前实验板上使用的就是无源蜂鸣器,要操作蜂鸣器需要使用方波进行控制。3.2.3 示例代码控制蜂鸣器发出滴答的声音,延时函数可以使用单片机小精灵软件自动生成。#include <reg51.h> //添加通用的51单片机头文件 #include <intrins.h> void delay200us(void); void BEEP_Ctl(int cmd,int time); sbit BEEP=P1^5; //定义蜂鸣器引脚(需要使用杜邦线连接IO口) int main(void) int i; while(1) i=!i; BEEP_Ctl(i,1000); 开启蜂鸣器(2KHZ~5KHZ方波) 1s等于1000ms 1ms等于1000us 1HZ=s 1KHZ=ms 1MHZ=us 1/5000HZ=0.0002s=200us void BEEP_Ctl(int cmd,int time) int i; if(cmd) //打开蜂鸣器 for(i=0;i<time;i++) BEEP=0; delay200us(); BEEP=1; delay200us(); else //关闭蜂鸣器 BEEP=0; for(i=0;i<time;i++)delay200us(); void delay200us(void) unsigned char a,b; for(b=39;b>0;b--) for(a=1;a>0;a--); }3.2.4 使用无源蜂鸣器播放音乐电子琴的1234567分别对应CDEFGAB。

第二章 搭建51单片机开发环境

第二章 搭建开发环境2.1 STC单片机命名介绍STC90C516RD+的配置:RAM大小: 1280字节(1.25KB)ROM大小: 64KB2.2 安装keil软件Keil软件可以通过百度下载或者使用光盘提供的软件包进行安装。百度网盘keil5.25下载地址: 百度网盘 请输入提取码 提取码:k2542.3 新建工程2.4 下载程序2.5 使用辅助工具计算延时时间延时毫秒的函数:晶振频率为12Mvoid DelayMs(int ms) int i; unsigned char a,b; for(i=0;i<ms;i++) for(b=199;b>0;b--) for(a=1;a>0;a--); }2.6 STC90C51系列单片机引脚图

第一章 51单片机开发入门知识介绍

第一章 单片机开发入门知识介绍1.1 51单片机介绍51单片机是对所有兼容Intel 8031指令系统的单片机的统称。该系列单片机的始祖是Intel的8004单片机,后来随着Flash rom技术的发展,8004单片机取得了长足的进展,成为应用最广泛的8位单片机之一,其代表型号是ATMEL公司的AT89系列,它广泛应用于工业测控系统之中。很多公司都有51系列的兼容机型推出, 51单片机是基础入门的一个单片机,还是应用最广泛的一种。主要产品代表:(1)、Intel(英特尔)的:80C31、80C51、87C51,80C32、80C52、87C52等;(2)、ATMEL(爱特梅尔)的:89C51、89C52、89C2051,89S51(RC),89S52(RC)等;(3)、Philips(飞利浦)、华邦、Dallas(达拉斯)、Siemens(西门子)等公司;(4)、STC(国产宏晶)单片机:89c51、89c52、89c516、90c516等。宏晶科技是新一代增强型8位单片微型计算机标准的制定者和领导厂商。1.2 市场上的主流单片机种类单片机是一种集成电路芯片,是采用超大规模集成电路技术把具有数据处理能力的中央处理器CPU随机存储器RAM、只读存储器ROM、多种I/O口和中断系统、定时器/计时器等功能(可能还包括显示驱动电路、脉宽调制电路、模拟多路转换器、A/D转换器等电路)集成到一块硅片上构成的一个小而完善的微型计算机系统,在工业控制领域的广泛应用。以下是目前市场上的主流单片机:(1)、8051单片机8051单片机最早由Intel公司推出,随后Intel公司将80C51内核使用权,以专利互换或出让给世界许多著名IC制造厂商,这样80C51单片机就变成了众多芯片制造厂商支持的大家族,统称为80C51系列单片机。客观事实表明,80C51已成为8位单片机的主流。(2)、AVR单片机AVR单片机是1997年由ATMEL(爱特梅尔)公司研发出的增强型内置Flash的RISC(Reduced Instruction Set CPU) 精简指令集高速8位单片机。可以广泛应用于计算机外部设备、工业实时控制、仪器仪表、通讯设备、家用电器等各个领域。AVR单片机最大的特点是精简指令型单片机,执行速度,在相同的振荡频率下是8位MCU中最快的一种单片机。(3)、PIC单片机PIC单片机是Microchip(美国微芯半导体)公司的产品,它也是一种精简指令型的单片机,指令数量比较少,中档的PIC系列仅仅有35条指令而已,低档的仅有33条指令。适用于用量大,档次低,价格敏感的产品,在办公自动化设备,消费电子产品,电讯通信,智能仪器仪表,汽车电子,金融电子,工业控制不同领域都有广泛的应用。PIC最大的特点是不搞单纯的功能堆积,而是从实际出发,重视产品的性能与价格比,靠发展多种型号来满足不同层次的应用要求。PIC系列从低到高有几十个型号,可以满足各种需要。其中,PIC12C508单片机仅有8个引脚,是世界上最小的单片机。(4)、MSP430MSP430系列单片机是美国德州仪器(TI)1996年开始推向市场的一种16位超低功耗、具有精简指令集(RISC)的混合信号处理器(Mixed Signal Processor)。MSP430单片机称之为混合信号处理器,是由于其针对实际应用需求,将多个不同功能的模拟电路、数字电路模块和微处理器集成在一个芯片上,以提供“单片机”解决方案。该系列单片机多应用于需要电池供电的便携式仪器仪表中。MSP430系列单片机是一个16位的单片机,运算速度快,超低功耗,MSP430 系列单片机的电源电压采用的是1.8-3.6V电压。(5)、ARM处理器ARM即以英国ARM(Advanced RISC Machines)公司的内核芯片作为CPU,同时附加其他外围功能的嵌入式开发板,用以评估内核芯片的功能和研发各科技类企业的产品。ARM是一个32位元精简指令集(RISC)处理器架构,ARM处理器广泛地使用在许多嵌入式系统设计。ARM处理器的特点有指令长度固定,执行效率高,低成本等。ARM微处理器,已遍及工业控制、消费类电子产品、通信系统、网络系统、无线系统等各类产品市场,基于ARM技术的微处理器应用约占据了32位RISC微处理器75%以上的市场份额,ARM技术正在逐步渗入到我们生活的各方面。ARM 微处理器目前包括下面几个系列,以及其它厂商基于 ARM 体系结构的处理器,除了具有ARM 体系结构的共同特点以外,每一个系列的 ARM 微处理器都有各自的特点和应用领域。- ARM7 系列- ARM9 系列- ARM9E 系列- ARM10E 系列- ARM11系列- Cortex 系列 : Cortex系列处理器是基于ARMv7架构的,分为Cortex-M、Cortex-R和Cortex-A三类。由于应用领域的不同,基于v7架构的Cortex处理器系列所采用的技术也不相同。基于v7A的称为“Cortex-A系列。- SecurCore 系列- OptimoDE Data Engines- Intel的Xscale- Intel的StrongARM ARM11系列1.3 FPGA与单片机区别(1)、FPGA和单片机在概念上的区别单片机:单片机可以简单理解为集成在单一芯片上的微型计算机,也有运算器、控制器、存储器、总线及输入输出设备,采用也是存储程序执行的方式,对单片机的编程就是对其中的ROM写入程序,在加电后ROM中的程序会像计算机内存中的程序一样得到逐条的执行。单片机有很强的接口性能,非常适合于工业控制,因此又叫微控制器(MCU) 。FPGA:FPGA则是操控层次更低,所以自由度更大的芯片,对FPGA的编程在编译后是转化为FPGA内的连线表,相当于FPGA内提供了大量的与非门、或非门、触发器(可以用与非门形成吧)等基本数字器件,编程决定了有多少器件被使用以及它们之间的连接。只要FPGA规模够大,这些数字器件理论上能形成一切数字系统,包括单片机甚至CPU。FPGA是作为专用集成电路(ASIC)领域中的一种半定制电路而出现的,既解决了定制电路的不足,又克服了原有可编程器件门电路数有限的缺点。(2)、FPGA和单片机在结构上的区别单片机是一种微处理器,类似于电脑CPU的,它一般采用的是哈佛总线结构,或者冯诺依曼结构,对单片机的编程很大程度上要考虑到它的结构和各个寄存器的作用,单片机用途比较广,一般用在控制流水线上,还有日 常你看得到的东西上!FPGA 它的结构是查找表结构,其程序不用去太考虑芯片的结构,要注意的是时序上问题,它的结构比较复杂,功能也很强大,一般应用在通信领域等比较高端的场合,目前在FPGA还算是一个新兴的行业,当然它的价格也要比单片机贵得多!单片机是一个微控制器,通过加载模块软件来实现某种功能,单片机是成型的芯片;FPGA是用来设计芯片的芯片。(3)、FPGA和单片机速度上的区别FPGA由于是硬件电路,运行速度直接取决于晶振速度,系统稳定,特别适合高速接口电路。单片机由于是单线程,哪怕是常用的M3系列流水线也是单线程执行,程序语句需要等待单片机周期才能执行。(4)、单片机和FPGA的本质区别FPGA和单片机的区别,本质上是软件和硬件的区别,FPGA更偏向于硬件电路,而单片机更偏于软件。单片机设计属软件范畴;它的硬件(单片机芯片)是固定的,通过软件编程语言描述软件指令在硬件芯片上的执行;FPGA设计属硬件范畴,它的硬件(FPGA)是可编程的,是一个通过硬件描述语言在FPGA芯片上自定义集成电路的过程;1.4 DSP和单片机区别从实现运算的角度,单片机、ARM、DSP都可以称之为CPU。DSP是通用数字信号处理器,是一种独特的微处理器,是以数字信号来处理大量信息的器件。它不仅具有可编程性,而且其实时运行速度可达每秒数以千万条复杂指令程序,远远超过通用微处理器。DSP适用于数字信号处理,例如FFT、数字滤波算法、加密算法和复杂控制算法等。DSP实时运行速度可达每秒数以千万条复杂指令程序。DSP器件比16位单片机单指令执行时间快8~10倍,完成一次乘加运算快16~30倍,其采用的设计是数据总线和地址总线分开,使程序和数据分别存储在两个分开的空间,允许取指令和执行指令完全重叠,其工作原理是接收模拟信号,转换为0或1的数字信号,再对数字信号进行修改、删除、强化,并在其他系统芯片中把数字数据解译回模拟数据或实际环境格式,它的强大数据处理能力和高运行速度,是最值得称道的两大特色。DSP芯片,由于它运算能力很强,速度很快,体积很小,而且采用软件编程具有高度的灵活性,因此为从事各种复杂的应用提供了一条有效途径。其主要应用是实时快速地实现各种数字信号处理算法。

基于STM32设计的实时心率检测仪

一、开发环境介绍主控芯片:  STM32F103ZET6代码编程软件: keil5心率检测模块: PulseSensorWIFI模块: ESP8266 --可选的。直接使用串口有线传输给上位机也可以。上位机:  C++(QT) 设计的。 支持PC机电脑、Android手机显示。与上位机的传输协议:  支持串口传输、WIFI网络传输两种。 如果是PC就可以直接连接串口传输数据,如果不方便可以直接通过WIFI---TCP协议传输。代码下载地址: 心率检测二、PulseSensor心率模块介绍   PulseSensor 是一款用于脉搏心率测量的光电反射式模拟传感器。  可以将其佩戴于手指、耳垂、手腕等处,通过杜邦线--导线将引脚连接到单片机,可将采集到的模拟信号传输给单片机,单片机配置ADC用来转换为数字信号,再通过单片机简单计算后就可以得到心率数值;为了方便联动健康管理系统,也方便自己了解自己的心率,可将脉搏波形通过串口、WIFI等方式上传到电脑、手机显示波形,然后根据提前配置的参数,结合算法确定是否正常。   PulseSensor 是一款开源硬件, 目前国外官网上已有其对应的单片机程序,也附带有对应的上位机Processing 程序, 比较适用于心率方面的科学研究和教学演示,也非常适合用于二次开发;上位机也可以自己开发,根据自己的需求定制,达到自己想要的功能。传感器的接口一共 3 个,其中标有S的为模拟信号输出线标有+的为电源输入线(中间);标有-的为地线。总结一下:S → 脉搏信号输出(要接单片机 AD 接口)+ → 5v(或 3.3v)电源输入- → GND 地传感器的硬件参数介绍:传统的测量方法介绍:整个心率传感器的结构如下图: 三、STM32的控制代码STM32的采集代码比较简单,因为就只需要配置对应引脚的ADC功能采集即可。可以采集10次,去掉最大值最小值取平均值,拿到最终结果再传递给上位机显示。3.1 ADC的配置代码/* 函数功能: 初始化ADC1 硬件连接: PA1 --ADC1的通道1 配置的模式:模拟输入 void ADC1_Init(void) /*1. 配置GPIO口*/ RCC->APB2ENR|=1<<2; //开启PA时钟 GPIOA->CRL&=0xFFFFFF0F; GPIOA->CRL|=0x00000000; /*2. 配置ADC相关寄存器*/ RCC->APB2ENR|=1<<9;//开启ADC1时钟 RCC->APB2RSTR|=1<<9; //开启ADC1复位时钟 RCC->APB2RSTR&=~(1<<9);//关闭ADC1复位时钟 RCC->CFGR&=~(0x3<<14); //清除ADC的时钟配置 RCC->CFGR|=0x2<<14; //配置6分频 ADC1->CR2|=1<<20; //开启外部事件转换 ADC1->CR2|=0x7<<17; //SW开关方式控制ADC转换(作为外部事件) ADC1->SMPR2|=0x7<<3; //配置通道1的采样时间 ADC1->CR2|=1<<0;//开启ADC并启动转换 ADC1->CR2|=1<<3;//开启ADC校准初始化 while(ADC1->CR2&1<<3){}//等待初始化完成 ADC1->CR2|=1<<2;//开启ADC校准 while(ADC1->CR2&1<<2){} //等待ADC校准完成 函数功能: 根据传入的通道编号获取一次该通道的ADC值 u16 ADC1_GetData(u8 ch) ADC1->SQR3&=0xFFFFFFE0; //0xE0-->11100000 //清除原来的通道编号 ADC1->SQR3|=ch<<0; //配置现在即将转换的通道号 ADC1->CR2|=1<<22; //开启一次ADC规则通道转换 while(!(ADC1->SR&1<<1)){} //等待转换完成 return ADC1->DR; //读出ADC的结果值 3.2  ESP8266 WIFI 配置代码#include "esp8266.h" 函数功能:向ESP82668266发送命令 函数参数: cmd:发送的命令字符串 ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值: 0,发送成功(得到了期待的应答结果) 1,发送失败 u8 ESP8266_SendCmd(u8 *cmd,u8 *ack,u16 waittime) u8 res=0; USART3_RX_STA=0; USART3_RX_CNT=0; UsartStringSend(USART3,cmd);//发送命令 if(ack&&waittime) //需要等待应答 while(--waittime) //等待倒计时 DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 if(ESP8266_CheckCmd(ack)) res=0; //printf("cmd->ack:%s,%s\r\n",cmd,(u8*)ack); break;//得到有效数据 USART3_RX_STA=0; USART3_RX_CNT=0; if(waittime==0)res=1; return res; 函数功能:ESP8266发送命令后,检测接收到的应答 函数参数:str:期待的应答结果 返 回 值:0,没有得到期待的应答结果 其他,期待应答结果的位置(str的位置) u8* ESP8266_CheckCmd(u8 *str) char *strx=0; if(USART3_RX_STA) //接收到一次数据了 USART3_RX_BUF[USART3_RX_CNT]=0;//添加结束符 strx=strstr((const char*)USART3_RX_BUF,(const char*)str); //查找是否应答成功 //printf("RX=%s",USART3_RX_BUF); return (u8*)strx; 函数功能:向ESP8266发送指定数据 函数参数: data:发送的数据(不需要添加回车) ack:期待的应答结果,如果为空,则表示不需要等待应答 waittime:等待时间(单位:10ms) 返 回 值:0,发送成功(得到了期待的应答结果)luojian u8 ESP8266_SendData(u8 *data,u8 *ack,u16 waittime) u8 res=0; USART3_RX_STA=0; UsartStringSend(USART3,data);//发送数据 if(ack&&waittime) //需要等待应答 while(--waittime) //等待倒计时 DelayMs(10); if(USART3_RX_STA)//接收到期待的应答结果 if(ESP8266_CheckCmd(ack))break;//得到有效数据 USART3_RX_STA=0; USART3_RX_CNT=0; if(waittime==0)res=1; return res; 函数功能:ESP8266退出透传模式 返 回 值:0,退出成功; 1,退出失败 u8 ESP8266_QuitTrans(void) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(15); //大于串口组帧时间(10ms) while((USART3->SR&0X40)==0); //等待发送空 USART3->DR='+'; DelayMs(500); //等待500ms return ESP8266_SendCmd("AT\r\n","OK",20);//退出透传判断. 函数功能:获取ESP8266模块的连接状态 返 回 值:0,未连接;1,连接成功. u8 ESP8266_ConstaCheck(void) u8 *p; u8 res; if(ESP8266_QuitTrans())return 0; //退出透传 ESP8266_SendCmd("AT+CIPSTATUS\r\n",":",50); //发送AT+CIPSTATUS指令,查询连接状态 p=ESP8266_CheckCmd("+CIPSTATUS\r\n:"); res=*p; //得到连接状态 return res; 函数功能:获取ip地址 函数参数:ipbuf:ip地址输出缓存区 void ESP8266_GetWanip(u8* ipbuf) u8 *p,*p1; if(ESP8266_SendCmd("AT+CIFSR\r\n","OK",50))//获取WAN IP地址失败 ipbuf[0]=0; return; p=ESP8266_CheckCmd("\""); p1=(u8*)strstr((const char*)(p+1),"\""); *p1=0; sprintf((char*)ipbuf,"%s",p+1); 四、QT设计的上位机代码 4.1  软件运行效果图 软件有两个版本: 1. 网络版本 2. 串口版本网络版本主要是通过TCP协议传输数据显示,串口版本直接通过串口传输。4.2 widget.cpp代码代码较多,这里就主UI的部分代码。#include "widget.h" #include "ui_widget.h" #define AppFontName "Microsoft YaHei" #define AppFontSize 9 #define TextColor QColor(255,255,255) #define Plot_NoColor QColor(0,0,0,0) //曲线1的颜色 #define HeartRate_Plot_DotColor QColor(236,110,0) #define HeartRate_Plot_LineColor QColor(246,98,0) #define HeartRate_Plot_BGColor QColor(246,98,0,80) //曲线2的颜色 #define HeartRate_Plot_DotColor_2 Qt::blue #define HeartRate_Plot_LineColor_2 Qt::blue #define HeartRate_Plot_BGColor_2 Qt::blue #define TextWidth 1 #define LineWidth 2 #define DotWidth 5 //一个刻度里的小刻度数量--太小的话显示的时间会重叠 #define HeartRate_Plot_Count 5 //Y轴最大范围值 #define HeartRate_Plot_MaxY 3000 * 设置QT界面的样式 void Widget::SetStyle(const QString &qssFile) { QFile file(qssFile); if (file.open(QFile::ReadOnly)) { QString qss = QLatin1String(file.readAll()); qApp->setStyleSheet(qss); QString PaletteColor = qss.mid(20,7); qApp->setPalette(QPalette(QColor(PaletteColor))); file.close(); qApp->setStyleSheet(""); Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); /*服务器线程*/ //开始信号 connect(this,SIGNAL(StartServerThread()),&tcp_server_class,SLOT(run())); //日志信号 connect(&tcp_server_class,SIGNAL(LogSend(QString)),this,SLOT(Log_Display(QString))); //移动到线程 tcp_server_class.moveToThread(&tcp_server_thread); tcp_server_thread.start(); //启动线程 StartServerThread(); //创建服务器 this->setWindowTitle("万邦易嵌-健康监控管家"); //波形图界面初始化 InitForm(); InitPlot(); HeartRate_InitPlot(); HeartRate_LoadPlot(); SetStyle(":/blue.css"); //开始加载数据 plot_timer->start(100); Widget::~Widget() delete ui; //日志显示 void Widget::Log_Display(QString text) QPlainTextEdit *plainTextEdit_log=ui->plainTextEdit_log; //设置光标到文本末尾 plainTextEdit_log->moveCursor(QTextCursor::End, QTextCursor::MoveAnchor); //当文本数量超出一定范围就清除 if(plainTextEdit_log->toPlainText().size()>1024*4) plainTextEdit_log->clear(); plainTextEdit_log->insertPlainText(text); //移动滚动条到底部 QScrollBar *scrollbar = plainTextEdit_log->verticalScrollBar(); if(scrollbar) scrollbar->setSliderPosition(scrollbar->maximum()); //查看服务器状态 void Widget::on_toolButton_server_stat_clicked() QString text="TCP服务器IP地址列表:\n"; QList<QHostAddress> list = QNetworkInterface::allAddresses(); for(int i=0;i<list.count();i++) QHostAddress addr=list.at(i); if(addr.protocol() == QAbstractSocket::IPv4Protocol) text+=addr.toString()+"\n"; text+="TCP服务器端口号:8888\n"; if(ClientSocket) if(ClientSocket->socketDescriptor()==-1) text+="设备未连接\n"; text+="设备连接成功\n"; text+="设备未连接\n"; text+="数据协议:\n"; text+="A:心电数据1,B:新电数据2,C:运动步数,D:运动距离,E:体表温度\n"; text+="例如: \"A:1633215,B:1833215,C:45,D:28,E:66.55\""; QMessageBox::about(this,"状态信息",text); //窗口关闭事件 void Widget::closeEvent(QCloseEvent *event) tcp_server_thread.quit(); tcp_server_thread.wait(); void Widget::InitForm() //初始化随机数种子 QTime time = QTime::currentTime(); qsrand(time.msec() + time.second() * 1000); //初始化动态曲线定时器 plot_timer = new QTimer(this); connect(plot_timer, SIGNAL(timeout()), this, SLOT(HeartRate_LoadPlot())); plots.append(ui->plot2); void Widget::InitPlot() //设置纵坐标名称 plots.at(0)->yAxis->setLabel("心电数据(单位:%)"); //设置纵坐标范围 plots.at(0)->yAxis->setRange(0, HeartRate_Plot_MaxY); //设置支持鼠标移动缩放波形界面 plots.at(0)->setInteractions(QCP::iRangeDrag | QCP::iRangeZoom); //设置背景颜色 #if 1 foreach (QCustomPlot *plot, plots) //设置字体大小 QFont font = QFont(AppFontName, AppFontSize - 2); plot->legend->setFont(font); plot->xAxis->setLabelFont(font); plot->yAxis->setLabelFont(font); plot->xAxis->setTickLabelFont(font); plot->yAxis->setTickLabelFont(font); //设置坐标颜色/坐标名称颜色 plot->yAxis->setLabelColor(TextColor); plot->xAxis->setTickLabelColor(TextColor); plot->yAxis->setTickLabelColor(TextColor); plot->xAxis->setBasePen(QPen(TextColor, TextWidth)); plot->yAxis->setBasePen(QPen(TextColor, TextWidth)); plot->xAxis->setTickPen(QPen(TextColor, TextWidth)); plot->yAxis->setTickPen(QPen(TextColor, TextWidth)); plot->xAxis->setSubTickPen(QPen(TextColor, TextWidth)); plot->yAxis->setSubTickPen(QPen(TextColor, TextWidth)); //设置画布背景色 QLinearGradient plotGradient; plotGradient.setStart(0, 0); plotGradient.setFinalStop(0, 350); plotGradient.setColorAt(0, QColor(80, 80, 80)); plotGradient.setColorAt(1, QColor(50, 50, 50)); plot->setBackground(plotGradient); //设置坐标背景色 QLinearGradient axisRectGradient; axisRectGradient.setStart(0, 0); axisRectGradient.setFinalStop(0, 350); axisRectGradient.setColorAt(0, QColor(80, 80, 80)); axisRectGradient.setColorAt(1, QColor(30, 30, 30)); plot->axisRect()->setBackground(axisRectGradient); //设置图例提示位置及背景色 plot->axisRect()->insetLayout()->setInsetAlignment(0, Qt::AlignTop | Qt::AlignRight); plot->legend->setBrush(QColor(255, 255, 255, 200)); plot->replot(); #endif void Widget::HeartRate_InitPlot() plots.at(0)->addGraph(); plots.at(0)->graph(0)->setName("心电数据1"); plots.at(0)->graph(0)->setPen(QPen(HeartRate_Plot_LineColor, LineWidth)); plots.at(0)->graph(0)->setScatterStyle( QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(HeartRate_Plot_DotColor, LineWidth), QBrush(HeartRate_Plot_DotColor), DotWidth)); //设置动态曲线的横坐标格式及范围 plots.at(0)->xAxis->setTickLabelType(QCPAxis::ltDateTime); plots.at(0)->xAxis->setDateTimeFormat("HH:mm:ss"); plots.at(0)->xAxis->setAutoTickStep(true); plots.at(0)->xAxis->setTickStep(0.5); plots.at(0)->xAxis->setRange(0, HeartRate_Plot_Count, Qt::AlignRight); plots.at(0)->addGraph();//相当于添加一条新的曲线 plots.at(0)->graph(1)->setName("心电数据2"); plots.at(0)->graph(1)->setPen(QPen(HeartRate_Plot_LineColor_2, LineWidth)); plots.at(0)->graph(1)->setScatterStyle( QCPScatterStyle(QCPScatterStyle::ssCircle, QPen(HeartRate_Plot_DotColor_2, LineWidth), QBrush(HeartRate_Plot_DotColor_2), DotWidth)); //设置是否需要显示曲线的图例说明 foreach (QCustomPlot *plot, plots) plot->legend->setVisible(true); plot->replot(); //得到数据指针 mData_0 = plots.at(0)->graph(0)->data(); mData_1 = plots.at(0)->graph(1)->data(); void addToDataBuffer(QCPDataMap *mData,double x, double y) QCPData newData; newData.key = x; newData.value = y; mData->insert(x, newData); //加载曲线数据 void Widget::HeartRate_LoadPlot() int i; bool flag=false; for(i=0;i<5;i++) //得到秒单位的时间 HeartRate_plot_key = QDateTime::currentDateTime().toMSecsSinceEpoch() / 1000.0; //心电数据1 HeartRate_plot_value=uart_queue_data.read_queueA(); if(HeartRate_plot_value>0) flag=true; addToDataBuffer(mData_0,HeartRate_plot_key,HeartRate_plot_value); //心电数据2 HeartRate_plot_value=uart_queue_data.read_queueB(); if(HeartRate_plot_value>0) flag=true; addToDataBuffer(mData_1,HeartRate_plot_key,HeartRate_plot_value); if(flag) plots.at(0)->xAxis->setRange(HeartRate_plot_key, HeartRate_Plot_Count , Qt::AlignRight); plots.at(0)->rescaleAxes(false); //设置图表完全可见 plots.at(0)->replot(); A:心电数据1,B:新电数据2,C:运动步数,D:运动距离,E:体表温度 例如: "A:1633215,B:1833215,C:45,D:28,E:66.55" int val=uart_queue_data.read_queueC(); if(val>0) ui->lcdNumber_bumber->display(val); val=uart_queue_data.read_queueD(); if(val>0) ui->lcdNumber_len->display(val); double tmp_val=uart_queue_data.read_queueE(); if(tmp_val>0) ui->lcdNumber_temp->display(tmp_val); void Widget::on_toolButton_src_data_clicked() ui->stackedWidget->setCurrentIndex(0); void Widget::on_toolButton_image_data_clicked() ui->stackedWidget->setCurrentIndex(1); void Widget::on_toolButton_clear_clicked() mData_0->clear(); mData_1->clear(); void Widget::on_commandLinkButton_clicked() QDesktopServices::openUrl(QUrl("https://blog.csdn.net/xiaolong1126626497/article/details/116694318"));

FFMPEG音视频开发: 发布RTSP流(采用EasyDarwin作为流媒体服务器)

环境介绍当前使用的操作系统:  win10 64位一、部署EasyDarwin服务器据官网介绍,EasyDarwin拥有完整的源代码,可以帮助开发者更快更简单实现流媒体音视频产品功能,使用完全免费;下面就介绍一下EasyDarwin的使用过程。官网地址: EasyDarwin 开源流媒体服务器 Open Source Streaming Server如果官网下载速度比较慢,可以在CSDN上下载:EasyDarwin-windows-10.7z-桌面系统文档类资源-CSDN下载windows系统下搭建RTSP流媒体服务器很好用的框架,可以拉流和收流。使用详情请看博客。Ea更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/xiaolong1126626497/29954155运行成功会弹出一个控制台窗口:然后打开浏览器输入 http://localhost:10008, 进入控制页面,默认用户名密码是admin/admin 二、ffmpeg命令行推rtsp流进行测试win32下使用FFMPEG 4.2.2库下载地址:Windows下32位的FFMPEG4.2.2的库,包括编译运行库和头文件_windows编译ffmpeg-桌面系统代码类资源-CSDN下载https://download.csdn.net/download/xiaolong1126626497/12321684ffmpeg推流本地视频文件到RTSP服务器: (UDP这是采用协议)C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -re -i "D:/BaiduNetdiskDownload/测试视频/Earth_enc_ass.mp4" -vcodec copy -codec copy -f rtsp rtsp://127.0.0.1:554/streamC:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -re -i "D:/BaiduNetdiskDownload/测试视频/Earth_enc_ass.mp4" -vcodec copy -codec copy -f rtsp rtsp://127.0.0.1:554/stream 打开EasyDarwin后台网页可以看到流已经上传上来了。接下来使用VLC、PotPlayer、或者自己编写一个流媒体播放器就可以拉取ffmpeg发布的RTSP流。下面这个是采用自己开发的流媒体播发器播放的效果:这是采用PotPlayer播放器播放的效果:采用TCP协议方式推流:(支持外网更加稳定)C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -re -i "D:/BaiduNetdiskDownload/测试视频/Earth_enc_ass.mp4" -rtsp_transport tcp -vcodec h264 -f rtsp rtsp://127.0.0.1:554/stream 播放测试:三、采用ffmpeg代码方式推rtsp流到服务器对于RTMP和RTSP两种格式需要使用两种不同的封装器。//RTMP封装器 //avformat_alloc_output_context2(&oc,nullptr,"flv",filename); //发布到网络 //RTSP封装器 avformat_alloc_output_context2(&oc,nullptr,"rtsp",filename); //发布到网络 软件运行效果: 网页上已经提示收到发布的rtsp流:采用PotPlayer播放的效果:   效果:  实现桌面同屏/声画同步。如果想自己采用FFMPEG代码方式推流本地摄像头+声卡数据或者摄像头+声卡数据到服务器,可以参考这里:FFMPEG音视频开发: 完成摄像头、桌面本地录制与rtmp推流(windows)_DS小龙哥的专栏-CSDN博客一、基本介绍该软件里推流和视频保存使用FFMPEG库完成,视频和音频可以同步推流和录制,FFMPEG本身支持跨平台编译开发,QT也支持跨平台,在Android、Linux、windows都运行良好,只需要在不同平台编译对应的ffmpeg库即可,逻辑代码部分通用。由于核心代码和在发表博客里的代码差不多这里就不再贴出代码。FFMPEG编程使用参考的链接:(1) 使用NDKR19C编译...https://blog.csdn.net/xiaolong1126626497/article/details/105324396 如果想自己采用FFMPEG代码方式开发流媒体播放器,可以参考这里:QT软件开发: 基于FFMPGE设计的流媒体播放器(rtmp/rtsp)_DS小龙哥的专栏-CSDN博客一、环境介绍操作系统:win10 64位QT版本:QT5.12.6编译器:MinGW 32ffmpeg版本: 4.2.2完整工程下载地址(下载即可编译运行):https://download.csdn.net/download/xiaolong1126626497/20644890本工程使用的FFMPEG库下载地址:https://download.csdn.net/download/xiaolong1126626497/13328939二、功能介绍使用...https://blog.csdn.net/xiaolong1126626497/article/details/119247481

Linux应用开发: SQLite数据库交叉编译部署与运用

一、数据库介绍1.1 数据库简介数据库是以一定方式储存在一起、能与多个用户共享、具有尽可能小的冗余度、与应用程序彼此独立的数据集合,可视为电子化的文件柜——存储电子文件的处所,用户可以对文件中的数据进行新增、查询、更新、删除等操作。数据库是存放数据的仓库。它的存储空间很大,可以存放百万条、千万条、上亿条数据。但是数据库并不是随意地将数据进行存放,是有一定的规则的,否则查询的效率会很低。当今世界是一个充满着数据的互联网世界,充斥着大量的数据。即这个互联网世界就是数据世界。数据的来源有很多,比如出行记录、消费记录、浏览的网页、发送的消息等等。除了文本类型的数据,图像、音乐、声音都是数据。1.2 常用数据库介绍目前关系型数据库主要有MySQL、SQL Server、数蚕数据库、Oracle数据库。MySQL:免费产品,中小企业使用广泛。SQL Server:微软的商业化产品,微软SQL语句兼容性好,商业化成熟度高。数蚕数据库:数蚕科技针对中小型企业的数据库,c++接口特性良好,SQL特性较弱。Oracle 数据库:商业化程度最高的关系数据库, 优良的性能和企业扩展能力。SQLite数据库:是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。SQLite是一个进程内的库,实现了自给自足的、无服务器的、零配置的、事务性的 SQL 数据库引擎。它是一个零配置的数据库,这意味着与其他数据库一样,不需要在系统中配置。1.3 sqlite数据库介绍SQLite数据库是一种嵌入式数据库,他的目标是尽量简单,因此它抛弃了传统企业级数据库的种种复杂特性,只实现对于数据库而言的必备的功能。尽管简单性是SQLite追求的首要目标,但是其功能和性能都非常出色,它具有这样一些特性:支持ACID事务(ACID是Automic、Consisten、Isolated和Durable的缩写)零配置,不需要任何管理性的配置过程支持SQL92标准所有数据存放单独的文件中,支持的最大文件可达2TB数据库可以在不同字节的机器间共享体积小系统开销小,检索效率高简单易用的API接口可以和TCL、Python、C/C++、JAVA、Ruby、Lua、Perl、PHP等多种语言绑定自包含,不依赖于外部支持良好注释的代码代码测试覆盖率高达95%以上开放源码,可用于任何合法途径1.4 sqlite数据库可视化管理工具下载SQLite Administrator是一款轻量级的sqlite可视化工具,主要可用来管理SQLite数据库文件,可进行创建、设计和管理等操作,具有创建数据库、表、视图、索引、触发器、查询等内容的功能。SQLite Administrator提供的代码编辑器具有自动完成和语法着色,支持中文,可用于记录个人资料及开发 SQLite 数据。二、sqlite数据库编译安装(ARM)目标:  将sqlite交叉编译后部署到嵌入式开发板环境下运行。当前使用的目标开发板是: 友善之臂的tiny4412开发板,交叉编译器的版本是官方自带的4.5.1 宿主机采用的是Redhat6.3 、当然使用ubuntu、或者其他发行版都可以。2.1 SQLite数据库下载下载地址:  SQLite Home Page 2.2 编译数据库(ARM)​[wbyq@wbyq pc_work]$ tar xvf /mnt/hgfs/linux-share-dir/sqlite-autoconf-3250200.tar.gz [wbyq@wbyq pc_work]$ cd sqlite-autoconf-3250200/ [wbyq@wbyq sqlite-autoconf-3250200]$ ./configure --host=arm-linux --prefix=$PWD/install [wbyq@wbyq sqlite-autoconf-3250200]$ make && make install [wbyq@wbyq sqlite-autoconf-3250200]$ tree install install ├── bin │ └── sqlite3 ├── include │ ├── sqlite3ext.h │ └── sqlite3.h ├── lib │ ├── libsqlite3.a │ ├── libsqlite3.la │ ├── libsqlite3.so -> libsqlite3.so.0.8.6 │ ├── libsqlite3.so.0 -> libsqlite3.so.0.8.6 │ ├── libsqlite3.so.0.8.6 │ └── pkgconfig │ └── sqlite3.pc └── share └── man └── man1 └── sqlite3.1 7 directories, 10 files [wbyq@wbyq sqlite-autoconf-3250200]$2.3 搭建编译环境和程序运行环境 1. 将生成的库文件拷贝到开发板的lib目录下,方便开发板上执行包含数据库的可执行文件时,能找到动态库。[wbyq@wbyq sqlite-autoconf-3250200]$ cp install/lib/*.so* /home/wbyq/rootfs/lib/ -dvf "install/lib/libsqlite3.so" -> "/home/wbyq/rootfs/lib/libsqlite3.so" "install/lib/libsqlite3.so.0" -> "/home/wbyq/rootfs/lib/libsqlite3.so.0" "install/lib/libsqlite3.so.0.8.6" -> "/home/wbyq/rootfs/lib/libsqlite3.so.0.8.6" 2. 为了交叉编译器在编译,包含数据库的源文件时,方便找到头文件和库文件,需要将生成的库文件和头文件分别拷贝到交叉编译目录下。1.[wbyq@wbyq sqlite-autoconf-3250200]$ sudo cp install/lib/*.so* /home/wbyq/work/arm-linux-gcc/opt/FriendlyARM/toolschain/4.5.1/arm-none-linux-gnueabi/sys-root/usr/lib/ -dvf [wbyq@wbyq sqlite-autoconf-3250200]$ sudo cp install/include/* /home/wbyq/work/arm-linux-gcc/opt/FriendlyARM/toolschain/4.5.1/arm-none-linux-gnueabi/sys-root/usr/include/ -fv2.4 编写例子代码#include <stdio.h> #include <sqlite3.h> /* callback函数只有在对数据库进行select, 操作时才会调用 */ static int select_callback(void *data, int argc, char **argv, char **azColName){ int i; printf("%s", (char*)data); for(i=0; i < argc; i++){ printf("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); printf("\n"); return 0; int main(int argc,char **argv) sqlite3 *db; int err; char *zErrMsg=0; if(argc!=2) printf("./app <数据库文件名称>\n"); return 0; /*1. 创建数据库*/ err=sqlite3_open(argv[1],&db); if(err) printf("Can't open database: %s\n", sqlite3_errmsg(db)); sqlite3_close(db); return 0; /*2. 使用字符串形式构造SQL语言*/ CREATE TABLE 是告诉数据库系统创建一个新表的关键字。CREATE TABLE 语句后跟着表的唯一的名称或标识 char *sql = " CREATE TABLE TempData( \ ID INT PRIMARY KEY, \ DS18B20Name CHAR(7), \ Number INT, \ Time CHAR(12), \ Data FLOAT \ );"; /*3. 使用sql字符串指定的sql语言 创建数据表SensorData*/ sqlite3_exec(db,sql,0, select_callback , &zErrMsg ); /*4. 插入多条数据到数据表*/ sql = "INSERT INTO 'TempData' VALUES(81,'DS18B20',3,'20191109114322',11.4);" ; sqlite3_exec(db,sql,0,select_callback,&zErrMsg ); sql = "INSERT INTO 'TempData' VALUES(84,'DS18B20',6,'20191109114323',22.4);" ; sqlite3_exec(db,sql,0,select_callback,&zErrMsg ); sql = "INSERT INTO 'TempData' VALUES(87,'DS18B20',9,'20191109114323',77.4);" ; sqlite3_exec(db,sql,0,select_callback,&zErrMsg ); /*5. 关闭数据库*/ sqlite3_close(db); printf("数据库关闭成功.\n"); return 0; }2.5 程序编译测试all: arm-linux-gcc sqlite_create.c -o app -lsqlite3 cp app /home/wbyq/rootfs/code rm app -f进入到开发板的控制台终端测试程序。

音视频开发: VS2017编译器编译libVLC错误解决

\VLC\sdk\include\vlc/libvlc_media.h(368): error C2065: “libvlc_media_read_cb”: 未声明的标识符 \VLC\sdk\include\vlc/libvlc_media.h(368): error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int \VLC\sdk\include\vlc/libvlc_media.h(368): error C2513: “int”: 在“=”前没有声明变量 \VLC\sdk\include\vlc/libvlc_media.h(368): error C2143: 语法错误: 缺少“;”(在“(”的前面) \VLC\sdk\include\vlc/libvlc_media.h(368): error C2062: 意外的类型“void” \VLC\sdk\include\vlc/libvlc_media.h(478): error C2061: 语法错误: 标识符“libvlc_media_read_cb” \VLC\sdk\include\vlc/libvlc_media.h(368): error C2065: “libvlc_media_read_cb”: 未声明的标识符 \VLC\sdk\include\vlc/libvlc_media.h(368): error C4430: 缺少类型说明符 - 假定为 int。注意: C++ 不支持默认 int \VLC\sdk\include\vlc/libvlc_media.h(368): error C2513: “int”: 在“=”前没有声明变量 \VLC\sdk\include\vlc/libvlc_media.h(368): error C2143: 语法错误: 缺少“;”(在“(”的前面) \VLC\sdk\include\vlc/libvlc_media.h(368): error C2062: 意外的类型“void” \VLC\sdk\include\vlc/libvlc_media.h(478): error C2061: 语法错误: 标识符“libvlc_media_read_cb” 打开vlc.h加上: typedef __int64 ssize_t;编译完运行异常结束: 需要把 plugins 目录和sdk/dll目录下的文件拷贝到程序运行同级目录下。QT的QThread里的延时函数与VLC里定义的重名了,导致编译错误。可以将VLC里的延时函数屏蔽掉。

STM32F103实现IAP在线升级应用程序

一、环境介绍MCU:  STM32F103ZET6编程IDE:  Keil5.25工程附加源码包下载地址: CSDNhttps://download.csdn.net/download/xiaolong1126626497/25652410二、 IAP介绍     IAP,全称是“In-Application Programming”,中文解释为“在程序中编程”。IAP是一种对通过微控制器的对外接口(如USART,IIC,CAN,USB,以太网接口甚至是无线射频通道)对正在运行程序的微控制器进行内部程序的更新的技术(注意这完全有别于ICP或者ISP技术)。    ICP(In-Circuit Programming)技术即通过在线仿真器对单片机进行程序烧写,而ISP技术则是通过单片机内置的bootloader程序引导的烧写技术。无论是ICP技术还是ISP技术,都需要有机械性的操作如连接下载线,设置跳线帽等。若产品的电路板已经层层密封在外壳中,要对其进行程序更新无疑困难重重,若产品安装于狭窄空间等难以触及的地方,更是一场灾难。但若进引入了IAP技术,则完全可以避免上述尴尬情况,而且若使用远距离或无线的数据传输方案,甚至可以实现远程编程和无线编程。这绝对是ICP或ISP技术无法做到的。某种微控制器支持IAP技术的首要前提是其必须是基于可重复编程闪存的微控制器。STM32微控制器带有可编程的内置闪存,同时STM32拥有在数量上和种类上都非常丰富的外设通信接口,因此在STM32上实现IAP技术是完全可行的。      实现IAP技术的核心是一段预先烧写在单片机内部的IAP程序。这段程序主要负责与外部的上位机软件进行握手同步,然后将通过外设通信接口将来自于上位机软件的程序数据接收后写入单片机内部指定的闪存区域,然后再跳转执行新写入的程序,最终就达到了程序更新的目的。     在STM32微控制器上实现IAP程序之前首先要回顾一下STM32的内部闪存组织架构和其启动过程。STM32的内部闪存地址起始于0x8000000,一般情况下,程序文件就从此地址开始写入。此外STM32是基于Cortex-M3内核的微控制器,其内部通过一张“中断向量表”来响应中断,程序启动后,将首先从“中断向量表”取出复位中断向量执行复位中断程序完成启动。而这张“中断向量表”的起始地址是0x8000004,当中断来临,STM32的内部硬件机制亦会自动将PC指针定位到“中断向量表”处,并根据中断源取出对应的中断向量执行中断服务程序。最后还需要知道关键的一点,通过修改STM32工程的链接脚本可以修改程序文件写入闪存的起始地址。     在STM32微控制器上实现IAP方案,除了常规的串口接收数据以及闪存数据写入等常规操作外,还需注意STM32的启动过程和中断响应方式。下图显示了STM32常规的运行流程: 图解读如下: 1、 STM32复位后,会从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序。2、 复位中断服务程序执行的最终结果是跳转至C程序的main函数,而main函数应该是一个死循环,是一个永不返回的函数。 3、 在main函数执行的过程中,发生了一个中断请求,此时STM32的硬件机制会将PC指针强制指回中断向量表处。 4、 根据中断源进入相应的中断服务程序。 5、 中断服务程序执行完毕后,程序再度返回至main函数中执行。     若在STM32中加入了IAP程序: 1、 STM32复位后,从地址为0x8000004处取出复位中断向量的地址,并跳转执行复位中断服务程序,随后跳转至IAP程序的main函数。2、 执行完IAP过程后(STM32内部多出了新写入的程序,地址始于0x8000004+N+M)跳转至新写入程序的复位向量表,取出新程序的复位中断向量的地址,并跳转执行新程序的复位中断服务程序,随后跳转至新程序的main函数。新程序的main函数应该也具有永不返回的特性。同时应该注意在STM32的内部存储空间在不同的位置上出现了2个中断向量表。 3、 在新程序main函数执行的过程中,一个中断请求来临,PC指针仍会回转至地址为0x8000004中断向量表处,而并不是新程序的中断向量表,注意到这是由STM32的硬件机制决定的。 4、 根据中断源跳转至对应的中断服务,注意此时是跳转至了新程序的中断服务程序中。 5、 中断服务执行完毕后,返回main函数。 二、hex文件与bin文件区别      Intel HEX文件是记录文本行的ASCII文本文件,在Intel HEX文件中,每一行是一个HEX记录,由十六进制数组成的机器码或者数据常量。Intel HEX文件经常被用于将程序或数据传输存储到ROM、EPROM,大多数编程器和模拟器使用Intel HEX文件。       很多编译器的支持生成HEX格式的烧录文件,尤其是Keil c。但是编程器能够下载的往往是BIN格式,因此HEX转BIN是每个编程器都必须支持的功能。HEX格式文件以行为单位,每行由“:”(0x3a)开始,以回车键结束(0x0d,0x0a)。行内的数据都是由两个字符表示一个16进制字节,比如”01”就表示数0x01;”0a”,就表示0x0a。对于16位的地址,则高位在前低位在后,比如地址0x010a,在HEX格式文件中就表示为字符串”010a”。hex和bin文件格式      Hex文件,这里指的是Intel标准的十六进制文件,也就是机器代码的十六进制形式,并且是用一定文件格式的ASCII码来表示。具体格式介绍如下: Intel hex 文件常用来保存单片机或其他处理器的目标程序代码。它保存物理程序存储区中的目标代码映象。一般的编程器都支持这种格式。  hex和bin文件格式Hex文件,这里指的是Intel标准的十六进制文件,也就是机器代码的十六进制形式,并且是用一定文件格式的ASCII码来表示。具体格式介绍如下: Intel hex 文件常用来保存单片机或其他处理器的目标程序代码。它保存物理程序存储区中的目标代码映象。一般的编程器都支持这种格式。 三、使用Keil软件完成hex文件转bin文件选项框里的代码:C:\app_setup\for_KEIL\ARM\ARMCC\bin\fromelf.exe --bin -o ./OBJECT/STM32_MD.bin ./OBJECT/STM32_MD.axf解析如下:C:\app_setup\for_KEIL\ARM\ARMCC\bin\fromelf.exe:是keil软件安装目录下的一个工具,用于生成bin--bin -o ./OBJECT/STM32_MD.bin :指定生成bin文件的目录和名称./OBJECT/STM32_MD.axf  :指定输入的文件. 生成hex文件需要axf文件新工程的编译指令:C:\Keil_v5\ARM\ARMCC\bin\fromelf.exe  --bin -o ./obj/STM32HD.bin ./obj/STM32HD.axf 将该文件下载到STM32内置FLASH,复位开发板,即可启动程序。四、 使用win hex软件将bin文件搞成数组 生成数组之后,可以直接将数组编译到程序里,然后使用STM32内置FLASH编程代码,将该程序烧写到内置FLASH里,再复位开发板即可运行新的程序。五、 Keil编译程序大小计算​ Program Size: Code=x RO-data=x RW-data=x ZI-data=x 的含义Code(代码): 程序所占用的FLASH大小,存储在FLASH.     2. RO-data(只读的数据): Read-only-data,程序定义的常量,如const型,存储在FLASH中。     3. RW-data(有初始值要求的、可读可写的数据):     4. Read-write-data,已经被初始化的变量,存储在FLASH中。初始化时RW-data从flash拷贝到SRAM。     5.  ZI-data:Zero-Init-data,未被初始化的可读写变量,存储在SRAM中。ZI-data不会被算做代码里因为不会被初始化。ROM(Flash) size = Code + RO-data + RW-data;RAM size = RW-data + ZI-data简单的说就是在烧写的时候是FLASH中的被占用的空间为:Code+RO Data+RW Data程序运行的时候,芯片内部RAM使用的空间为: RW Data + ZI Data六、工程编译信息与堆栈信息查看对于没有OS的程序,堆栈大小是在 startup.s 里设置的:  Stack_Size      EQU     0x00000800 对于使用用 uCos 的系统,OS自带任务的堆栈,在 os_cfg.h 里定义:/* ——————— TASK STACK SIZE ———————- */ #define OS_TASK_TMR_STK_SIZE 128 /* Timer task stack size (# of OS_STK wide entries) */ #define OS_TASK_STAT_STK_SIZE 128 /* Statistics task stack size (# of OS_STK wide entries) */ #define OS_TASK_IDLE_STK_SIZE 128 /* Idle task stack size (# of OS_STK wide entries) */ 用户程序的任务堆栈,在 app_cfg.h 里定义:#define APP_TASK_MANAGER_STK_SIZE 512 #define APP_TASK_GSM_STK_SIZE 512 #define APP_TASK_OBD_STK_SIZE 512 #define OS_PROBE_TASK_STK_SIZE 128  总结:1, 合理设置堆栈很重要 2, 多种方法结合,相互核对、校验 3, 尽量避免大数组,如果一定要用,尽量定义为 全局变量,使其不占用堆栈空间, 如果函数有重入可能性,则要注意保护。七、实现STM32在线升级程序 7.1 升级的思路与步骤1. 首先得完成STM32内置FLASH编程操作2. 将(升级的程序)新的程序编译生成bin文件(编译之前需要在Keil软件里设置FLASH的起始位置)3. 创建一个专门用于升级的boot程序(IAP Bootloader)4. 使用网络、串口、SD卡等方式接收到bin文件,再将bin文件烧写到STM32内置FLASH里5. 设置主堆栈指针6. 将用户代码区第二个字(第4个字节)为程序开始地址(强制转为函数指针)7. 执行函数,进行程序跳转7.2 待升级的程序FLASH起始设置Bootloader的程序大小先固定为: 20KB,最好是越小越好,可以预留更加多的空间给APP程序使用。STM32内置FLASH闪存的起始地址是: 0x08000000 ,大小是512KB。现在将内置FLASH闪存前20KB的空间留给Bootloader程序使用,后面剩下的空间就给APP程序使用。设置FLASH的起始位置(APP主程序):中断向量表偏移量设置设置编译bin文件 7.3 Bootloader的程序设置//设置写入的地址,必须偶数,因为数据读写都是按照2个字节进行 #define FLASH_APP_ADDR 0x08005000 //应用程序存放到FLASH中的起始地址 int main() printf("UART1 OK.....\n"); printf("进入IAP Bootloader程序!\n"); while(1) key=KEY_Scanf(); if(key==1) //KEY1按下,写入STM32 FLASH printf("正在更新IAP程序...............\n"); iap_write_appbin(FLASH_APP_ADDR,(u8*)app_bin_data,sizeof(app_bin_data));//烧写新的程序到内置FLASH printf("程序更新成功....\n"); iap_load_app(FLASH_APP_ADDR);//执行FLASH APP代码 函数功能:跳转到应用程序段 appxaddr:用户代码起始地址. typedef void (*iap_function)(void); //定义一个函数类型的参数. void IAP_LoadApp(u32 app_addr) //给函数指针赋值合法地址 jump2app=(iap_function)*(vu32*)(app_addr+4);//用户代码区第二个字为程序开始地址(复位地址) __set_MSP(*(vu32*)app_addr); //设置主堆栈指针 jump2app(); //跳转到APP.

Linux驱动开发: Linux下RTC实时时钟驱动

Linux内核版本: 3.51.1 Linux下RTC时间的读写分析1.1.1 系统时间与RTC实时时钟时间Linux系统下包含两个时间:系统时间和RTC时间。系统时间:是由主芯片的定时器进行维护的时间,一般情况下都会选择芯片上最高精度的定时器作为系统时间的定时基准,以避免在系统运行较长时间后出现大的时间偏移。特点是掉电后不保存。RTC时间:是指系统中包含的RTC芯片内部所维护的时间。RTC芯片都有电池+系统电源的双重供电机制,在系统正常工作时由系统供电,在系统掉电后由电池进行供电。因此系统电源掉电后RTC时间仍然能够正常运行。每次Linux系统启动后在启动过程中会检测和挂载RTC驱动,在挂载后会自动从RTC芯片中读取时间并设置到系统时间中去。此后如果没有显式的通过命令去控制RTC的读写操作,系统将不会再从RTC中去获取或者同步设置时间。linux命令中的date和time等命令都是用来设置系统时间的,而hwclock命令是用来设置和读写RTC时间的。1.1.2  Linux内核RTC实时时钟配置查看与选择:进入到内核根目录下,输入: make menuconfig 进入到内核配置菜单:根据选项进入到RTC实时驱动菜单:根据内核的配置得知3个信息(红色选中的配置选项): 1. 系统时间默认从RTC0里获取时间进行设置。(rtc0表示/dev下第一个rtc驱动,如果安装了第二个RTC驱动,就以rtc1表示,依次类推)2. 使用proc查看RTC信息,默认只能从rtc0节点里获取(系统里的第一个rtc驱动) 3. 内核默认选择CPU本身自带的RTC作为系统实时时钟。驱动源码\linux-3.5\drivers\rtc\ rtc-s3c.c是三星公司编写的RTC驱动。1.1.3 date命令使用介绍date是用来显示或设定系统的日期与时间的命令。命令使用格式: date [参数]... [+格式]命令可以的参数如下:系统时间的方式格式示例1.1.4 系统RTC实时时钟时间的获取与设置1. 将RTC时间同步到系统时间为了在启动时自动执行RTC时间同步到系统时间,可以把hwclock -s命令加入到profile或者rcS文件中。2. 获取显示RTC时间3. 将系统时间同步到RTC,用于设置时间4. 查看RTC的信息1.2 Linux内核RTC子系统结构1.2.1 RTC框架相关的核心文件1. /drivers/rtc/class.c      这个文件向linux设备模型核心注册了一个类RTC,然后向驱动程序提供了注册/注销接口2. /drivers/rtc/rtc-dev.c    这个文件定义了基本的设备文件操作函数,如:open,read等3. /drivers/rtc/interface.c  顾名思义,这个文件主要提供了用户程序与RTC驱动的接口函数,用户程序一般通过ioctl与RTC驱动交互,这里定义了每个ioctl命令需要调用的函数4. /drivers/rtc/rtc-sysfs.c    与sysfs有关5. /drivers/rtc/rtc-proc.c    与proc文件系统有关6. /include/linux/rtc.h      定义了与RTC有关的数据结构Linux内核源码自带的RTC驱动代码存放位置:1.2.2 内核提供的rtc底层注册与注销函数1. RTC框架注册函数使用示例: rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE);使用rtc_device_register函数注册成功之后,在/dev/下可以看到rtcx的设备节点(x是rtc的顺序编号)。2. RTC框架注销函数经过RTC注册函数形参分析,RTC子系统的注册需要通过平台设备框架完成,在平台设备的驱动端的probe函数里进行rtc注册,remove函数里进行注销,在rtc设备端向驱动端传递RTC硬件需要的一些信息。1.2.3 文件操作集合接口 rtc_class_ops 这个结构是RTC驱动程序要实现的基本操作函数。驱动程序通过初始化这样一个结构,将自己实现的函数与RTC核心联系起来。这里面的大部分函数都要驱动程序来实现。而且这些函数都是操作底层硬件的,属于最底层的函数。这个驱动接口与应用层的hwclock命令关联在一起,可以通过hwclock命令调用底层RTC这些函数。RTC子系统里驱动一般只需要实现设置时间和获取时间的函数接口即可,用户可以在应用层通过ioctl函数传入对应的命令调用驱动层的接口,实现时间获取与设置。常用的两个命令:1.2.4 RTC时间结构rtc_time代表了RTC记录的时间与日期,从RTC设备读回的时间和日期就保存在这个结构体中。1.2.5 闹钟结构1.3 编写RTC驱动代码1.3.1 准备工作要测试自己的编写的RTC驱动,提前需要将内核自带的RTC驱动先去除掉,再重新编译烧写内核,再安装测试。以tiny4412开发板为例,去除掉自带的rtc驱动。1. 进入到内核配置菜单: make menuconfig Device Drivers ---> [*] Real Time Clock --->2. 重新编译内核,再重新烧写内核到SD或者EMMC:默认没有RTC驱动的情况下,获取系统时间是从1970年开始的:1.3.2 RTC驱动代码编写—框架示例以下代码只是演示了RTC驱动的注册框架。1. RTC设备端代码:#include "linux/module.h" #include "linux/init.h" #include <linux/platform_device.h> * device 设备端 //释放平台总线 static void pdev_release(struct device *dev) printk("rtc_pdev:the rtc_pdev is close!!!\n"); /*设备端结构体*/ struct platform_device rtc_pdev= /*设备结构体,设备名字很重要!*/ .name = "tiny4412rtc", /*设备名*/ .id = -1, /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/ .dev = /*驱动卸载时调用*/ .release = pdev_release,/*释放资源*/ /*平台设备端入口函数*/ static int __init plat_dev_init(void) platform_device_register(&rtc_pdev);/*注册平台设备端*/ return 0; /*平台设备端出口函数*/ static void __exit plat_dev_exit(void) platform_device_unregister(&rtc_pdev);/*注销平台设备端*/ module_init(plat_dev_init); module_exit(plat_dev_exit); MODULE_LICENSE("GPL");2. RTC驱动端代码 #include <linux/module.h> /*驱动模块相关*/ #include <linux/init.h> #include <linux/fs.h> /*文件操作集合*/ #include <linux/cdev.h> #include <linux/device.h> #include <linux/miscdevice.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/interrupt.h> /*中断相关头文件*/ #include <linux/irq.h> /*中断相关头文件*/ #include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/ #include <linux/wait.h> #include <linux/sched.h> #include <linux/timer.h> /*内核定时器*/ #include <asm-generic/poll.h> #include <linux/poll.h> /* poll机制*/ #include <linux/platform_device.h> /* 平台设备驱动相关头文件*/ #include <linux/rtc.h> static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) printk("获取时间成功\n"); return 0; static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm) printk("设置时间成功\n"); return 0; static int tiny4412_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) printk("getalarm调用成功\n"); return 0; static int tiny4412_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) printk("getalarm调用成功\n"); return 0; static int tiny4412_rtc_proc(struct device *dev, struct seq_file *seq) printk("proc调用成功\n"); return 0; static int tiny4412_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) printk("alarm_irq_enable调用成功\n"); return 0; static int tiny4412_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg) printk("ioctl调用成功\n"); return 0; /*RTC文件操作*/ static const struct rtc_class_ops tiny4412_rtcops = { .read_time = tiny4412_rtc_gettime, .set_time = tiny4412_rtc_settime, .read_alarm = tiny4412_rtc_getalarm, .set_alarm = tiny4412_rtc_setalarm, .proc = tiny4412_rtc_proc, .alarm_irq_enable = tiny4412_rtc_alarm_irq_enable, .ioctl = tiny4412_rtc_ioctl, struct rtc_device *rtc=NULL; /*当设备匹配成功执行的函数-资源探查函数*/ static int drv_probe(struct platform_device *pdev) rtc = rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE); if(rtc==NULL) printk("RTC驱动注册失败\n"); printk("RTC驱动注册成功\n"); return 0; static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/ rtc_device_unregister(rtc); printk("RTC驱动卸载成功\n"); return 0; /*平台设备驱动端结构体-包含和probe匹配的设备名字*/ struct platform_driver drv= .probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/ .remove = drv_remove, /*创建一个remove函数,用于设备退出*/ .driver = .name = "tiny4412rtc", /*设备名称,用来与设备端匹配(非常重要)*/ /*平台驱动端的入口函数*/ static int __init plat_drv_init(void) platform_driver_register(&drv);/*注册平台驱动*/ return 0; /*平台驱动端的出口函数*/ static void __exit plat_drv_exit(void) platform_driver_unregister(&drv);/*释放平台驱动*/ module_init(plat_drv_init); /*驱动模块的入口*/ module_exit(plat_drv_exit); /*驱动模块的出口*/ MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/3. 安装RTC驱动4. 查看生成的RTC的设备节点5. 查看rtc信息查看/proc/driver/rtc文件时,底层驱动函数接口也相继被调用,只不过刚才写的RTC驱动没有完善,所以获取的信息不正确,是默认值。6. 设置RTC时间相关的命令测试通过命令测试,设置时间和获取时间都调用了底层的RTC函数接口,剩下的工作就是完善驱动代码了。1.3.3 完善RTC驱动上一步完成了RTC驱动代码框架编写,这一步就先不添加RTC硬件代码,使用软件方式模拟时间传递给应用层。注意:  内核里RTC时间换算的时间是从:  1900年开始计算的,月份是从0开始的。在给rtc结构赋值时,在正常的年份上需要减去1900,月份再减去1赋值示例://此函数可以通过应用层的ioctl的RTC_RD_TIME命令进行调用 static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) rtc_tm->tm_year=2018-1900; //年 rtc_tm->tm_mon=8-1; //月 rtc_tm->tm_mday=18; //日 rtc_tm->tm_hour=18; //时 rtc_tm->tm_min=18; //分 rtc_tm->tm_sec=18;//秒 printk("从RTC底层获取时间成功!\n"); return 0; }应用层获取的时间如下:完善过后的RTC设备驱动端代码#include <linux/module.h> /*驱动模块相关*/ #include <linux/init.h> #include <linux/fs.h> /*文件操作集合*/ #include <linux/cdev.h> #include <linux/device.h> #include <linux/miscdevice.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/interrupt.h> /*中断相关头文件*/ #include <linux/irq.h> /*中断相关头文件*/ #include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/ #include <linux/wait.h> #include <linux/sched.h> #include <linux/timer.h> /*内核定时器*/ #include <asm-generic/poll.h> #include <linux/poll.h> /* poll机制*/ #include <linux/platform_device.h> /* 平台设备驱动相关头文件*/ #include <linux/rtc.h> //此函数可以通过应用层的ioctl的RTC_RD_TIME命令进行调用 static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) rtc_tm->tm_year=2018-1900; //年 rtc_tm->tm_mon=8-1; //月 rtc_tm->tm_mday=18; //日 rtc_tm->tm_hour=18; //时 rtc_tm->tm_min=18; //分 rtc_tm->tm_sec=18;//秒 printk("从RTC底层获取时间成功!\n"); return 0; //此函数可以通过应用层的ioctl的RTC_SET_TIME命令进行调用 static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm) printk("RTC收到的时间为:%d-%d-%d %d-%d-%d\n",1900 + tm->tm_year, tm->tm_mon, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return 0; //获取闹钟时间 static int tiny4412_rtc_getalarm(struct device *dev, struct rtc_wkalrm *alrm) alrm->enabled=0; //默认闹钟处于关闭状态 alrm->time.tm_year=2018-1900; //年 alrm->time.tm_mon=8-1; //月 alrm->time.tm_mday=18; //日 alrm->time.tm_hour=18; //时 alrm->time.tm_min=18; //分 alrm->time.tm_sec=18; //秒 printk("从RTC底层获取闹钟时间成功!\n"); return 0; //设置闹钟时间 static int tiny4412_rtc_setalarm(struct device *dev, struct rtc_wkalrm *alrm) printk("RTC闹钟设置成功\n"); return 0; //proc接口调用 static int tiny4412_rtc_proc(struct device *dev, struct seq_file *seq) printk("proc调用成功\n"); return 0; //闹钟中断使能 static int tiny4412_rtc_alarm_irq_enable(struct device *dev, unsigned int enabled) printk("alarm_irq_enable调用成功\n"); return 0; //可以实现用户自定义的命令 static int tiny4412_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg) printk("ioctl调用成功\n"); return 0; /*RTC文件操作*/ static const struct rtc_class_ops tiny4412_rtcops = { .read_time = tiny4412_rtc_gettime, .set_time = tiny4412_rtc_settime, .read_alarm = tiny4412_rtc_getalarm, .set_alarm = tiny4412_rtc_setalarm, .proc = tiny4412_rtc_proc, .alarm_irq_enable = tiny4412_rtc_alarm_irq_enable, .ioctl = tiny4412_rtc_ioctl, struct rtc_device *rtc=NULL; /*当设备匹配成功执行的函数-资源探查函数*/ static int drv_probe(struct platform_device *pdev) rtc = rtc_device_register("tiny4412_rtc",&pdev->dev, &tiny4412_rtcops,THIS_MODULE); if(rtc==NULL) printk("RTC驱动注册失败\n"); printk("RTC驱动注册成功\n"); return 0; static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/ rtc_device_unregister(rtc); printk("RTC驱动卸载成功\n"); return 0; /*平台设备驱动端结构体-包含和probe匹配的设备名字*/ struct platform_driver drv= .probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/ .remove = drv_remove, /*创建一个remove函数,用于设备退出*/ .driver = .name = "tiny4412rtc", /*设备名称,用来与设备端匹配(非常重要)*/ /*平台驱动端的入口函数*/ static int __init plat_drv_init(void) platform_driver_register(&drv);/*注册平台驱动*/ return 0; /*平台驱动端的出口函数*/ static void __exit plat_drv_exit(void) platform_driver_unregister(&drv);/*释放平台驱动*/ module_init(plat_drv_init); /*驱动模块的入口*/ module_exit(plat_drv_exit); /*驱动模块的出口*/ MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/安装测试结果:[root@XiaoLong /code]# date Thu Jan 1 00:00:13 UTC 1970 [root@XiaoLong /code]# insmod plat_rtc_device.ko [root@XiaoLong /code]# insmod plat_rtc_drver.ko [ 24.350000] 从RTC底层获取时间成功! [ 24.350000] 从RTC底层获取闹钟时间成功! [ 24.350000] 从RTC底层获取时间成功! [ 24.350000] tiny4412rtc tiny4412rtc: rtc core: registered tiny4412_rtc as rtc0 [ 24.350000] RTC驱动注册成功 [root@XiaoLong /code]# cat /proc/driver/rtc [ 37.085000] 从RTC底层获取时间成功! [ 37.085000] proc调用成功 rtc_time : 18:18:18 rtc_date : 2018-08-18 alrm_time : 18:18:18 alrm_date : 2018-08-18 alarm_IRQ : no alrm_pending : no update IRQ enabled : no periodic IRQ enabled : no periodic IRQ frequency : 1 max user IRQ frequency : 64 24hr : yes [root@XiaoLong /code]# [root@XiaoLong /code]# hwclock -s [ 58.600000] 从RTC底层获取时间成功! [root@XiaoLong /code]# hwclock -r [ 61.020000] 从RTC底层获取时间成功! Sat Aug 18 18:18:18 2018 0.000000 seconds [root@XiaoLong /code]# hwclock -w [ 62.920000] RTC收到的时间为:2018-7-18 18-18-22 [ 62.920000] 从RTC底层获取时间成功! [ 62.920000] alarm_irq_enable调用成功 [root@XiaoLong /code]# date Sat Aug 18 18:18:24 UTC 20181.3.4 RTC应用层代码应用层想要与RTC驱动交互,可以使用ioctl函数特定的一些命令进行。RTC子系统常用的ioctl命令如下://支持的全部命令 在interface.c文件中有使用范例。 这些命令在用户自己写应用层代码时可以用到 RTC_ALM_READ rtc_read_alarm 读取闹钟时间 RTC_ALM_SET rtc_set_alarm 设置闹钟时间 RTC_RD_TIME rtc_read_time 读取时间与日期 RTC_SET_TIME rtc_set_time 设置时间与日期 RTC_PIE_ON RTC_PIE_OFF rtc_irq_set_state 开关RTC全局中断的函数 RTC_AIE_ON RTC_AIE_OFF rtc_alarm_irq_enable 使能禁止RTC闹钟中断 RTC_UIE_OFF RTC_UIE_ON rtc_update_irq_enable 使能禁止RTC更新中断 RTC_IRQP_SET rtc_irq_set_freq 设置中断的频率示例代码:#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/rtc.h> struct rtc_time time; //保存时间值 int main(int argc,char **argv) if(argc!=2) printf("传参格式:/dev/rtc\r\n"); return; int fd=open(argv[1],O_RDWR); // 2==O_RDWR if(fd<0) printf("驱动设备文件打开失败!\r\n"); return 0; time.tm_year=2017; time.tm_mon=10; time.tm_mday=13; time.tm_hour=21; time.tm_min=10; time.tm_sec=10; //注意:年月日必须填写正常,否则会导致底层函数无法调用成功 ioctl(fd,RTC_SET_TIME,&time); //底层自己实现了ioctl函数,设置RTC时间 while(1) ioctl(fd,RTC_RD_TIME,&time); printf("%d-%d-%d %d:%d:%d\r\n",time.tm_year,time.tm_mon,time.tm_mday,time.tm_hour,time.tm_min,time.tm_sec); sleep(1); }1.3.5 标准时间到秒单位时间转换函数硬件上有些RTC实时时钟设置只计算秒数,不提供年月日时分秒格式的时间设置,这时候就需要自己对标准时间进行转换。将标准时间转为秒单位时间:/* * 自01-01-1970就是将公历日期转换为秒。 int rtc_tm_to_time(struct rtc_time *tm, unsigned long *time) *time = mktime(tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); return 0; }1.3.6 DS1302时钟芯片驱动编写示例上面代码都是模拟时钟,学习RTC框架的用法,下面的的代码就加入了实际的RTC硬件,实现完整的RTC计时。DS1302驱动端代码:#include <linux/module.h> /*驱动模块相关*/ #include <linux/init.h> #include <linux/fs.h> /*文件操作集合*/ #include <linux/cdev.h> #include <linux/device.h> #include <linux/miscdevice.h> #include <asm/io.h> #include <asm/uaccess.h> #include <linux/interrupt.h> /*中断相关头文件*/ #include <linux/irq.h> /*中断相关头文件*/ #include <linux/gpio.h> /*硬件相关->定义了寄存器名字与地址*/ #include <linux/wait.h> #include <linux/sched.h> #include <linux/timer.h> /*内核定时器*/ #include <asm-generic/poll.h> #include <linux/poll.h> /* poll机制*/ #include <linux/platform_device.h> /* 平台设备驱动相关头文件*/ #include <linux/rtc.h> #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/delay.h> /*--------------------------------DS1302相关操作代码---------------------------------------------*/ static unsigned char RTC_bin2bcd(unsigned val) return ((val/10)<<4)+val%10; static unsigned RTC_bcd2bin(unsigned char val) return (val&0x0f)+(val>>4)*10; 函数功能:DS1302初始化 Tiny4412硬件连接: CLK :GPB_4 DAT :GPB_5 RST :GPB_6 void DS1302IO_Init(void) /*1. 注册GPIO*/ gpio_request(EXYNOS4_GPB(4), "DS1302_CLK"); gpio_request(EXYNOS4_GPB(5), "DS1302_DAT"); gpio_request(EXYNOS4_GPB(6), "DS1302_RST"); /*2. 配置GPIO口模式*/ s3c_gpio_cfgpin(EXYNOS4_GPB(4), S3C_GPIO_OUTPUT); //时钟 s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT); //数据 // s3c_gpio_cfgpin(EXYNOS4_GPB(2), S3C_GPIO_INPUT); //输入模式 s3c_gpio_cfgpin(EXYNOS4_GPB(6), S3C_GPIO_OUTPUT); //复位 /*3. 上拉GPIO口*/ gpio_set_value(EXYNOS4_GPB(4), 1); //CLK gpio_set_value(EXYNOS4_GPB(5), 1); //DAT gpio_set_value(EXYNOS4_GPB(6), 1); //RST gpio_set_value(EXYNOS4_GPB(6), 0); //RST脚置低 gpio_set_value(EXYNOS4_GPB(4), 0); //SCK脚置低 //#define RTC_CMD_READ 0x81 /* Read command */ //#define RTC_CMD_WRITE 0x80 /* Write command */ //#define RTC_ADDR_RAM0 0x20 /* Address of RAM0 */ //#define RTC_ADDR_TCR 0x08 /* Address of trickle charge register */ //#define RTC_ADDR_YEAR 0x06 /* Address of year register */ //#define RTC_ADDR_DAY 0x05 /* Address of day of week register */ //#define RTC_ADDR_MON 0x04 /* Address of month register */ //#define RTC_ADDR_DATE 0x03 /* Address of day of month register */ //#define RTC_ADDR_HOUR 0x02 /* Address of hour register */ //#define RTC_ADDR_MIN 0x01 /* Address of minute register */ //#define RTC_ADDR_SEC 0x00 /* Address of second register */ //DS1302地址定义 #define ds1302_sec_add 0x80 //秒数据地址 #define ds1302_min_add 0x82 //分数据地址 #define ds1302_hr_add 0x84 //时数据地址 #define ds1302_date_add 0x86 //日数据地址 #define ds1302_month_add 0x88 //月数据地址 #define ds1302_day_add 0x8a //星期数据地址 #define ds1302_year_add 0x8c //年数据地址 #define ds1302_control_add 0x8e //控制数据地址 #define ds1302_charger_add 0x90 #define ds1302_clkburst_add 0xbe //初始时间定义 static unsigned char time_buf[8] = {0x20,0x10,0x06,0x01,0x23,0x59,0x55,0x02};//初始时间2010年6月1号23点59分55秒 星期二 static unsigned char readtime[14];//当前时间 static unsigned char sec_buf=0; //秒缓存 static unsigned char sec_flag=0; //秒标志位 //向DS1302写入一字节数据 static void ds1302_write_byte(unsigned char addr, unsigned char d) unsigned char i; gpio_set_value(EXYNOS4_GPB(6), 1); //启动DS1302总线 //写入目标地址:addr addr = addr & 0xFE; //最低位置零,寄存器0位为0时写,为1时读 for(i=0;i<8;i++) if(addr&0x01){gpio_set_value(EXYNOS4_GPB(5), 1);} else{gpio_set_value(EXYNOS4_GPB(5), 0);} gpio_set_value(EXYNOS4_GPB(4), 1); //产生时钟 gpio_set_value(EXYNOS4_GPB(4), 0); addr=addr >> 1; //写入数据:d for(i=0;i<8;i++) if(d & 0x01) {gpio_set_value(EXYNOS4_GPB(5), 1);} else {gpio_set_value(EXYNOS4_GPB(5), 0);} gpio_set_value(EXYNOS4_GPB(4), 1); //产生时钟 gpio_set_value(EXYNOS4_GPB(4), 0); d = d >> 1; gpio_set_value(EXYNOS4_GPB(6), 0); //停止DS1302总线 //从DS1302读出一字节数据 static unsigned char ds1302_read_byte(unsigned char addr) unsigned char i,temp; gpio_set_value(EXYNOS4_GPB(6), 1);//启动DS1302总线 //写入目标地址:addr addr=addr | 0x01; //最低位置高,寄存器0位为0时写,为1时读 for(i=0; i<8; i++) if(addr & 0x01){gpio_set_value(EXYNOS4_GPB(5), 1);} else {gpio_set_value(EXYNOS4_GPB(5), 0);} gpio_set_value(EXYNOS4_GPB(4), 1); gpio_set_value(EXYNOS4_GPB(4), 0); addr=addr >> 1; s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_INPUT); //输入模式 //输出数据:temp for(i=0; i<8; i++) temp=temp>>1; if(gpio_get_value(EXYNOS4_GPB(5))){temp |= 0x80;} else{temp&=0x7F;} gpio_set_value(EXYNOS4_GPB(4), 1); gpio_set_value(EXYNOS4_GPB(4), 0); s3c_gpio_cfgpin(EXYNOS4_GPB(5), S3C_GPIO_OUTPUT); //输出模式 gpio_set_value(EXYNOS4_GPB(6), 0); //停止DS1302总线 return temp; //向DS302写入时钟数据 static void ds1302_write_time(struct rtc_time *time) ds1302_write_byte(ds1302_control_add,0x00); //关闭写保护 ds1302_write_byte(ds1302_sec_add,0x80); //暂停时钟 //ds1302_write_byte(ds1302_charger_add,0xa9); //涓流充电 /*设置RTC时间*/ //因为DS1302的年份只能设置后两位,所有需要使用正常的年份减去2000,得到实际的后两位 ds1302_write_byte(ds1302_year_add,RTC_bin2bcd(time->tm_year-2000)); //年 ds1302_write_byte(ds1302_month_add,RTC_bin2bcd(time->tm_mon)); //月 ds1302_write_byte(ds1302_date_add,RTC_bin2bcd(time->tm_mday)); //日 ds1302_write_byte(ds1302_hr_add,RTC_bin2bcd(time->tm_hour)); //时 ds1302_write_byte(ds1302_min_add,RTC_bin2bcd(time->tm_min)); //分 ds1302_write_byte(ds1302_sec_add,RTC_bin2bcd(time->tm_sec)); //秒 //ds1302_write_byte(ds1302_day_add,RTC_bin2bcd(time->tm_wday)); //周 time->tm_wday一周中的某一天 ds1302_write_byte(ds1302_control_add,0x80); //打开写保护 static int DS1302_rtc_ioctl(struct device *dev, unsigned int cmd,unsigned long arg) /*设置RTC时间*/ struct rtc_time time; copy_from_user(&time,(const void __user *)arg,sizeof(struct rtc_time)); ds1302_write_time(&time); return 0; //此函数通过应用层的ioctl的RTC_RD_TIME命令进行调用 static int tiny4412_rtc_gettime(struct device *dev, struct rtc_time *rtc_tm) rtc_tm->tm_year=RTC_bcd2bin(ds1302_read_byte(ds1302_year_add))+2000; //年 rtc_tm->tm_mon=RTC_bcd2bin(ds1302_read_byte(ds1302_month_add)); //月 rtc_tm->tm_mday=RTC_bcd2bin(ds1302_read_byte(ds1302_date_add)); //日 rtc_tm->tm_hour=RTC_bcd2bin(ds1302_read_byte(ds1302_hr_add)); //时 rtc_tm->tm_min=RTC_bcd2bin(ds1302_read_byte(ds1302_min_add)); //分 rtc_tm->tm_sec=RTC_bcd2bin((ds1302_read_byte(ds1302_sec_add))&0x7f);//秒,屏蔽秒的第7位,避免超出59 //time_buf[7]=ds1302_read_byte(ds1302_day_add); //周 return 0; //此函数通过应用层的ioctl的RTC_SET_TIME命令进行调用 static int tiny4412_rtc_settime(struct device *dev, struct rtc_time *tm) ds1302_write_time(tm); return 0; /*RTC文件操作*/ static const struct rtc_class_ops DS1302_rtcops = { .ioctl=DS1302_rtc_ioctl, .read_time = tiny4412_rtc_gettime, .set_time = tiny4412_rtc_settime static struct rtc_device *rtc=NULL; /*当设备匹配成功执行的函数-资源探查函数*/ static int drv_probe(struct platform_device *pdev) rtc = rtc_device_register("DS1302RTC",&pdev->dev, &DS1302_rtcops,THIS_MODULE); if(rtc==NULL) printk("RTC驱动注册失败1\n"); printk("RTC驱动注册成功1\n"); /*1. 初始化GPIO口*/ DS1302IO_Init(); msleep(10); return 0; static int drv_remove(struct platform_device *dev)/*当设备卸载后调用这条函数*/ /*释放GPIO口*/ gpio_free(EXYNOS4_GPB(4)); gpio_free(EXYNOS4_GPB(5)); gpio_free(EXYNOS4_GPB(6)); rtc_device_unregister(rtc); printk("RTC驱动卸载成功\n"); return 0; /*平台设备驱动端结构体-包含和probe匹配的设备名字*/ struct platform_driver drv= .probe = drv_probe, /*需要创建一个probe函数,这个函数是对设备进行操作*/ .remove = drv_remove, /*创建一个remove函数,用于设备退出*/ .driver = .name = "DS1302rtc", /*设备名称,用来与设备端匹配(非常重要)*/ /*平台驱动端的入口函数*/ static int __init plat_drv_init(void) platform_driver_register(&drv);/*注册平台驱动*/ return 0; /*平台驱动端的出口函数*/ static void __exit plat_drv_exit(void) platform_driver_unregister(&drv);/*释放平台驱动*/ module_init(plat_drv_init); /*驱动模块的入口*/ module_exit(plat_drv_exit); /*驱动模块的出口*/ MODULE_LICENSE("GPL"); /*驱动的许可证-声明*/DS1320设备端代码 #include "linux/module.h" #include "linux/init.h" #include <linux/platform_device.h> * device 设备端 //释放平台总线 static void pdev_release(struct device *dev) printk("rtc_pdev:the rtc_pdev is close!!!\n"); /*设备端结构体*/ struct platform_device rtc_pdev= /*设备结构体,设备名字很重要!*/ .name = "DS1302rtc", /*设备名*/ .id = -1, /*-1表示创建成功后这边设备的名字就叫myled,若该值为0,1则设备名是myled.0,myled.1...*/ .dev = /*驱动卸载时调用*/ .release = pdev_release,/*释放资源*/ /*平台设备端入口函数*/ static int __init plat_dev_init(void) platform_device_register(&rtc_pdev);/*注册平台设备端*/ return 0; /*平台设备端出口函数*/ static void __exit plat_dev_exit(void) platform_device_unregister(&rtc_pdev);/*注销平台设备端*/ module_init(plat_dev_init); module_exit(plat_dev_exit); MODULE_LICENSE("GPL");

基于STM32移植UCGUI图形界面框架(3.9.0源码版本)

一、环境介绍keil:    5.25MCU:  STM32F103ZET6UCGUI版本:  3.90(纯源码版本)3.9.0是源码版本,可以看到全部源码,也方便学习;后续的版本都是提供lib库文件,不再提供源码了。基于STM32的STemwin移植教程可以看这里: https://blog.csdn.net/xiaolong1126626497/article/details/117933355https://blog.csdn.net/xiaolong1126626497/article/details/117933355本篇文章使用的UCGUI资料包下载:UCGUI图形界面库完整资料包(附带STM32移植教程与示例工程).zip-嵌入式文档类资源-CSDN下载这是UCGUI图形界面库完整资料包(附带STM32移植教程与示例工程)。版本是3.9.0最后提供更多下载资源、学习资料请访问CSDN下载频道.https://download.csdn.net/download/xiaolong1126626497/24238463二、UCGUI介绍2.1 UCGUI      µC/GUI 是一种用于嵌入式应用的图形支持软件。它被设计用于为任何使用一个图形 LCD的应用提供一个有效的不依赖于处理器和 LCD 控制器的图形用户接口。它能工作于单任务或多任务的系统环境下。 µC/GUI 适用于使用任何 LCD 控制和 CPU 的任何尺寸的物理和虚拟显示。它的设计是模块化的,由在不同的模块中的不同的层组成。一个层,称作 LCD 驱动程序,包含了对 LCD 的全部访问。 µC/GUI 适用于所有的 CPU,因为它 100%由的 ANSI 的 C 语言编写的。    µC/GUI 很适合大多数的使用黑色/白色和彩色 LCD 的应用程序。 它有一个很好的颜色管理器,允许它处理灰阶。  µC/GUI 也提供一个可扩展的 2D 图形库和一个视窗管理器,在使用一个最小的 RAM 时能支持显示窗口。    UCGUI官网地址:Micrium Software and Documentation - Silicon Labs2.2  GUI相关文件介绍①.GUI/LCDDriver 文件夹中存放的是一些 LCD 驱动代码,如果你使用的 LCD 在这里可以找到代码,可以直接通过 Config 中的 LCDConfig.h :#define LCD_CONTROLLER -1 // -1:表示没有选择的 LCD 驱动,而是使用里边的样本程序进行修改。uCGUI 中具体支持哪些 LCD 可以查询《uCGUI 用户手册》的第 22 章 LCD 驱动程序。里边详细的说明了,支持些什么控制器的 LCD。②.在工程中只需加载需要的 LCD 驱动代码文件即可。如果设置为-1,则选择加载 LCDDummy.C 或 LCDTemplate.C 文件(不同的版本,此代码的文件名可能会不同)。文件夹的主要内容如下: Config  -----------    配置文件 GUI    -----------    源代码 GUI_X  ----------    操作系统接口函数定义文件   GUI 源代码文件: 1) AntiAlias:  抗锯齿显示效果支持。 2) ConvertColor: 彩色显示的色彩转换支持。 3) ConvertMono: (b/w)和灰度显示的色彩转换支持。 4) Core: 核心文件,提供了GUI基本的功能。 5) Font: 字库。 6) JPEG: 图片操作函数。 7) LCDDriver: LCD驱动支持。 8) MemDev: 内存设备支持。主要功能是防止在项目重叠时触摸屏的闪烁。 9) Widget: 窗体控件库。 10) WM: 窗口管理库。  注意:JPEG、MemDev、Widget、WM是可裁剪项,若要支持Widget(窗体控件),需要   WM(窗口管理器)的支持;使用控件时,需要将相应的头文件包含进去,比如我们需要使用按钮BUTTON,那么我们需要先包含BUTTON.h头文件,否则控件即使支持也不可用。二、移植步骤移植准备工作:一个块STM32开发板一个完好的LCD显示屏一个完整的基于开发板的KEIL工程(包含完整的LCD驱动代码)一个完整的UCGUI 3.9源码包2.1 创建文件夹首先在KEIL工程目录下创建一个UCGUI的文件夹,用来存放移植需要用到的源码文件。效果图:2.2 拷贝源码文件    将GUI_V3.9_官方源码\uCGUI3.90版源码\Start路径下的Config文件夹和GUI文件拷贝到刚才在KEIL工程目录下创建的UCGUI文件夹里。   效果图:     将GUI_V3.9_官方源码\uCGUI3.90版源码\Sample路径下的GUI_X文件夹拷贝到刚才在KEIL工程目录下创建的UCGUI文件夹里。(GUI_X文件夹是操作系统的接口)    效果图:2.3 创建UCGUI工程打开KEIL工程,添加UCGUI源代码。    根据 UCGUI\GUI 目录下的文件创建KEIL的工程目录,名字最好用源码默认的名字不要修改,以防止后面查找不方便。额外再添加创建一个UCGUI_Config目录,用来存放UCGUI的配置文件创建好的工程目录—效果图:2.4 添加UCGUI源文件工程创建OK之后,将对应源文件添加到KEIL工程目录下,将对应的头文件加入工程。效果图:添加的源文件和头文件需要对照GUI文件夹逐个添加,不能漏掉文件。源文件只添加.c 。其他文件一律不添加。UCGUI_Config文件夹添加的文件如下所示:UCGUI\GUI_X\GUI_X.c //OS系统接口 UCGUI\Config\GUITouchConf.h //配置触摸屏 UCGUI\Config\ GUIConf.h //配置GUI UCGUI\Config\ LCDConf.h //配置LCD显示屏参数 效果图:2.5 修改配置文件修改LCD配置文件,打开LCDConf.h文件,替换下面的代码:#ifndef LCDCONF_H #define LCDCONF_H #define LCD_XSIZE (240) /* 水平分辨率X-resolution of LCD, Logical coor. */ #define LCD_YSIZE (320) /* 垂直分辨率Y-resolution of LCD, Logical coor. */ #define LCD_BITSPERPIXEL (16) /*lcd 颜色深度*/ #define LCD_CONTROLLER (-1) /*lcd 控制器的具体型号*/ #define LCD_FIXEDPALETTE (565) /*RGB 颜色位数*/ #define LCD_SWAP_RB (1) /*红蓝反色交换*/ #define LCD_INIT_CONTROLLER() LCD9341_Init (); /*底层初始化函数,自己写的,而非源码自带,这一步非常重要*/ #endif /* LCDCONF_H */因为我们有LCD的驱动,不需要UCGUI的LCD驱动配置。其中:LCD9341_Init (); 函数是我们自己工程的LCD初始化函数。我们的LCD初始化函数名字不能是LCD_Init(),因为UCGUI自带的LCD初始化函数也是这个名字,我们自己的工程里也不能出现LCD名字的结构体。不然,会出现重定义的错误。 修改UCGUI配置文件,打开GUIConf.h文件,修改成下面的代码:#ifndef GUICONF_H #define GUICONF_H #define GUI_OS (0) /* 系统支持 */ #define GUI_SUPPORT_TOUCH (0) /* 支持触摸屏 */ #define GUI_SUPPORT_UNICODE (0) /* 支持混合ASCII / UNICODE字符串 */ #define GUI_DEFAULT_FONT &GUI_Font6x8 //#define GUI_ALLOC_SIZE 12500 /* Size of dynamic memory ... For WM and memory devices*/ #define GUI_ALLOC_SIZE 1024*1024 /* Size of dynamic memory ... For WM and memory devices*/ #define GUI_WINSUPPORT 0 /* 窗口管理器包可用 */ #define GUI_SUPPORT_MEMDEV 0 /* 内存设备可用 */ #define GUI_SUPPORT_AA 0 /* 抗锯齿可用 */ #endif /* Avoid multiple inclusion */ 初次移植,将不需要用到的配置全部关闭,等UCGU的基本操作熟悉之后再打开使用。2.6 修改UCGUI底层驱动打开LCDDummy.c文件,添加UCGUI底层的画点函数和读点函数。先在LCDDummy.c里加入LCD头文件。并且定义使用自己的LCD驱动。添加画点函数该函数在LCDDummy.c文件(大约382行)处。void LCD_L0_SetPixelIndex(int x, int y, int PixelIndex) /* Convert logical into physical coordinates (Dep. on LCDConf.h) */ #if LCD_SWAP_XY | LCD_MIRROR_X| LCD_MIRROR_Y int xPhys = LOG2PHYS_X(x, y); int yPhys = LOG2PHYS_Y(x, y); #else #define xPhys x #define yPhys y #endif /* Write into hardware ... Adapt to your system */ LCD_DrawPoint_color(x,y,PixelIndex); //添加画点函数 } 添加读点函数该函数在LCDDummy.c文件(大约407行)处。1.unsigned int LCD_L0_GetPixelIndex(int x, int y) { LCD_PIXELINDEX PixelIndex; /* Convert logical into physical coordinates (Dep. on LCDConf.h) */ #if LCD_SWAP_XY | LCD_MIRROR_X| LCD_MIRROR_Y int xPhys = LOG2PHYS_X(x, y); int yPhys = LOG2PHYS_Y(x, y); #else #define xPhys x #define yPhys y #endif /* Read from hardware ... Adapt to your system */ PixelIndex=LCD_ReadPoint(x,y); //添加读点函数 return PixelIndex; }2.7 编译工程文件修改完毕之后,回到主函数,添加#include "GUI.h"头文件。并在主函数里加入下面的代码,测试GUI移植是否成功。1.GUI_Init(); //GUI初始化 GUI_SetBkColor(GUI_BLUE); //设置颜色 GUI_Clear(); //清屏 GUI_GotoXY(60,50); //设置字符显示的XY坐标 GUI_DispString("Hello World!!"); //显示字符 GUI_DrawCircle(100,200,50); //画圆 代码写完,编译工程,编译时间1-10分钟左右(电脑性能决定)。  效果图:编译成功之后,将代码下载到开发板运行。效果如图:2.8 编译错误解决办法如果编译出现下面的错误:..\OBJ\KEY.axf: Error: L6218E: Undefined symbol exit (referred from jerror.o).根据错误提示,打开jerror.c文件,找到error_exit函数,将该函数的最后一行代码exit(EXIT_FAILURE); 该为return 。改完,再次编译,错误解决。效果图:方法:查找自己原来的工程里是否有以LCD命名的相关结构体,查看原来工程里的LCD初始化函数是否是LCD_Init(), 如果是,就修改函数名字。一般解决了上述的问题,移植一般都没有问题。出现了问题,可以查看错误信息,判断是什么错误,针对性解决。三、加入触摸屏    加入触摸屏功能之前,要保证原本工程已经有正常的触摸屏驱动代码,能正确的转换触摸屏的X Y坐标值。3.1 打开GUIConf.h文件,修改当前GUI支持触摸屏#define GUI_SUPPORT_TOUCH (1) /* 支持触摸屏 */ #define GUI_ALLOC_SIZE 5000 //修改内存空间改大一点,防止编译不过 效果图:3.2 打开GUITouchConf.h文件,替换成下边代码。#ifndef GUITOUCH_CONF_H #define GUITOUCH_CONF_H #define GUI_TOUCH_AD_LEFT 0 //屏幕左边尺寸 #define GUI_TOUCH_AD_RIGHT 240 //屏幕右边尺寸 #define GUI_TOUCH_AD_TOP 0 //屏幕顶端尺寸 #define GUI_TOUCH_AD_BOTTOM 320 //屏幕底部尺寸 #define GUI_TOUCH_SWAP_XY 0 //是否交换坐标 #define GUI_TOUCH_MIRROR_X 0 //设置镜像X #define GUI_TOUCH_MIRROR_Y 0 //设置镜像Y #endif /* GUITOUCH_CONF_H */ 效果图:3.3 将GUI_X_Touch.c文件加入到工程UCGUI_Config目录。GUI_X_Touch.c在UCGUI\GUI_X路径下。并修改获取X Y 坐标代码,加入触摸屏驱动头文件。修改代码如下:#include "GUI.h" #include "GUI_X.h" #include "touch.h" //头文件 void GUI_TOUCH_X_ActivateX(void) { void GUI_TOUCH_X_ActivateY(void) { int GUI_TOUCH_X_MeasureX(void) { //添加获取触摸X坐标代码 Touch_check(); //扫描触摸屏 return TOUCH.x; //X坐标 int GUI_TOUCH_X_MeasureY(void) { //添加获取触摸Y坐标代码 Touch_check(); //扫描触摸屏 return TOUCH.y; //Y坐标 }效果图:3.4 添加初始化函数上边步骤完成之后,在主函数添加(自己工程的)触摸屏的初始化函数,触摸屏即可正常运行。3.5 轮询检测触摸屏如果GUI没有加入系统,需要定义一个定时器去扫描触摸屏,在定时器的中断服务函数里加入GUI_TOUCH_Exec();函数。扫描的频率为:10毫秒一次如果GUI加入了系统,可以创建一个单独的任务,在任务里添加GUI_TOUCH_Exec();第四章 加入UCOSII系统4.1 移植准备步骤本小节的的移植是基于UCOSII系统的移植移植系统之前的准备工作:一份UCOSII源码熟悉UCOSII的基本操作(创建工程和任务编写)打开GUIConf.h文件,修改当前GUI支持系统#define GUI_OS (1) /* 系统支持 */ 效果图:4.2 在GUI.h加入UCOSII的头文件。添加相关头文件路径效果图:4.3 添加UCOSII源码到工程目录效果图:注意:加载源码的时候,不能将ucos_ii.c加入到工程。加入了ucos_ii.c文件会出现重定义。”ucos_ii.c的代码作用是加载源文件,因为UCOSII其他源文件我们已手动加入到工程,已不需要ucos_ii.c的代码”。4.4 设置UCOSII任务调度时间基准初始化滴答时钟,开启滴答时钟中断,设置滴答时钟10毫秒中断一次。(用任何一个硬件定时器都可以代替)在滴答时钟中断服务函数里加入下面的代码:(记得在滴答定时器中断函数的代码文件里引用UCOSII的头文件:#include "includes.h" )/* 滴答时钟中断服务函数 void SysTick_Handler(void) OSIntEnter(); //进入中断 OSTimeTick(); //调用ucos的时钟服务程序 OSIntExit(); //触发任务切换软中断 }效果图:4.5 修改错误与正确的宏4.6 添加UCOS支持文件在进行上边的步骤,之后,编译工程,会出现如下的错误:Build target 'UCGUI移植' linking... ..\OBJ\KEY.axf: Error: L6218E: Undefined symbol GUI_X_GetTaskId (referred from guitask.o). ..\OBJ\KEY.axf: Error: L6218E: Undefined symbol GUI_X_InitOS (referred from guitask.o). ..\OBJ\KEY.axf: Error: L6218E: Undefined symbol GUI_X_Lock (referred from guitask.o). ..\OBJ\KEY.axf: Error: L6218E: Undefined symbol GUI_X_Unlock (referred from guitask.o). Not enough information to list image symbols. Finished: 1 information, 0 warning and 4 error messages. "..\OBJ\KEY.axf" - 4 Error(s), 0 Warning(s). Target not created 根据报错提示信息,打开GUITask.c 文件。GUITask.c有提示需要在GUI_X.c文件实现几个函数。根据提示,继续打开GUI_X.C 。前边有提到,GUI_X.C文件主要是提供OS系统接口,配置/系统相关的外部环境。效果图:我们当前移植的OS是UCOSII系统,打开KEIL工程路径下的GUI_X文件夹:效果图: 该目录下有6个与系统接口相关的文件,我们移植的是UCOS系统,其中GUI_X_uCOS.c文件是UCOSII系统的接口。我们将GUI_X_uCOS.c文件加入到工程效果图: 将GUI_X.C文件的三个底层函数拷贝一份到GUI_X_uCOS.c文件void GUI_X_Log (const char *s) { GUI_USE_PARA(s); } void GUI_X_Warn (const char *s) { GUI_USE_PARA(s); } void GUI_X_ErrorOut (const char *s) { GUI_USE_PARA(s); } 效果图:完成上面的步骤之后,将GUI_X.c文件从工程中卸载掉,因为GUI_X_uCOS.c与GUI_X.c文件实现的函数有很多是相同的,不卸载GUI_X.c会出现重定义的错误。接着修改GUI_X_uCOS.c文件,替换UCOS的延时函数。(大约在78行)。void GUI_X_ExecIdle (void) //OS_X_Delay(1); OSTimeDly(50); //UCOS延时函数 } 效果图:4.7 建立任务测试效果//开始任务 void start_task(void *pdata) OS_CPU_SR cpu_sr=0; pdata = pdata; OS_ENTER_CRITICAL(); //进入临界区(无法被中断打断) OSTaskCreate(led0_task,(void *)0,(OS_STK*)&LED0_TASK_STK[LED0_STK_SIZE-1],LED0_TASK_PRIO); //创建的任务 OSTaskCreate(led1_task,(void *)0,(OS_STK*)&LED1_TASK_STK[LED1_STK_SIZE-1],LED1_TASK_PRIO); OSTaskSuspend(START_TASK_PRIO); //挂起起始任务. OS_EXIT_CRITICAL(); //退出临界区(可以被中断打断) //LED0任务 void led0_task(void *pdata) u8 i; u16 cnt=0; /*************************************** 画一个进度条控件 ****************************************/ hProgBar_1 = PROGBAR_Create(0, 0, 240, 40, WM_CF_SHOW); //设置进度条的大小坐标参数 PROGBAR_SetBarColor(hProgBar_1,0,GUI_GREEN); //参数(句柄,1(0)代表本函数是显示进度条覆盖的区域还未覆盖的区域,进度条覆盖的颜色) PROGBAR_SetBarColor(hProgBar_1,1,GUI_RED); //参数(句柄,1(0)代表本函数是显示进度条覆盖的区域还未覆盖的区域,进度条未覆盖的颜色) PROGBAR_SetValue(hProgBar_1,99); //参数(句柄 ,99是进度条显示的99%) while(1) i=!i; LED2(i); LED3(i); PROGBAR_SetValue(hProgBar_1,cnt); //显示进度条1控件 的进度 OSTimeDlyHMSM(0,0,1,0); //将一个任务延时若干时间(设定时、 分、 秒、 毫秒) WM_Exec(); cnt++; if(cnt>=100) cnt=0; //LED1任务 void led1_task(void *pdata) u8 i; u16 cnt; hProgBar_2 = PROGBAR_Create(0, 80, 240, 40, WM_CF_SHOW); //设置进度条的大小坐标参数 PROGBAR_SetBarColor(hProgBar_2,0,GUI_GREEN); //参数(句柄,1(0)代表本函数是显示进度条覆盖的区域还未覆盖的区域,进度条覆盖的颜色) PROGBAR_SetBarColor(hProgBar_2,1,GUI_RED); //参数(句柄,1(0)代表本函数是显示进度条覆盖的区域还未覆盖的区域,进度条未覆盖的颜色) while(1) //GUIDEMO_main(); //运行DEMO代码 i=!i; LED1(i); LED4(i); PROGBAR_SetValue(hProgBar_2,cnt); //显示进度条2控件 的进度 WM_Exec(); //显示生效 OSTimeDlyHMSM(0,0,0,500); //将一个任务延时若干时间(设定时、 分、 秒、 毫秒) cnt++; if(cnt>=100) cnt=0; } 效果图:第五章 移植DEMO代码 将DEMO文件全部加入工程编译,最后运行DEMO代码。//LED0任务 void led0_task(void *pdata) u8 i; while(1) i=!i; LED2(i); LED3(i); GUI_TOUCH_Exec(); //监视和刷新触摸板 OSTimeDlyHMSM(0,0,0,10); //将一个任务延时若干时间(设定时、 分、 秒、 毫秒) //LED1任务 void led1_task(void *pdata) u8 i; while(1) GUIDEMO_main(); //运行DEMO代码 i=!i; LED1(i); LED4(i); OSTimeDlyHMSM(0,0,0,10); //将一个任务延时若干时间(设定时、 分、 秒、 毫秒) } 系统时间计算如果跑UCOS系统可以设置UCOS工作频率高一些:  #define OS_TICKS_PER_SEC  1000   //一秒的节拍时间。节拍时间计算方式:滴答时钟中断时间 * 节拍次数 =  1秒第六章 常用相关函数解析6.1 初始化函数6.2 设置XY坐标6.3 设置LCD背景颜色6.4 设置LCD前景颜色6.5 在当前坐标显示文本6.6 指定坐标显示文本6.7 显示文本API函数集合6.8 选择文本绘图模式6.9 选择文本对齐方式6.10 设置当前文本坐标6.11 返回当前文本坐标6.12 清除视窗相关函数​第七章 储存设备默认情形下,存储设备是被激活的。为了优化软件的性能,对存储设备的支持可以在配置文件 GUIConf.h 中加入下面一行而关闭:#define GUI_SUPPORT_MEMDEV  0要是使用时,需要将宏开关打开:

C/C++程序开发: cJSON的使用(创建与解析JSON数据)

一、cJSON介绍      cJSON 是一个超轻巧,携带方便,单文件,可以作为 ANSI-C 标准的 JSON 解析器,是一个用C语言编写的简单好用的JSON解析器;它只包含一个C文件和一个头文件,可以非常容易集成到自己工程项目中。        并且cJSON是用ANSI C(C89)编写的,可以兼容所有支持C语言的平台和编译器。        cJSON下载地址:  cJSON download | SourceForge.nethttps://sourceforge.net/projects/cjson/       cJSON的GitHub仓库地址:https://github.com/DaveGamble/cJSONhttps://github.com/DaveGamble/cJSON二、JSON简介、语法介绍 2.1 JSON是什么?    JSON是JavaScript Object Notation(JavaScript对象表示法),是一种轻量级的数据交换格式。        JSON主要是用来存储和交换文本信息,类似XML格式;但是JSON比XML更小、更快,更易解析。    JSON是基于ECMAScript (欧洲计算机协会制定的js规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。       简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。 易于人阅读和编写,同时也易于机器解析和生成,并有效地提升网络传输效率。 比如: Web服务器接口基本都是采用JSON反馈数据,采用JSON格式字符串来描述符信息。 JSON文件的后缀一般是.json,这个只是为了方便辨识。   简单的说,JSON就是按照指定格式序列化的字符串,就算不使用任何现成的解析库,自己也可以按照正常解析字符串的思路去解析;有现成的标准JSON解析库,那就大大减轻了我们的工作量。     JSON格式的数据示例:  这是表示当前时间的JSON字符串{ "success": "1", "result": { "timestamp": "1631849514", "datetime_1": "2021-09-17 20:31:54", "datetime_2": "2021年09月17日 20时31分54秒", "week_1": "5", "week_2": "星期五", "week_3": "周五", "week_4": "Friday" }   JSON格式的数据示例:  这是表示未来几天天气预报的json字符串{ "success": "1", "result": [ "weaid": "1", "days": "2021-09-17", "week": "星期五", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "26℃/17℃", "humidity": "0%/0%", "weather": "晴", "weather_icon": "http://api.k780.com/upload/weather/d/0.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/0.gif", "wind": "北风转西南风", "winp": "小于3级", "temp_high": "26", "temp_low": "17", "humi_high": "0", "humi_low": "0", "weatid": "1", "weatid1": "1", "windid": "8", "winpid": "0", "weather_iconid": "0", "weather_iconid1": "0" "weaid": "1", "days": "2021-09-18", "week": "星期六", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "25℃/17℃", "humidity": "0%/0%", "weather": "多云", "weather_icon": "http://api.k780.com/upload/weather/d/1.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/1.gif", "wind": "西南风", "winp": "小于3级", "temp_high": "25", "temp_low": "17", "humi_high": "0", "humi_low": "0", "weatid": "2", "weatid1": "2", "windid": "5", "winpid": "0", "weather_iconid": "1", "weather_iconid1": "1" "weaid": "1", "days": "2021-09-19", "week": "星期日", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "19℃/15℃", "humidity": "0%/0%", "weather": "小雨转中雨", "weather_icon": "http://api.k780.com/upload/weather/d/7.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/8.gif", "wind": "西南风转北风", "winp": "小于3级转小于3级", "temp_high": "19", "temp_low": "15", "humi_high": "0", "humi_low": "0", "weatid": "8", "weatid1": "9", "windid": "5", "winpid": "0", "weather_iconid": "7", "weather_iconid1": "8" "weaid": "1", "days": "2021-09-20", "week": "星期一", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "26℃/16℃", "humidity": "0%/0%", "weather": "多云转晴", "weather_icon": "http://api.k780.com/upload/weather/d/1.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/0.gif", "wind": "北风", "winp": "3-4级转3-4级", "temp_high": "26", "temp_low": "16", "humi_high": "0", "humi_low": "0", "weatid": "2", "weatid1": "1", "windid": "8", "winpid": "1", "weather_iconid": "1", "weather_iconid1": "0" "weaid": "1", "days": "2021-09-21", "week": "星期二", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "27℃/16℃", "humidity": "0%/0%", "weather": "晴", "weather_icon": "http://api.k780.com/upload/weather/d/0.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/0.gif", "wind": "西北风转北风", "winp": "小于3级", "temp_high": "27", "temp_low": "16", "humi_high": "0", "humi_low": "0", "weatid": "1", "weatid1": "1", "windid": "7", "winpid": "0", "weather_iconid": "0", "weather_iconid1": "0" "weaid": "1", "days": "2021-09-22", "week": "星期三", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "26℃/18℃", "humidity": "0%/0%", "weather": "多云", "weather_icon": "http://api.k780.com/upload/weather/d/1.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/1.gif", "wind": "北风转东北风", "winp": "小于3级", "temp_high": "26", "temp_low": "18", "humi_high": "0", "humi_low": "0", "weatid": "2", "weatid1": "2", "windid": "8", "winpid": "0", "weather_iconid": "1", "weather_iconid1": "1" "weaid": "1", "days": "2021-09-23", "week": "星期四", "cityno": "beijing", "citynm": "北京", "cityid": "101010100", "temperature": "24℃/16℃", "humidity": "0%/0%", "weather": "多云", "weather_icon": "http://api.k780.com/upload/weather/d/1.gif", "weather_icon1": "http://api.k780.com/upload/weather/n/1.gif", "wind": "东北风", "winp": "小于3级", "temp_high": "24", "temp_low": "16", "humi_high": "0", "humi_low": "0", "weatid": "2", "weatid1": "2", "windid": "1", "winpid": "0", "weather_iconid": "1", "weather_iconid1": "1" }2.2  JSON语法介绍JSON里就分为两种结构:  对象和数组,通过这两种结构可以表示各种复杂的结构。JSON语法规则1. 大括号 { } 用来保存对象2. 中括号 [ ] 用来保存数组,数组里也可以包含多个对象,对象里又可以包含数组,可以嵌套3. JSON的值表示语法:  key : value   -->   "width": 12804. 多个数据由逗号分隔:  {"width": 1920,"height": 1080}JSON值可以是以下几种类型:1. 数字(整数或浮点数)  2. 字符串(在双引号中)3. 逻辑值(true 或 false)4. 数组(在中括号中)5. 对象(在大括号中)6. null (空值)三、cJSON创建简单JSON数据并解析3.1 新建工程这是下载下来的cJSON源文件,将它加到自己工程中即可。 我这里使用VS2017建立工程,演示实例。建好工程之后,将文件添加到工程里:在VS2017里使用C语言的字符串处理函数会报错,提示不安全;1>d:\linux-share-dir\vs2017\console_cjsontest\console_cjsontest\cjson.c(155): error C4996: 'strcpy': This function or variable may be unsafe. Consider using strcpy_s instead. To disable deprecation, use _CRT_SECURE_NO_WARNINGS. See online help for details.解决办法是:找到【项目属性】,点击【C++】里的【预处理器】,对【预处理器】进行编辑,在里面加入一段代码:_CRT_SECURE_NO_WARNINGS。3.2 创建JSON数据  接下来目标是使用cJSON创建出下面这样一个JSON格式数据:{ "text": "我是一个字符串数据", "number": 666, "state1": false, "state2": true, "state3": null }示例代码如下:#include <iostream> //因为当前工程使用的是cpp后缀文件,引用C语言的文件需要使用下面的这种方式 extern "C" { #include <string.h> #include <stdio.h> #include "cJSON.h" int main() //1. 创建cJSON对象 cJSON* root = cJSON_CreateObject(); //2. 创建数据 cJSON_AddStringToObject(root, "text","我是一个字符串数据"); cJSON_AddNumberToObject(root,"number",666); cJSON_AddBoolToObject(root, "state1", cJSON_False); cJSON_AddBoolToObject(root, "state2", cJSON_True); cJSON_AddNullToObject(root, "state3"); //3. 打印生成的结果 char *json_data=cJSON_PrintUnformatted(root); printf("%s\n",json_data); //4. 释放空间 cJSON_Delete(root); return 0; 3.3 解析JSON数据 1.#include <iostream> //因为当前工程使用的是cpp后缀文件,引用C语言的文件需要使用下面的这种方式 extern "C" { #include <string.h> #include <stdio.h> #include "cJSON.h" //将要解析的JSON数据. char data[] = "{" "\"text\": \"我是一个字符串数据\"," "\"number\" : 666," "\"state1\" : false," "\"state2\" : true," "\"state3\" : null" "}"; int main() //1. 载入JSON数据 cJSON* root = cJSON_Parse(data); if (root == NULL)return 0; //2. 解析字段 cJSON* item; item=cJSON_GetObjectItem(root,"text"); if (item) printf("text=%s\n",item->valuestring); item = cJSON_GetObjectItem(root, "number"); if (item) printf("text=%d\n", item->valueint); item = cJSON_GetObjectItem(root, "state1"); if (item) printf("state1=%d\n", item->valueint); item = cJSON_GetObjectItem(root, "state2"); if (item) printf("state2=%d\n", item->valueint); item = cJSON_GetObjectItem(root, "state3"); if (item) printf("state3=%d\n", item->valueint); //3. 释放空间 cJSON_Delete(root); return 0;  ​四、cJSON创建嵌套的对象数据 目标: 使用cJSON创建出下面这样一个JSON格式数据1.{ "data1": { "text": "我是一个字符串数据1", "number": 666, "state1": false, "state2": true, "state3": null "data2": { "text": "我是一个字符串数据2", "number": 666, "state1": false, "state2": true, "state3": null }4.1 创建json数据#include <iostream> //因为当前工程使用的是cpp后缀文件,引用C语言的文件需要使用下面的这种方式 extern "C" { #include <string.h> #include <stdio.h> #include "cJSON.h" int main() //1. 创建cJSON对象 cJSON* root = cJSON_CreateObject(); //2. 创建对象数据1 cJSON* item1 = cJSON_CreateObject(); cJSON_AddStringToObject(item1, "text","我是一个字符串数据1"); cJSON_AddNumberToObject(item1,"number",666); cJSON_AddBoolToObject(item1, "state1", cJSON_False); cJSON_AddBoolToObject(item1, "state2", cJSON_True); cJSON_AddNullToObject(item1, "state3"); cJSON_AddItemToObject(root, "data1", item1); //3. 创建对象数据2 cJSON* item2 = cJSON_CreateObject(); cJSON_AddStringToObject(item2, "text", "我是一个字符串数据2"); cJSON_AddNumberToObject(item2, "number", 666); cJSON_AddBoolToObject(item2, "state1", cJSON_False); cJSON_AddBoolToObject(item2, "state2", cJSON_True); cJSON_AddNullToObject(item2, "state3"); cJSON_AddItemToObject(root, "data2", item2); //3. 打印生成的结果 char *json_data=cJSON_PrintUnformatted(root); printf("%s\n",json_data); //4. 释放空间 cJSON_Delete(root); return 0; 4.2 解析JSON数据#include <iostream> //因为当前工程使用的是cpp后缀文件,引用C语言的文件需要使用下面的这种方式 extern "C" { #include <string.h> #include <stdio.h> #include "cJSON.h" //将要解析的JSON数据. char data[] = "{" "\"data1\": {" "\"text\": \"我是一个字符串数据1\"," "\"number\" : 666," "\"state1\" : false," "\"state2\" : true," "\"state3\" : null" "}," "\"data2\": {" "\"text\":\"我是一个字符串数据2\"," "\"number\" : 666," "\"state1\" : false," "\"state2\" : true," "\"state3\" : null" "}" "}"; int main() //1. 载入JSON数据 cJSON* root = cJSON_Parse(data); if (root == NULL)return 0; //2. 解析字段 cJSON* item; item=cJSON_GetObjectItem(root,"data1"); if (item) cJSON *obj; obj=cJSON_GetObjectItem(item, "text"); if (obj) printf("text=%s\n", obj->valuestring); obj=cJSON_GetObjectItem(item, "number"); if (obj) printf("number=%d\n", obj->valueint); obj = cJSON_GetObjectItem(item, "state1"); if (obj) printf("state1=%d\n", obj->valueint); obj = cJSON_GetObjectItem(item, "state2"); if (obj) printf("state2=%d\n", obj->valueint); obj = cJSON_GetObjectItem(item, "state3"); if (obj) printf("state3=%d\n", obj->valueint); item = cJSON_GetObjectItem(root, "data2"); if (item) cJSON *obj; obj = cJSON_GetObjectItem(item, "text"); if (obj) printf("text=%s\n", obj->valuestring); obj = cJSON_GetObjectItem(item, "number"); if (obj) printf("number=%d\n", obj->valueint); obj = cJSON_GetObjectItem(item, "state1"); if (obj) printf("state1=%d\n", obj->valueint); obj = cJSON_GetObjectItem(item, "state2"); if (obj) printf("state2=%d\n", obj->valueint); obj = cJSON_GetObjectItem(item, "state3"); if (obj) printf("state3=%d\n", obj->valueint); //3. 释放空间 cJSON_Delete(root); return 0; 五、cJSON带数组的JSON数据 目标: 使用cJSON创建出下面这样一个JSON格式数据1.{ "text": [ "width": 1280, "height": 720 "width": 1920, "height": 1080 "width": 3840, "height": 2160 }5.1 创建json数据#include <iostream> //因为当前工程使用的是cpp后缀文件,引用C语言的文件需要使用下面的这种方式 extern "C" { #include <string.h> #include <stdio.h> #include "cJSON.h" int main() cJSON *width = NULL; cJSON *height = NULL; int i; const unsigned int resolution_numbers[3][2] = { {1280, 720}, {1920, 1080}, {3840, 2160} //1. 创建cJSON对象 cJSON* root = cJSON_CreateObject(); //2. 创建数组对象 cJSON *array = cJSON_CreateArray(); cJSON_AddItemToObject(root, "text", array); for (i = 0; i < (sizeof(resolution_numbers) / (2 * sizeof(int))); ++i) cJSON *obj = cJSON_CreateObject(); cJSON_AddItemToArray(array, obj); width = cJSON_CreateNumber(resolution_numbers[i][0]); cJSON_AddItemToObject(obj, "width", width); height = cJSON_CreateNumber(resolution_numbers[i][1]); cJSON_AddItemToObject(obj, "height", height); //3. 打印生成的结果 char *json_data=cJSON_PrintUnformatted(root); printf("%s\n",json_data); //4. 释放空间 cJSON_Delete(root); return 0; 5.2 解析JSON数据#include <iostream> //因为当前工程使用的是cpp后缀文件,引用C语言的文件需要使用下面的这种方式 extern "C" { #include <string.h> #include <stdio.h> #include "cJSON.h" //将要解析的JSON数据. char data[] = "{" "\"text\": [" "{" "\"width\": 1280," "\"height\" : 720" "}," "{" "\"width\": 1920," "\"height\" : 1080" "}," "{" "\"width\": 3840," "\"height\" : 2160" "}" "]" "}"; int main() //1. 载入JSON数据 cJSON* root = cJSON_Parse(data); if (root == NULL)return 0; //2. 解析字段 cJSON* item; int i; item = cJSON_GetObjectItem(root, "text"); if (item) //获取数组的大小 int ArraySize = cJSON_GetArraySize(item); //解析数组的里的每个成员 for (i = 0; i < ArraySize; i++) //取出数组下标对象 cJSON *array_item = cJSON_GetArrayItem(item, i); if (array_item == NULL)continue; //解析数据 cJSON *obj = cJSON_GetObjectItem(array_item, "width"); if (obj) printf("width=%d\n",obj->valueint); obj = cJSON_GetObjectItem(array_item, "height"); if (obj) printf("height=%d\n", obj->valueint); //3. 释放空间 cJSON_Delete(root); return 0;

Linux驱动开发: 杂项字符设备

一、什么是杂项设备?        杂项设备(misc device)也是在嵌入式系统中用得比较多的一种设备驱动。        在Linux内核的include\linux目录下有Miscdevice.h文件,misc设备定义及其内核提供的相关函数在这里。        其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主设备10,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。        也就是说,misc设备其实也就是特殊的字符设备。        在Linux驱动中把无法归类的五花八门的设备定义为混杂设备(用miscdevice结构体表述)。        miscdevice共享一个主设备号MISC_MAJOR(即10),但次设备号不同。        所有的miscdevice设备形成了一个链表,对设备访问时内核根据次设备号查找对应的miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。        在内核中用struct miscdevice表示miscdevice设备,然后调用其file_operations结构中注册的文件操作接口进行操作。     miscdevice的API实现在drivers/char/misc.c中。二、描述杂项设备的结构内核用struct miscdevice的结构体来描述杂项设备:1.struct miscdevice { int minor; //次设备号,杂项设备的主设备?10 const char *name; //设备的名称 const struct file_operations *fops; //文件操作 /* 下面的成员是供内核使用 ,驱动编写不需要理会 */ struct list_head list; //misc_list的链表头 struct device *parent; //父设备 struct device *this_device; //当前设备,是device_create的返回值 };         minor:次设备号,0~255,当传递255时候,会自动由内核分配一个可用次设备号。        name:备名/dev/下的设备节点名。        fops:文件操作方法指针。特点:当安装此类驱动后,会在系统的/dev下生成相应的设备节点文件。三、内核提供来编写杂项设备的API函数3.1 注册函数 3.2 注销函数四、杂项设备的设备号&特征设备号是用来标志设备。分为设备号和此设备号。其中杂项设备的设备号如下:主设备号:固定为10。次设备号:0~255。a.主设备号固定为10b.注册后会自动在/dev/目录下生成设备文件c.使用一个核心结构 struct miscdevice 封装起来了。五、编写驱动程序步骤如下: 1)先写一个模块基本代码 2)增加设备模型所需要的头文件 3)在模块的初始化函数注册设备对应结构体 4)在模块的出口注销设备对应的结构 5)按照对应设备模型:注册函数需要的参数反向推出应该的结构体成员。       针对杂项模型:1)定义一个struct miscdevice  ,并且填充 minor,fops,name成员 2)定义一个 struct file_operations ,并且填充需要成员 3)在初始化函数中调用misc_register注册一上步实现的 struct miscdevice 结构变量; 4)在出口函数中调用misc_deregister注销一上步实现的 struct miscdevice 结构变量; 5)写一个应用程序测试驱动是否按照自己想法运行,对比结果。        最核心的工作在实现 file_operations 的接口函数,这些函数才是真正操作硬件的代码。其他的都模型代码。六、杂项设备示例6.1. 驱动程序代码清单/*驱动代码 misc.c */ #include <linux/module.h> /* Needed by all modules */ #include <linux/init.h> /* Needed for the module-macros */ #include <linux/fs.h> #include <linux/miscdevice.h> static ssize_t misc_read (struct file *pfile, char __user *buff, size_t size, loff_t *off) printk(KERN_EMERG "line:%d,%s is call\n",__LINE__,__FUNCTION__); return 0; static ssize_t misc_write(struct file *pfile, const char __user *buff, size_t size, loff_t *off) printk(KERN_EMERG "line:%d,%s is call\n",__LINE__,__FUNCTION__); return 0; static int misc_open(struct inode *pinode, struct file *pfile) printk(KERN_EMERG "line:%d,%s is call\n",__LINE__,__FUNCTION__); return 0; static int misc_release(struct inode *pinode, struct file *pfile) printk(KERN_EMERG "line:%d,%s is call\n",__LINE__,__FUNCTION__); return 0; //文件操作方法:根据具体设备实现需要的功能 static const struct file_operations misc_fops = { .read = misc_read, .write = misc_write, .release = misc_release, .open = misc_open, #define DEV_NAME "abc" #if 1 //C99 标准 新增加的结构体初始化方法,这种可以选其中部分进行初始化,初始化顺序不限 static struct miscdevice misc_dev = { .fops = &misc_fops, /* 设备文件操作方法 */ .minor = 255, /* 次设备号 */ .name = DEV_NAME, /* 设备名/dev/下的设备节点名 */ #else //c89标准风格的结构体成员初始,只能按顺序初始化成员(做了解) static struct miscdevice misc_dev = { 255, /* 次设备号 */ DEV_NAME, /* 设备名/dev/下的设备节点名 */ &misc_fops /* 设备文件操作方法 */ #endif static int __init hello_init(void) misc_register(&misc_dev); printk(KERN_EMERG "misc init \n"); return 0; static void __exit hello_exit(void) misc_deregister(&misc_dev); printk(KERN_EMERG "Goodbye,misc\n"); module_init(hello_init); module_exit(hello_exit); MODULE_LICENSE("GPL"); 6.2. 应用程序代码#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #define DEV_NAME "/dev/abc" int main(void) char buf[] = {1, 0, 0, 0}; int i = 0; int fd; //打开设备文件 O_RDWR, O_RDONLY, O_WRONLY, fd = open(DEV_NAME, O_RDWR); if(fd < 0) printf("open :%s failt!\r\n", DEV_NAME); return -1; //写数据到内核空间 write(fd, buf, 4); //从内核空间中读取数据 read(fd, buf, 4); //关闭设备 close(fd); return 0; }6.3.  Makefile 代码 obj-m := misc.o KDIR := /home/work/linux-3.5 make -C $(KDIR) M=$(PWD) modules rm -f *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~ arm-linux-gcc app.c -o app cp -f *.ko app /home/work/rootfs/root clean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.markers *.unsigned *.order *~

Linux应用开发:嵌入式Linux下矢量字体运用

一、freetype简介       FreeType库是一个完全免费(开源)的、高质量的且可移植的字体引擎,它提供统一的接口来访问多种字体格式文件,可以非常方便我们开发字体显示相关的程序功能。它支持单色位图、反走样位图的渲染。FreeType库是高度模块化的程序库,虽然它是使用ANSI C开发,但是采用面向对象的思想,因此,FreeType的用户可以灵活地对它进行裁剪。关于freetype的详细信息可以参考freetype的官方网站:https://www.freetype.org/来获取更多相关的信息。      CSDN下载地址: https://download.csdn.net/download/tech_pro/9873843二、下载源码编译安装2.1 编译freetype2.2 部署编译环境和运行环境2.3 windows电脑上的矢量字体存放目录2.4 示例代码1.#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <termios.h> #include <unistd.h> #include <string.h> #include <linux/fb.h> #include <sys/mman.h> #include <math.h> #include <wchar.h> #include <ft2build.h> #include FT_FREETYPE_H #include FT_STROKER_H #define LCD_DEVICE "/dev/fb0" int lcd_fd; struct fb_var_screeninfo vinfo;//可变参数 struct fb_fix_screeninfo finfo; //固定参数 unsigned char *lcd_mem=NULL; //LCD首地址 typedef unsigned int u32; typedef unsigned short u16; typedef unsigned char u8; /*定义一个结构体存放矢量字体的配置*/ struct FREE_TYPE_CONFIG FT_Library library; FT_Face face; FT_GlyphSlot slot; FT_Vector pen; /* untransformed origin */ FT_Error error; FT_BBox bbox; FT_Glyph glyph; struct FREE_TYPE_CONFIG FreeTypeConfig; 函数功能: 封装画点函数 函数参数: u32 x,u32 y,u16 c void LCD_DrawPoint(u32 x,u32 y,u32 c) u32 *lcd_p=(u32*)(lcd_mem+vinfo.xres*vinfo.bits_per_pixel/8*y+x*vinfo.bits_per_pixel/8); *lcd_p=c; 函数功能: 封装读点函数 函数参数: u32 x,u32 y,u16 c u32 LCD_ReadPoint(u32 x,u32 y) u32 *lcd_p=(u32*)(lcd_mem+vinfo.xres*vinfo.bits_per_pixel/8*y+x*vinfo.bits_per_pixel/8); return *lcd_p; /* LCD显示矢量字体的位图信息 * bitmap : 要显示的字体的矢量位图 * x : 显示的x坐标 * y : 显示的y坐标 void LCD_DrawBitmap(FT_Bitmap* bitmap,FT_Int x,FT_Int y) FT_Int i,j,p,q; FT_Int x_max=x+bitmap->width; FT_Int y_max=y+bitmap->rows; /* 将位图信息循环打印到屏幕上 */ for(i=x,p=0;i<x_max;i++,p++) for(j=y,q=0;j<y_max;j++,q++) if((i>x_max)||(j>y_max)||(i<0)||(j<0))continue; if(bitmap->buffer[q*bitmap->width+p]!=0) LCD_DrawPoint(i, j,0xFF0033); LCD_DrawPoint(i, j,0xFFFFFF); 函数功能: 初始化FreeType配置 int InitConfig_FreeType(char *font_file) FT_Error error; /*1. 初始化freetype库*/ error=FT_Init_FreeType(&FreeTypeConfig.library); if(error) printf("freetype字体库初始化失败.\n"); return -1; /*2. 打开加载的字体文件*/ error=FT_New_Face(FreeTypeConfig.library,font_file,0,&FreeTypeConfig.face); if(error) printf("矢量字体文件加载失败.\n"); return -2; return 0; 函数功能: 释放FreeType配置 void FreeType_Config(void) FT_Done_Face(FreeTypeConfig.face); FT_Done_FreeType(FreeTypeConfig.library); 函数功能: 在LCD屏显示一串文本数据 函数参数: u32 x 坐标位置 u32 y 坐标位置 u32 size 字体大小 wchar_t *text 显示的文本数据 int LCD_DrawText(u32 x,u32 y,u32 size,wchar_t *text) FT_Error error; int i = 0; int bbox_height_min = 10000; int bbox_height_max = 0; /*3. 设置字符的像素的大小为size*size*/ error=FT_Set_Pixel_Sizes(FreeTypeConfig.face,size,0); if(error) printf("字符的像素大小设置失败.\n"); return -1; /*4. 设置字体文件的轮廓的插槽*/ FreeTypeConfig.slot=FreeTypeConfig.face->glyph; /* 设置坐标为原点坐标 * 将LCD坐标转换成笛卡尔坐标 * 单位是 1/64 Point FreeTypeConfig.pen.x=x*64; FreeTypeConfig.pen.y=(vinfo.yres-size-y)*64; /*5. 循环的将文字显示出来*/ for(i=0;i<wcslen(text);i++) FT_Set_Transform(FreeTypeConfig.face,0,&FreeTypeConfig.pen); //设置字体的起始坐标位置 /*装载字符编码,填充face的glyph slot成员*/ error=FT_Load_Char(FreeTypeConfig.face,text[i],FT_LOAD_RENDER); if(error) printf("装载字符编码失败.\n"); return -1; /*通过glyph slot来获得glyph*/ FT_Get_Glyph(FreeTypeConfig.slot,&FreeTypeConfig.glyph); /*通过glyph来获得cbox*/ FT_Glyph_Get_CBox(FreeTypeConfig.glyph,FT_GLYPH_BBOX_TRUNCATE,&FreeTypeConfig.bbox); /*获得字体高度的最大值和最小值*/ if(bbox_height_min>FreeTypeConfig.bbox.yMin)bbox_height_min=FreeTypeConfig.bbox.yMin; if(bbox_height_max<FreeTypeConfig.bbox.yMax)bbox_height_max=FreeTypeConfig.bbox.yMax; /*画点,把笛卡尔坐标转换成LCD坐标*/ LCD_DrawBitmap(&FreeTypeConfig.slot->bitmap, FreeTypeConfig.slot->bitmap_left, vinfo.yres-FreeTypeConfig.slot->bitmap_top); if(FreeTypeConfig.slot->bitmap_left+size*2>vinfo.xres) FreeTypeConfig.pen.x=0; //更新X坐标位置 FreeTypeConfig.pen.y=(vinfo.yres-size-y-size)*64; //更新Y坐标位置 /* 更新原点坐标位置 */ FreeTypeConfig.pen.x+=FreeTypeConfig.slot->advance.x; FreeTypeConfig.pen.y+=FreeTypeConfig.slot->advance.y; return 0; int main(int argc,char **argv) if(argc!=2) printf("./app <xxx.ttf 字体文件>\n"); return 0; /*1. 打开设备文件*/ lcd_fd=open(LCD_DEVICE,O_RDWR); if(lcd_fd<0) printf("%s open error.\n",LCD_DEVICE); return 0; /*2. 获取可变参数*/ ioctl(lcd_fd,FBIOGET_VSCREENINFO,&vinfo); printf("x=%d,y=%d,pixel=%d\n",vinfo.xres,vinfo.yres,vinfo.bits_per_pixel); /*3. 获取固定参数*/ ioctl(lcd_fd,FBIOGET_FSCREENINFO,&finfo); printf("smem_len=%d\n",finfo.smem_len); printf("line_length=%d\n",finfo.line_length); /*4. 映射LCD地址*/ lcd_mem=mmap(NULL,finfo.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,lcd_fd,0); if(lcd_mem==NULL) printf("映射LCD地址失败.\n"); return -1; memset(lcd_mem,0xFFFFFF,finfo.smem_len); /*5. 初始化配置FreeType*/ InitConfig_FreeType(argv[1]); /*6. 在指定位置显示文本*/ wcslen() 函数用于计算宽字符的个数,支持区分中文和英文字符,文本需要在UTF-8编码下。 定义宽字符串示例: wchar_t *wp=L"1234567890中国"; //12 printf("wcslen p:%d\n",wcslen(wp)); 返回值是12 LCD_DrawText(50,56*0,56,L"北京万邦易嵌科技有限公司"); LCD_DrawText(150,56*1,56,L"www.wanbangee.com"); LCD_DrawText(200,56*3,48,L"FreeType矢量字体"); LCD_DrawText(150,56*5,80,L"Linux驱动开发"); /*7. 释放FreeType配置*/ FreeType_Config(); close(lcd_fd); return 0; }2.5 编译源代码的Makefile示例all: arm-linux-gcc video_app.c -I /home/wbyq/work/freetype-2.4.10/_install/include -I /home/wbyq/work/freetype-2.4.10/_install/include/freetype2 -o app -lfreetype -ljpeg cp app /home/wbyq/work/rootfs/code rm app -f2.6 运行程序效果示例

Linux驱动开发: FrameBuffe(LCD)驱动开发

一、FrameBuffer 帧缓冲设备的原理1.1 概念在linux系统中LCD这类设备称为帧缓冲设备,英文frameBuffer设备。frameBuffer 是出现在2.2.xx 内核当中的一种驱动程序接口。帧缓冲(framebuffer)是Linux 系统为显示设备提供的一个接口,它将显示缓冲区抽象,屏蔽图像硬件的底层差异,允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。用户不必关心物理显示缓冲区的具体位置及存放方式,这些都由帧缓冲设备驱动本身来完成。framebuffer机制模仿显卡的功能,将显卡硬件结构抽象为一系列的数据结构,可以通过framebuffer的读写直接对显存进行操作。用户可以将framebuffer看成是显存的一个映像,将其映射到进程空间后,就可以直接进行读写操作,写操作会直接反映在屏幕上。1.2 帧缓冲的理解用户可以将Framebuffer看成是显示内存的一个映像,将其映射到进程地址空间之后,就可以直接进行读写操作,而写操作可以立即反应在屏幕上。这种操作是抽象的、统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。1.3 了解帧缓冲设备文件framebuffer的设备文件一般是 /dev/fb0、/dev/fb1等,最多支持32个设备。framebuffer是个字符设备,主设备号为29,对应于/dev/fb%d设备文件。通常,使用如下方式(前面的数字表示次设备号):(次设备号)0 = /dev/fb0 第一个fb设备(次设备号)1 = /dev/fb1 第二个fb 设备。fb 也是一种普通的内存设备,可以读写其内容。例如,屏幕抓屏:cp /dev/fb0 myfilefb;注意,这个命令保存下来的仅是rgb数据,并不是图片格式,Window下是打不开的。(前提是framebuffer驱动里实现了read函数)1.4 如何去操作这个设备文件对程序员和Linux系统来说,framebuffer设备与其他的文件没有区别;可以通过配置对framebuffer设备文件完成对硬件的参数设置。framebuffer映射操作:二、LCD知识介绍2.1 了解LCD显示的色深   色深(BPP):一个像素使用多少个二进制位表示它的颜色。   1bpp :一个像素使用 1 个二进制位表示它的颜色,可以有两种色,这种屏单色屏。   2bpp :一个像素使用 2 个二进制位表示它的颜色,可以有4种色,恢阶。   4BPP :   8BPP : 一个像素使用 8 个二进制位表示它的颜色,可以有256种色。   16BPP: 一个像素使用 16 个二进制位表示它的颜色,可以有65536种色,伪彩色。   stm32 这种等级 CPU一般使用这个级别的。          RGB565--常用  RGB5551   24BPP: 一个像素使用 24 个二进制位表示它的颜色,可以有16M种色,真彩色。          RGB888   32BPP: 一个像素使用 32 个二进制位表示它的颜色。 增加了透明度控制。          ARGB888   所有颜色都由RGB三原色组成。通过调节三者比例程现不同颜色。2.2  LCD屏的时序      要驱动一个TFT屏,首先来认识一下LCD工作时序图。在(芯片数据手册)Exynos 4412 SCP_Users Manual_Ver.0.10.00_Preliminary0.pdf的1816页,第41.3.11.2章节LCD RGB Interface Timing。LCD的工作时序图如下:  可以把LCD看成一个二维数据。从左到右,从上到下,一个点一点描绘(逐行扫描)。当最后一个点描绘完成,循环扫描。所有显示器显示图像的原理都是从上到下,从左到右的。那么这幅图在LCD上的显示原理就是:LCD提供的外部接口信号说明:(Synchronization:同步,vertical:垂直,horizontal:水平)上面时序图上各时钟延时参数的含义如下:(这些参数的值,LCD产生厂商会提供相应的数据手册)。它们的英文全称:VBPD(vertical back porch) :垂直后肩VFBD(vertical front porch) :垂直前肩VSPW(vertical sync pulse width):垂直同步脉冲宽度HBPD(horizontal back porch):水平前肩HFPD(horizontal front porth):水平后肩HSPW(horizontal sync pulse width):水平同步脉冲宽度LINEVAL: 屏实际高度-1。HOZVAL: 屏实际宽度-1。 像素时钟信号:一个像素时钟(时间)扫描一个点。      帧 :由行组成      行 :由像素组成扫描过程:      发起帧同步信号-->行同步信号-->像素时钟信号 (三种信号是同步出现)。2.3  LCD时序参数的值      以上参数都是由LCD屏厂家提供的。参考光盘资料:在以上文档的14页,我们一般用的是经典值。 除了以上的时序参数外,还要注意LCD控制器的默认时序极性和LCD屏的时序极性是否相同,不同则配置为相同。(VSYNC,HSYNC,DEN,像素采样边沿)。三、应用层FrameBuffer 帧缓冲设备编程(LCD屏编程)    在Linux 系统中LCD的应用程序编程是有特定编写模板的。    下面我们就一步一步的来编写linux下的lcd应用程序。3.1 编程步骤(1)  打开/dev/fbX  Open的第一个参数:/dev/fbx,打开lcd设备文件,lcd设备文件在dev目录下都是以fb*存在的。     得到一个可代表这个设备文件的文件描述符。(2) 获取可变参数、固定参数     如:要获取可变参数     那么就定义一个变量来存放这个可变参数:   然后用ioctl接口实现数据的传递:     既然我们要获取参数,那么ioctl方向肯定是从内核到应用空间;     根据FBIOGET_VSCREENINFO命令,从fbX设备文件中获取对应的数据,存放在vinfo指向的结构中。    具体是什么结构的数据就看从内核传递上来是什么数据了。    FBIOGET_VSCREENINFO命令命名方式可以分解如下:这样就可以直观的看出来,是获取屏幕的可变信息。如:要获取固定参数同样也定义一个变量来存放这个固定参数:通过lcd驱动提供的iotcl接口获取:(3) 计算屏幕大小(字节为单位)计算屏幕大小是为了后面的mmap函数使用,这个屏幕大小是根据可变参数来计算的,公式如下:屏幕的一个点就是x方向的32位数据和y方向的32数据构成。其中Bpp/8意义是每一个像素占用了多少个字节。即32/8=4byte。我们可以看出来,屏幕内存大小是以字节为单位的。如:这个screensize有何用处呢,就是给mmap函数去映射空间时使用的。(4) 内存映射(mmap函数)Linux下一切都是文件,我们在对Lcd设备文件操作就是对lcd屏进行了操作。我们一般想到对屏的操作方法:我们一般想到对屏的操作方法:1- LCD操作可以像其他字符设备一样可以调用read,write,对lcd屏进行操作;                但是出于效率考虑,一般不这样操作。                例如: 我要让屏幕实现黑白切换。 (驱动层需要实现read和write函数)While(1){ Memset(buf,0,screensize); Write(fp,buf,screensize); Msleep(1); Memset(buf,0,screensize); Write(fp,buf,screensize); Msleep(1); }     分析这里为什么会出现效率低的问题:                        我要把数据给内核,write就会调用copy_from_user传递数据;                        如果屏容量1024*768*4 约有3M左右的数据,我在屏上刷一幅图片要传递3M左右            的数据,1S要刷50次呢,就要在1s内传递150M左右的数据;                        memset函数也传递了一次数据,一个2次数据传递,150*2=300M。                        那么主频低cpu在这样短时间里处理就会卡顿等。                不用read,write原因:                        我们在调用memset的时候,传递了一次数据给缓冲区;然后再使用write的copy_fr/       om_user又传递了一次数据,一共就传递数据了2次。                        那么是mmap又是怎么样子的呢?                        只要值映射的时候传递了一次数据,在效率上提高了一倍。2- 内核一般对LCD屏操作是通过内存映射(mmap)方式来实现。               这是一种常见的文件操作方法,系统编程课程会有提到。我们需要知道lcd编程的概念:把lcd看成是一块内存,使用mmap函数把它的缓冲区映射到进程空间中,然后通过映射后的地址直接操作驱动中的显示缓冲区,往这块缓冲写数据,lcd就会按数值转换成相应颜色显示在LCD屏上。那么就需要把lcd设备文件映射到进程的地址空间中。首先来介绍mmap函数:(man 2 mmap可以查看该函数的用法)void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 功能:把文件的内存映射到进程的地址空间中。参数:addr: 设置一个可用的地址,一般情况你不知道哪个地址是可用的。       所以,给0表示由系统自动分配。length:映射的长度。单位是字节prot:属性。如PROT_READ(可读),PROT_WRITE(可写)等。         PROT_EXEC         映射后的地址空间可以执行代码.         PROT_READ       映射后可以读.         PROT_WRITE      映射后可以写         PROT_NONE        映射后不能访问   flags:保护标志。常用的是共享方式 MAP_SHARED。fd :open返回的文件描述符。offset:是从文件的哪个地方开始映射。   返回值:映射后的首地址指针。如下:fbp =(unsigned char *) mmap (0, //表示系统自动分别可以映射地址 screensize, //映射大小(字节) PROT_READ | PROT_WRITE, //映射得到的空间属性 MAP_SHARED, // 保护标志 fp, //要映射的文件描述符 0); //从文件的那个地方开始映射把lcd设备文件映射到进程的地址空间,从映射开始0开始、大小为 screensize 的内容,得到一个指向这块空间首地址的指针fbp。(5) 使用映射后的地址对屏进行操作   使用上面得到的 fbp 指针,我们就可以来操作lcd的显示缓冲区了。   示例1:要在(100,200) 处画一个点。1- 计算偏移(固定的)地址:       off = (800*200+100) * 4;  屏幕上的点是呈(x,y)坐标形式,而进程地址空间呈一块连续的地址内存。所以要计算由屏上的点对于进程空间的地址。写成通用的表示:       Off = (xres*y + x) * bpp/8;2- 写入颜色数据(是一个32位的数据):*(unsigned int *)(fbp+off) = 0x00ff0000;//低地址存低字节(小端格式) 或: * (fbp+off) = 0x00; //B(蓝色) * (fbp+off+1) = 0x00; //G(绿色) * (fbp+off+2) = 0xff; //R(红色) * (fbp+off+3) = 0x00; //透明色*(fbp+off)就是我们要写入颜色值的地方。Fbp就是屏的起始点地址指针,fbp+off是指定点的地址指针。这里的颜色表示是32bpp色深形式,rgb+透明色形式。从低地址到高地址为B(蓝色)、G(绿色)、透明色。我们可以把以上两个步骤封装成一个函数:void show_pixle(int x,int y,unsigned int c) location = x * (vinfo.bits_per_pixel / 8) + y * finfo.line_length; *(unsigned int*)(fbp + location) = c; } 参数:X:要画的点(x,y)的x坐标Y:要画的点(x,y)的y坐标C:要写入的颜色值(6) 释放映射知道映射的首地址指针,映射的大小就可以把之前映射的进程地址空间释放,还给系统作其他用途。munmap (fbp, screensize);(7) 关闭文件然后就是把你打的设备文件关闭了。close (fp);OK,到此,你对这个文件的操作就结束了。3.2 示例代码: 获取屏幕信息输出的结果:3.3 示例代码: 显示一个中文字符在LCD屏指定位置显示一个中文字符。#include <linux/fb.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <stdio.h> #include <sys/mman.h> #include <string.h> unsigned char font[]={/*-- 文字: 国 --*/ /*-- 宋体42; 此字体下对应的点阵为:宽x高=56x56 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x01,0x80,0x00,0x00,0x00,0x03,0x00,0x01,0xE0,0x00,0x00, 0x00,0x07,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0xE0,0x01,0xF0,0x00,0x00,0x00,0x07, 0xC0,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01, 0xF0,0x00,0x00,0x01,0x87,0x80,0x01,0xF0,0x00,0x00,0x03,0xC7,0x80,0x01,0xF6,0x00, 0x00,0x07,0xE7,0x80,0x01,0xF7,0xFF,0xFF,0xFF,0xF7,0x80,0x01,0xF1,0x80,0x7C,0x00, 0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80, 0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0, 0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C, 0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x06,0x07,0x80,0x01,0xF0,0x00,0x7C,0x0F,0x07, 0x80,0x01,0xF1,0x80,0x7C,0x1F,0x87,0x80,0x01,0xF1,0xFF,0xFF,0xFF,0xC7,0x80,0x01, 0xF0,0xE0,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00,0x7C,0x00,0x07,0x80,0x01,0xF0,0x00, 0x7F,0xC0,0x07,0x80,0x01,0xF0,0x00,0x7D,0xF0,0x07,0x80,0x01,0xF0,0x00,0x7C,0xFC, 0x07,0x80,0x01,0xF0,0x00,0x7C,0x7E,0x07,0x80,0x01,0xF0,0x00,0x7C,0x3F,0x07,0x80, 0x01,0xF0,0x00,0x7C,0x3F,0x07,0x80,0x01,0xF0,0x00,0x7C,0x1F,0x07,0x80,0x01,0xF0, 0x00,0x7C,0x0F,0x07,0x80,0x01,0xF0,0x00,0x7C,0x0E,0x07,0x80,0x01,0xF0,0x00,0x7C, 0x06,0x07,0x80,0x01,0xF0,0x00,0x7C,0x03,0x87,0x80,0x01,0xF0,0x00,0x7C,0x07,0xC7, 0x80,0x01,0xFC,0x00,0x7C,0x0F,0xE7,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0x80,0x01, 0xF7,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00, 0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00, 0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0x80, 0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x07,0x80,0x01,0xF0, 0x00,0x00,0x00,0x07,0x80,0x01,0xF0,0x00,0x00,0x00,0x06,0x00,0x01,0xC0,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,}; unsigned char *fbmem=NULL; struct fb_var_screeninfo var; // 可变参数-- struct fb_fix_screeninfo fix; // 固定参数-- /*画点*/ void show_pixel(int x,int y,int color) unsigned long *show32 = NULL; /* 定位到LCD屏上的位置*/ show32 =(unsigned long *)(fbmem + y*var.xres*var.bits_per_pixel/8 + x*var.bits_per_pixel/8); *show32 =color; /*向指向的LCD地址赋数据*/ 函数功能:在LCD屏上显示字体 void LCD_ShowFont(unsigned int x,unsigned int y,unsigned int w,unsigned int h,unsigned char *p) {undefined unsigned int i,j; unsigned char data; unsigned int x0=x; //保存X坐标 for(i=0;i<w/8*h;i++) {undefined data=p[i]; for(j=0;j<8;j++) {undefined if(data&0x80)show_pixel(x,y,0xF800); //字体颜色画红色 else show_pixel(x,y,0x0000); //背景颜色画白色 x++; //画完一个点,x坐标要向后移动 data<<=1; //依次判断 if(x-x0==w) //判断是否需要换新行 {undefined x=x0; //横坐标归位 y++; //换新的一行 int main(int argc,char **argv) {undefined int fd; /* 1、打开/dev/fb0 */ fd = open("/dev/fb0",2); if(fd <= 0) {undefined printf("open is error!!\n"); return -1; /* 2、获取可变参数,固定参数 */ /* 2.2、FBIOGET_VSCREENINFO获取可变参数:x,y,bpp */ ioctl(fd,FBIOGET_VSCREENINFO,&var); printf("横坐标=%d\n",var.xres); printf("纵坐标=%d\n",var.yres); printf("一个像素点的位数=%dbit\n",var.bits_per_pixel); printf("横坐标*纵坐标*一个像素点的位数/8=Framebuffer的大小=%d字节\n",var.xres*var.yres*var.bits_per_pixel/8); /* 2.2、FBIOGET_FSCREENINFO获取固定参数:显存大小 */ ioctl(fd,FBIOGET_FSCREENINFO,&fix); printf("屏幕显示缓冲区大小=%d字节\n",fix.smem_len); //FD缓冲区长度 printf("虚拟横坐标=%d字节\n",var.xres_virtual); printf("虚拟纵坐标=%d字节\n",var.yres_virtual); /* 3、获取显存虚拟起始地址 */ * start:虚拟起始地址 null 自动分配 * length: 映射的大小 * prot :权限 PROT_READ | PROT_WRITE * flags : MAP_SHARED * fd:文件描述符 fbmem =(unsigned char *)mmap(NULL,fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd, 0); printf("LCD 映射地址:%p\n",fbmem); if(fbmem == (unsigned char *)-1) {undefined printf("mmap error!!!\n"); munmap(fbmem,fix.smem_len); //取消映射 return -1; /* 4、清屏函数----把fbmem空间覆盖成0x0 ,清屏成黑色*/ memset(fbmem,0x00,fix.smem_len); LCD_ShowFont(50,150,56,56,font); //显示汉字 close(fd); return 0; 四、MMAP驱动实现4.1 mmap简介mmap函数用于将一个文件或者其它对象映射进内存,通过对这段内存的读取和修改,来实现对文件的读取和修改,而不需要再调用read,write等操作。头文件:<sys/mman.h>函数原型:4.2 mmap系统调用接口参数说明映射函数​addr: 指定映射的起始地址,通常设为NULL,由系统指定。length:映射到内存的文件长度。prot:  映射的保护方式,可以是:PROT_EXEC:映射区可被执行PROT_READ:映射区可被读取PROT_WRITE:映射区可被写入PROT_NONE:映射区不能存取Flags:  映射区的特性,可以是:MAP_SHARED:写入映射区的数据会复制回文件,且允许其他映射该文件的进程共享。MAP_PRIVATE:对映射区的写入操作会产生一个映射区的复制(copy_on_write),对此区域所做的修改不会写回原文件。fd:由open返回的文件描述符,代表要映射的文件。offset:以文件开始处的偏移量,必须是分页大小的整数倍,通常为0,表示从文件头开始映射。解除映射功能:取消参数start所指向的映射内存,参数length表示欲取消的内存大小。返回值:解除成功返回0,否则返回-14.3 Linux内核的mmap接口Linux内核中使用结构体vm_area_struct来描述虚拟内存的区域,其中几个主要成员如下:unsigned long vm_start   虚拟内存区域起始地址unsigned long vm_end   虚拟内存区域结束地址unsigned long vm_flags  该区域的标志该结构体定义在<linux/mm_types.h>头文件中。该结构体的vm_flags成员赋值的标志为:VM_IO和VM_RESERVED。其中:VM_IO表示对设备IO空间的映射,M_RESERVED表示该内存区不能被换出,在设备驱动中虚拟页和物理页的关系应该是长期的,应该保留起来,不能随便被别的虚拟页换出(取消)。mmap操作接口在字符设备的文件操作集合(struct file_operations)中有mmap函数的接口。原型如下:其中第二个参数struct vm_area_struct *相当于内核找到的,可以拿来用的虚拟内存区间。mmap内部可以完成页表的建立。4.5 实现mmap映射       映射一个设备是指把用户空间的一段地址关联到设备内存上,当程序读写这段用户空间的地址时,它实际上是在访问设备。这里需要做的两个操作:1. 找到可以用来关联的虚拟地址区间。2.  实现关联操作。mmap设备操作实例如下:其中的buf就是在内核中申请的一段空间。使用kmalloc函数实现。代码如下:4.6 remap_pfn_range函数remap_pfn_range函数用于一次建立所有页表。函数原型如下:      其中vma是内核为我们找到的虚拟地址空间,addr要关联的是虚拟地址,pfn是要关联的物理地址,size是关联的长度是多少。    ioremap与phys_to_virt、virt_to_phys的区别:ioremap是用来为IO内存建立映射的, 它为IO内存分配了虚拟地址,这样驱动程序才可以访问这块内存。phys_to_virt只是计算出某个已知物理地址所对应的虚拟地址。virt_to_phys :物理地址4.7 示例代码(1) 驱动代码示例(2) 应用层代码示例五、FrameBuffer帧缓冲驱动框架帧缓冲设备相关的数据结构存放在: fb.h头文件里使用帧缓冲相关函数与数据结构需要添加头文件: #include <linux/fb.h>5.1  帧缓冲注册与注销函数1. FrameBuffer注册函数函数功能介绍: 注册帧缓冲设备函数参数介绍:@ fb_info  :帧缓冲(LCD)设备信息。说明: 注册成功之后,会在/dev目录下生成fb开头的设备文件。2. FrameBuffer注销函数函数功能介绍: 注销帧缓冲设备函数参数介绍:@ fb_info  :注册时填入的帧缓冲(LCD)设备信息。5.2 struct fb_info结构介绍以下列出了struct fb_info常用的几个成员:LCD屏专属的文件操作接口,在fb.h定义,以下列出了常用的几个函数接口:可变形参常用的结构成员:固定参数常用的结构体成员:填充示例:5.3 分配显存地址功能介绍:分配一块物理地址连续的内存, 供后续 DMA 传输使用。函数参数:struct device *dev : 传入设备指针, 没有填 NULL。size_t size : 分配的空间大小。dma_addr_t *dma_handle :存放分配之后的物理地址。gfp_t flag :分配的属性。 常用: GFP_KERNEL返回值: 物理地址对应的虚拟地址, 内核代码本身只能操作虚拟地址。示例:5.4  帧缓冲框架注册示例代码以下演示了帧缓冲的驱动框架,剩下事情可以直接加入硬件代码。1. 帧缓冲驱动代码2. 应用层代码运行结果:六、OLED显示屏驱动+帧缓冲驱动模板6.1 OLED简介OLED,即有机发光二极管( Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示 OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。在此我们使用的是中景园电子的 0.96 寸 OLED 显示屏,该屏有以下特点:1)0.96 寸 OLED 有黄蓝,白,蓝三种颜色可选;其中黄蓝是屏上 1/4 部分为黄光,下 3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;蓝色则为纯蓝,也就是黑底蓝字。2)分辨率为 128*643)多种接口方式;OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3 线或 4 线的串行 SPI 接口方式、 IIC 接口方式(只需要 2 根线就可以控制 OLED 了!),这五种接口是通过屏上的 BS0~BS2 来配置的。4)OLED屏开发了两种接口的 Demo 板,接口分别为七针的 SPI/IIC 兼容模块,四针的IIC 模块。0.96 寸 OLED屏外观6.2 OLED驱动代码示例6.3 应用层测试代码示例#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/ioctl.h> #include <linux/fb.h> #include <sys/ioctl.h> #include <sys/mman.h> #include <string.h> #define OLED_RefreshGRAM 0x12345 /*将应用层的数据刷新到OLED屏幕上*/ #define OLED_ClearGRAM 0x45678 /*将应用层的数据刷新到OLED屏幕上*/ struct fb_var_screeninfo var; //可变参数 struct fb_fix_screeninfo fix; //固定参数 unsigned char *fb_mem=NULL; //LCD屏的首地址 extern unsigned char font1[]; extern unsigned char font2[]; 函数功能: 画点 void Show_Pixel(int x,int y,unsigned char color) {undefined unsigned char *lcd=(unsigned char *)(fb_mem+y*var.xres*var.bits_per_pixel/8+x*var.bits_per_pixel/8); *lcd=color; //颜色赋值 函数功能: 显示中文 说明: 取模的字体是按照横向取模进行取点阵码。 取模的字体宽度是8的倍数。 void ShowFont(int x,int y,int size,unsigned char *data) {undefined int i,j,x0=x; unsigned char tmp; for(i=0;i<size/8*size;i++) {undefined tmp=data[i]; for(j=0;j<8;j++) {undefined if(tmp&0x80)Show_Pixel(x0,y,0xF); //else 画背景色 x0++; tmp<<=1; if(x0-x==size) {undefined y++; x0=x; int main(int argc,char **argv) {undefined if(argc!=2) {undefined printf("./app /dev/fbX\n"); return 0; int fd=open(argv[1],O_RDWR); if(fd<0) {undefined perror("设备文件打开失败"); return 0; /*1. 获取LCD屏的可变形参*/ ioctl(fd,FBIOGET_VSCREENINFO,&var); printf("分辨率:%d*%d\n",var.xres,var.yres); printf("像素点位数:%d\n",var.bits_per_pixel); /*2. 获取LCD屏的固定形参*/ ioctl(fd,FBIOGET_FSCREENINFO,&fix); printf("映射的长度:%d\n",fix.smem_len); printf("一行的字节数:%d\n",fix.line_length); /*3. 映射LCD缓冲区地址到进程空间*/ fb_mem=mmap(NULL,fix.smem_len,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0); if(fb_mem==NULL) {undefined perror("空间映射失败!\n"); return 0; /*-- 幼圆42; 此字体下对应的点阵为:宽x高=56x56 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xC0,0x00,0x00, 0x00,0x00,0x00,0x0F,0xE0,0x00,0x00,0x00,0x00,0x00,0x07,0xF0,0x00,0x00,0x00,0x00, 0x00,0x01,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0xFC,0x00,0x00,0x00,0x00,0x00,0x00, 0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x3E,0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0x00, 0x00,0x00,0x00,0x00,0x00,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0x00,0x00,0x00, 0x00,0x00,0x00,0x3F,0x80,0x00,0x00,0x00,0x00,0x00,0x3F,0x80,0x00,0x00,0x00,0x00, 0x00,0x7F,0xC0,0x00,0x00,0x00,0x00,0x00,0x7F,0xC0,0x00,0x00,0x00,0x00,0x00,0x7B, 0xC0,0x00,0x00,0x00,0x00,0x00,0xFB,0xE0,0x00,0x00,0x00,0x00,0x00,0xF1,0xE0,0x00, 0x00,0x00,0x00,0x00,0xF1,0xE0,0x00,0x00,0x00,0x00,0x01,0xF1,0xF0,0x00,0x00,0x00, 0x00,0x01,0xE0,0xF0,0x00,0x00,0x00,0x00,0x03,0xE0,0xF8,0x00,0x00,0x00,0x00,0x03, 0xC0,0xF8,0x00,0x00,0x00,0x00,0x07,0xC0,0x7C,0x00,0x00,0x00,0x00,0x07,0x80,0x7C, 0x00,0x00,0x00,0x00,0x0F,0x80,0x3C,0x00,0x00,0x00,0x00,0x0F,0x00,0x3E,0x00,0x00, 0x00,0x00,0x1F,0x00,0x1E,0x00,0x00,0x00,0x00,0x1E,0x00,0x1F,0x00,0x00,0x00,0x00, 0x3E,0x00,0x0F,0x00,0x00,0x00,0x00,0x7C,0x00,0x0F,0x80,0x00,0x00,0x00,0x7C,0x00, 0x07,0xC0,0x00,0x00,0x00,0xF8,0x00,0x07,0xC0,0x00,0x00,0x01,0xF0,0x00,0x03,0xE0, 0x00,0x00,0x01,0xE0,0x00,0x01,0xE0,0x00,0x00,0x03,0xE0,0x00,0x01,0xF0,0x00,0x00, 0x07,0xC0,0x00,0x00,0xF8,0x00,0x00,0x0F,0x80,0x00,0x00,0xF8,0x00,0x00,0x1F,0x00, 0x00,0x00,0x7C,0x00,0x00,0x1F,0x00,0x00,0x00,0x3E,0x00,0x00,0x3E,0x00,0x00,0x00, 0x3E,0x00,0x00,0x7C,0x00,0x00,0x00,0x1F,0x00,0x00,0xF8,0x00,0x00,0x00,0x0F,0x80, 0x01,0xF0,0x00,0x00,0x00,0x07,0xC0,0x03,0xE0,0x00,0x00,0x00,0x03,0xE0,0x07,0xC0, 0x00,0x00,0x00,0x03,0xF0,0x0F,0x80,0x00,0x00,0x00,0x01,0xF8,0x1F,0x00,0x00,0x00, 0x00,0x00,0xFC,0x1E,0x00,0x00,0x00,0x00,0x00,0x7C,0x1C,0x00,0x00,0x00,0x00,0x00, 0x3C,0x00,0x00,0x00,0x00,0x00,0x00,0x00, unsigned char font1[]= {undefined /*-- 文字: 嵌 --*/ /*-- 幼圆42; 此字体下对应的点阵为:宽x高=56x56 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x38, 0x00,0x00,0x00,0x03,0x80,0x00,0x7C,0x00,0x03,0x80,0x07,0xC0,0x00,0x7C,0x00,0x03, 0xC0,0x07,0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07,0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07, 0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07,0xC0,0x00,0x7C,0x00,0x03,0xC0,0x07,0xC0,0x00, 0x7C,0x00,0x03,0xC0,0x07,0xFF,0xFF,0xFF,0xFF,0xFF,0xC0,0x03,0xFF,0xFF,0xFF,0xFF, 0xFF,0x80,0x01,0xFF,0xFF,0xFF,0xFF,0xFF,0x80,0x00,0xFF,0xFF,0xFF,0xFF,0xFC,0x00, 0x00,0x00,0x00,0x00,0x60,0x00,0x00,0x00,0xE0,0x07,0x00,0xF0,0x00,0x00,0x01,0xE0, 0x0F,0x01,0xF0,0x00,0x00,0x01,0xE0,0x0F,0x01,0xF0,0x00,0x00,0x01,0xE0,0x0F,0x01, 0xE0,0x00,0x00,0x01,0xE0,0x0F,0x01,0xE0,0x00,0x00,0x1F,0xFF,0xFF,0xF3,0xFF,0xFF, 0xF0,0x3F,0xFF,0xFF,0xFB,0xFF,0xFF,0xF8,0x3F,0xFF,0xFF,0xFB,0xFF,0xFF,0xF8,0x1F, 0xFF,0xFF,0xE7,0xC3,0x80,0x7C,0x01,0xE0,0x0F,0x07,0x87,0x80,0x7C,0x01,0xE0,0x0F, 0x0F,0x87,0x80,0x7C,0x01,0xE0,0x0F,0x0F,0x87,0x80,0x78,0x01,0xE0,0x0F,0x1F,0x07, 0x80,0x78,0x01,0xE0,0x0F,0x1F,0x07,0x80,0xF8,0x01,0xE0,0x0F,0x3E,0x07,0x80,0xF8, 0x01,0xE0,0x0F,0x3E,0x07,0x80,0xF0,0x01,0xE0,0x0F,0x3C,0x07,0x81,0xF0,0x01,0xE0, 0x0F,0x3C,0x07,0x81,0xE0,0x01,0xE0,0x0F,0x00,0x07,0x83,0xE0,0x01,0xFF,0xFF,0x00, 0x07,0xC3,0xC0,0x01,0xFF,0xFF,0x00,0x07,0xC0,0x00,0x01,0xFF,0xFF,0x00,0x0F,0xE0, 0x00,0x01,0xFF,0xFF,0x00,0x0F,0xE0,0x00,0x01,0xE0,0x0F,0x00,0x0F,0xF0,0x00,0x01, 0xE0,0x0F,0x00,0x1E,0xF0,0x00,0x01,0xE0,0x0F,0x00,0x1E,0xF8,0x00,0x01,0xE0,0x0F, 0x00,0x3E,0x78,0x00,0x01,0xE0,0x0F,0x00,0x7C,0x7C,0x00,0x01,0xE0,0x0F,0x00,0x78, 0x3E,0x00,0x01,0xE0,0x0F,0x00,0xF8,0x1E,0x00,0x01,0xE0,0x0F,0x01,0xF0,0x1F,0x00, 0x01,0xE0,0x0F,0x03,0xE0,0x0F,0x80,0x01,0xE0,0x0F,0x07,0xE0,0x07,0xC0,0x01,0xE0, 0x0F,0x0F,0xC0,0x03,0xE0,0x01,0xE0,0x0F,0x1F,0x80,0x01,0xF0,0x01,0xFF,0xFF,0x3F, 0x00,0x01,0xF8,0x00,0xFF,0xFF,0x7E,0x00,0x00,0xFC,0x00,0x7F,0xFE,0x78,0x00,0x00, 0x3C,0x00,0x00,0x00,0x70,0x00,0x00,0x18, 6.4 OLED显示效果图七、LCD驱动编写7.1 编写S70屏幕驱动     如果自己编写了LCD驱动(S720屏幕),测试LCD驱动之前,先去除内核自带的LCD驱动,编译烧写内核:群创S70驱动代码:#include <linux/module.h> #include <linux/kernel.h> #include <linux/errno.h> #include <linux/string.h> #include <linux/mm.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/fb.h> #include <linux/init.h> #include <linux/dma-mapping.h> #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/clk.h> #include <linux/cpufreq.h> #include <asm/io.h> #include <asm/div64.h> #include <asm/mach/map.h> #include <mach/regs-gpio.h> #define MHZ (1000*1000) #define PRINT_MHZ(m) ((m) / MHZ), ((m / 1000) % 1000) /* LCD参数 */ #define VSPW 0 #define VBPD 22 #define LINEVAL 479 #define VFPD 21 #define HSPW 0 #define HBPD 45 #define HOZVAL 799 #define HFPD 209 #define LeftTopX 0 #define LeftTopY 0 #define RightBotX 799 #define RightBotY 479 /* LCD控制寄存器 */ static unsigned long *vidcon0; /* Configures video output format and displays enable/disable. */ static unsigned long *vidcon1; /* Specifies RGB I/F control signal */ static unsigned long *vidcon2; /* Specifies output data format control. */ static unsigned long *vidtcon0; /* video time control 0 */ static unsigned long *vidtcon1; /* video time control 1 */ static unsigned long *vidtcon2; /* video time control 2 */ static unsigned long *wincon0; /* window control 0 */ static unsigned long *vidosd0a; /* video window 0 position control */ static unsigned long *vidosd0b; /* video window 0 position control1 */ static unsigned long *vidosd0c; /* video window 0 position control */ static unsigned long *vidw00add0b0; /* window 0 buffer start address, buffer 0 */ static unsigned long *vidw00add1b0; /* window 0 buffer end address, buffer 0 */ static unsigned long *vidw00add2; /* window 0 buffer size */ static unsigned long *wpalcon; static unsigned long *shadowcon; static unsigned long *winchmap2; /* 用于LCD的GPIO */ static unsigned long *gpf0con = NULL; static unsigned long *gpf1con = NULL; static unsigned long *gpf2con = NULL; static unsigned long *gpf3con = NULL; static unsigned long *gpf0pud = NULL; static unsigned long *gpf1pud = NULL; static unsigned long *gpf2pud = NULL; static unsigned long *gpf3pud = NULL; static unsigned long *gpf0drv = NULL; static unsigned long *gpf1drv = NULL; static unsigned long *gpf2drv = NULL; static unsigned long *gpf3drv = NULL; /* 用于背光 */ static unsigned long *gpd0con = NULL; static unsigned long *gpd0dat = NULL; static unsigned long *lcdblk_cfg = NULL; static unsigned long *lcdblk_cfg2 = NULL; static struct fb_info *mylcd; static u32 pseudo_pal[16]; static struct device dev = {undefined .init_name = "exynos4-fb.0", static inline unsigned int chan_to_field(unsigned int chan, struct fb_bitfield *bf) {undefined chan &= 0xffff; chan >>= 16 - bf->length; return chan << bf->offset; static int mylcdfb_setcolreg(unsigned int regno, unsigned int red, unsigned int green, unsigned int blue, unsigned int transp, struct fb_info *info) {undefined unsigned int val; if (regno > 16) return 1; /* 用red,green,blue三原色构造出val */ val = chan_to_field(red, &info->var.red); val |= chan_to_field(green, &info->var.green); val |= chan_to_field(blue, &info->var.blue); //((u32 *)(info->pseudo_palette))[regno] = val; pseudo_pal[regno] = val; return 0; static struct fb_ops mylcdfb_ops = {undefined .owner = THIS_MODULE, .fb_setcolreg = mylcdfb_setcolreg, .fb_fillrect = cfb_fillrect, .fb_copyarea = cfb_copyarea, .fb_imageblit = cfb_imageblit, static void init_fimd(void) {undefined gpf0con = ioremap(0x11400180,16); gpf0pud = gpf0con + 2; gpf0drv = gpf0con + 3; gpf1con = ioremap(0x114001A0,16); gpf1pud = gpf1con + 2; gpf1drv = gpf1con + 3; gpf2con = ioremap(0x114001C0,16); gpf2pud = gpf2con + 2; gpf2drv = gpf2con + 3; gpf3con = ioremap(0x114001E0,16); gpf3pud = gpf3con + 2; gpf3drv = gpf3con + 3; /* 设置管脚为LCD接口功能 */ *gpf0con = 0x22222222; *gpf1con = 0x22222222; *gpf2con = 0x22222222; *gpf3con = 0x22222222; /* 设置为上拉 */ *gpf0pud = 0x0000FFFF; *gpf1pud = 0x0000FFFF; *gpf2pud = 0x0000FFFF; *gpf3pud = 0x00000FFF; /* 设置为最高驱动能力 */ *gpf0drv = 0x0000FFFF; *gpf1drv = 0x0000FFFF; *gpf2drv = 0x0000FFFF; *gpf3drv = 0x00000FFF; static void init_blanklight(void) {undefined gpd0con = ioremap(0x114000a0,4); gpd0dat = ioremap(0x114000a4,4); *gpd0con |= 1<<4; *gpd0dat |= 1<<1; static void init_lcd_regs(void) {undefined lcdblk_cfg = ioremap(0x10010210,4); lcdblk_cfg2 = lcdblk_cfg + 1; vidcon0 = ioremap(0x11C00000,4); vidcon1 = ioremap(0x11C00004,4); wincon0 = ioremap(0x11C00020,4); vidosd0a = ioremap(0x11C00040,4); vidosd0b = ioremap(0x11C00044,4); vidosd0c = ioremap(0x11C00048,4); vidw00add0b0 = ioremap(0x11C000A0,4); vidw00add1b0 = ioremap(0x11C000D0,4); vidw00add2 = ioremap(0x11C00100,4); vidtcon0 = ioremap(0x11C00010,4); vidtcon1 = ioremap(0x11C00014,4); vidtcon2 = ioremap(0x11C00018,4); wpalcon = ioremap(0x11C001A0,4); shadowcon = ioremap(0x11C00034,4); winchmap2 = ioremap(0x11C0003c,4); static void rm_all_regs(void) {undefined iounmap(gpf0con); iounmap(gpf1con); iounmap(gpf2con); iounmap(gpf3con); iounmap(gpd0con); iounmap(gpd0dat); iounmap(lcdblk_cfg); iounmap(vidcon0); iounmap(vidcon1); iounmap(vidtcon2); iounmap(wincon0); iounmap(vidosd0a); iounmap(vidosd0b); iounmap(vidosd0c); iounmap(vidw00add0b0); iounmap(vidw00add1b0); iounmap(vidw00add2); iounmap(vidtcon0); iounmap(vidtcon1); iounmap(shadowcon); iounmap(winchmap2); /* 入口函数 */ static int mylcd_init(void) {undefined struct clk *bus; struct clk *lcd_clk; /* 1. 分配一个fb_info */ mylcd = framebuffer_alloc(0, NULL); /* 2. 设置 */ /* 2.1 设置固定的参数 */ strcpy(mylcd->fix.id, "mylcd"); //mylcd->fix.smem_start = ; //显存的物理起始地址 mylcd->fix.smem_len = 800*480*4; mylcd->fix.type = FB_TYPE_PACKED_PIXELS; mylcd->fix.visual = FB_VISUAL_TRUECOLOR; mylcd->fix.line_length = 800*4; /* 2.2 设置可变的参数 */ mylcd->var.xres = 800; mylcd->var.yres = 480; mylcd->var.xres_virtual = 800; mylcd->var.yres_virtual = 480; mylcd->var.bits_per_pixel = 32; /*RGB = 8:8:8*/ mylcd->var.red.offset = 16; mylcd->var.red.length = 8; mylcd->var.green.offset = 8; mylcd->var.green.length = 8; mylcd->var.blue.offset = 0; mylcd->var.blue.length = 8; mylcd->var.activate = FB_ACTIVATE_NOW; /* 2.3 设置操作函数 */ mylcd->fbops = &mylcdfb_ops; /* 2.4 其他的设置 */ //mylcd->screen_base = ; /*显存的虚拟起始地址*/ mylcd->screen_size = 800*480*4; mylcd->pseudo_palette = pseudo_pal; /* 3. 硬件相关的操作 */ /* 3.1 配置GPIO用于LCD */ init_fimd(); /* 3.2 使能时钟 */ bus = clk_get(&dev, "lcd"); if (IS_ERR(bus)) {undefined printk(KERN_INFO "failed to get lcd clock source\n"); clk_enable(bus); printk("bus clock = %lu\n",clk_get_rate(bus)); lcd_clk = clk_get(&dev, "sclk_fimd"); if (IS_ERR(lcd_clk)) {undefined printk(KERN_INFO "failed to get lcd clock source\n"); clk_enable(lcd_clk); printk("lcd clock = %lu\n",clk_get_rate(lcd_clk)); /* LCD控制器初始化 */ init_lcd_regs(); * LCDBLK_CFG * [11:10] :Video Type Selection 00 = RGB Interface *lcdblk_cfg &= ~(0x3<<10); *lcdblk_cfg |= 1 << 1; *lcdblk_cfg2 |= 1; *VIDCON0: * [13:6]: CLKVAL_F //设置LCD时钟分频系数 * VCLK == 33.3Mhz * VCLK = FIMD * SCLK/(CLKVAL+1) * VCLK = 800000000 / (CLKVAL + 1) * 33300000 = 800000000 /(CLKVAL + 1) * CLKVAL + 1 = 24.02 * CLKVAL = 23 *vidcon0 &= ~((0xff<<6)|(0x7<<26)|(1<<18)); *vidcon0 |= 23<<6; /* 设置极性(要修改) *VIDTCON1: * [5]:IVSYNC ===> 1 : Inverted(反转) * [6]:IHSYNC ===> 1 : Inverted(反转) * [7]:IVCLK ===> 1 : Fetches video data at VCLK rising edge (下降沿触发) * [10:9]:FIXVCLK ====> 01 : VCLK running *vidcon1 = (1 << 9) | (1 << 7) | (1 << 5) | (1 << 6); /* 设置时序(需要修改) */ *vidtcon0 = (VBPD << 16) | (VFPD << 8) | (VSPW << 0); *vidtcon1 = (HBPD << 16) | (HFPD << 8) | (HSPW << 0); /* 设置屏幕的大小 * LINEVAL[21:11]:多少行 = 480 * HOZVAL [10:0] :水平大小 = 800 *vidtcon2 = (LINEVAL << 11) | (HOZVAL << 0); *WINCON0: * [5:2]: 选择窗口的像素 1011 ===> 24BPP * [1]: 使能或者禁止窗口输出 1 = Enables * [15] : 大小端 :1=Enable ; *wincon0 &= ~(0xf << 2); *wincon0 |= (0xB<<2)|(1<<15); /* 窗口0,左上角的位置(0,0) */ /* 窗口0,右下角的位置(0,0) */ *vidosd0a = (LeftTopX<<11) | (LeftTopY << 0); *vidosd0b = (RightBotX<<11) | (RightBotY << 0); /* 大小 */ *vidosd0c = (LINEVAL + 1) * (HOZVAL + 1); /* 分配显存 */ mylcd->screen_base = dma_alloc_writecombine(NULL, mylcd->fix.smem_len, &mylcd->fix.smem_start, GFP_KERNEL); *vidw00add0b0 = mylcd->fix.smem_start; //显存的物理起始地址 *vidw00add1b0 = mylcd->fix.smem_start + mylcd->fix.smem_len; //显存的物理结束地址 /* C0_EN_F 0 Enables Channel 0. * 0 = Disables 1 = Enables *shadowcon |= 0x1; /* LCD控制器开启 */ *vidcon0 |= 0x3; /* 开启总控制器 */ *wincon0 |= 1; /* 开启窗口0 */ /*4.注册*/ register_framebuffer(mylcd); return 0; static void mylcd_exit(void) {undefined unregister_framebuffer(mylcd); dma_free_writecombine(NULL, mylcd->fix.smem_len, &(mylcd->fix.smem_start), GFP_KERNEL); rm_all_regs(); framebuffer_release(mylcd); module_init(mylcd_init); module_exit(mylcd_exit); MODULE_LICENSE("GPL"); 7.2 增加自己的LCD驱动下面步骤演示,在内核自带的LCD驱动框架上增加自己的LCD信息。打开tiny4412-lcds.c文件增加LCD信息列表,增加之后再编译内核烧写LCD_wbyq结构体信息如下(拷贝S70屏幕的信息):修改UBOOT启动代码传入的LCD参数,将参数改为自己的LCD名字

QT软件开发: 解析JSON数据(QJsonX)

一、前言本篇文章介绍使用QJsonDocument、QJsonObject、QJsonArray来解析JSON数据。      QJsonDocument类提供了一种读取和写入JSON文档的方法。      QJsonDocument是一个封装完整JSON文档的类,可以从UTF-8编码的基于文本的表示以及Qt自己的二进制格式读取和写入此文档。      可以使用QJsonDocument::fromJson()将JSON文档从基于文本的表示转换为QJsonDocument.toJson()将其转换回文本。解析器非常快速高效,可以将JSON转换为Qt使用的二进制表示形式。     已解析文档的有效性可以使用!isNull()     可以使用isArray()和isObject()查询文档,确定它是包含数组还是对象。可以使用array()或          object()检索文档中包含的数组或对象,然后进行读取或操作。     还可以使用fromBinaryData()或fromRawData()从存储的二进制表示形式创建文档。二、解析JSON示例代码2.1 json数据示例{ "streams": [ "index": 0, "codec_name": "mpeg1video", "codec_long_name": "MPEG-1 video", "codec_type": "video", "codec_time_base": "1/25", "codec_tag_string": "mp4v", "codec_tag": "0x7634706d", "width": 1920, "height": 1080, "coded_width": 0, "coded_height": 0, "has_b_frames": 1, "sample_aspect_ratio": "1:1", "display_aspect_ratio": "16:9", "pix_fmt": "yuv420p", "level": -99, "color_range": "tv", "chroma_location": "center", "refs": 1, "r_frame_rate": "25/1", "avg_frame_rate": "25/1", "time_base": "1/90000", "start_pts": 900, "start_time": "0.010000", "duration_ts": 6411600, "duration": "71.240000", "bit_rate": "104857200", "max_bit_rate": "104857200", "nb_frames": "1781", "disposition": { "default": 1, "dub": 0, "original": 0, "comment": 0, "lyrics": 0, "karaoke": 0, "forced": 0, "hearing_impaired": 0, "visual_impaired": 0, "clean_effects": 0, "attached_pic": 0, "timed_thumbnails": 0 "tags": { "language": "und", "handler_name": "VideoHandler" "index": 1, "codec_name": "mp3", "codec_long_name": "MP3 (MPEG audio layer 3)", "codec_type": "audio", "codec_time_base": "1/48000", "codec_tag_string": "mp4a", "codec_tag": "0x6134706d", "sample_fmt": "fltp", "sample_rate": "48000", "channels": 2, "channel_layout": "stereo", "bits_per_sample": 0, "r_frame_rate": "0/0", "avg_frame_rate": "0/0", "time_base": "1/48000", "start_pts": 0, "start_time": "0.000000", "duration_ts": 3420288, "duration": "71.256000", "bit_rate": "384000", "max_bit_rate": "384000", "nb_frames": "2969", "disposition": { "default": 1, "dub": 0, "original": 0, "comment": 0, "lyrics": 0, "karaoke": 0, "forced": 0, "hearing_impaired": 0, "visual_impaired": 0, "clean_effects": 0, "attached_pic": 0, "timed_thumbnails": 0 "tags": { "language": "und", "handler_name": "SoundHandler" }2.2 解析代码示例 这个JSON数据是描述一个视频的详细信息的,是通过ffprobe.exe命令返回的。代码如下: 1./* 工程: ASS_SubtitleVideoPlayer 日期: 2021-09-07 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 获取视频尺寸 QSize Widget::GetVideoSize(QString video_name) QString cmd=QString("ffprobe.exe -v quiet -of json -i %1 -show_streams").arg(video_name); qDebug()<<"cmd:"<<cmd; qDebug()<<"当前路径:"<<QCoreApplication::applicationDirPath(); QProcess process; process.setWorkingDirectory(QCoreApplication::applicationDirPath()); process.start(cmd); process.waitForFinished(); process.waitForReadyRead(); QByteArray qba = process.readAll(); //qDebug()<<"读取的数据:"<<qba; QJsonParseError jsonError; // 转化为 JSON 文档 QJsonDocument doucment = QJsonDocument::fromJson(qba, &jsonError); //解析未发生错误 if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) //JSON文档为对象 if (doucment.isObject()) //转化为对象 QJsonObject object = doucment.object(); //判断是否包含指定的key if (object.contains("streams")) QJsonValue arrayValue = object.value("streams"); QJsonArray array = arrayValue.toArray(); for(int i=0;i<array.size();i++) QJsonValue Array = array.at(i); QJsonObject data = Array.toObject(); //取出对应的值: 这里得到视频的宽和高 int width = data["width"].toInt(); int height = data["height"].toInt(); return QSize(width,height); return QSize(0,0); }三、JSON解析示例:  简单的数据 3.1 数据示例{ "code": 0, "data": "1424237430621474816",// 具体播放的视频id "msg": "操作成功", "success": true }3.2 代码示例这是解析服务器返回的JSON数据。/* 日期: 2021-08-13 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 网络请求返回的结果 void Widget::slot_request_videoID_replyFinished(QNetworkReply *reply) QString err_code; QByteArray read_data; int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt(); read_data=reply->readAll(); if(200 != statusCode) err_code=tr("视频ID:上传错误,错误代码:%1\n").arg(statusCode); return; qDebug()<<"视频ID:"<<read_data; QJsonParseError jsonError; // 转化为 JSON 文档 QJsonDocument doucment = QJsonDocument::fromJson(read_data, &jsonError); //解析未发生错误 if (!doucment.isNull() && (jsonError.error == QJsonParseError::NoError)) //JSON文档为对象 if (doucment.isObject()) //转化为对象 QJsonObject object = doucment.object(); //包含指定的key if (object.contains("success")) // 获取指定 key 对应的 value QJsonValue value = object.value("success"); // 判断 value 是否为对象 if (value.isBool()) bool success=value.toBool(); qDebug() << "success:" << success; if(success) //包含指定的key if (object.contains("data")) // 获取指定 key 对应的 value QJsonValue value = object.value("data"); // 判断 value 是否为对象 if (value.isString()) m_video_id = value.toString(); qDebug() << "m_video_id:" << m_video_id; //包含指定的key if (object.contains("msg")) // 获取指定 key 对应的 value QJsonValue value = object.value("msg"); // 判断 value 是否为对象 if (value.isString()) QString msg = value.toString(); qDebug() << "视频ID获取:" << msg; m_video_id="88888888888888888"; qDebug()<<"视频ID:JSON解析错误.";

Linux驱动开发: 网络设备驱动开发

Linux内核版本: 3.5一、Linux下网络相关命令1.1  ifconfig命令:设置网卡IP地址功能ifconfig用于查看和更改网络接口的地址和参数,包括IP地址、网络掩码、广播地址,使用权限是超级用户。语法:fconfig -interface [options] address主要参数   ifconfig是用来设置和配置网卡的命令行工具。为了手工配置网络,这是一个必须掌握的命令。使用该命令的好处是无须重新启动机器。要赋给eth0接口IP地址207.164.186.2,并且马上激活它,使用下面命令:该命令的作用是设置网卡eth0的IP地址、网络掩码和网络的本地广播地址。若运行不带任何参数的ifconfig命令,这个命令将显示机器所有激活接口的信息。带有“-a”参数的命令则显示所有接口的信息,包括没有激活的接口。注意,用ifconfig命令配置的网络设备参数,机器重新启动以后将会丢失。查看网卡的IP地址信息关闭与启动网卡修改网卡MAC地址在一张网卡上绑定多个IP地址在Linux下,可以使用ifconfig方便地绑定多个IP地址到一张网卡。例如,eth0接口的原有IP地址为192.168.0 .254,可以执行下面命令:1.2 ping命令功能:ping检测主机网络接口状态,使用权限是所有用户。语法:ping [-dfnqrRv][-c][-i][-I][-l][-p][-s][-t] IP地址主要参数ing命令是使用最多的网络指令,通常我们使用它检测网络是否连通,它使用ICMP协议。但是有时会有这样的情况,我们可以浏览器查看一个网页,但是却无法ping通,这是因为一些网站处于安全考虑安装了防火墙。使用实例1.3 网卡启动与关闭除了使用ifconfig配置之外,也可以使用ifup、ifdown命令来实现。二、查看内核已经支持的网卡驱动进入到内核配置菜单目录下:[root@wbyq linux-3.5]# make menuconfig三、移植ENC28J60网卡驱动3.1 ENC28J60芯片介绍     ENC28J60 是带有行业标准串行外设接口(Serial Peripheral Interface,SPI)的独立以太网 控制器。它可作为任何配备有 SPI 的控制器的以太网接口。ENC28J60 符合 IEEE 802.3 的全部规范,采用了一系列包过滤机制以对传入数据包进行限制。 它还提供了一个内部 DMA 模块, 以实现快速数据吞吐和硬件支持的 IP 校验和计算。 与主控制器的通信通过两个中断引脚和 SPI 实现,数据传输速率高达 10 Mb/s。两个专用的引脚用于连接 LED,进行网络活动状态指示。ENC28J60 总共只有 28 脚,提供 QFN/TF。 ENC28J60 的主要特点如下:兼容 IEEE802.3 协议的以太网控制器集成 MAC 和 10 BASE-T 物理层支持全双工和半双工模式数据冲突时可编程自动重发SPI 接口速度可达 10Mbps8K 数据接收和发送双端口 RAM提供快速数据移动的内部 DMA 控制器可配置的接收和发送缓冲区大小两个可编程 LED 输出带7个中断源的两个中断引脚TTL 电平输入提供多种封装:SOIC/SSOP/SPDIP/QFN 等。ENC28J60 的典型应用电路如下图:ENC28J60 由七个主要功能模块组成:1) SPI 接口,充当主控制器和 ENC28J60 之间通信通道。2) 控制寄存器,用于控制和监视 ENC28J60。3) 双端口 RAM 缓冲器,用于接收和发送数据包。4) 判优器,当 DMA、发送和接收模块发出请求时对 RAM 缓冲器的访问进行控制。5) 总线接口,对通过 SPI 接收的数据和命令进行解析。6) MAC(Medium Access Control)模块,实现符合 IEEE 802.3 标准的 MAC 逻辑。7) PHY(物理层)模块,对双绞线上的模拟数据进行编码和译码。 ENC28J60 还包括其他支持模块,诸如振荡器、片内稳压器、电平变换器(提供可以接受 5V 电压的 I/O 引脚)和系统控制逻辑。引脚功能说明:3.2 ENC28J60以太网模块介绍      ENC28J60 网络模块采用 ENC28J60 作为主芯片,单芯片即可实现以太网接入, 利用该模块,基本上只要是个单片机,就可以实现以太网连接。    模块实物图如下:模块的主要引脚功能:     其中 GND 和 V3.3 用于给模块供电,MISO/MOSI/SCK 用于 SPI 通信,CS 是片选信号,INT 为中断输出引脚,RST 为模块复位信号。3.3 查看内核已经支持的网卡源码    在内核linux-3.5/drivers/net/ethernet源码目录下可以查看已经支持的网卡源码。   ENC28J60网卡源码就存放在: /linux-3.5/drivers/net/ethernet/microchip目录下3.4 配置内核SPI总线设备端ENC28J60使用的是SPI总线通信,先查看内核SPI总线板级注册是否支持。进入到内核配置菜单:  [root@wbyq linux-3.5]# make menuconfig(使用的测试开发板是友善之臂的Tiny4412开发板)   因为开发板引出的SPI接口只有SPI0,所以只能配置SPI0总线。 1. 修改SPI0总线板级注册信息打开开发板底层板级配置文件:2. 修改SPI设备端名称:SPI子系统匹配使用的是平台设备模型,驱动端与设备端的名称需要一致。3. 修改完以上两步配置之后,再重新编译内核,烧写内核。3.5 修改ENC28J60驱动代码将/drivers/net/ethernet/microchip目录下的ENC28J60源码复制出来,单独修改。1. 编写Makefile文件,负责编译成模块。2. 修改ENC28J60驱动源码里的名称与SPI总线设备端保持一致。3. 修改驱动端的probe函数,增加对SPI模式配置与中断号获取,正常情况下可以直接在SPI设备端直接修改,驱动端直接获取信息即可。 除了修改以上信息之外,其他信息不用修改,直接编译驱动安装即可。3.6 驱动安装测试四、网络设备相关API函数介绍4.1 动态分配net_device结构函数参数:分配的空间大小。如果自己没有定义自己的结构体,就直接填sizeof(struct net_device)函数返回值:执行成功返回申请的空间地址。空间分配的函数还有一个alloc_netdev()函数。alloc_etherdev()是alloc_netdev()针对以太网的"快捷"函数4.2 释放net_device结构该函数用于释放alloc_etherdev分配的net_device结构体,与alloc_etherdev成对使用。4.3 注册网络设备函数形参:网络设备信息struct net_device函数返回值:执行成功返回0。struct net_device结构体原型如下:const struct net_device_ops 网络设备虚拟文件操作集合:分配net_device结构体之后初始化示例​4.4 注销网络设备功能:注销网络设备参数:注销的网络设备结构体4.5 随机生成MAC地址  该函数使用软件方式随机生成一个MAC地址,并给传入的net_device 结构体内部成员dev_addr赋值。示例:4.6 以太网最小一帧数据长度定义使用网卡发送数据时,如何发现发送的实际数据小于以太网规定的最小长度,需要进行补齐:4.7 分配新的套接字缓冲区该函数用于分配新的套接字缓冲区,用于存放即将上报给上层(TCP/IP协议层)的网络数据。示例:​4.8 获取数据包的协议ID从网卡里读取到数据包之后,可以通过该函数获取数据包的协议类型。示例:1.5 网络设备框架介绍5.1 网络设备驱动框图5.2 ndo_start_xmit函数接口代码编写示例5.3 通过netif_rx函数上报数据代码编写示例六、 网络设备驱动框架代码6.1 网络设备驱动编程步骤1. 调用alloc_etherdev函数,分配net_device对象2. 对返回的net_device结构指针进行初始化赋值,比如:网卡名称,MAC地址,文件操作集合等等。    如果网卡没有固定的MAC地址,可以通过eth_hw_addr_random函数随机生成。3. 网络设备文件操作集合实现的接口如下:4. 调用register_netdev函数完成网络设备注册。   注销函数: unregister_netdev5. 网卡收到数据通过netif_rx函数上传给应用层6.2 网络设备驱动框架代码以下代码是一个网络设备驱动模型,演示了网卡如何获取上层应用程序传递下来的数据并发送出去,网卡接收到数据如何传递给上层应用程序。6.3 ENC28J60网卡驱动代码     以下代码,在上面的网络设备驱动模型里加入了ENC28J60驱动代码,实现了完整的网卡驱动程序。    以下代码中的ENC28J60驱动直接是使用模拟SPI时序,没有使用SPI子系统。   由于测试的ENC28J60网卡中断无法正常产生,故使用内核定时器进行轮询读取网卡数据,读取之后再上传给应用层。网卡驱动安装后应用层测试效果如下:​Enc28j60.h文件代码Enc28j60.c文件代码:#include <linux/init.h> #include <linux/module.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> #include <linux/delay.h> #include "enc28j60.h" #include <linux/gpio.h> #include <mach/gpio.h> #include <plat/gpio-cfg.h> #include <linux/delay.h> #include <linux/workqueue.h> #include <linux/delay.h> #include <linux/interrupt.h> #include <linux/irq.h> #include <linux/timer.h> 参考的网卡程序: cs89x0.c与Enc28j60.c 以下是ENC28J60驱动移植接口: SPI0接口: GPB_0--SCK GPB_1--CS GPB_2--MISO GPB_3--MOSI GPX1(0)--中断 static u32 ENC28J60_IRQ; //中断编号 /*SPI底层硬件IO定义*/ #define Tiny4412_GPIO_SPI_SCK EXYNOS4_GPB(0) #define Tiny4412_GPIO_SPI_CS EXYNOS4_GPB(1) #define Tiny4412_GPIO_SPI_MISO EXYNOS4_GPB(2) #define Tiny4412_GPIO_SPI_MOSI EXYNOS4_GPB(3) #define ENC28J60_IRQ_NUMBER EXYNOS4_GPX1(0) /*Tiny4412开发板引出的IO口第9个IO口*/ #define ENC28J60_CS(x) if(x){gpio_set_value(Tiny4412_GPIO_SPI_CS,1);}else{gpio_set_value(Tiny4412_GPIO_SPI_CS,0);} //ENC28J60片选信号 #define ENC28J60_MOSI(x) if(x){gpio_set_value(Tiny4412_GPIO_SPI_MOSI,1);}else{gpio_set_value(Tiny4412_GPIO_SPI_MOSI,0);} //输出 #define ENC28J60_MISO (gpio_get_value(Tiny4412_GPIO_SPI_MISO)) //输入 #define ENC28J60_SCLK(x) if(x){gpio_set_value(Tiny4412_GPIO_SPI_SCK,1);}else{gpio_set_value(Tiny4412_GPIO_SPI_SCK,0);} //时钟线 static u8 ENC28J60BANK; static u32 NextPacketPtr; static struct timer_list timer_date; //网卡MAC地址,必须唯一 u8 ENC28J60_MacAddr[6]={0x04,0x02,0x35,0x00,0x00,0x01}; //MAC地址 static struct net_device *tiny4412_net=NULL; //网络设备指针结构 函数功能:底层SPI接口收发一个字节 说 明:模拟SPI时序,ENC28J60时钟线空闲电平为低电平,在第一个下降沿采集数据 u8 ENC28J60_SPI_ReadWriteOneByte(u8 tx_data) {undefined u8 rx_data=0; u8 i; for(i=0;i<8;i++) {undefined if(tx_data&0x80){ENC28J60_MOSI(1);} else {ENC28J60_MOSI(0);} tx_data<<=1; {ENC28J60_SCLK(1); } rx_data<<=1; if(ENC28J60_MISO)rx_data|=0x01; {ENC28J60_SCLK(0);}//第一个下降沿采集数据 return rx_data; 函数功能:复位ENC28J60,包括SPI初始化/IO初始化等 MISO--->PA6----主机输入 MOSI--->PA7----主机输出 SCLK--->PA5----时钟信号 CS----->PA4----片选 RESET-->PG15---复位 void ENC28J60_Reset(void) {undefined /*释放GPIO*/ gpio_free(Tiny4412_GPIO_SPI_SCK); gpio_free(Tiny4412_GPIO_SPI_CS); gpio_free(Tiny4412_GPIO_SPI_MISO); gpio_free(Tiny4412_GPIO_SPI_MOSI); /*1. 配置GPIO模式*/ printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_SCK, "Tiny4412_Tiny4412_SPI_SCK")); printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_CS, "Tiny4412_Tiny4412_SPI_CS")); printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_MISO, "Tiny4412_Tiny4412_SPI_MISO")); printk("%d\n",gpio_request(Tiny4412_GPIO_SPI_MOSI, "Tiny4412_Tiny4412_SPI_MOSI")); printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_SCK, S3C_GPIO_OUTPUT)); printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_CS, S3C_GPIO_OUTPUT)); printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_MISO, S3C_GPIO_INPUT)); printk("%d\n",s3c_gpio_cfgpin(Tiny4412_GPIO_SPI_MOSI, S3C_GPIO_OUTPUT)); mdelay(100); 函数功能:读取ENC28J60寄存器(带操作码) 参 数: op:操作码 addr:寄存器地址/参数 返 回 值:读到的数据 u8 ENC28J60_Read_Op(u8 op,u8 addr) {undefined u8 dat=0; ENC28J60_CS(0); dat=op|(addr&ADDR_MASK); ENC28J60_SPI_ReadWriteOneByte(dat); dat=ENC28J60_SPI_ReadWriteOneByte(0xFF); //如果是读取MAC/MII寄存器,则第二次读到的数据才是正确的,见手册29页 if(addr&0x80)dat=ENC28J60_SPI_ReadWriteOneByte(0xFF); ENC28J60_CS(1); return dat; 函数功能:读取ENC28J60寄存器(带操作码) 参 数: op:操作码 addr:寄存器地址 data:参数 void ENC28J60_Write_Op(u8 op,u8 addr,u8 data) {undefined u8 dat = 0; ENC28J60_CS(0); dat=op|(addr&ADDR_MASK); ENC28J60_SPI_ReadWriteOneByte(dat); ENC28J60_SPI_ReadWriteOneByte(data); ENC28J60_CS(1); 函数功能:读取ENC28J60接收缓存数据 参 数: len:要读取的数据长度 data:输出数据缓存区(末尾自动添加结束符) void ENC28J60_Read_Buf(u32 len,u8* data) {undefined ENC28J60_CS(0); ENC28J60_SPI_ReadWriteOneByte(ENC28J60_READ_BUF_MEM); while(len) {undefined len--; *data=(u8)ENC28J60_SPI_ReadWriteOneByte(0); data++; *data='\0'; ENC28J60_CS(1); 函数功能:向ENC28J60写发送缓存数据 参 数: len:要写入的数据长度 data:数据缓存区 void ENC28J60_Write_Buf(u32 len,u8* data) {undefined ENC28J60_CS(0); ENC28J60_SPI_ReadWriteOneByte(ENC28J60_WRITE_BUF_MEM); while(len) {undefined len--; ENC28J60_SPI_ReadWriteOneByte(*data); data++; ENC28J60_CS(1); 函数功能:设置ENC28J60寄存器Bank 参 数: ban:要设置的bank void ENC28J60_Set_Bank(u8 bank) if((bank&BANK_MASK)!=ENC28J60BANK)//和当前bank不一致的时候,才设置 ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,(ECON1_BSEL1|ECON1_BSEL0)); ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,(bank&BANK_MASK)>>5); ENC28J60BANK=(bank&BANK_MASK); 函数功能:读取ENC28J60指定寄存器 参 数:addr:寄存器地址 返 回 值:读到的数据 u8 ENC28J60_Read(u8 addr) ENC28J60_Set_Bank(addr);//设置BANK return ENC28J60_Read_Op(ENC28J60_READ_CTRL_REG,addr); 函数功能:向ENC28J60指定寄存器写数据 参 数: addr:寄存器地址 data:要写入的数据 void ENC28J60_Write(u8 addr,u8 data) ENC28J60_Set_Bank(addr); ENC28J60_Write_Op(ENC28J60_WRITE_CTRL_REG,addr,data); 函数功能:向ENC28J60的PHY寄存器写入数据 参 数: addr:寄存器地址 data:要写入的数据 void ENC28J60_PHY_Write(u8 addr,u32 data) {undefined u16 retry=0; ENC28J60_Write(MIREGADR,addr); //设置PHY寄存器地址 ENC28J60_Write(MIWRL,data); //写入数据 ENC28J60_Write(MIWRH,data>>8); while((ENC28J60_Read(MISTAT)&MISTAT_BUSY)&&retry<0XFFF)retry++;//等待写入PHY结束 函数功能:初始化ENC28J60 参 数:macaddr:MAC地址 返 回 值: 0,初始化成功; 1,初始化失败; u8 ENC28J60_Init(u8* macaddr) u16 retry=0; ENC28J60_Reset(); //复位底层引脚接口 ENC28J60_Write_Op(ENC28J60_SOFT_RESET,0,ENC28J60_SOFT_RESET);//软件复位 while(!(ENC28J60_Read(ESTAT)&ESTAT_CLKRDY)&&retry<500)//等待时钟稳定 {undefined retry++; mdelay(1); if(retry>=500)return 1;//ENC28J60初始化失败 // do bank 0 stuff // initialize receive buffer // 16-bit transfers,must write low byte first // set receive buffer start address 设置接收缓冲区地址 8K字节容量 NextPacketPtr=RXSTART_INIT; // Rx start //接收缓冲器由一个硬件管理的循环FIFO 缓冲器构成。 //寄存器对ERXSTH:ERXSTL 和ERXNDH:ERXNDL 作 //为指针,定义缓冲器的容量和其在存储器中的位置。 //ERXST和ERXND指向的字节均包含在FIFO缓冲器内。 //当从以太网接口接收数据字节时,这些字节被顺序写入 //接收缓冲器。 但是当写入由ERXND 指向的存储单元 //后,硬件会自动将接收的下一字节写入由ERXST 指向 //的存储单元。 因此接收硬件将不会写入FIFO 以外的单 //设置接收起始字节 ENC28J60_Write(ERXSTL,RXSTART_INIT&0xFF); ENC28J60_Write(ERXSTH,RXSTART_INIT>>8); //ERXWRPTH:ERXWRPTL 寄存器定义硬件向FIFO 中 //的哪个位置写入其接收到的字节。 指针是只读的,在成 //功接收到一个数据包后,硬件会自动更新指针。 指针可 //用于判断FIFO 内剩余空间的大小 8K-1500。 //设置接收读指针字节 ENC28J60_Write(ERXRDPTL,RXSTART_INIT&0xFF); ENC28J60_Write(ERXRDPTH,RXSTART_INIT>>8); //设置接收结束字节 ENC28J60_Write(ERXNDL,RXSTOP_INIT&0xFF); ENC28J60_Write(ERXNDH,RXSTOP_INIT>>8); //设置发送起始字节 ENC28J60_Write(ETXSTL,TXSTART_INIT&0xFF); ENC28J60_Write(ETXSTH,TXSTART_INIT>>8); //设置发送结束字节 ENC28J60_Write(ETXNDL,TXSTOP_INIT&0xFF); ENC28J60_Write(ETXNDH,TXSTOP_INIT>>8); // do bank 1 stuff,packet filter: // For broadcast packets we allow only ARP packtets // All other packets should be unicast only for our mac (MAADR) // The pattern to match on is therefore // Type ETH.DST // ARP BROADCAST // 06 08 -- ff ff ff ff ff ff -> ip checksum for theses bytes=f7f9 // in binary these poitions are:11 0000 0011 1111 // This is hex 303F->EPMM0=0x3f,EPMM1=0x30 //接收过滤器 //UCEN:单播过滤器使能位 //当ANDOR = 1 时://1 = 目标地址与本地MAC 地址不匹配的数据包将被丢弃 //0 = 禁止过滤器 //当ANDOR = 0 时://1 = 目标地址与本地MAC 地址匹配的数据包会被接受 //0 = 禁止过滤器 //CRCEN:后过滤器CRC 校验使能位//1 = 所有CRC 无效的数据包都将被丢弃 //0 = 不考虑CRC 是否有效 //PMEN:格式匹配过滤器使能位 //当ANDOR = 1 时: //1 = 数据包必须符合格式匹配条件,否则将被丢弃 //0 = 禁止过滤器 //当ANDOR = 0 时: //1 = 符合格式匹配条件的数据包将被接受 //0 = 禁止过滤器 ENC28J60_Write(ERXFCON,ERXFCON_UCEN|ERXFCON_CRCEN|ERXFCON_PMEN); ENC28J60_Write(EPMM0,0x3f); ENC28J60_Write(EPMM1,0x30); ENC28J60_Write(EPMCSL,0xf9); ENC28J60_Write(EPMCSH,0xf7); // do bank 2 stuff // enable MAC receive //bit 0 MARXEN:MAC 接收使能位 //1 = 允许MAC 接收数据包 //0 = 禁止数据包接收 //bit 3 TXPAUS:暂停控制帧发送使能位 //1 = 允许MAC 发送暂停控制帧(用于全双工模式下的流量控制) //0 = 禁止暂停帧发送 //bit 2 RXPAUS:暂停控制帧接收使能位 //1 = 当接收到暂停控制帧时,禁止发送(正常操作) //0 = 忽略接收到的暂停控制帧 ENC28J60_Write(MACON1,MACON1_MARXEN|MACON1_TXPAUS|MACON1_RXPAUS); // bring MAC out of reset //将MACON2 中的MARST 位清零,使MAC 退出复位状态。 ENC28J60_Write(MACON2,0x00); // enable automatic padding to 60bytes and CRC operations //bit 7-5 PADCFG2:PACDFG0:自动填充和CRC 配置位 //111 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC //110 = 不自动填充短帧 //101 = MAC 自动检测具有8100h 类型字段的VLAN 协议帧,并自动填充到64 字节长。如果不 //是VLAN 帧,则填充至60 字节长。填充后还要追加一个有效的CRC //100 = 不自动填充短帧 //011 = 用0 填充所有短帧至64 字节长,并追加一个有效的CRC //010 = 不自动填充短帧 //001 = 用0 填充所有短帧至60 字节长,并追加一个有效的CRC //000 = 不自动填充短帧 //bit 4 TXCRCEN:发送CRC 使能位 //1= 不管PADCFG如何,MAC都会在发送帧的末尾追加一个有效的CRC。 如果PADCFG规定要 //追加有效的CRC,则必须将TXCRCEN 置1。 //0 = MAC不会追加CRC。 检查最后4 个字节,如果不是有效的CRC 则报告给发送状态向量。 //bit 0 FULDPX:MAC 全双工使能位 //1 = MAC工作在全双工模式下。 PHCON1.PDPXMD 位必须置1。 //0 = MAC工作在半双工模式下。 PHCON1.PDPXMD 位必须清零。 ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,MACON3,MACON3_PADCFG0|MACON3_TXCRCEN|MACON3_FRMLNEN|MACON3_FULDPX); // set inter-frame gap (non-back-to-back) //配置非背对背包间间隔寄存器的低字节 //MAIPGL。 大多数应用使用12h 编程该寄存器。 //如果使用半双工模式,应编程非背对背包间间隔 //寄存器的高字节MAIPGH。 大多数应用使用0Ch //编程该寄存器。 ENC28J60_Write(MAIPGL,0x12); ENC28J60_Write(MAIPGH,0x0C); // set inter-frame gap (back-to-back) //配置背对背包间间隔寄存器MABBIPG。当使用 //全双工模式时,大多数应用使用15h 编程该寄存 //器,而使用半双工模式时则使用12h 进行编程。 ENC28J60_Write(MABBIPG,0x15); // Set the maximum packet size which the controller will accept // Do not send packets longer than MAX_FRAMELEN: // 最大帧长度 1500 ENC28J60_Write(MAMXFLL,MAX_FRAMELEN&0xFF); ENC28J60_Write(MAMXFLH,MAX_FRAMELEN>>8); // do bank 3 stuff // write MAC address // NOTE: MAC address in ENC28J60 is byte-backward //设置MAC地址 ENC28J60_Write(MAADR5,macaddr[0]); ENC28J60_Write(MAADR4,macaddr[1]); ENC28J60_Write(MAADR3,macaddr[2]); ENC28J60_Write(MAADR2,macaddr[3]); ENC28J60_Write(MAADR1,macaddr[4]); ENC28J60_Write(MAADR0,macaddr[5]); //配置PHY为全双工 LEDB为拉电流 ENC28J60_PHY_Write(PHCON1,PHCON1_PDPXMD); // no loopback of transmitted frames 禁止环回 //HDLDIS:PHY 半双工环回禁止位 //当PHCON1.PDPXMD = 1 或PHCON1.PLOOPBK = 1 时: //此位可被忽略。 //当PHCON1.PDPXMD = 0 且PHCON1.PLOOPBK = 0 时: //1 = 要发送的数据仅通过双绞线接口发出 //0 = 要发送的数据会环回到MAC 并通过双绞线接口发出 ENC28J60_PHY_Write(PHCON2,PHCON2_HDLDIS); // switch to bank 0 //ECON1 寄存器 //寄存器3-1 所示为ECON1 寄存器,它用于控制 //ENC28J60 的主要功能。 ECON1 中包含接收使能、发 //送请求、DMA 控制和存储区选择位。 ENC28J60_Set_Bank(ECON1); // enable interrutps //EIE: 以太网中断允许寄存器 //bit 7 INTIE: 全局INT 中断允许位 //1 = 允许中断事件驱动INT 引脚 //0 = 禁止所有INT 引脚的活动(引脚始终被驱动为高电平) //bit 6 PKTIE: 接收数据包待处理中断允许位 //1 = 允许接收数据包待处理中断 //0 = 禁止接收数据包待处理中断 ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,EIE,EIE_INTIE|EIE_PKTIE); // enable packet reception //bit 2 RXEN:接收使能位 //1 = 通过当前过滤器的数据包将被写入接收缓冲器 //0 = 忽略所有接收的数据包 ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_RXEN); if(ENC28J60_Read(MAADR5)== macaddr[0])return 0;//初始化成功 else return 1; 函数功能:读取EREVID u8 ENC28J60_Get_EREVID(void) {undefined //在EREVID 内也存储了版本信息。 EREVID 是一个只读控 //制寄存器,包含一个5 位标识符,用来标识器件特定硅片 //的版本号 return ENC28J60_Read(EREVID); 函数功能:通过ENC28J60发送数据包到网络 参 数: len :数据包大小 packet:数据包 void ENC28J60_Packet_Send(u32 len,u8* packet) {undefined //设置发送缓冲区地址写指针入口 ENC28J60_Write(EWRPTL,TXSTART_INIT&0xFF); ENC28J60_Write(EWRPTH,TXSTART_INIT>>8); //设置TXND指针,以对应给定的数据包大小 ENC28J60_Write(ETXNDL,(TXSTART_INIT+len)&0xFF); ENC28J60_Write(ETXNDH,(TXSTART_INIT+len)>>8); //写每包控制字节(0x00表示使用macon3的设置) ENC28J60_Write_Op(ENC28J60_WRITE_BUF_MEM,0,0x00); //复制数据包到发送缓冲区 //printf("len:%d\r\n",len); //监视发送数据长度 ENC28J60_Write_Buf(len,packet); //发送数据到网络 ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON1,ECON1_TXRTS); //复位发送逻辑的问题。参见Rev. B4 Silicon Errata point 12. if((ENC28J60_Read(EIR)&EIR_TXERIF))ENC28J60_Write_Op(ENC28J60_BIT_FIELD_CLR,ECON1,ECON1_TXRTS); 函数功能:从网络获取一个数据包内容 函数参数: maxlen:数据包最大允许接收长度 packet:数据包缓存区 返 回 值:收到的数据包长度(字节) u32 ENC28J60_Packet_Receive(u32 maxlen,u8* packet) {undefined u32 rxstat; u32 len; if(ENC28J60_Read(EPKTCNT)==0)return 0; //是否收到数据包? //设置接收缓冲器读指针 ENC28J60_Write(ERDPTL,(NextPacketPtr)); ENC28J60_Write(ERDPTH,(NextPacketPtr)>>8); // 读下一个包的指针 NextPacketPtr=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0); NextPacketPtr|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8; //读包的长度 len=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0); len|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8; len-=4; //去掉CRC计数 //读取接收状态 rxstat=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0); rxstat|=ENC28J60_Read_Op(ENC28J60_READ_BUF_MEM,0)<<8; //限制接收长度 if (len>maxlen-1)len=maxlen-1; //检查CRC和符号错误 // ERXFCON.CRCEN为默认设置,一般我们不需要检查. if((rxstat&0x80)==0)len=0;//无效 else ENC28J60_Read_Buf(len,packet);//从接收缓冲器中复制数据包 //RX读指针移动到下一个接收到的数据包的开始位置 //并释放我们刚才读出过的内存 ENC28J60_Write(ERXRDPTL,(NextPacketPtr)); ENC28J60_Write(ERXRDPTH,(NextPacketPtr)>>8); //递减数据包计数器标志我们已经得到了这个包 ENC28J60_Write_Op(ENC28J60_BIT_FIELD_SET,ECON2,ECON2_PKTDEC); return(len); /*--------------------------工作队列、定时器、中断服务函数---------------------------------------*/ static struct work_struct work_list; 工作队列处理函数 以下函数用于读取网卡里的数据。 读取完毕之后,再通过netif_rx()函数上报到应用层 u8 Enc28j60_Rx_Buff[1518]; /*ENC28J60最大可接收的字节*/ static void workqueue_function(struct work_struct *work) {undefined int length; /*从ENC28J60的寄存器里读取接收到的数据*/ length=ENC28J60_Packet_Receive(1518,Enc28j60_Rx_Buff); if(length<=0) {undefined return; /*2. 分配新的套接字缓冲区*/ struct sk_buff *skb = dev_alloc_skb(length+NET_IP_ALIGN); skb_reserve(skb, NET_IP_ALIGN); //对齐 skb->dev = tiny4412_net; /*将硬件上接收到的数据拷贝到sk_buff里*/ memcpy(skb_put(skb, length),Enc28j60_Rx_Buff,length); /* 获取上层协议类型 */ skb->protocol = eth_type_trans(skb,tiny4412_net); /* 记录接收时间戳 */ tiny4412_net->last_rx = jiffies; /*接收的数据包*/ tiny4412_net->stats.rx_packets++; /*接收的字节数量*/ tiny4412_net->stats.rx_bytes += skb->len; /* 把数据包交给上层 */ netif_rx(skb); 函数功能: 中断服务函数 irqreturn_t ENC28J60_irq_handler(int irq, void *dev) {undefined schedule_work(&work_list); return IRQ_HANDLED; static void timer_function(unsigned long data) {undefined /*共享工作队列调度*/ schedule_work(&work_list); /*修改定时器超时*/ mod_timer(&timer_date,jiffies+usecs_to_jiffies(100)); /*注明: ENC28J60的中断不灵敏,就使用定时器轮询弥补*/ /*----------------------------网络设备相关代码--------------------------------------*/ /*1. 设备初始化调用,该函数在注册成功后会调用一次,可以编写网卡初始化相关代码*/ static int tiny4412_ndo_init(struct net_device * dev) {undefined /*1. ENC28J60网卡初始化*/ u8 stat=ENC28J60_Init(ENC28J60_MacAddr); if(stat) {undefined printk("ENC28J60网卡初始化失败!\r\n"); /*2. 获取中断编号*/ ENC28J60_IRQ=gpio_to_irq(ENC28J60_IRQ_NUMBER); printk("ENC28J60_IRQ=%d\n",ENC28J60_IRQ); /*3. 初始化工作队列*/ INIT_WORK(&work_list,workqueue_function); /*4. 注册中断*/ if(request_irq(ENC28J60_IRQ,ENC28J60_irq_handler,IRQ_TYPE_EDGE_FALLING,"ENC28J60_NET",NULL)!=0) {undefined printk("ENC28J60中断注册失败!\n"); /*使用定时器100ms*/ timer_date.expires=jiffies+usecs_to_jiffies(100); timer_date.function=timer_function; /*5. 初始化定时器*/ init_timer(&timer_date); /*6. 添加定时器到内核并启动*/ add_timer(&timer_date); printk("网络设备初始化!\n"); return 0; /*2. 打开网络接口,对应ifconfig up命令,编写网络设备硬件初始化的相关代码*/ static int tiny4412_ndo_open(struct net_device *dev) {undefined printk("网络设备打开成功!\n"); return 0; /*3. 关闭网络设备,对应ifconfig down命令,实现的内容与OPEN相反*/ static int tiny4412_ndo_stop(struct net_device *dev) {undefined printk("网络设备关闭成功!\n"); return 0; /*4. 启动网络数据包传输的方法*/ static netdev_tx_t tiny4412_ndo_start_xmit(struct sk_buff *skb,struct net_device *dev) {undefined int len; char *data, shortpkt[ETH_ZLEN]; /*5. 设置MAC地址,对应的命令: ifconfig eth888 hw ether 00:AA:BB:CC:DD:EE */ static int tiny4412_set_mac_address(struct net_device *dev, void *addr) {undefined struct sockaddr *address = addr; memcpy(dev->dev_addr, address->sa_data, dev->addr_len); printk("修改的MAC地址如下:\n"); printk("%X-%X-%X-%X-%X-%X\n", tiny4412_net->dev_addr[0], tiny4412_net->dev_addr[1], tiny4412_net->dev_addr[2], tiny4412_net->dev_addr[3], tiny4412_net->dev_addr[4], tiny4412_net->dev_addr[5]); //设置MAC地址 ENC28J60_Write(MAADR5,tiny4412_net->dev_addr[0]); ENC28J60_Write(MAADR4,tiny4412_net->dev_addr[1]); ENC28J60_Write(MAADR3,tiny4412_net->dev_addr[2]); ENC28J60_Write(MAADR2,tiny4412_net->dev_addr[3]); ENC28J60_Write(MAADR1,tiny4412_net->dev_addr[4]); ENC28J60_Write(MAADR0,tiny4412_net->dev_addr[5]); return 0; /*网络设备虚拟文件操作集合*/ static struct net_device_ops netdev_ops_test= {undefined .ndo_open = tiny4412_ndo_open, .ndo_stop = tiny4412_ndo_stop, .ndo_start_xmit = tiny4412_ndo_start_xmit, .ndo_init = tiny4412_ndo_init, .ndo_set_mac_address= tiny4412_set_mac_address, /*--------------------------驱动框架------------------------------------*/ static int __init Net_test_init(void) {undefined /*1. 分配及初始化net_device对象,参数:私有数据大小(单位:字节数)*/ tiny4412_net=alloc_etherdev(sizeof(struct net_device)); /*2. net结构体赋值*/ strcpy(tiny4412_net->name, "eth888");//网络设备的名称,使用ifconfig -a可以查看到。 tiny4412_net->netdev_ops=&netdev_ops_test; //虚拟文件操作集合 tiny4412_net->if_port = IF_PORT_10BASET; //协议规范 tiny4412_net->watchdog_timeo = 4 * HZ; //看门狗超时时间 /*3. 随机生成MAC地址*/ eth_hw_addr_random(tiny4412_net); printk("随机生成的MAC地址如下:\n"); printk("%X-%X-%X-%X-%X-%X\n", tiny4412_net->dev_addr[0], tiny4412_net->dev_addr[1], tiny4412_net->dev_addr[2], tiny4412_net->dev_addr[3], tiny4412_net->dev_addr[4], tiny4412_net->dev_addr[5]); ENC28J60_MacAddr[0]=tiny4412_net->dev_addr[0]; ENC28J60_MacAddr[1]=tiny4412_net->dev_addr[1]; ENC28J60_MacAddr[2]=tiny4412_net->dev_addr[2]; ENC28J60_MacAddr[3]=tiny4412_net->dev_addr[3]; ENC28J60_MacAddr[4]=tiny4412_net->dev_addr[4]; ENC28J60_MacAddr[5]=tiny4412_net->dev_addr[5]; /*注册网络设备*/ register_netdev(tiny4412_net); printk("网络设备注册成功!\n"); return 0; static void __exit Net_test_exit(void) //注销网络设备 unregister_netdev(tiny4412_net); free_netdev(tiny4412_net); /*1. 释放GPIO口使用权*/ gpio_free(Tiny4412_GPIO_SPI_SCK); gpio_free(Tiny4412_GPIO_SPI_CS); gpio_free(Tiny4412_GPIO_SPI_MISO); gpio_free(Tiny4412_GPIO_SPI_MOSI); /*2. 释放中断号*/ free_irq(ENC28J60_IRQ,NULL); /*3. 停止定时器*/ del_timer_sync(&timer_date); /*4. 清除工作*/ cancel_work_sync(&work_list); printk("网络设备注销成功!\n"); module_init(Net_test_init); module_exit(Net_test_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL");

QT软件开发: 访问QImage每个像素点填充指定颜色

需求:  需要将QImage加载的图片里指定的颜色值替换成另一种指定的颜色。示例代码: QImage image; QString filename=QFileDialog::getOpenFileName(this,"选择打开的文件","C:/",tr("*.bmp *.jpg *.png")); //filename==选择文件的绝对路径 //加载图片 image.load(filename); int w,h; //得到图片的宽高 w=image.width(); h=image.height(); //遍历每个像素点 for(int i=0;i<h;i++) for(int j=0;j<w;j++) QRgb rgb=image.pixel(j,i); if(rgb==0) //如果是透明色(全透明的颜色). 就填充成黑色 image.setPixel(j,i,0xFF000000); //保存为新图片 image.save("D:/linux-share-dir/test/123.png");

QT软件开发: 重载QGraphicsItem的type()函数

一、前言        在QGraphicsScene 上绘制图形时,经常会使用items()这种便利函数,获取当前画布上所有的图形列表;因为绘制的时候,可能会绘制不同的图形,在得到所有的图形列表之后,通常需要对其中的 QGraphicsItem 进行类型检测,确定实际item的类型,然后对其进行类型转换得到正确的item的类型。这样既可以访问标准 item也可以 访问自定义 item。     实际的运用案例: //获取画布上的所有图元 QList<QGraphicsItem *> items = scene->items(); //遍历画布上的所有图元 for (int i = 0; i < items.count(); i++) //获取图元的类型 int type=items.at(i)->type(); //矩形图元 if(type==2) qDebug()<<"矩形图元设置颜色"; //转换成正确类型 BRectangle *item=static_cast<BRectangle *>(CurrentSelectedItem_stu.item); QPen pen = item->pen(); pen.setColor(text_color); item->setPen(pen); item->SetFrameColor(text_color); //圆形图元 else if(type==3) qDebug()<<"圆形图元设置颜色"; //转换成正确类型 BEllipse *item=static_cast<BEllipse *>(CurrentSelectedItem_stu.item); QPen pen = item->pen(); pen.setColor(text_color); item->setPen(pen); item->SetFrameColor(text_color); }强制转换类型时要使用static_cast语法,常规C语言的强转语法QT会报一堆警告。语法: static_cast<强转的目标类型>(待强转的变量); CTextItem *p=static_cast<CTextItem *>(CurrentSelectedItem_stu.item);二、 QGraphicsItem::Type介绍    QGraphicsItem::Type 是标准 item 类中 virtual type() 函数返回的类型值。所有标准 item 与唯一的 Type 值相关联。    QGraphicsItem::UserType 是自定义 itemQGraphicsItem 或任何标准 item 的子类的最小允许类型值。该值与 QGraphicsItem::type() 的重新实现结合使用并声明一个 Type 枚举值。QGraphicsItem里是这样定义的:class Q_WIDGETS_EXPORT QGraphicsItem public: ...... Type = 1, UserType = 65536 virtual int type() const; ....... }三、重载 type()函数   一般自己开发时,都会重载QGraphicsItem类,实现自己的类,完成对图形的统一管理,为了方便区分图形类型,就可以自己重载type()函数,完成自己的类型定义与区分。示例:// 自定义图元 - 基础类 class BGraphicsItem : public QObject, public QAbstractGraphicsShapeItem Q_OBJECT public: enum ItemType { Circle = 1, // 圆 Ellipse, // 椭圆 Concentric_Circle, // 同心圆 Pie, // 饼 Chord, // 和弦 Rectangle, // 矩形 Square, // 正方形 Polygon, // 多边形 Round_End_Rectangle,// 圆端矩形 Rounded_Rectangle // 圆角矩形 QPointF getCenter() { return m_center; } void setCenter(QPointF p) { m_center = p; } QPointF getEdge() { return m_edge; } void setEdge(QPointF p) { m_edge = p; } //获取类型 ItemType getType() { return m_type; } //c++11中引入了override关键字,被override修饰的函数其派生类必须重载。 //修饰需要被重写 virtual int type() const override return m_type; //设置边框的颜色 void SetFrameColor(QColor c); protected: BGraphicsItem(QPointF center, QPointF edge, ItemType type); virtual void focusInEvent(QFocusEvent *event) override; virtual void focusOutEvent(QFocusEvent *event) override; protected: QPointF m_center; QPointF m_edge; ItemType m_type; BPointItemList m_pointList; QPen m_pen_isSelected; QPen m_pen_noSelected; };重写type()函数时,需要加上override关键字,不然会有警告。 warning: 'type' overrides a member function but is not marked 'override' note: overridden virtual function is herec++11中引入了override关键字,被override修饰的函数其派生类必须重载。

视频播放器:QtCreator运行VLC-Qt官方示例代码

一、前言VLC-Qt是一个结合了 Qt 应用程序和 libVLC 的免费开源库。它包含了用于媒体播放的核心类,以及用于更快速地进行媒体播放器开发的一些 GUI 类。VLC-Qt 集成了整个 libVLC,因此具备 libVLC 的所有特性;采用VLC-Qt可以快速的开发一款播放器。二、VLC-Qt下载官网地址:https://vlc-qt.tano.si/Github 地址:https://github.com/vlc-qt示例地址:https://github.com/vlc-qt/examples登录官网后看到的页面如下:我目前的开发环境是:Qt版本:  5.12.6编译器: MinGW 32bitIDE :  QtCreator所以我这里就直接下载Windows 32-bit (MinGW),这是MinGW 32bit 编译好的库文件。 三、运行示例代码下载好的文件如下:接下来访问https://github.com/vlc-qt/examples,下载官方示例代码。解压后将VLC-Qt_1.1.0_win32_mingw拷贝到examples-master\simple-player目录下。然后双击simple-player.pro打开工程,开始配置。# # VLC-Qt Simple Player TARGET = simple-player TEMPLATE = app CONFIG += c++11 QT += widgets SOURCES += main.cpp \ SimplePlayer.cpp \ EqualizerDialog.cpp HEADERS += SimplePlayer.h \ EqualizerDialog.h FORMS += SimplePlayer.ui \ EqualizerDialog.ui #LIBS += -lVLCQtCore -lVLCQtWidgets # Edit below for custom library location LIBS += -L$$PWD/../VLC-Qt_1.1.0_win32_mingw/lib -llibVLCQtCore.dll -llibVLCQtWidgets.dll INCLUDEPATH += $$PWD/../VLC-Qt_1.1.0_win32_mingw/include DEPENDPATH += $$PWD/../VLC-Qt_1.1.0_win32_mingw/lib 构建成功后,将VLC-Qt_1.1.0_win32_mingw\bin 目录下的所有文件拷贝到当前工程可执行文件目录下。 最后再编译运行:

Linux驱动开发: USB驱动开发

1.1  什么是USB?      USB是连接计算机系统与外部设备的一种串口总线标准,也是一种输入输出接口的技术规范,被广泛地应用于个人电脑和移动设备等信息通讯产品,USB就是简写,中文叫通用串行总线。最早出现在1995年,伴随着奔腾机发展而来。自微软在Windows 98中加入对USB接口的支持后,USB接口才推广开来,USB设备也日渐增多,如数码相机、摄像头、扫描仪、游戏杆、打印机、键盘、鼠标等等,其中应用最广的就是摄像头和U盘了。     USB包括老旧的USB 1.1标准和时下正流行的USB 2.0标准。传统的USB 1.1最高传输速率为12Mbps,一般厂商将其符合USB 1.1标准的产品称为“全速USB”。而高速USB 2.0最初推出时最高传输速率仅为240Mbps,后来USB2.0推广组(USB Promoter Group)在1999年10月将该速率提高到480Mbps,比传统的USB 1.1快40倍。      USB2.0向下兼容USB 1.1,当然USB1.1设备也“向上兼容”USB 2.0,但是无法实现USB2.0的传输能力,并自动以低速传输。USB 2.0连接线的最大长度为5米,但如果用五个USB适配器,则最大长度可达30米。      最新一代是USB 3.1,传输速度为10Gbit/s,三段式电压5V/12V/20V,最大供电100W ,新型Type C插型不再分正反。 USB采用四线电缆,其中两根是用来传送数据的串行通道,另两根为下游(Downstream)设备提供电源,对于高速且需要高带宽的外设,USB以全速12Mbps的传输数据;对于低速外设,USB则以1.5Mbps的传输速率来传输数据。USB总线会根据外设情况在两种传输模式中自动地动态转换。USB是基于令牌的总线。类似于令牌环网络或FDDI基于令牌的总线。USB主控制器广播令牌,总线上设备检测令牌中的地址是否与自身相符,通过接收或发送数据给主机来响应。USB通过支持悬挂/恢复操作来管理USB总线电源。USB系统采用级联星型拓扑,该拓扑由三个基本部分组成:主机(Host),集线器(Hub)和功能设备。      主机,也称为根,根结或根Hub,它做在主板上或作为适配卡安装在计算机上,主机包含有主控制器和根集线器(Root Hub),控制着USB总线上的数据和控制信息的流动,每个USB系统只能有一个根集线器,它连接在主控制器上。     集线器是USB结构中的特定成分,它提供叫做端口(Port)的点将设备连接到USB总线上,同时检测连接在总线上的设备,并为这些设备提供电源管理,负责总线的故障检测和恢复。集线可为总线提供能源,亦可为自身提供能源(从外部得到电源),自身提供能源的设备可插入总线提供能源的集线器中,但总线提供能源的设备不能插入自身提供能源的集线器或支持超过四个的下游端口中,如总线提供能源设备的需要超过100mA电源时,不能同总线提供电源的集线器连接。USB介绍: http://www.usb.org/home1.2 USB设备主要优点总结1. 可以热插拔用户在使用外接设备时,不需要关机再开机等动作,而是在电脑工作时,直接将USB插上使用。2. 携带方便USB设备大多以“小、轻、薄”见长,对用户来说,随身携带大量数据时,很方便。当然USB硬盘是首要之选了。3. 标准统一大家常见的是IDE接口的硬盘,串口的鼠标键盘,并口的打印机扫描仪,可是有了USB之后,这些应用外设统统可以用同样的标准与个人电脑连接,这时就有了USB硬盘、USB鼠标、USB打印机等等。4. 可以连接多个设备USB在个人电脑上往往具有多个接口,可以同时连接几个设备,如果接上一个有四个端口的USB HUB时,就可以再连上四个USB设备,以此类推 (注:最高可连接至127个设备,扩展到一定数量时需要外加电源)1.3  USB电器接口定义一般的排列方式是:红白绿黑从左到右定义:红色-USB电源: 标有-VCC、Power、5V、5VSB字样白色-USB数据线:(负)-DATA-、USBD-、PD-、USBDT-绿色-USB数据线:(正)-DATA+、USBD+、PD+、USBDT+黑色-地线: GND、Ground1. 4 USB的插入检测机制        USB端口的D+和D-均用一个15k的电阻接地,当无设备接入时,均处于低电平;在设备端在D+(表示高速设备或者全速设备)或者D-(表示低速设备)接了一个1.5k的上拉电阻到+3.3v,一旦将设备接入,USB端口的D+或者D-其中一个被拉高为3v,系统识别到外部设备接入。       注意:高速设备首先会被识别为全速设备,然后再通过集线器和设备二者的确认最后切换到高速模式下。     在高速模式下,采用的是电流传输模式,这个时候上拉电阻需要从D+上断开。     usb主机检测到USB设备插入后,就要对设备进行枚举了。枚举的作用就是从设备是那个读取一些信息,知道设备是什么样的设备,如果通信,这样主机就可以根据这些信息选择合适的驱动程序。调试USB设备,很重要的一点就是USB枚举过程,只要枚举成功了,那就成功一大半了。      当设备没有枚举成功时(可以通过一个10K的电阻将USB的电源端和D+或者D-连接起来,电脑会发现一个无法识别的设备,这个设备的PID和VID都是0,根据每个特性可以简单的判定设备的枚举是否成功。 二、 USB标准描述符        USB协议为USB设备定义了一套描述设备功能和属性的有固定结构的描述符,包括标准的描述符即设备描述符、配置描述符、接口描述符、端点描述符和字符串描述符。USB设备通过这些描述符向USB主机汇报设备的各种各样属性,主机通过对这些描述符的访问对设备进行类型识别、配置并为其提供相应的客户端驱动程序。        USB设备通过描述符反映自己的设备特性。USB描述符是由特定格式排列的一组数据结构组成。     在USB设备枚举过程中,主机端的协义软件需要解析从USB设备读取的所有描述符信息。在USB主向设备发送读取描述符的请求后,USB设备将所有的描述符以连续的数据流方式传输给USB主机。主机从第一个读到的字符开始,根据双方规定好的数据格式,顺序地解析读到的数据流。     USB描述符包含标准描述符、类描述符和厂商特定描述3种形式。任何一种设备必须遵循USB标准描述符(除了字符串描述符可选外)。     在USB1.X中,规定了5种标准描述符:设备描述符(Device Descriptor)、配置描述符(Configuration Descriptor)、接口描述符(Interface Descriptor)、端点描述符(Endpoint Descriptor)和字符串描述符(String Descriptor)。    每个USB设备只有一个设备描述符,而一个设备中可包含一个或多个配置描述符,即USB设备可以有多种配置。设备的每一个配置中又可以包含一个或多个接口描述符,即USB设备可以支持多种功能(接口),接口的特性通过描述符提供。    在USB主机访问USB设备的描述符时,USB设备依照设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符顺序将所有描述符传给主机。一设备至少要包含设备描述符、配置描述符和接口描述符,如果USB设备没有端点描述符,则它仅仅用默认管道与主机进行数据传输。2. 1 设备描述符30.2.1 设备描述符 /* USB_DT_DEVICE: Device descriptor */ struct usb_device_descriptor { __u8 bLength; __u8 bDescriptorType; __le16 bcdUSB; __u8 bDeviceClass; __u8 bDeviceSubClass; __u8 bDeviceProtocol; __u8 bMaxPacketSize0; __le16 idVendor; __le16 idProduct; __le16 bcdDevice; __u8 iManufacturer; __u8 iProduct; __u8 iSerialNumber; __u8 bNumConfigurations; } __attribute__ ((packed)); //USB设备信息与驱动端匹配成功的时候调用。 static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数 printk("识别到USB光谱仪设备,正在进行设备初始化.\n"); printk("该设备的厂商ID=%#x,设备ID:%#x\n",id->idVendor,id->idProduct); /*通过接口获取设备信息*/ struct usb_device *dev = interface_to_usbdev(intf); printk("bcdUSB = %#x\n",dev->descriptor.bcdUSB); //从USB设备描述符中获取USB版本 printk("vidUSB = %#x\n",dev->descriptor.idVendor); //从USB设备描述符中获取厂商ID printk("pidUSB = %#x\n",dev->descriptor.idProduct);//从USB设备描述符中获取产品ID /*-------------------------------*/ }设备描述符给出了USB设备的一般信息,包括对设备及在设备配置中起全程作用的信息,包括制造商标识号ID、产品序列号、所属设备类号、默认端点的最大包长度和配置描述符的个数等。一个USB设备必须有且仅有一个设备描述符。设备描述符是设备连接到总线上时USB主机所读取的第一个描述符,它包含了14个字段,结构如下:其中bDescriptorType为描述符的类型,其含义可查下表(此表也适用于标准命令Get_Descriptor中wValue域高字节的取值含义):设备类代码bDeviceClass可查下表:下表列出了一个USB鼠标的设备描述符的例子,供大家分析一下:Linux内核中定义的设备描述符结构:struct usb_device_descriptor _ _u8 bLength; //描述符长度 _ _u8 bDescriptorType; //描述符类型编号 _ _le16 bcdUSB; //USB版本号 _ _u8 bDeviceClass; //USB分配的设备类code _ _u8 bDeviceSubClass;// USB分配的子类code _ _u8 bDeviceProtocol; //USB分配的协议code _ _u8 bMaxPacketSize0; //endpoint0最大包大小 _ _le16 idVendor; //厂商编号 _ _le16 idProduct; //产品编号 _ _le16 bcdDevice; //设备出厂编号 _ _u8 iManufacturer; //描述厂商字符串的索引 _ _u8 iProduct; //描述产品字符串的索引 _ _u8 iSerialNumber; //描述设备序列号字符串的索引 _ _u8 bNumConfigurations; //可能的配置数量 } _ _attribute_ _ ((packed));2.2 配置描述符      配置描述符中包括了描述符的长度(属于此描述符的所有接口描述符和端点描述符的长度的和)、供电方式(自供电/总线供电)、最大耗电量等。主果主机发出USB标准命令Get_Descriptor要求得到设备的某个配置描述符,那么除了此配置描述符以外,此配置包含的所有接口描述符与端点描述符都将提供给USB主机。下面是一种硬盘的配置描述符示例: Linux内核中定义的配置描述符结构{ _ _u8 bLength; //描述符长度 _ _u8 bDescriptorType; //描述符类型编号 _ _le16 wTotalLength; //配置所返回的所有数据的大小 _ _u8 bNumInterfaces; // 配置所支持的接口数 _ _u8 bConfigurationValue; //Set_Configuration命令需要的参数值 _ _u8 iConfiguration; //描述该配置的字符串的索引值 _ _u8 bmAttributes; //供电模式的选择 _ _u8 bMaxPower; //设备从总线提取的最大电流 } _ _attribute_ _ ((packed));2.3 接口描述符    配置描述符中包含了一个或多个接口描述符,这里的“接口”并不是指物理存在的接口,在这里把它称之为“功能”更易理解些,例如一个设备既有录音的功能又有扬声器的功能,则这个设备至少就有两个“接口”。    如果一个配置描述符不止支持一个接口描述符,并且每个接口描述符都有一个或多个端点描述符,那么在响应USB主机的配置描述符命令时,USB设备的端点描述符总是紧跟着相关的接口描述符后面,作为配置描述符的一部分被返回。接口描述符不可直接用Set_Descriptor和Get_Descriptor来存取。    如果一个接口仅使用端点0,则接口描述符以后就不再返回端点描述符,并且此接口表现的是一个控制接口的特性,它使用与端点0相关联的默认管道进行数据传输。在这种情况下bNumberEndpoints域应被设置成0。接口描述符在说明端点个数并不把端点0计算在内。对于bInterfaceClass字段,表示接口所属的类别,USB协议根据功能将不同的接口划分成不的类,其具体含义如下表所示: ​Linux内核中定义的接口描述符结构1.struct usb_interface_descriptor _ _u8 bLength; //描述符长度 _ _u8 bDescriptorType; //描述符类型 _ _u8 bInterfaceNumber; // 接口的编号 _ _u8 bAlternateSetting; //备用的接口描述符编号 _ _u8 bNumEndpoints; //该接口使用的端点数,不包括端点0 _ _u8 bInterfaceClass; //接口类型 _ _u8 bInterfaceSubClass; //接口子类型 _ _u8 bInterfaceProtocol; //接口所遵循的协议 _ _u8 iInterface; //描述该接口的字符串索引值 } _ _attribute_ _ ((packed));2.4 端点描述符      端点是设备与主机之间进行数据传输的逻辑接口,除配置使用的端点0(控制端点,一般一个设备只有一个控制端点)为双向端口外,其它均为单向。端点描述符描述了数据的传输类型、传输方向、数据包大小和端点号(也可称为端点地址)等。     除了描述符中描述的端点外,每个设备必须要有一个默认的控制型端点,地址为0,它的数据传输为双向,而且没有专门的描述符,只是在设备描述符中定义了它的最大包长度。主机通过此端点向设备发送命令,获得设备的各种描述符的信息,并通过它来配置设备。static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数 /*获取端点描述符*/ struct usb_endpoint_descriptor *endpoint = &interface->endpoint[0].desc; switch(endpoint->bmAttributes) case 0:printk("设备支持控制传输.\n");break; case 1:printk("设备支持同步传输.\n");break; case 2:printk("设备支持批量传输.\n");break; case 3:printk("设备支持中断传输.\n");break; struct usb_endpoint_descriptor { __u8 bLength; __u8 bDescriptorType; __u8 bEndpointAddress; __u8 bmAttributes; __le16 wMaxPacketSize; __u8 bInterval; __u8 bRefresh; __u8 bSynchAddress; } __attribute__ ((packed));下表是一种鼠标的端点描述符的示例,该端点是一个中断端点:一个 USB 端点有 4 种不同类型, 分别具有不同的数据传送方式:控制CONTROL控制端点被用来控制对 USB 设备的不同部分访问. 通常用作配置设备、获取设备信息、发送命令到设备或获取设备状态报告。这些端点通常较小。每个 USB 设备都有一个控制端点称为"端点 0", 被 USB 核心用来在插入时配置设备。USB协议保证总有足够的带宽留给控制端点传送数据到设备.中断INTERRUPT每当 USB 主机向设备请求数据时,中断端点以固定的速率传送小量的数据。此为USB 键盘和鼠标的主要的数据传送方法。它还用以传送数据到 USB 设备来控制设备。通常不用来传送大量数据。USB协议保证总有足够的带宽留给中断端点传送数据到设备.批量BULK批量端点用以传送大量数据。这些端点常比中断端点大得多. 它们普遍用于不能有任何数据丢失的数据。USB 协议不保证传输在特定时间范围内完成。如果总线上没有足够的空间来发送整个BULK包,它被分为多个包进行传输。这些端点普遍用于打印机、USB Mass Storage和USB网络设备上。等时ISOCHRONOUS等时端点也批量传送大量数据, 但是这个数据不被保证能送达。这些端点用在可以处理数据丢失的设备中,并且更多依赖于保持持续的数据流。如音频和视频设备等等。控制和批量端点用于异步数据传送,而中断和同步端点是周期性的。这意味着这些端点被设置来在固定的时间连续传送数据,USB 核心为它们保留了相应的带宽。端点在linux内核中使用结构 struct usb_host_endpoint 来描述,它所包含的真实端点信息在另一个结构中:struct usb_endpoint_descriptor(端点描述符,包含所有的USB特定数据)。Linux内核中定义的端点描述符结构struct usb_endpoint_descriptor _ _u8 bLength; //描述符长度 _ _u8 bDescriptorType; //描述符类型 _ _u8 bEndpointAddress; //端点地址:0~3位是端点号,第7位是方向(0-OUT,1-IN) _ _u8 bmAttributes; //端点属性:bit[0:1] 的值为00表示控制,为01表示同步,为02表示批量,为03表示中断 _ _le16 wMaxPacketSize; 本端点接收或发送的最大信息包的大小 _ _u8 bInterval;//轮询数据传送端点的时间间隔 //对于批量传送的端点以及控制传送的端点,此域忽略 //对于同步传送的端点,此域必须为1 _ _u8 bRefresh; _ _u8 bSynchAddress; } _ _attribute_ _ ((packed));2.5 字符串描述符     字符串描述符是一种可选的USB标准描述符,描述了如制商、设备名称或序列号等信息。如果一个设备无字符串描述符,则其它描述符中与字符串有关的索引值都必须为0。字符串使用的是Unicode编码。    主机请示得到某个字符串描述符时一般分成两步:首先主机向设备发出USB标准命令Get_Descriptor,其中所使用的字符串的索引值为0,设备返回一个字符串描述符,此描述符的结构如下: 该字符串描述符双字节的语言ID的数组,wLANGID[0]~wLANGID[x]指明了设备支持的语言,具体含义可查看USB_LANGIDs.pdf。     主机根据自己需要的语言,再次向设备发出USB标准命令Get_Descriptor,指明所要求得到的字符串的索引值和语言。这次设备所返回的是Unicode编号的字符串描述符,其结构如下:bString域为设备实际返回的以UNICODE编码的字符串流,我们在编写设备端硬件驱动的时候需要将字符串转换为UNICODE编码,可以通过一些UNICODE转换工具进行转换。这里推荐一款USB描述符生成工具“USB Unicode 字符串描述符生成器”,它专门为编写设备端驱动程序的需要而定制,可以非常方便将需要的字符串转换成UNICODE格式,进而导入C或汇编程序代码中。Linux内核中定义的字符串描述符结构struct usb_string_descriptor _ _u8 bLength; //描述符长度 _ _u8 bDescriptorType; //描述符类型 _ _le16 wData[1]; } _ _attribute_ _ ((packed)); 三、HID描述符3.1 HID描述符介绍       USB 设备中有一大类就是 HID 设备,即 Human Interface Devices,人机接口设备。这类设备包括鼠标、键盘等,主要用于人与计算机进行交互。 它是 USB 协议最早支持的一种设备类。 HID 设备可以作为低速、全速、高速设备用。由于 HID 设备要求用户输入能得到及时响应,故其传输方式通常采用中断方式。     在 USB 协议中, HID 设备的定义放置在接口描述符中, USB 的设备描述符和配置描述符中不包含 HID 设备的信息。因此,对于某些特定的 HID 设备,可以定义多个接口,只有其中一个接口为 HID 设备类即可。    当定义一个设备为 HID 设备时,其设备描述符应为:          其接口描述符应该:另外(接口描述符)对无引导的 HID 设备,子类代码 bInterfaceSubClass 应置 0,此时 bInterfaceProtocol 无效,置零即可。即为:   对支持引导的 USB 设备,子类代码 bInterfaceSubClass 应置 1,此时 bInterfaceProtocol 可以为 1 或 2, 1 表示键盘接口, 3 表示鼠标接口。其参考设置如下:       HID 设备支持 USB 标准描述符中的五个:设备描述符、配置描述符、接口描述符、端点描述符、字符串描述符。除此之外, HID 设备还有三种特殊的描述符: HID 描述符、报告描述符、物理描述符。一个 USB 设备只能支持一个 HID 描述符,但可以支持多个报告描述符,而物理描述符则可以有也可以没有。3.2 HID 描述符HID 描述符用于识别 HID 设备中所包含的额外描述符,例如报告描述符或物理描述符等。其格式如下:各字段含义如下:bLength: HID 描述符长度。bDescriptorType: HID 描述符类型,值为 0x21。bcdHID: HID 设备所遵循的 HID 版本号,为 4 位 16 进制的 BCD 码数据。 1.0 即 0x0100, 1.1 即 0x01bCountryCode: HID 设备国家/地区代码。bNumDescriptor: HID 设备支持的其01, 2.0即 0x0200。他设备描述符的数量。由于 HID 设备至少需要包括一个报告描述符,故其值至小为 0x01。bDescriptorType: HID 描述符附属的类别描述符长度。bDescriptorType/wDescriptorLength:可选字段,用于表示 HID 描述符附属的类别描述符类型及长度。3.3 报告描述符       HID 设备的报告描述符是一种数据报表,主要用于定义 HID 设备和 USB 主机之间的数据交换格式, HID 设备报告描述符的类型值为 0x22。     报告描述符使用自定义的数据结构,用于传输特定的数据包。    例如: 对于键盘,需要在数据包中指明按键的值,报告描述符把这些数据打包发给主机,主机对发来的数据进行处理。 它有四个组成部分, 其格式如下:各字段含义:bSize:占用两个位, 指示数据部分,即[data]字段的长度, 00b表没有数据字节, 01b表只有一个数据字节, 10b 表示有两个数据字节, 11b 表有 4 个数据字节。bType:数据项类型,用于指明数据项的类型。 00b主数据类型, 01b全局数据类型, 10b局部数据类型, 11b保留。bTag:数据项标签,用于指明数据项的功能。报告描述符需要包含的数据项标签有:输入输出数据项标签、用法数据项标签、用法页数据项标签、逻辑最小和最大值数据项标签、报告大小数据项标签以及报告计数数据项标签。[data]:数据字节,随着前面 bSize 定义的大小而变化。3.4 物理描述符     HID 设备的物理描述符主要用于报告物理设备的激活信息,其类型值为 0x23,它是可选的,对大部分设备不需要使用此描述符。四、 linux内核下USB相关的API函数与数据结构        前面介绍了USB相关一些基础概念与重要的数据结构,接下来就分析在linux内核中如何编写一个USB 驱动程序,编写与一个USB设备驱动程序的方法和其他总线驱动方式类似,驱动程序把驱动程序对象注册到USB子系统中,稍后再使用制造商和设备标识来判断是否安装了硬件。当然,这些制造商和设备标识需要我们编写进USB 驱动程序中。     USB 驱动程序依然遵循设备模型 —— 总线、设备、驱动。和I2C 总线设备驱动编写一样,所有的USB驱动程序都必须创建的主要结构体是 struct usb_driver,它们向USB 核心代码描述了USB 驱动程序。但这是个外壳,只是实现设备和总线的挂接,具体的USB 设备是什么样的,如何实现的,比如一个字符设备,我们还需填写相应的文件操作接口。USB构成框图:4.1 USB设备注册与注销//注册USB设备 #define usb_register(driver) \ usb_register_driver(driver, THIS_MODULE, KBUILD_MODNAME) //注销USB设备 void usb_deregister(struct usb_driver *driver) //需要添加的头文件 #include <linux/usb.h>4.2 USB设备注册框架示例#include <linux/init.h> #include <linux/module.h> #include <linux/usb.h> //定义USB的IDTAB static const struct usb_device_id usbtest[] = { {//148f:760b USB_DEVICE(0x148f, 0x760b), /*360WIFI的制造商ID和产品ID */ //USB设备信息与驱动端匹配成功的时候调用。 static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数 printk("USB 驱动匹配成功!\n"); return 0; //USB断开的时候调用 static void test_disconnect(struct usb_interface *intf) printk("USB 设备释放成功!\n"); //定义USB驱动结构体 static struct usb_driver usbtest_driver = { .name = "linux_usb_drv", .id_table = usbtest, .probe = test_probe, .disconnect = test_disconnect static int __init usbtest_init(void) //注册USB设备驱动 usb_register(&usbtest_driver); return 0; module_init(usbtest_init); static void __exit usbtest_exit(void) //注销USB设备驱动 usb_deregister(&usbtest_driver); module_exit(usbtest_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL");4.3 struct usb_driver数据结构struct usb_driver { const char *name; //USB设备的名字,名称可以随便填写。 int (*probe) (struct usb_interface *intf, const struct usb_device_id *id); //资源探索函数,当usB驱动端与设备端匹配成功的时候调用。 void (*disconnect) (struct usb_interface *intf); //USB设备断开时调用。 const struct usb_device_id *id_table; //USB设备的匹配ID列表 …………….. };列出的为必填成员。struct usb_device_id 设备ID结构原型如下:struct usb_device_id { /* 确定设备信息去和结构体中哪几个字段匹配来判断驱动的适用性,比如是否是HID协议等*/ __u16 match_flags; /*用于特定于产品的匹配 */ __u16 idVendor; /*USB设备的制造商ID,须向www.usb.org申请*/ __u16 idProduct; // USB设备的产品ID,有制造商自定 __u16 bcdDevice_lo; /* USB设备的产品版本号最低值*/ __u16 bcdDevice_hi; /* 和最高值,以BCD码来表示。*/ /* 分别定义设备的类,子类和协议,他们由 USB 论坛分配并定义在 USB 规范中. 这些值指定这个设备的行为, 包括设备上所有的接口 */ __u8 bDeviceClass; __u8 bDeviceSubClass; __u8 bDeviceProtocol; /* 分别定义单个接口的类,子类和协议,他们由 USB 论坛分配并定义在 USB 规范中 */ __u8 bInterfaceClass; __u8 bInterfaceSubClass; __u8 bInterfaceProtocol; /* 这个值不用来匹配驱动的, 驱动用它来在 USB 驱动的探测回调函数中区分不同的设备 该成员一般来保存一个结构体指针,存放该设备特殊的数据 kernel_ulong_t driver_info; 填充struct usb_device_id结构体中的__u16  match_flags成员用到的宏定义:#define USB_DEVICE_ID_MATCH_VENDOR 0x0001 /*供应商厂家ID*/ #define USB_DEVICE_ID_MATCH_PRODUCT 0x0002 /*产品ID*/ 快速填充usb_device_id结构体的相关宏:1.USB_DEVICE_ID_MATCH_INT_INFO --根据接口信息 USB_DEVICE_ID_MATCH_DEV_INFO --根据设备的信息 USB_DEVICE_ID_MATCH_DEVICE_AND_VERSION --根据设备制造信息和版本 USB_DEVICE_ID_MATCH_DEV_RANGE --根据设备版本 USB_DEVICE_ID_MATCH_DEVICE --根据设备制造信息 struct usb_device_id结构体填充示例1—(摘自DM9620-USB网卡)static const struct usb_device_id products[] = { USB_DEVICE(0x07aa, 0x9601), /* Corega FEther USB-TXC */ .driver_info = (unsigned long)&dm9620_info, USB_DEVICE(0x0a46, 0x9601), /* Davicom USB-100 */ .driver_info = (unsigned long)&dm9620_info, } struct usb_device_id结构体填充示例2------->(摘自内核自带的鼠标驱动)static struct usb_device_id usb_mouse_id_table [] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* 结束 */ };USB鼠标USB驱动端结构体填充示例:static struct usb_driver usb_mouse_driver = { .name = "usbmouse", /*鼠标的名称-不是匹配使用*/ .probe = usb_mouse_probe, /*资源探测函数-匹配成功调用*/ .disconnect = usb_mouse_disconnect,/*当鼠标断开连接调用*/ .id_table = usb_mouse_id_table, /*匹配驱动使用*/ };4.4 获取当前设备信息struct usb_device *interface_to_usbdev(struct usb_interface *intf)用法示例:static int usb_driver_probe(struct usb_interface *intf, const struct usb_device_id *id) struct usb_device *dev = interface_to_usbdev(intf); /*设备与驱动匹配成功时,在_probe 函数里获取设备的信息*/ }4.5 创建数据传输管道     管道是USB设备通信的通道,内核中提供了创建管道的宏,从以下内核定义宏中我们可以分析出,管道是一个 int 型的变量,由设备号、端点地址、端点类型组合而成。//控制CONTROL #define usb_sndctrlpipe(dev, endpoint) \ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint)) //默认输出 #define usb_rcvctrlpipe(dev, endpoint) \ ((PIPE_CONTROL << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN) //输入 //中断INTERRUPT #define usb_sndisocpipe(dev, endpoint) \ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint))//默认输出 #define usb_rcvisocpipe(dev, endpoint) \ ((PIPE_ISOCHRONOUS << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)//输入 //批量BULK #define usb_sndbulkpipe(dev, endpoint) \ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint))//默认输出 #define usb_rcvbulkpipe(dev, endpoint) \ ((PIPE_BULK << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)//输入 //等时ISOCHRONOUS #define usb_sndintpipe(dev, endpoint) \ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint))//默认输出 #define usb_rcvintpipe(dev, endpoint) \ ((PIPE_INTERRUPT << 30) | __create_pipe(dev, endpoint) | USB_DIR_IN)//输入 static inline unsigned int __create_pipe(struct usb_device *dev,unsigned int endpoint) return (dev->devnum << 8) | (endpoint << 15); }   端点是有四种的,对应着管道也就有四种,同时端点是有IN也有OUT,相应的管道也就有两个方向,于是二四得八,上面就出现了八个创建管道的宏。有了struct usb_device结构体,也就是说知道了设备地址,再加上端点号,就可以根据需要创建指定的管道。__create_pipe宏只是一个幕后的角色,用来将设备地址和端点号放在管道正确的位置上。示例:struct usb_device *dev = interface_to_usbdev(intf); struct usb_host_interface *interface; struct usb_endpoint_descriptor *endpoint; /* 定义端点描述符 */ interface = intf->cur_altsetting; endpoint = &interface->endpoint[0].desc; int pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /*创建管道*/4.6 动态分配urb     urb(USB Request Block)Linux内核中USB驱动实现上的一个数据结构,用于组织每一次的USB设备驱动的数据传输请求。1.struct urb *usb_alloc_urb(int iso_packets, gfp_t mem_flags) @iso_packets:这个urb的iso包的数量 @mem_flags:要分配的内存类型,请参阅kmalloc()以获取列表示例:static struct urb *myurb= usb_alloc_urb(0, GFP_KERNEL);4.7 urb数据结构初始化(控制) static inline void usb_fill_control_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, unsigned char *setup_packet, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context)4.8 urb数据结构初始化(中断) static inline void usb_fill_int_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context, int interval)4.9 urb数据结构初始化(批量)static inline void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev, unsigned int pipe, void *transfer_buffer, int buffer_length, usb_complete_t complete_fn, void *context)4.10 urb数据结构初始化(等时) 等时urb 没有和中断、控制、批量urb 类似的初始化函数,因此它们在提交到USB核心之前,需要在驱动程序中手动的初始化。例如:1.urb->dev = dev; urb->context = uvd; urb->pipe = usb_rcvisocpipe(dev,uvd->video_endp-1); urb->interval = 1; urb->transfer_flags = URB_IOS_ASAP; urb->transfer_buffer = can->sts_buf[i]; urb_complete = konicawc_isoc_irq; urb->number_of_packets = FRAMES_PRE_DESC; urb->transfer_buffer_lenth = FRAMES_PRE_DESC; for (j=0; j < FRAMES_PRE_DESC; j++){ urb->ios_frame_desc[j].offset = j; urb->ios_frame_desc[j].length = 1; }五、编写USB鼠标驱动(中断传输方式)5.1 USB驱动注册框架代码#include <linux/init.h> #include <linux/module.h> #include <linux/usb.h> [ 25.845000] usb 1-2.2: new high-speed USB device number 6 using s5p-ehci [ 25.950000] usb 1-2.2: New USB device found, idVendor=0661, idProduct=294b [ 25.950000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 25.950000] usb 1-2.2: Product: EZ-USB [ 25.950000] usb 1-2.2: Manufacturer: Cypress [ 726.360000] usb 1-2.2: new high-speed USB device number 7 using s5p-ehci [ 726.475000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5 [ 726.480000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370 [ 726.480000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 726.480000] usb 1-2.2: Product: 802.11 n WLAN [ 726.480000] usb 1-2.2: Manufacturer: Ralink [ 726.485000] usb 1-2.2: SerialNumber: 1.0 //定义USB的IDTAB static const struct usb_device_id tiny4412_usb_id[] = {USB_DEVICE(0x148f,0x5370)}, {USB_DEVICE(0x0661,0x294b)}, MODULE_DEVICE_TABLE 有两个功能。 一是:将设备加入到外设队列中, 二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。 该宏定义在<linux/module.h>下 这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table 局部变量,这个变量指向第二个参数 MODULE_DEVICE_TABLE (usb,tiny4412_usb_id); //USB设备信息与驱动端匹配成功的时候调用。 static int test_probe(struct usb_interface *intf,const struct usb_device_id *id) //资源探索函数 printk("tiny4412 probe success .\n"); return 0; //USB断开的时候调用 static void test_disconnect(struct usb_interface *intf) printk("tiny4412 usb drv disconnect success.\n"); //定义USB驱动结构体 static struct usb_driver tiny4412_usb_driver = { .name = "tiny4412_usb_drv", .id_table = tiny4412_usb_id, .probe = test_probe, .disconnect = test_disconnect static int __init tiny4412_usb_init(void) //注册USB设备驱动 usb_register(&tiny4412_usb_driver); printk("tiny4412 usb drv install success.\n"); return 0; static void __exit tiny4412_usb_exit(void) //注销USB设备驱动 usb_deregister(&tiny4412_usb_driver); printk("tiny4412 usb drv exit success.\n"); module_init(tiny4412_usb_init); module_exit(tiny4412_usb_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL");运行示例: 拔插USB WIFI 弹出的提示信息。[root@wbyq code]# insmod linux_usb_drv.ko [ 19.160000] usbcore: registered new interface driver tiny4412_usb_drv [ 19.160000] tiny4412 usb drv install success. [root@wbyq code]# [ 25.430000] usb 1-2.2: new high-speed USB device number 5 using s5p-ehci [ 25.545000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5 [ 25.550000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370 [ 25.550000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 25.550000] usb 1-2.2: Product: 802.11 n WLAN [ 25.550000] usb 1-2.2: Manufacturer: Ralink [ 25.555000] usb 1-2.2: SerialNumber: 1.0 [ 25.570000] tiny4412 probe success .5.2 编写USB鼠标驱动[root@wbyq linux-3.5]# make menuconfig由于内核自带了usb鼠标驱动,所以需要去除:Device Drivers ---> HID support ---> USB HID support ---> <> USB HID transport layer //HID传输层去掉之后,再重新编译内核,烧写内核。鼠标驱动代码: 该模板适用于键盘驱动。#include <linux/init.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/usb/input.h> #include <linux/hid.h> 本程序为USB鼠标驱动程序,要安装本驱动,需要先将内核自带的USB驱动程序卸载掉 //定义USB的IDTAB 24ae:2002 static const struct usb_device_id tiny4412_usb_id[] = { {//148f:7601 USB_DEVICE(0x148f,0x7601),/*360WIFI的制造商ID和产品ID */ USB_DEVICE(0x24ae,0x2002),/*当前鼠标的ID*/ //USB鼠标的ID static struct usb_device_id usb_mouse_id[] = { { USB_INTERFACE_INFO(USB_INTERFACE_CLASS_HID, USB_INTERFACE_SUBCLASS_BOOT, USB_INTERFACE_PROTOCOL_MOUSE) }, { } /* 终止进入 */ int size; static unsigned char *buf =NULL; static struct urb *myurb=NULL; dma_addr_t buf_phy; /*USB中断处理程序*/ static void usb_complete(struct urb *urb) int i; for(i=0;i<size;i++) printk("0x%x ",buf[i]); printk("\n"); /* 重新提交异步请求*/ usb_submit_urb(myurb, GFP_KERNEL); //USB设备信息与驱动端匹配成功的时候调用。 static int usb_probe(struct usb_interface *intf,const struct usb_device_id *id) printk("USB驱动匹配成功! ID: 0x%X,0x%X\n",id->idVendor,id->idProduct); /*通过接口获取设备信息*/ struct usb_device *dev = interface_to_usbdev(intf); /*获取当前接口设置*/ struct usb_host_interface *interface=intf->cur_altsetting; /*获取端点描述符*/ struct usb_endpoint_descriptor *endpoint = &interface->endpoint[0].desc; /*中断传输:创建输入管道*/ int pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress); /*从端点描述符中获取传输的数据大小 */ size = endpoint->wMaxPacketSize; printk("设备传输数据包大小:%d\n",size); /*分配数据传输缓冲区*/ buf = usb_alloc_coherent(dev,size,GFP_ATOMIC,&buf_phy); /*分配新的urb,urb是usb设备驱动中用来描述与usb设备通信所用的基本载体和核心数据结构*/ myurb = usb_alloc_urb(0,GFP_KERNEL); /*中断方式初始化urb*/ usb_fill_int_urb(myurb,dev,pipe,buf,size,usb_complete,NULL,endpoint->bInterval); myurb->transfer_dma = buf_phy; myurb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; /*为端点提交异步传输请求*/ usb_submit_urb(myurb, GFP_KERNEL); return 0; //USB断开的时候调用 static void usb_disconnect(struct usb_interface *intf) struct usb_device *dev = interface_to_usbdev(intf); usb_kill_urb(myurb); usb_free_urb(myurb); usb_free_coherent(dev,size,buf, buf_phy); printk("USB 设备释放成功!\n"); //定义USB驱动结构体 static struct usb_driver tiny4412_usb_driver = { .name = "tiny4412_usb", .id_table = usb_mouse_id, .probe = usb_probe, .disconnect = usb_disconnect static int __init tiny4412_usb_init(void) //注册USB设备驱动 usb_register(&tiny4412_usb_driver); return 0; static void __exit tiny4412_usb_exit(void) //注销USB设备驱动 usb_deregister(&tiny4412_usb_driver); module_init(tiny4412_usb_init); module_exit(tiny4412_usb_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL");六、编写USB光谱仪驱动(批量传输方式)      USB光谱仪的USB接口支持使用批量方式传输数据,当前程序里使用的是同步方式提交请求。整体驱动思路:(1). 在驱动层里先定义光谱仪设备的设备ID和厂商ID,当设备插入时,ID匹配成功,就会调用probe函数,在probe函数里完成设备信息探测,比如: 端点数据传输方向,数据传输大小,传输方式等等。探测成功后,就注册一个字符设备,创建设备节点,方便应用程序调用驱动完成设备控制。(2).  驱动层向应用层提供了read和write接口函数,方便根据预先定义的结构体进行数据通信。   具体情况可以参考 –下面的应用层程序示例代码。6.1 头文件定义支持的命令#ifndef SPECTROMETER_H #define SPECTROMETER_H #define Get_Module_Information 0x01000000UL #define Get_Spectrometer_Information 0x02000000UL #define Get_Module_Property 0x03000000UL #define Get_Data_Position_Property 0x04000000UL #define Get_Data_Count_Property 0x05000000UL #define Get_Data_Transmit_Property 0x06000000UL #define Get_Data_Trigger_Offset_Property 0x07000000UL #define Get_Exposure_Property 0x08000000UL #define Get_Gain_Property 0x08000000UL #define Get_Ad_Offset_Property 0x0A000000UL #define Get_Wavelength_Property 0x0C000000UL #define Get_Image_Size 0x0D000000UL #define Get_Capture_Mode 0x0F000000UL #define Set_Capture_Mode 0x10000000UL #define Get_Data_Position 0x11000000UL #define Set_Data_Position 0x12000000UL #define Get_Data_Count 0x13000000UL #define Set_Data_Count 0x14000000UL #define Get_Data_Transmit 0x15000000UL #define Set_Data_Transmit 0x16000000UL #define Get_Data_TriggerOffset 0x17000000UL #define Set_Data_TriggerOffset 0x18000000UL #define Get_Exposure_Time 0x19000000UL #define Set_Exposure_Time 0x1A000000UL #define Get_Exposure_Cycle 0x1B000000UL #define Set_Exposure_Cycle 0x1C000000UL #define Get_Trigger_Mode 0x1D000000UL #define Set_Trigger_Mode 0x1E000000UL #define Get_Trigger_Polarity 0x1F000000UL #define Set_Trigger_Polarity 0x20000000UL #define Get_Trigger_Output 0x21000000UL #define Set_Trigger_Output 0x22000000UL #define Get_Gain 0x25000000UL #define Set_ain 0x26000000UL #define Get_Ad_Offset 0x27000000UL #define Set_Ad_OffSet 0x28000000UL #define Get_Calibration_Coefficient 0x29000000UL #define Set_Calibration_Coefficient 0x3B000000UL #define Get_Cooling_Temperature 0x2A000000UL #define Capture_Start 0x2F000000UL #define Fire_Trigger 0x30000000UL #define Capture_Stop 0x31000000UL #define Read_Eeprom_User_Area 0x33000000UL #define Write_Eeprom_User_Area 0x34000000UL #define Get_Status_Request 0x3D000000UL #define Get_Defective_Pixel 0x3E000000UL #pragma pack(1) struct DEV_CMD unsigned char buff[500]; //读写数据缓冲区 unsigned int write_len; //写数据长度 unsigned int read_len; //读数据长度 #define IOCTL_CMD_RW 0x39654128 #endif6.2 应用层程序示例打开光谱仪设备节点,获取设备的信息或者设置设备信息。1.#include <stdio.h> #include <sys/types.h> #include <sys/stat.h> #include <sys/ioctl.h> #include <stdlib.h> #include "spectrometer_cmd_list.h" #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #define DEV_NAME "/dev/spectrometer_usb_drv" int main(int argc,char **argv) /*1. 打开设备文件*/ int fd=open(DEV_NAME,2); if(fd<0) printf("%s 设备文件打开失败.\n",DEV_NAME); return 0; /*2. 读写光谱仪设备*/ struct DEV_CMD dev_cmd; memset(&dev_cmd,0,sizeof(struct DEV_CMD)); dev_cmd.write_len=8; //写数据长度 dev_cmd.read_len=500; //读数据长度,如果只想写不想读. 这里长度填0即可. //写入的命令.赋值 01 00 00 00 dev_cmd.buff[0]=0x01; dev_cmd.buff[1]=0x00; dev_cmd.buff[2]=0x00; dev_cmd.buff[3]=0x00; dev_cmd.buff[4]=0x00; dev_cmd.buff[5]=0x00; dev_cmd.buff[6]=0x00; dev_cmd.buff[7]=0x00; //提交数据 if(ioctl(fd,IOCTL_CMD_RW,&dev_cmd)) printf("ioctl执行失败.\n"); printf("写成功: %d 字节.\n",dev_cmd.write_len); if(dev_cmd.read_len>0) printf("读成功: %d 字节.\n",dev_cmd.read_len); printf("读取数据如下:\n"); int i; for (i = 0; i < dev_cmd.read_len; i++) printf("%#x ",dev_cmd.buff[i]); printf("\n"); //关闭设备 close(fd); return 0; }6.3 支持的命令详情6.4 驱动层程序示例#include <linux/init.h> #include <linux/module.h> #include <linux/usb.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/miscdevice.h> #include <linux/fs.h> #include <linux/io.h> #include "spectrometer_cmd_list.h" #include <asm/uaccess.h> #include <linux/bcd.h> #include <linux/uaccess.h> /*容纳所有设备特定内容的结构 */ struct usb_spectrometer struct usb_device *udev; /* 此设备的USB设备 */ struct usb_interface *interface; /*该设备的接口*/ struct usb_anchor submitted; /* 万一需要撤回提交*/ struct urb *bulk_in_urb; /*用urb读取数据*/ unsigned char *bulk_in_buffer; /* 接收数据的缓冲区 */ size_t bulk_in_size; /*接收缓冲区的大小 */ size_t bulk_in_filled; /* 缓冲区中的字节数 */ size_t bulk_in_copied; /* 已经复制到用户空间 */ __u8 bulk_in_endpointAddr; /* 端点中的批量地址 */ __u8 bulk_out_endpointAddr; /*批量输出端点的地址 */ int errors; /* 最后一个请求被取消 */ bool ongoing_read; /* 读正在进行*/ bool processed_urb; /* 表示尚未处理 */ static struct usb_spectrometer *dev; [ 25.845000] usb 1-2.2: new high-speed USB device number 6 using s5p-ehci [ 25.950000] usb 1-2.2: New USB device found, idVendor=0661, idProduct=294b [ 25.950000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=0 [ 25.950000] usb 1-2.2: Product: EZ-USB [ 25.950000] usb 1-2.2: Manufacturer: Cypress [ 726.360000] usb 1-2.2: new high-speed USB device number 7 using s5p-ehci [ 726.475000] usb 1-2.2: config 1 interface 0 altsetting 0 has 7 endpoint descriptors, different from the interface descriptor's value: 5 [ 726.480000] usb 1-2.2: New USB device found, idVendor=148f, idProduct=5370 [ 726.480000] usb 1-2.2: New USB device strings: Mfr=1, Product=2, SerialNumber=3 [ 726.480000] usb 1-2.2: Product: 802.11 n WLAN [ 726.480000] usb 1-2.2: Manufacturer: Ralink [ 726.485000] usb 1-2.2: SerialNumber: 1.0 //定义USB的IDTAB static const struct usb_device_id tiny4412_usb_id[] = {USB_DEVICE(0x148f,0x5370)}, {USB_DEVICE(0x0661,0x294b)}, MODULE_DEVICE_TABLE 有两个功能。 一是:将设备加入到外设队列中, 二是告诉程序阅读者该设备是热插拔设备或是说该设备支持热插拔功能。 该宏定义在<linux/module.h>下 这个宏有两个参数,第一个参数设备名,第二个参数该设备加入到模块中时对应产生的设备搜索符号,这个宏生成了一个名为__mod_pci_device_table 局部变量,这个变量指向第二个参数 MODULE_DEVICE_TABLE (usb,tiny4412_usb_id); static int usb_dev_open(struct inode *inode, struct file *file) printk("open:USB光谱仪设备.\n"); printk("命令结构大小_drv:%lu\n",sizeof(struct DEV_CMD)); return 0; //读写命令 static long usb_dev_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long argv) //转换指针类型 struct DEV_CMD *buff=(struct DEV_CMD *)argv; struct DEV_CMD dev_cmd; int ret=0; int actual_length=0; int err=0; printk("读写:USB光谱仪设备.\n"); //命令符合要求 if(IOCTL_CMD_RW==cmd) //拷贝应用层的数据到本地 if(copy_from_user(&dev_cmd,buff,sizeof(struct DEV_CMD))) printk("write: copy_from_user error...\n"); return -2; /*同步提交写请求*/ ret=usb_bulk_msg(dev->udev, usb_sndbulkpipe(dev->udev, dev->bulk_out_endpointAddr), dev_cmd.buff,dev_cmd.write_len,&actual_length,HZ*20); if(ret<0) printk("同步提交写请求错误.错误值:%d\n",ret); return -3; dev_cmd.write_len=actual_length; //成功写入的长度 printk("write:len=%d\n",actual_length); //读取的长度大于0.就表示需要读取数据 if(dev_cmd.read_len>0) //buff清0 memset(dev_cmd.buff,0,sizeof(dev_cmd.buff)); /*同步提交读请求*/ ret = usb_bulk_msg(dev->udev, usb_rcvbulkpipe(dev->udev,dev->bulk_in_endpointAddr), dev_cmd.buff,dev_cmd.read_len, &actual_length,HZ*20); if(ret<0) printk("同步提交读请求错误.错误值:%d\n",ret); return -4; //实际读取的长度 dev_cmd.read_len=actual_length; printk("read:len=%d\n",actual_length); //将数据拷贝到应用层 err=copy_to_user(buff,&dev_cmd,sizeof(struct DEV_CMD)); if(err) printk("read:read cmd error!!!\n"); return err; return 0; static int usb_dev_release(struct inode *inode, struct file *file) printk("release:USB光谱仪设备.\n"); return 0; static const struct file_operations usb_dev_fops = { .owner = THIS_MODULE, .unlocked_ioctl=usb_dev_unlocked_ioctl, .open = usb_dev_open, .release = usb_dev_release, static struct miscdevice usb_dev_miscdev = { .minor = MISC_DYNAMIC_MINOR, .name = "spectrometer_usb_drv", .fops = &usb_dev_fops, //USB设备信息与驱动端匹配成功的时候调用。 static int test_probe(struct usb_interface *interface,const struct usb_device_id *id) //资源探索函数 int i=0; size_t buffer_size; struct usb_device *dev_info; unsigned char *bcdUSB_p; struct usb_host_interface *host_inter; struct usb_endpoint_descriptor *endpoint; int size; dev=kzalloc(sizeof(*dev), GFP_KERNEL); dev->udev = usb_get_dev(interface_to_usbdev(interface)); dev->interface = interface; printk("识别到USB光谱仪设备,正在进行设备初始化.\n"); /*通过接口获取设备信息*/ dev_info = interface_to_usbdev(interface); bcdUSB_p=(unsigned char *)&dev_info->descriptor.bcdUSB; printk("设备与描述表兼容的USB设备说明版本号=0x%x%x\n", bcd2bin(*bcdUSB_p),bcd2bin(*(bcdUSB_p+1))); //从USB设备描述符中获取USB版本 printk("厂商ID = %#x\n",dev_info->descriptor.idVendor); //从USB设备描述符中获取厂商ID printk("设备ID = %#x\n",dev_info->descriptor.idProduct);//从USB设备描述符中获取产品ID printk("设备类 = %#x\n",interface->cur_altsetting->desc.bInterfaceClass); //从USB设备获取设备类 printk("设备从类 = %#x\n",interface->cur_altsetting->desc.bInterfaceSubClass);//从USB设备获取设备从类 printk("设备协议 = %#x\n",interface->cur_altsetting->desc.bInterfaceProtocol);//从USB设备获取设备协议 printk("驱动名称:%s\n",interface->dev.driver->name); printk("总线名称:%s\n",interface->dev.driver->bus->name); /*获取当前接口设置*/ host_inter=interface->cur_altsetting; /*获取端点描述符*/ for(i=0;i<host_inter->desc.bNumEndpoints;i++) endpoint = &host_inter->endpoint[i].desc; printk("端点号[%d]:%d\n",i,endpoint->bEndpointAddress&0xFF); if(endpoint->bEndpointAddress&1<<7) printk("端点[%d] 输入端点(设备到主机)\n",i); printk("端点[%d] 输出端点(主机到设备)\n",i); switch(endpoint->bmAttributes) case 0:printk("端点[%d] 设备支持控制传输.\n",i);break; case 1:printk("端点[%d] 设备支持同步传输.\n",i);break; case 2:printk("端点[%d] 设备支持批量传输.\n",i);break; case 3:printk("端点[%d] 设备支持中断传输.\n",i);break; /*从端点描述符中获取传输的数据大小 */ size = usb_endpoint_maxp(endpoint); printk("端点[%d] 传输的数据大小:%d\n",i,size); //输入端点 if(!dev->bulk_in_endpointAddr &&usb_endpoint_is_bulk_in(endpoint)) /* 批量输入端点 */ buffer_size = usb_endpoint_maxp(endpoint); dev->bulk_in_size = buffer_size; dev->bulk_in_endpointAddr = endpoint->bEndpointAddress; printk("probe:dev->bulk_in_size=%lu\n",dev->bulk_in_size); printk("probe:dev->bulk_in_endpointAddr=%d\n",dev->bulk_in_endpointAddr); dev->bulk_in_buffer = kmalloc(buffer_size,GFP_KERNEL); if(!dev->bulk_in_buffer) printk("无法分配bulk_in_buffer"); break; dev->bulk_in_urb = usb_alloc_urb(0,GFP_KERNEL); if(!dev->bulk_in_urb) printk("无法分配bulk_in_urb"); break; //输出端点 if(!dev->bulk_out_endpointAddr &&usb_endpoint_is_bulk_out(endpoint)) /* 批量输出端点 */ dev->bulk_out_endpointAddr = endpoint->bEndpointAddress; printk("probe:dev->bulk_out_endpointAddr=%d\n",dev->bulk_out_endpointAddr); /*向内核注册一个杂项字符设备*/ if(misc_register(&usb_dev_miscdev)==0) printk("USB光谱仪设备节点注册成功:/dev/%s ,主设备号:10,次设备号:%d\n", usb_dev_miscdev.name,usb_dev_miscdev.minor); return 0; //USB断开的时候调用 static void test_disconnect(struct usb_interface *intf) printk("USB光谱仪设备已断开.\n"); /*从内核注销一个杂项字符设备*/ misc_deregister(&usb_dev_miscdev); //定义USB驱动结构体 static struct usb_driver tiny4412_usb_driver = { .name = "spectrometer_usb_drv", .id_table = tiny4412_usb_id, .probe = test_probe, .disconnect = test_disconnect static int __init tiny4412_usb_init(void) printk("正在安装USB光谱仪驱动.\n"); //注册USB设备驱动 usb_register(&tiny4412_usb_driver); return 0; static void __exit tiny4412_usb_exit(void) //注销USB设备驱动 usb_deregister(&tiny4412_usb_driver); printk("USB光谱仪驱动卸载成功.\n"); module_init(tiny4412_usb_init); module_exit(tiny4412_usb_exit); MODULE_AUTHOR("xiaolong"); MODULE_LICENSE("GPL");

基于keil5新建STM32F10x寄存器版本工程

一、前言前面文章分享了很多关于STM32F103系列知识点、物联网相关的小项目,工程都采用的是寄存器方式编写;很多小伙伴接触STM32开始都采用库函数编程,不清楚如何使用寄存器方式开发STM32;这篇文章就讲一下如何新建寄存器风格的STM32工程,并介绍需要用到哪些官方系统文件等。比较具有代表性的几篇物联网教程:1.  智慧农业项目(基于腾讯物联网服务器)2.  遥控小车项目3.  智能门锁项目(基于腾讯物联网服务器)4.  物联网项目(基于阿里云物联网服务器)5.  智能家居项目(基于中国移动OneNet物联网服务器)二、环境介绍开发环境: keil5.25编程语言: C语言操作系统: win10 64位MCU型号: ST32F103C8T6 (F103系列都是通用的,区分容量即可)库函数的版本: 3.5 (新建工程需要用到库函数包里的一些系统必要文件)库函数资料包下载地址:  https://download.csdn.net/download/xiaolong1126626497/21469164STM32系列简介:STM32系列专为要求高性能、低成本、低功耗的嵌入式应用设计的。主流产品(STM32F0、STM32F1、STM32F3)、超低功耗产品(STM32L0、STM32L1、STM32L4、STM32L4+)、高性能产品(STM32F2、STM32F4、STM32F7、STM32H7)以STM32F103RBT6这个型号的芯片为例,该型号的组成为7个部分,其命名规则如下:STM32L电路的设计目的是以低电压实现高性能,有效延长电池供电设备的充电间隔。片上模拟功能的最低工作电源电压为1.8V。数字功能的最低工作电源电压为1.65V,在电池电压降低时,可以延长电池供电设备的工作时间。从应用类别来说,STM32可以有以下用处:1.  无人机制作:现在无人机主流的微控制器所用的就是stm32控制器。2.  简单仪器仪表:stm32可以用作简单示波器、频率计,对采集的数字信号进行处理并送入屏幕显示。3. 机器人:常看到的就是家用的扫地机器人,还要就是国内各种机器人比赛的小型机器人,几乎都用的是stm32控制器,毕竟现在stm32接口种类齐全、功能又多、价格便宜、资料齐全。4. 电源控制器:全国电子设计大赛常考题的电源题,很多同学制作电源首选控制器也大多会选择stm32。三、新建工程接下来就可以写代码点亮第一盏LED灯。#include "stm32f10x.h" int main() while(1)

基于C++(QT框架)设计的网络摄像头项目(支持跨平台运行)

一、运行效果展示1.1  windows系统运行效果展示​​​网络摄像头项目(Windows系统运行效果)1.2  Android系统运行效果展示网络摄像头项目(Android系统运行效果)1.3  Linux系统运行效果展示网络摄像头项目(Linux系统运行效果)二、功能简介2.1  功能介绍这是基于C++(QT框架)设计的网络摄像头项目,本篇文章介绍的网络摄像头项目并不是采用RTMP或者RTSP推流编码的网络摄像头产品,而是采用HTTP协议推送图片流的方式,采用浏览器访问查看摄像头画面。项目源码下载地址: https://download.csdn.net/download/xiaolong1126626497/21232910这是软件运行截图:这是浏览器访问截图:项目运行的效果:软件打开之后,先点击刷新信息,程序会扫描当前设备的摄像头和IP地址信息;并将访问的地址打印了出来,然后点击开启摄像头,界面就实时显示选择的摄像头画面。 在局域网内,其他设备打开浏览器,输入下面提示的地址访问,输入用户名和密码,即可查看到摄像头画面。程序里处理浏览器的请求是采用多线程方式,可以支持多个浏览器同时访问。代码思路代码采用的是C++(QT框架)编写,代码本身主要是分为两个部分:1.   绑定指定端口号,创建TCP服务器,用来响应客户端的请求(浏览器)2.  摄像头画面采集部分,摄像头数据采集采用单独的线程,采集之后将图像传递给界面刷新显示,并将图像填充到全局缓冲区,方便客户端处理线程将图像再传递给浏览器。每当有新的浏览器访问进来,就会单独开辟一个线程,去处理这个浏览器接下来的通信交互,浏览器断开连接之后,线程自动销毁;图像缓冲区是一个公共的缓冲区,摄像头图像采集线程向该缓冲区填充数据,与浏览器通信的线程就从这个缓冲区读取数据,采用的是条件变量+互斥锁同步访问。项目里用到的知识点主要是摄像头采集,线程处理、网络编程,HTTP协议等知识点。如果是搞QT开发,都可以当做入门学习参考;如果想要用其他语言实现,思路搞清楚也很容易。 标准C语言。在Linux下如果不需要界面,可以直接使用C语言完成项目效果,摄像头采集采用Linux下标准V4L2框架,线程就采用pthread_create创建,互斥锁、条件变量这些Linux都有,只要把程序思路搞清楚,实现起来还是很容易。2.2 跨平台运行代码是采用QT框架编写,所以支持跨平台编译运行;目前代码在Android、Linux、windows系统上都编译运行通过,达到相同效果;由于身边没有苹果设备,暂时未做测试。不同系统间代码上还是有少许区别,代码里通过宏的方式区分了,不同的系统运行不同的部分代码。如果要编译QT的代码在Android设备上运行,得先搭建对应的开发环境;如果不会环境搭建,可以参考下面链接。(1). Linux(ubuntu)下搭建QT Android开发环境https://blog.csdn.net/xiaolong1126626497/article/details/117256660(2). windows(win10 64bit)下搭建QT Android开发环境https://blog.csdn.net/xiaolong1126626497/article/details/117254453(3).  windows下Qt + VS2017 环境安装https://blog.csdn.net/xiaolong1126626497/article/details/1124028612.3  项目源码运行环境QT版本: 5.12.6编译器: MinGW 32系统:  Win10  Ubuntu18.042.4 代码运行介绍程序运行时,需要用到一些资源文件,这些资源文件在程序的源码目录下,名称是:  www。如果是windows、Linux系统环境,需要把资源目录拷贝到程序运行的同级目录下。如果是Android系统,这些资源文件需要在编译的时候打包进APK里,在工程目录下的Android目录里创建一个assets目录,将资源文件全部拷贝到assets目录下;程序编译的时候会自动将assets目录打包进apk,到时候Android程序运行时就可以直接去assets目录下获取资源文件。 当apk生成之后,可以解压出来看看,assets目录打包成功没有。检查了下,打包成功的,这样Android程序运行时,访问的路径就没有问题。2.5 Linux系统运行摄像头无法读取数据问题说明由于我测试的环境是在虚拟机里运行ubuntu系统,要在ubuntu里访问摄像头,需要先把摄像头挂载到虚拟机,才能访问。如果打开虚拟机发现右下角托盘里摄像头图标都没有,说明托盘程序可能被360、电脑管家之类的安全软件禁止了,需要开启自动启动,重启VM虚拟机软件再测试。直接挂载进来可能遇到摄像头打开了,但是图像无法获取的情况,这个问题可能是USB协议版本引起的。可以在这里切换USB协议测试,我的笔记本电脑是USB3.0才可以正常使用。三、代码分析3.1  初始化代码(构造函数)代码开发时,主要是针对在windows平台运行的,所有程序里很多都是偏向于windows环境的设计。构造函数里去除了系统原窗口标题栏,自定义了自己的标题;QT隐藏标题栏之后,是不能拖动拉伸的,需要自己实现,我这里采用的是GitHUB上开源的一个示例代码实现的这部分功能,效果不错,达到了想要的效果。剩下代码里初始化了托盘系统,方便程序最小化时,隐藏在windows系统的托盘图标栏里,其他的代码就是做写基本的初始化,信号槽的连接,IP地址、摄像头信息刷新等。Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); //自定义标题栏 FramelessHelper *pHelper = new FramelessHelper(this); setWindowFlags(Qt::FramelessWindowHint); pHelper->activateOn(this); //激活当前窗体 //pHelper->setTitleHeight(m_pTitleBar->height()); //设置窗体的标题栏高度 pHelper->setWidgetMovable(true); //设置窗体可移动 pHelper->setWidgetResizable(true); //设置窗体可缩放 // pHelper->setRubberBandOnMove(true); //设置橡皮筋效果-可移动 // pHelper->setRubberBandOnResize(true); //设置橡皮筋效果-可缩放 /*基本设置*/ this->setWindowIcon(QIcon(":/log.ico")); //设置图标 this->setWindowTitle("网络摄像头"); //打开的窗口在屏幕中间 QDesktopWidget *widget= QApplication::desktop(); move((widget->width()-this->width())/2,(widget->height()-this->height())/2); //刷新摄像头与IP地址信息 on_pushButton_config_clicked(); //刷新在线人数 timer = new QTimer(this); connect(timer, SIGNAL(timeout()), this, SLOT(timer_update())); timer->start(1000); tray= new QSystemTrayIcon(this);//初始化托盘对象tray connect(tray,SIGNAL(activated(QSystemTrayIcon::ActivationReason)),this,SLOT(show_Widget(QSystemTrayIcon::ActivationReason))); tray->setIcon(QIcon(QPixmap(":/log.png")));//设定托盘图标,引号内是自定义的png图片路径 tray->setToolTip("网络摄像头"); //提示文字 restoreAction = new QAction("打开", this); connect(restoreAction, SIGNAL(triggered()), this, SLOT(show())); quitAction = new QAction("退出", this); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); trayMenu = new QMenu(this); trayMenu->addAction(restoreAction); trayMenu->addSeparator(); trayMenu->addAction(quitAction); tray->setContextMenu(trayMenu); work_class=new VideoReadThread_0; work_thread=new QThread; //启动摄像头的信号 connect(this,SIGNAL(StartWorkThread()),work_class,SLOT(run())); //释放资源-释放摄像头 connect(this,SIGNAL(Stop_VideoAudioEncode_0()),work_class,SLOT(stop())); //连接摄像头采集信号,在主线程实时显示视频画面 connect(work_class,SIGNAL(VideoDataOutput(QImage)),this,SLOT(VideoDataDisplay_0(QImage))); //将类移动到子线程工作 work_class->moveToThread(work_thread); }3.2 摄像头采集部分摄像头采集采用的是QCamera + QVideoProbe实现。初始化代码示例: 初始化代码里完成摄像头的一些参数,捕获模式,槽函数关联等设置。初始化代码默认设置输出的图像格式是YUYV,在windows和Linux系统上是支持的,这个可能与摄像头有关,实际需要测试调整;Android系统上只支持NV21格式,如果是Android系统上运行,要记得修改格式。void VideoReadThread_0::Camear_Init() /*创建摄像头对象,根据选择的摄像头打开*/ camera = new QCamera(videoaudioencode_0.camera); m_pProbe = new QVideoProbe; if(m_pProbe != nullptr) m_pProbe->setSource(camera); // Returns true, hopefully. connect(m_pProbe, SIGNAL(videoFrameProbed(QVideoFrame)),this, SLOT(slotOnProbeFrame(QVideoFrame)), Qt::QueuedConnection); qDebug()<<"m_pProbe == nullptr"; /*配置摄像头捕 QCamera *camera; QVideoProbe *m_pProbe;获模式为帧捕获模式*/ camera->setCaptureMode(QCamera::CaptureVideo); //如果在Linux系统下运行就这样设置 //camera->setCaptureMode(QCamera::CaptureVideo);//如果在android系统下运行就这样设置 /*设置摄像头的采集帧率和分辨率*/ QCameraViewfinderSettings settings; settings.setPixelFormat(QVideoFrame::Format_YUYV); //设置像素格式 Android上只支持NV21格式 settings.setResolution(QSize(VIDEO_WIDTH,VIDEO_HEIGHT)); //设置摄像头的分辨率 camera->setViewfinderSettings(settings); /*启动摄像头*/ camera->start(); }捕获到摄像头的图像数据之后,QVideoProbe会发出videoFrameProbed信号,在关联的槽函数里完成数据处理。处理代码如下:void VideoReadThread_0::slotOnProbeFrame(const QVideoFrame &frame) // qDebug()<<"开始采集."; QVideoFrame cloneFrame(frame); cloneFrame.map(QAbstractVideoBuffer::ReadOnly); // qDebug()<<"height:"<<cloneFrame.height(); // qDebug()<<"width:"<<cloneFrame.width(); // qDebug()<<"bytesPerLine:"<<cloneFrame.bytesPerLine(); // qDebug()<<"mappedBytes:"<<cloneFrame.mappedBytes(); // qDebug()<<"pixelFormat:"<<cloneFrame.pixelFormat(); unsigned char rgb_buffer[VIDEO_WIDTH*VIDEO_HEIGHT*3]; if(cloneFrame.pixelFormat()==QVideoFrame::Format_YUYV) yuyv_to_rgb(cloneFrame.bits(),rgb_buffer,cloneFrame.width(),cloneFrame.height()); else if(cloneFrame.pixelFormat()==QVideoFrame::Format_NV21) NV21_TO_RGB24(cloneFrame.bits(),rgb_buffer,cloneFrame.width(),cloneFrame.height()); qDebug()<<"当前格式编码为%1,暂时不支持转换.\n"; return; cloneFrame.unmap(); //加载图片数据 QImage image(rgb_buffer, cloneFrame.width(), cloneFrame.height(), QImage::Format_RGB888); //绘制图片水印 QDateTime dateTime(QDateTime::currentDateTime()); //时间效果: 2020-03-05 16:25::04 周一 QString qStr=""; qStr+=dateTime.toString("yyyy-MM-dd hh:mm:ss ddd"); QPainter pp(&image); QPen pen = QPen(Qt::white); pp.setPen(pen); pp.setFont(QFont("宋体",20)); pp.drawText(QPointF(0,40),qStr); mutex.lock(); QBuffer buff; image.save(&buff,"jpg",80); // 30表示压宿率,值从0 – 100, 值越小表示编码出来的图像文件就越小,当然也就越不清晰 lcd_image_data=buff.data(); cond_wait.wakeAll(); mutex.unlock(); emit VideoDataOutput(image); //发送信号 }代码里区分了数据格式,如果是YUYV就调用对应的转换函数将数据转为RGB格式,如果是NV21也是一样,调用对应的转换函数将数据转为RGB格式后续再做其他处理。图像格式转换的代码如下:/* 函数功能: 将YUV数据转为RGB格式 函数参数: unsigned char *yuv_buffer: YUV源数据 unsigned char *rgb_buffer: 转换之后的RGB数据 int iWidth,int iHeight : 图像的宽度和高度 void yuyv_to_rgb(unsigned char *yuv_buffer,unsigned char *rgb_buffer,int iWidth,int iHeight) int x; int z=0; unsigned char *ptr = rgb_buffer; unsigned char *yuyv= yuv_buffer; for (x = 0; x < iWidth*iHeight; x++) int r, g, b; int y, u, v; if (!z) y = yuyv[0] << 8; y = yuyv[2] << 8; u = yuyv[1] - 128; v = yuyv[3] - 128; r = (y + (359 * v)) >> 8; g = (y - (88 * u) - (183 * v)) >> 8; b = (y + (454 * u)) >> 8; *(ptr++) = (r > 255) ? 255 : ((r < 0) ? 0 : r); *(ptr++) = (g > 255) ? 255 : ((g < 0) ? 0 : g); *(ptr++) = (b > 255) ? 255 : ((b < 0) ? 0 : b); if(z++) z = 0; yuyv += 4; void NV21_TO_RGB24(unsigned char *yuyv, unsigned char *rgb, int width, int height) const int nv_start = width * height ; int index = 0, rgb_index = 0; uint8_t y, u, v; int r, g, b, nv_index = 0,i, j; for(i = 0; i < height; i++){ for(j = 0; j < width; j ++){ //nv_index = (rgb_index / 2 - width / 2 * ((i + 1) / 2)) * 2; nv_index = i / 2 * width + j - j % 2; y = yuyv[rgb_index]; u = yuyv[nv_start + nv_index ]; v = yuyv[nv_start + nv_index + 1]; r = y + (140 * (v-128))/100; //r g = y - (34 * (u-128))/100 - (71 * (v-128))/100; //g b = y + (177 * (u-128))/100; //b if(r > 255) r = 255; if(g > 255) g = 255; if(b > 255) b = 255; if(r < 0) r = 0; if(g < 0) g = 0; if(b < 0) b = 0; index = rgb_index % width + (height - i - 1) * width; //rgb[index * 3+0] = b; //rgb[index * 3+1] = g; //rgb[index * 3+2] = r; //颠倒图像 //rgb[height * width * 3 - i * width * 3 - 3 * j - 1] = b; //rgb[height * width * 3 - i * width * 3 - 3 * j - 2] = g; //rgb[height * width * 3 - i * width * 3 - 3 * j - 3] = r; //正面图像 rgb[i * width * 3 + 3 * j + 0] = b; rgb[i * width * 3 + 3 * j + 1] = g; rgb[i * width * 3 + 3 * j + 2] = r; rgb_index++; }图像格式转换完成之后,下面就加载到QImage里,绘制上时间水印,将数据填充到缓冲区,然后再唤醒等待的线程,最后在传递一份数据给UI界面完成渲染显示。如果使用过程中,摄像头支持的是其他格式,那么这里需要自己增加对应的转换函数。3.3  浏览器交互线程这里是TCP客户端处理数据的线程,每当连接一个新的客户端,就会开辟一个新的线程独立运行。线程里完成浏览器请求的处理,回应,交互。完整的响应代码如下:void TcpServerThread::run() qDebug()<<"TcpServerThread_子线程run槽函数的ID:"<<QThread::currentThreadId(); QTcpSocket *tcpSocket; tcpSocket=new QTcpSocket; /*使用socketDescriptor套接字初始化QAbstractSocket*/ if(!tcpSocket->setSocketDescriptor(socketDescriptor)) return; //读取接收的数据 QString text; if(tcpSocket->waitForReadyRead()) text=tcpSocket->readAll(); //处理浏览器的请求 if(text.contains("GET / HTTP/1.1", Qt::CaseInsensitive)) //如果是Android系统 #ifdef Q_OS_ANDROID text="assets:/login.html"; #else text=QCoreApplication::applicationDirPath(); text+="/www/login.html"; #endif SendFileData(buff,text,"text/html",tcpSocket); else if(text.contains("GET /image.jpg HTTP/1.1", Qt::CaseInsensitive)) SendImageData(buff,tcpSocket); else if(text.contains("GET /favicon.ico HTTP/1.1", Qt::CaseInsensitive)) #ifdef Q_OS_ANDROID text="assets:/logo.ico"; #else text=QCoreApplication::applicationDirPath(); text+="/www/logo.ico"; #endif SendFileData(buff,text,"image/x-icon",tcpSocket); else if(text.contains("GET /three.min.js", Qt::CaseInsensitive)) #ifdef Q_OS_ANDROID text="assets:/three.min.js"; #else text=QCoreApplication::applicationDirPath(); text+="/www/three.min.js"; #endif SendFileData(buff,text,"application/x-javascript",tcpSocket); else if(text.contains("GET /style.css", Qt::CaseInsensitive)) #ifdef Q_OS_ANDROID text="assets:/style.css"; #else text=QCoreApplication::applicationDirPath(); text+="/www/style.css"; #endif SendFileData(buff,text,"text/css",tcpSocket); else if(text.contains("GET /Stats.min.js", Qt::CaseInsensitive)) #ifdef Q_OS_ANDROID text="assets:/Stats.min.js"; #else text=QCoreApplication::applicationDirPath(); text+="/www/Stats.min.js"; #endif SendFileData(buff,text,"application/x-javascript",tcpSocket); else if(text.contains("GET /logo.png HTTP/1.1", Qt::CaseInsensitive)) #ifdef Q_OS_ANDROID text="assets:/logo.png"; #else text=QCoreApplication::applicationDirPath(); text+="/www/logo.png"; #endif SendFileData(buff,text,"image/png",tcpSocket); else if(text.contains("userName=admin&passWord=12345678", Qt::CaseInsensitive)) #ifdef Q_OS_ANDROID text="assets:/index.html"; #else text=QCoreApplication::applicationDirPath(); text+="/www/index.html"; #endif SendFileData(buff,text,"text/html",tcpSocket); //卸载连接的套接字 fd_list.Del_fd(socketDescriptor); //关闭连接 tcpSocket->close(); delete tcpSocket; * 向浏览器响应请求数据 int TcpServerThread::SendFileData(char *buff, QString file_path, const char *type, QTcpSocket *tcpSocket) qint64 size=0; /*1. 读取文件*/ QFile file(file_path); if(file.open(QIODevice::ReadOnly)!=true) qDebug()<<"文件不存在:"<<file_path<<endl; return -1; size=file.size(); //得到文件大小 QByteArray byte=file.readAll(); //读取所有数据 file.close();//关闭文件 /*2. 构建响应格式字符串*/ sprintf(buff,"HTTP/1.1 200 OK\r\n" "Content-type:%s\r\n" "Content-Length:%lld\r\n" "\r\n",type,size); /*3. 向客户端发送响应请求*/ if(tcpSocket->write(buff,strlen(buff))<=0)return -2; tcpSocket->waitForBytesWritten(); //等待写 /*4. 向客户端发送响应实体数据*/ if(tcpSocket->write(byte)<=0)return -3; tcpSocket->waitForBytesWritten(); //等待写 return 0; 向客户端循环发送图片数据流 int TcpServerThread::SendImageData(char *buff, QTcpSocket *tcpSocket) /*1. 构建响应格式字符串*/ sprintf(buff,"HTTP/1.0 200 OK\r\n" "Server: wbyq\r\n" "Content-Type:multipart/x-mixed-replace;boundary=boundarydonotcross\r\n" "\r\n" "--boundarydonotcross\r\n"); /*2. 向客户端发送响应请求*/ if(tcpSocket->write(buff,strlen(buff))<=0)return -2; tcpSocket->waitForBytesWritten(); //等待写 /*3. 循环发送数据*/ while(thread_run_flag) //从全局缓冲区取数据 mutex.lock(); cond_wait.wait(&mutex); QByteArray image_data; //保存一帧图像数据 image_data=lcd_image_data; mutex.unlock(); // QBuffer data_buff; // QPixmap pixmap; // QScreen *screen = QGuiApplication::primaryScreen(); // pixmap=screen->grabWindow(0); //获取当前屏幕的图像 // //缩放图像 // pixmap = pixmap.scaled(lcd_image_w,lcd_image_h, Qt::KeepAspectRatio); // pixmap.save(&data_buff,"jpg",image_val); // QByteArray image_data1=data_buff.data(); /*4. 向浏览器发送响应头*/ sprintf(buff,"Content-type:image/jpeg\r\n" "Content-Length:%d\r\n" "\r\n",image_data.size()); if(tcpSocket->write(buff,strlen(buff))<=0)break; tcpSocket->waitForBytesWritten(); //等待写 /*5. 发送实体数据*/ if(tcpSocket->write(image_data)<=0)break; tcpSocket->waitForBytesWritten(); //等待写 /*6. 发送边界符*/ sprintf(buff,"\r\n--boundarydonotcross\r\n"); if(tcpSocket->write(buff,strlen(buff))<=0)break; tcpSocket->waitForBytesWritten(); //等待写 //等待一段时间 msleep(5); return 0; }当密码、账号验证通过之后,服务器就与浏览器建立长连接,循环发送图片流数据,浏览器完成连续刷新显示。 里面实现的思路,就是基本的TCP网络编程代码。四、  HTTP协议简单介绍这个网络摄像头项目主要是与浏览器交互的,要完成浏览器的交互,首先得知道HTTP协议的报文格式,如何响应,下面只是介绍当前项目里用到的部分知识,方便理解第三章的浏览器交互代码。4.1  HTTP协议介绍HTTP协议是Hyper Text Transfer Protocol(超文本传输协议)的缩写,服务器传输超文本到本地浏览器的传送协议。HTTP是一个基于TCP/IP通信协议来传递数据(HTML 文件, 图片文件, 查询结果等)。HTTP是无状态:HTTP协议是无状态协议。无状态是指协议对于事务处理没有记忆能力。缺少状态意味着如果后续处理需要前面的信息,则它必须重传,这样可能导致每次连接传送的数据量增大。另一方面,在服务器不需要先前信息时它的应答就较快。HTTP协议工作于客户端-服务端架构上。浏览器作为HTTP客户端通过URL向HTTP服务端即WEB服务器发送所有请求。Web服务器根据接收到的请求后,向客户端发送响应信息。​4.2 请求方法与报文格式 客户端请求消息客户端发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成,下图给出了请求报文的一般格式。服务器响应消息HTTP响应也由四个部分组成,分别是:状态行、消息报头、空行和响应正文。 HTTP协议常用的2种请求方法: GET, POST 方法。​4.3 HTTP响应头信息HTTP请求头提供了关于请求,响应或者其他的发送实体的信息。4.4 HTTP状态码当浏览者访问一个网页时,浏览者的浏览器会向网页所在服务器发出请求。当浏览器接收并显示网页前,此网页所在的服务器会返回一个包含HTTP状态码的信息头(server header)用以响应浏览器的请求。HTTP状态码的英文为HTTP Status Code。下面是常见的HTTP状态码:200 - 请求成功301 - 资源(网页等)被永久转移到其它URL404 - 请求的资源(网页等)不存在500 - 内部服务器错误4.5 HTTP content-typeContent-Type,内容类型,一般是指网页中存在的Content-Type,用于定义网络文件的类型和网页的编码,决定浏览器将以什么形式、什么编码读取这个文件,这就是经常看到一些Asp网页点击的结果却是下载到的一个文件或一张图片的原因。HTTP content-type 对照表:

QT软件开发: 将QGraphicsView视图界面保存为图片

需求: QGraphicsView 编辑完成之后,需要将界面保存为图片导出. QPixmap pix=ui->graphicsView->grab();展示代码示例:#include <QDialog> #include <QLabel> void MainWindow::on_pushButton_clicked() QPixmap pix=ui->graphicsView->grab(); QDialog *dialog=new QDialog; dialog->setWindowTitle("图片展示"); QHBoxLayout *layout=new QHBoxLayout; QLabel *label=new QLabel; label->resize(800,480); pix=pix.scaled(label->width(),label->height()); label->setPixmap(pix); layout->addWidget(label); dialog->setLayout(layout); dialog->show(); dialog->exec(); delete dialog;

FFMPEG音视频开发: 添加图片水印

FFMPEG版本: 4.2.2文字水印添加方法:https://blog.csdn.net/xiaolong1126626497/article/details/106584556实现代码:1.//添加图片水印 C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -i D:/666.mp4 -vf "movie=image/123.png[wm];[in][wm]overlay=30:10[out]" D:/linux-share-dir/video_file/test/output.mp4 参数解析: 1. D:/666.mp4 输入的视频 2. image/123.png 要添加进去的图片水印 3. D:/linux-share-dir/video_file/test/output.mp4 合成水印之后输出的视频

QT软件开发: 截取全屏、指定位置、当前窗口保存图片

一、环境介绍QT : 5.12.6操作系统: win10 x64编译器: MinGW32二、示例代码头文件#include <QScreen> #include <QTimer> connect(&timer, SIGNAL(timeout()), this, SLOT(update())); timer.start(50);2.1 截取全屏保存为图片void Form::update() static int cnt=0; QScreen *screen = QGuiApplication::primaryScreen(); //截取当前桌面全屏画面 screen->grabWindow(0).save(QString("%1.jpg").arg(cnt++)); }2.2 截取窗口指定位置保存图片void Form::update() static int cnt=0; QScreen *screen = QGuiApplication::primaryScreen(); QRect rect; rect.setX(this->mapToGlobal(this->pos()).x()); rect.setY(this->mapToGlobal(this->pos()).y()); rect.setWidth(this->width()); rect.setHeight(this->height()); screen->grabWindow(0,rect.x(),rect.y(),rect.width(),rect.height()).save(QString("%1.jpg").arg(cnt++));//截取当前桌面 }2.3 截取当前窗口保存为图片void Form::update() //截取当前窗口画面.---不能透过窗口截后面 this->grab().save(QString("%1.jpg").arg(cnt++));

QT软件开发: 窗口鼠标事件穿透

一、需求 弹出的子窗口要顶置在最前面,播放指定的动画,不能有任务栏图标,不能影响鼠标操作窗口背后的其他界面。 (相当于桌面动画效果)二、效果示例三、子窗口代码3.1 form.h#ifndef FORM_H #define FORM_H #include <QWidget> #include <QStyleOption> #include <QPainter> #include <QMouseEvent> #include <QDebug> #include <QDesktopWidget> #include <QMovie> namespace Ui { class Form; class Form : public QWidget Q_OBJECT public: explicit Form(QWidget *parent = nullptr); ~Form(); protected: void paintEvent(QPaintEvent *p1); private: Ui::Form *ui; QMovie *mv; #endif // FORM_H3.2 form.cpp1.#include "form.h" #include "ui_form.h" Form::Form(QWidget *parent) : QWidget(parent), ui(new Ui::Form) ui->setupUi(this); //放在窗口最前面执行,可以实现全穿透,不响应本窗口的事件 //所有鼠标事件都穿透的方法:直接设置子窗体的属性 //如果不执行该属性设置. 是可以响应本窗口的事件 setAttribute(Qt::WA_TransparentForMouseEvents, true); //隐藏标题栏 setWindowFlags(Qt::FramelessWindowHint);//无边框 //打开的窗口在屏幕中间 QDesktopWidget *widget= QApplication::desktop(); move((widget->width()-this->width())/2,(widget->height()-this->height())/2); //窗口大小 this->resize(320,240); //设置窗口顶置: 一直在最前面. 并且隐藏任务栏的图标 Qt::WindowFlags m_flags = windowFlags(); setWindowFlags(m_flags| Qt::FramelessWindowHint |Qt::WindowStaysOnTopHint|Qt::WindowStaysOnTopHint|Qt::Tool); //设置窗口背景透明 setAttribute(Qt::WA_TranslucentBackground); //播放GIF动画 mv = new QMovie(":/load.gif"); ui->label->setMovie(mv); mv->start();//开始播放 Form::~Form() delete ui; void Form::paintEvent(QPaintEvent *p1) QPainter p(this); //绘制样式 QStyleOption opt; opt.initFrom(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);//绘制样式 //定义画笔 QPen pen; pen.setWidth(10); pen.setColor(QColor("#00B0AE")); pen.setStyle(Qt::SolidLine); //线的样式--实线 p.setPen(pen); pen.setWidth(5); pen.setStyle(Qt::DashDotLine); //线的样式 p.setPen(pen); //创建画刷 // QBrush brush; // brush.setColor(QColor("#00B0AE")); // brush.setStyle(Qt::Dense1Pattern); //矩形框填充的样式 // p.setBrush(brush); p.drawRect(p1->rect());

QT软件开发: 设置QSlider样式

示例样式:ui->horizontalSlider->setStyleSheet("");/*滑块的样式*/ QSlider::groove:horizontal { border: 1px solid #00B0AE; background: #00B0AE; height: 2px; border-radius: 1px; padding-left:0px; padding-right:0px; /*滑块经过的颜色:前面的颜色*/ QSlider::sub-page:horizontal { background: #00B0AE; border: 1px solid #00B0AE; height: 2px; border-radius: 2px; QSlider::add-page:horizontal { background: #EAEAEA; border: 0px solid #EAEAEA; height: 2px; border-radius: 2px; QSlider::handle:horizontal background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.6 #00B0AE,stop:0.98409 rgba(255, 255, 255, 255)); width: 15px; margin-top: -6px; margin-bottom: -6px; border-radius: 5px; QSlider::handle:horizontal:hover { background: qradialgradient(spread:pad, cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, stop:0.6 #00B0AE,stop:0.98409 rgba(255, 255, 255, 255)); width: 15px; margin-top: -6px; margin-bottom: -6px; border-radius: 5px;

QT软件开发:设置QPushButton样式

一、设置圆角、鼠标按下、停留、正常颜色ui->pushButton->setStyleSheet("");QPushButton color: #00B0AE; background-color:#FFFFFF; font: 9pt "黑体"; border:1px groove #00B0AE;border-radius:10px; /*按钮停留态*/ QPushButton:hover /*背景颜色*/ background-color: rgba(235, 235, 235,200); /*按钮按下态*/ QPushButton:pressed /*背景颜色*/ background-color: rgba(235, 235, 235,100); } 二、设置按钮隐藏边框ui->pushButton->setStyleSheet("");color: rgb(0, 176, 174); font: 12pt "黑体"; border:none

//设置样式 this->setStyleSheet("#Widget{background-color: rgba(255, 0, 0, 150);}"); void Widget::paintEvent(QPaintEvent *p1) //绘制样式 QStyleOption opt; opt.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);//绘制样式 QBitmap bmp(this->size()); bmp.fill(); QPainter painter(&bmp); painter.setPen(Qt::NoPen); painter.setBrush(Qt::black); painter.setRenderHint(QPainter::Antialiasing); painter.drawRoundedRect(bmp.rect(), 12, 12); setMask(bmp);

QT软件开发: 点击鼠标在窗口里绘制矩形(窗口透明背景)

一、功能需求一般在软件开发中,需要都有选择区域的需求,比如:1.  截图软件,需要鼠标选择指定区域截图2. 屏幕录像软件,需要鼠标选择指定区域录像3. 图片浏览器,需要鼠标选择指定区域放大查看4. 视频播放器,需要鼠标选择指定区域放大播放...........工程下载地址: https://download.csdn.net/download/xiaolong1126626497/21043499二、运行效果 ​三、示例代码3.1  widget.cpp1.#include "widget.h" #include "ui_widget.h" Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) ui->setupUi(this); //隐藏标题栏 setWindowFlags(Qt::FramelessWindowHint);//无边框 置顶 //设置窗口背景透明 setAttribute(Qt::WA_TranslucentBackground); //全屏显示 showFullScreen(); //设置样式 this->setStyleSheet("#Widget{background-color: rgba(0, 0, 0, 150);}"); Widget::~Widget() delete ui; void Widget::paintEvent(QPaintEvent *p1) //绘制样式 QStyleOption opt; opt.initFrom(this); QPainter p(this); style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);//绘制样式 if(isPressedWidget) //定义画笔 QPen pen; pen.setWidth(5); pen.setColor(QColor("#00B0AE")); pen.setStyle(Qt::DashDotLine); p.setPen(pen); //创建画刷 QBrush brush; brush.setColor(QColor("#00B0AE")); brush.setStyle(Qt::Dense6Pattern); p.setBrush(brush); QRect tempRt(m_startPT, m_endPT); p.drawRect(tempRt); void Widget::mousePressEvent(QMouseEvent *event) m_endPT = m_startPT = event->pos(); isPressedWidget = true; // 当前鼠标按下的即是QWidget而非界面上布局的其它控件 void Widget::mouseMoveEvent(QMouseEvent *event) QPoint tmp_pos=event->pos(); if(tmp_pos.x()>m_startPT.x() || tmp_pos.y()>m_startPT.y()) m_endPT = event->pos(); this->update(); void Widget::mouseReleaseEvent(QMouseEvent *event) isPressedWidget = false; // 鼠标松开时,置为false QRect rect(m_startPT, m_endPT); qDebug()<<"选择的范围:"<<rect; 工程: HTTP_Request 日期: 2021-08-12 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 进入全屏 void Widget::on_pushButton_clicked() showFullScreen(); 工程: HTTP_Request 日期: 2021-08-12 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: 退出全屏 void Widget::on_pushButton_2_clicked() showNormal(); 工程: HTTP_Request 日期: 2021-08-12 作者: DS小龙哥 环境: win10 QT5.12.6 MinGW32 功能: close void Widget::on_pushButton_close_clicked() close(); 3.2 widget.h代码#ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QStyleOption> #include <QPainter> #include <QMouseEvent> #include <QDebug> QT_BEGIN_NAMESPACE namespace Ui { class Widget; } QT_END_NAMESPACE class Widget : public QWidget Q_OBJECT public: Widget(QWidget *parent = nullptr); ~Widget(); protected: //截取鼠标事件绘制窗口位置. 因为标题栏隐藏后.窗口是无法拖动的。 void mouseReleaseEvent(QMouseEvent *event); void mouseMoveEvent(QMouseEvent *event); void mousePressEvent(QMouseEvent *event); void paintEvent(QPaintEvent *p); private slots: void on_pushButton_clicked(); void on_pushButton_2_clicked(); void on_pushButton_close_clicked(); private: Ui::Widget *ui; bool isPressedWidget; QPoint m_startPT; QPoint m_endPT; #endif // WIDGET_H3.3 UI界面设计

基于STM32设计的掌上游戏机(运行NES游戏模拟器)详细开发过程

一、环境与硬件介绍开发环境:keil5代码风格: 寄存器风格,没有采用库函数,底层代码全部寄存器方式编写,运行效率高,注释清楚。MCU型号: STM32F103ZET6开发板: 正常的一块STM32开发板,带LCD插槽,带4颗独立按键。游戏模拟器:  NES游戏模拟器LCD :  ALIENTEK的3.5寸屏幕。(屏幕型号不重要,随便一款都可以的,把屏幕底层驱动代码写好,适配即可)声音输出设备 : 采用VS1053 (SPI接口,操作方便)游戏手柄: 支持FC游戏手柄完成这个掌上游戏机需要使用的硬件设备不复杂,如果想要体验游戏,需要的必备硬件:1. (必要)STM32F103系列最小系统版一个2. (必要)LCD屏一块。 2.8寸就可以了,价格便宜。3. (非必要)FC游戏手柄一个,驱动时序很简单(后面有单独章节介绍),支持组合键,玩游戏体验感非常好。    如果不用FC游戏手柄,使用开发板几个独立按键也行,只是手感不好。4.  (非必要)VS1053或者其他系列声卡模块一个,游戏是有声音的,要完美的体验游戏声卡肯定是要的,不要也可以玩,只是没有声音而已。VS1053模块支持SPI接口控制,时序简单,驱动代码也不复杂,资料比较多,学起来,理解起来很容易。5. (非必要)SD卡一张。主要存储NES游戏文件,可以动态加载想要玩的游戏,切换比较方便。如果没有SD卡,也想体验也可以,直接把游戏取模成二进制放在数组里存放到STM32的FLASH里即可,STM32F103ZET6有512K的FLASH,存放一个游戏完全够用,加载速度更加快。6. (非必要) SRAM外部扩展内存,如果不需要从SD里加载游戏,就不需要外部内存;如果使用SD卡加载游戏,就需要把游戏数据从SD卡里读取出来,然后放在SRAM外部扩展内存芯片里。因为STM32F103ZET6本身只有64K内存,放不下。游戏体验:STM32可以超频到128M,运行起来还是非常流畅,玩起来的感觉和正常的FC游戏机是一样的,没有卡顿,延迟。游戏模拟器移植的是NES模拟器,开发过程中,代码编写了3个版本:版本1: 精简版的掌上游戏机,最适合学习,代码牵扯很少,只有外设硬件只用到了LCD而已,最适合学习,理解代码运行原理;不支持声音输出,不支持FC游戏手柄,不支持SD卡和文件系统(也就是不支持从SD卡上选择游戏加载)。 这个版本的游戏是直接使用数组存放在代码里的,游戏的操作是通过开发板上的4个按键控制(开发板的4个按键,分别控制角色的前进、后退、暂停、跳跃),因为只有4个按键,没有支持组合按键,所以体验起来不是很舒服,控制比较困难,完美体验还是要继续加上FC游戏手柄。版本2: 这也是精简版的掌上游戏机,在版本1的基础之上加了VS1053模块,支持声音输出,体验感要好一点,能听到游戏声音。版本3: 这是完整版本的掌上游戏机,加入了FC游戏手柄支持,加入了VS1053声卡驱动,加入了SD卡和FATFS文件系统,可以正常从SD卡加载指定的游戏运行,体验非常好。3个版本的源代码和NES的游戏集合,在下面的第3章有下载地址。二、游戏运行效果(超级玛丽示例)2.1  超级玛丽运行截图2.2  仅仅使用独立按键操作游戏效果单手录制,单手操作,操作起来起来不太方便。STM32上移植NES游戏框架-运行超级玛丽游戏2.3 游戏自动待机运行效果(没有操作)基于STM32移植NES游戏框架-超级玛丽游戏(动画)三、资料下载地址3.1 NES游戏集合下载 一共有293款游戏,总有一款适合你。常见的超级玛丽、魂斗罗、都有包含的。地址:https://download.csdn.net/download/xiaolong1126626497/207224513.2  工程源码下载地址: https://download.csdn.net/download/xiaolong1126626497/20973545 一共3个版本,它们之间的区别在第一章已经介绍过。三个都是keil工程,下载下来直接编译、下载运行体验。四、什么是NES ? NES就是红白机的游戏,所谓的NES意思是欧美版的红白机,FC的美版,Nintendo entertainment system(任天堂娱乐系统),而日本的红白机则叫family computer(FC)。发展历史-来至百度百科1983年7月15日,由日本任天堂株式会社(原本是生产日式扑克即“花札”)的宫本茂先生领导开发的一种第三代家用电子游戏机:FC,全称:Family Computer,也称作:Famicom;在日本以外的地区发售时则被称为NES,全称:Nintendo Entertainment System;在中国大陆、台湾和香港等地,因其外壳为红白两色,所以人们俗称其为“红白机”,正式进入市场销售,并于后来取得了巨大成功,由此揭开了家用电子游戏机遍布世界任何角落,电子游戏全球大普及的序幕。1985年,NES在北美地区的销量3300万台,比日本地区高出近一倍, 也占据了其全球市场份额的一半。  NES在北美首发时的捆绑游戏《打鸭子》(Duck hunt)总共取得近3000万套(基本全部来自北美市场)销量, [6]  这在红白机游戏中名列第二,仅次于《超级马力欧》。 1986年,任天堂在美国收3.1亿美元,这一年美国游戏产业的规模4.3亿美元,而在一年前,深陷雅达利冲击的美国游戏业的收入仅1亿美元。 [7]  1988年发售的《超级马力欧兄弟3》(Super Mario Bros. 3)在美国售出700万套,在日本销量达400万,销售额5.5亿美元。1989年,任天堂的游戏机已占领美国90%和日本95%的市场,任天堂成为游戏界巨无霸。 2003年7月,FC发售二十周年,任天堂宣布FC游戏机正式停产。至此,FC全世界已累计销售6000万部以上。至今中国大陆、台湾、香港与泰国甚至日本等地仍然在制造FC规格的兼容品。任天堂成为了现代游戏产业的开创者,在很多方面上确立了现代电子游戏的标准。FC巨大成功使任天堂年纯利从1985年开始一直保持5亿美元以上 ,其股票成为东京证券交易所绩优股代名词,一度超越了3万日元,市值超松下等企业,很多人都把任天堂成功誉为新时代商业神话。 任天堂红白机(FC/NES)发行于1983年,在日本发行之后引起了不小的轰动,两年之后进军北美市场,更加奠定了任天堂的家用游戏机霸主地位。当人们正需要一个高品质的家用游戏机的时候,任天堂拿出了他们的全部家当,首发的数款游戏都赢得了玩家的赞誉,超级马力欧更成为了永远的经典。在那个年代,拥有一台红白机应该是孩子们最大的梦想了。 根据外媒的数据,在1990年30%的美国家庭都拥有NES主机。五、工程源码分析: 以精简版本(1)为例工程源码全部采用寄存器代码风格,基本上每行都有详细的注释;虽然STM32支持库函数方式开发,效率更加快,但是寄存器方式可以更方便了解CPU底层寄存器的一些配置,对以后在学习使用其他类型的微处理器是非常有帮助的。5.1 工程文件布局 5.2 主函数代码主函数里完成LCD屏幕初始化,按键初始化,LED灯初始化,串口初始化,FC游戏手柄初始化,默认把LCD屏幕清屏为黑色。LCD屏采用FSMC驱动的,把FSMC时序速度配置到最快,达到STM32能支持的最快速度,提高LCD刷屏速度。初始化完毕最后,调用了LoadNes函数,完成游戏加载;如果加载失败,就回到下面执行while循环,闪烁LED灯。代码如下:#include "stm32f10x.h" #include "led.h" #include "lcd.h" #include "delay.h" #include "key.h" #include "usart.h" #include <string.h> #include <stdio.h> #include "joypad.h" extern u8 LoadNes(u8* pname,u32); //游戏文件可以通过winhex文件生成C源码数组 extern const unsigned char nes_data1[40976];//超级玛丽游戏的文件 extern const unsigned char nes_data2[262160];//魂斗罗游戏的文件 移植说明: 1. 加入游戏手柄 2. 优化了游戏刷新的帧率 3. 加入开发板本身自带按键控制 int main() BeepInit(); //蜂鸣器初始化 LedInit(); //LED灯初始化 UsartInit(USART1,72,115200); KeyInit(); //按键初始化 printf("串口工作正常!\r\n"); LcdInit(); //LCD初始化 //JoypadInit(); //游戏手柄初始化 LcdClear(0xFFFF); 0000 0000:保留 0000 0001: DATAST保持时间=2个HCLK时钟周期 0000 0010: DATAST保持时间=3个HCLK时钟周期 1111 1111: DATAST保持时间=256个HCLK时钟周期(这是复位后的默认数值) 0、1、2、3、4、5、6、7、8、9、10、11、12、13、14 LcdClear(0); //开始运行游戏 LoadNes((unsigned char*)nes_data1,40976); //超级玛丽 //LoadNes((unsigned char*)nes_data2,262160); //魂斗罗 while(1) LED1=!LED1; DelayMs(400); 5.3 加载NES游戏:LoadNes函数介绍LoadNes函数原型:u8 LoadNes(unsigned char* pname,u32 size)该函数传入NES游戏数据地址,和游戏数据大小进来。现在这个版本没有使用SD卡和文件系统,游戏的文件数据是直接加到代码里编译的。这两个数组是超级玛丽和魂斗罗的数据。(直接使用打开文件,使用WinHEX软件打开,全选,右键编辑,选择复制,选择C源码,复制成数组形式粘贴到keil里即可) 函数里面主要完成了NES模拟器基本的初始化。主要完成了STM32超频配置,配置锁相环为16倍,超频到128MHZ。超频配置代码如下:1./* 函数功能:频率设置 参 数:PLL,倍频数 void NesClockSet(u8 PLL) u8 temp=0; RCC->CFGR&=0XFFFFFFFC; //修改时钟频率为内部8M RCC->CR&=~0x01000000; //PLLOFF RCC->CFGR&=~(0XF<<18); //清空原来的设置 PLL-=2; //抵消2个单位 RCC->CFGR|=PLL<<18; //设置PLL值 2~16 RCC->CFGR|=1<<16; //PLLSRC ON FLASH->ACR|=0x12; //FLASH 2个延时周期 RCC->CR|=0x01000000; //PLLON while(!(RCC->CR>>25)); //等待PLL锁定 RCC->CFGR|=0x02; //PLL作为系统时钟 while(temp!=0x02) //等待PLL作为系统时钟设置成功 temp=RCC->CFGR>>2; temp&=0x03; } 接下来初始化NES游戏模拟器的必要参数,最后调用NesEmulateFrame函数进入NES游戏主循环代码,开始运行游戏。LoadNes函数完整代码如下:1./* 函数功能:开始nes游戏 参 数:pname:nes游戏路径 u32 size 游戏大小 返 回 值: 0,正常退出 1,内存错误 2,文件错误 3,不支持的map u8 LoadNes(unsigned char* pname,u32 size) u8 res=0; res=NesSramMalloc(); //申请内存 romfile=(u8*)pname; //游戏源码地址 NESrom_crc32=get_crc32(romfile+16,size-16);//获取CRC32的值 res=LoadNesRom(); //加载ROM printf("res=%d\r\n",res); NesClockSet(16); //设置系统时钟为128MHZ 16*8 JoypadInit(); //游戏手柄初始化 cpu6502_init(); //初始化6502,并复位 Mapper_Init(); //map初始化 PPU_reset(); //ppu复位 apu_init(); //apu初始化 NesEmulateFrame(); //进入NES模拟器主循环 return res; } 5.3 NES游戏主循环代码​​、详细代码如下://nes模拟器主循环 void NesEmulateFrame(void) u8 nes_frame; NesSetWindow();//设置窗口 while(1) // LINES 0-239 PPU_start_frame(); for(NES_scanline = 0; NES_scanline< 240; NES_scanline++) run6502(113*256); NES_Mapper->HSync(NES_scanline); //扫描一行 if(nes_frame==0)scanline_draw(NES_scanline); else do_scanline_and_dont_draw(NES_scanline); NES_scanline=240; run6502(113*256);//运行1线 NES_Mapper->HSync(NES_scanline); start_vblank(); if(NMI_enabled()) cpunmi=1; run6502(7*256);//运行中断 NES_Mapper->VSync(); // LINES 242-261 for(NES_scanline=241;NES_scanline<262;NES_scanline++) run6502(113*256); NES_Mapper->HSync(NES_scanline); end_vblank(); NesGetGamepadval(); //每3帧读取游戏手柄数据 nes_frame++; if(nes_frame>NES_SKIP_FRAME) nes_frame=0;//跳帧 } 进来就先调用了NesSetWindow(void)函数,设置窗口大小,这里面就调用了LCD的接口,如果是其他的LCD屏,使用本代码只需要把这里适配一下即可。u8 nes_xoff=0; //显示在x轴方向的偏移量(实际显示宽度=256-2*nes_xoff) //设置游戏显示窗口 void NesSetWindow(void) u16 lcdwidth,lcdheight; lcdwidth=256; lcdheight=240; nes_xoff=0; LcdSetWindow(32,0,lcdwidth,lcdheight); LcdWriteRAM_Prepare();//写入LCD RAM的准备 }接下来就进入到NES游戏的主循环代码,开始循环一帧一帧的刷出图像数据,达到游戏的效果。设置窗口大小之后,下面就是从NES游戏数据文件里取出颜色数据,然后for循环一行一行刷屏即可。上面的设置窗口大小的代码其实并不是必要的,只是当前使用的LCD支持坐标自增(一般LCD都支持的),设置LCD的窗口范围之后,连续给LCD写数据,LCD的坐标会自动自增,提高刷屏效率而已。如果你的LCD屏并不支持坐标自增或者你不会写代码,也想移植,那完全不用设置窗口那个函数,你只需要提供一个画点函数,把for循环里的刷屏代码里行扫描改掉就行。函数里的这个for循环就是主要刷出图像的代码,如果想要移植到其他LCD屏,主要就改这里,示例代码如下:for(NES_scanline = 0; NES_scanline< 240; NES_scanline++) run6502(113*256); NES_Mapper->HSync(NES_scanline); //扫描一行 if(nes_frame==0)scanline_draw(NES_scanline); else do_scanline_and_dont_draw(NES_scanline); } 里面调用scanline_draw函数是按行扫描(也就是一行一行绘制图像),scanline_draw函数里面也是一个for循环,细化到每个像素点,按照每个像素点绘制到屏幕上,代码里的LCD_RAM就是当前LCD屏的地址,因为当前LCD屏采用的是FSMC,这个LCD_RAM就是FSMC地址,向这个地址写数据,FSMC就产生8080时序将数据送给LCD显示屏,刷新显示出来。scanline_draw函数详细刷屏代码如下:extern u8 nes_xoff; //显示在x轴方向的偏移量(实际显示宽度=256-2*nes_xoff) void scanline_draw(int LineNo) uint16 i; u16 sx,ex; do_scanline_and_draw(ppu->dummy_buffer); sx=nes_xoff+8; ex=256+8-nes_xoff; if(lcddev.width==480) for(i=sx;i<ex;i++) LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 for(i=sx;i<ex;i++) LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 i++; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 LCD_RAM=NES_Palette[ppu->dummy_buffer[i]];//得到颜色值 }else for(i=sx;i<ex;i++) LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i++]]; LCD_RAM=NES_Palette[ppu->dummy_buffer[i]]; }运行完刷屏的for循环函数,一帧游戏图像就显示在LCD上了。接下来就是扫描按键值,完成游戏人物的控制,函数里调用了NesGetGamepadval()函数,读取按键值刷新按键状态。NesGetGamepadval()函数代码如下:/* 键值说明: 开始键:8 选择建:4 方向右:128 方向左:64 方向上:16 方向下:32 功能键上/左:2 功能键下/右:1 组合键:方向右与 读取游戏手柄数据和功能键左 :130 void NesGetGamepadval(void) u8 key; // PADdata0=GetJoypadKey(); //读取手柄1的值 //printf("%d\r\n",PADdata0); key=GetKeyValue(0); if(key==1)PADdata0=8; else if(key==2)PADdata0=128; else if(key==3)PADdata0=64; else if(key==4)PADdata0=1; else PADdata0=0; }NES游戏模拟器定义了两个全局变量,分别记录游戏手柄1和游戏手柄2的数据,因为NES游戏是可以两个人一起玩的。u8 PADdata0; //手柄1键值 [7:0]右7 左6 下5 上4 Start3 Select2 B1 A0 u8 PADdata1; //手柄2键值 [7:0]右7 左6 下5 上4 Start3 Select2 B1 A0 只需要在这个函数给这两个全局变量赋予正确的值,游戏人物就可以按照正常的动作画面出现。至于你的物理按键采用FC游戏手柄,还是普通的其他按键,只要这两个全局变量的值正确那就没问题。  所有手柄采用什么不重要,关键把代码这里逻辑看懂,看懂了你就知道程序的运行逻辑了。到此,版本1的 主要代码就分析完毕了,其他的详细过程可以看工程源码,把程序跑起来了,一切都懂了。六、工程源码分析: 以完整版本(3)为例这个版本加入了游戏手柄,VS1053、SD、FATFS文件系统等功能,这里接着第五章分析,下面就主要分析新加入的代码内容。6.1 FC游戏手柄介绍 FC游戏手柄,大致可分为两种:一种手柄插口是 11 针的,一种是 9 针的。但 11 针的现在市面上很少了,现在几乎都是使用 9 针 FC 组装手柄,下面就是介绍的是 9 针 FC 手柄,该手柄还有一个特点,就是可以直接和DR9 的串口头对插!这样同开发板的连接就简单了。FC 手柄的外观如图所示:这种手柄一般有 10 个按键(实际是 8 个键值):上、下、左、右、 Start、 Select、 A、 B、 A连发、 B 连发。这里的 A 和 A 连发是一个键值,而 B 和 B 连发也是一个键值,只是连发按键当你一直按下的时候,会不停的发送(方便快速按键,比如发炮弹之类的功能)。FC 手柄的控制电路,由 1 个 8 位并入串出的移位寄存器(CD4021),外加一个时基集成电路(NE555,用于连发)构成。不过现在的手柄,为了节约成本,直接就在 PCB 上做绑定了,所以你拆开手柄,一般是看不到里面有四四方方的 IC,而只有一个黑色的小点,所有电路都集成到这个里面了,但是他们的控制和读取方法还是一样的。游戏上手柄数据读取时序从上图可看出,读取手柄按键值的信息十分简单:先 Latch(锁存键值),然后就得到了第一个按键值(A),之后在 Clock 的作用下,依次读取其他按键的键值,总共 8 个按键键值。常规状态下,LATCH为低电平,CLK为高电平,DATA为高电平,这也是初始化端口时的状态。 单片机读取键值时序很简单,LATCH先发送一个高脉冲,数据将锁存到手柄内部的移位寄存器,然后在CLK时钟下降沿数据将从DATA低位在先连续发出。按键映射到数据的对应位上,有键按下则对应位为0,无键按下则为1.即不按任何键时,读取数据为0xFF。键值:[7]:右[6]:左[5]:下[4]:上[3]:Start[2]:Select[1]:B[0]:A驱动代码示例:功 能:手柄初始化函数 硬件连接: CLK :PD3 --时钟线 PB10:DATA --数据线 PB11:LAT --锁存接口 void JoypadInit(void) /*1. 开时钟*/ RCC->APB2ENR|=1<<5; //PD RCC->APB2ENR|=1<<3; //PB /*2. 配置模式*/ GPIOD->CRL&=0xFFFF0FFF; GPIOD->CRL|=0x00003000; GPIOB->CRH&=0xFFFF00FF; GPIOB->CRH|=0x00003800; /*3. 上拉*/ GPIOD->ODR|=1<<3; 功 能:获取手柄的按键值 返回值:保存了一帧按键的状态 键值: [7]:右 [6]:左 [5]:下 [4]:上 [3]:Start [2]:Select [1]:B [0]:A u8 GetJoypadKey(void) u8 key=0,i; JOYPAD_LAT=1; //开始锁存 DelayUs(30); JOYPAD_LAT=0; //锁存当前的按键状态 for(i=0;i<8;i++) key=key>>1; if(JOYPAD_DATA==0)key|=0x80; JOYPAD_CLK=1; //输出一个上升沿,告诉手柄发送数据 DelayUs(30); JOYPAD_CLK=0; //数据线保持稳定 DelayUs(30); return key; } 6.2 加载NES游戏:nes_load函数 这里的nes_load函数和第五章的区别就是,游戏数据的来源是从SD卡读取的。 传入游戏名称去SD卡上打开指定文件,读取数据进来。这里用到了外部SRAM内存,因为读出的数据需要存放到数组里,STM32F103ZET6本身的内存只有64K,肯定不够用,这里申请的空间是从外部SRAM模块里申请的,所以开发板还得带一个SRAM芯片才行,没有自带就去淘宝买一个SRAM模块即可(淘宝有个叫微雪的店铺就有卖)。详细代码如下:u8 nes_load(u8* pname) FIL *file; UINT br; u8 res=0; file=malloc(sizeof(FIL)); if(file==0)return 1; //内存申请失败. res=f_open(file,(char*)pname,FA_READ); if(res!=FR_OK) //打开文件失败 printf("%s 文件打开失败!\r\n",pname); free(file); return 2; printf("%s 文件打开成功!\r\n",pname); res=nes_sram_malloc(file->fsize); //申请内存 if(res==0) f_read(file,romfile,file->fsize,&br); //读取nes文件 NESrom_crc32=get_crc32(romfile+16, file->fsize-16);//获取CRC32的值 res=nes_load_rom(); //加载ROM if(res==0) NesClockSet(16); //UsartInit(USART1,128,115200); JoypadInit(); cpu6502_init(); //初始化6502,并复位 Mapper_Init(); //map初始化 PPU_reset(); //ppu复位 apu_init(); //apu初始化 nes_sound_open(0,APU_SAMPLE_RATE); //初始化播放设备 nes_emulate_frame(); //进入NES模拟器主循环 nes_sound_close(); //关闭声音输出 f_close(file); free(file);//释放内存 nes_sram_free(); //释放内存 return res; } 这里面调用了nes_sound_open函数初始化了音频设备(VS1053)。这个非常重要,要理解游戏声音是如何输出的,就认真看这里的流程。nes_sound_open函数里初始化了VS1053音频设备,然后开启了定时器中断,使用定时器去调用VS1053的播放接口,在定时器中断服务器函数里完成声音数据的输出,这里声音是存放在一个全局缓冲区里,后面游戏在主循环里运行的时候会不断的向这个缓冲区填数据,定时器超时进中断就查询是否有音乐可以播放,有就播放,没有就出来。  VS1052声音播放代码示例://音频播放回调函数 void nes_vs10xx_feeddata(void) u8 n; u8 nbytes; u8 *p; if(nesplaybuf==nessavebuf)return;//还没有收到新的音频数据 if(VS1053_DREQ!=0)//可以发送数据给VS10XX p=nesapusbuf[nesplaybuf]+nesbufpos; nesbufpos+=32; if(nesbufpos>APU_PCMBUF_SIZE) nesplaybuf++; if(nesplaybuf>(NES_APU_BUF_NUM-1))nesplaybuf=0; nbytes=APU_PCMBUF_SIZE+32-nesbufpos; nesbufpos=0; }else nbytes=32; for(n=0;n<nbytes;n++) if(p[n]!=0)break; //判断是不是剩余所有的数据都为0? if(n==nbytes)return; //都是0,则直接不写入VS1053了,以免引起哒哒声. VS1053_XDCS=0; for(n=0;n<nbytes;n++) VS1053_SPI_ReadWriteByte(p[n]); VS1053_XDCS=1; }nes_sound_open函数代码如下://NES打开音频输出 int nes_sound_open(int samples_per_sync,int sample_rate) u8 *p; u8 i; p=malloc(100); //申请100字节内存 if(p==NULL)return 1; //内存申请失败,直接退出 printf("sound open:%d\r\n",sample_rate); for(i=0;i<sizeof(nes_wav_head);i++)//复制nes_wav_head内容 p[i]=nes_wav_head[i]; if(lcddev.width==480) //是480*480屏幕 sample_rate=8000; //设置8Khz,约原来速度的0.75倍 p[24]=sample_rate&0XFF; //设置采样率 p[25]=(sample_rate>>8)&0XFF; p[28]=sample_rate&0XFF; //设置字节速率(8位模式,等于采样率) p[29]=(sample_rate>>8)&0XFF; nesplaybuf=0; nessavebuf=0; VS1053_Reset(); //硬复位 VS1053_SoftReset(); //软复位 VS1053_SetVol(200); //设置音量等参数 //复位解码时间 VS1053_WriteCmd(SPI_DECODE_TIME,0x0000); VS1053_WriteCmd(SPI_DECODE_TIME,0x0000); //操作两次 while(VS1053_SendMusicData(p)); //发送wav head while(VS1053_SendMusicData(p+32)); //发送wav head TimerInit(TIM6,72,1000); //1ms中断一次 free(p); //释放内存 return 1; }初始化完毕之后,就调用nes_emulate_frame函数进入到游戏主循环。6.3 游戏主循环代码现在这份代码比第五章代码增加了一个声音输出函数,调用VS1053,播放游戏的声音。 apu_soundoutput函数代码如下://apu声音输出 void apu_soundoutput(void) u16 i; apu_process(wave_buffers,APU_PCMBUF_SIZE); for(i=0;i<30;i++)if(wave_buffers[i]!=wave_buffers[i+1])break;//判断前30个数据,是不是都相等? if(i==30&&wave_buffers[i])//都相等,且不等于0 for(i=0;i<APU_PCMBUF_SIZE;i++)wave_buffers[i]=0;//是暂停状态输出的重复无效数据,直接修改为0.从而不输出杂音. clocks=0; nes_apu_fill_buffer(0,wave_buffers); }最后调用了nes_apu_fill_buffer 函数将数据赋值给VS1053缓冲区进行播放。在前面已经分析了音频初始化代码,里面初始化了定时器,会不断的查询缓冲区是否有音乐数据需要播放,有就播放,没有就输出,这个函数就是向音频缓冲区填充数据的。nes_apu_fill_buffer 函数代码如下://NES音频输出到VS1053缓存 void nes_apu_fill_buffer(int samples,u8* wavebuf) u16 i; u8 tbuf; for(i=0;i<APU_PCMBUF_SIZE;i++) nesapusbuf[nessavebuf][i]=wavebuf[i]; tbuf=nessavebuf; tbuf++; if(tbuf>(NES_APU_BUF_NUM-1))tbuf=0; while(tbuf==nesplaybuf)//输出数据赶上音频播放的位置了,等待. DelayMs(5); nessavebuf=tbuf; } 到此,音频的主要代码就分析完毕了。 可以下载程序去体验一下游戏,怀恋童年时光了

QT软件开发: 窗口打开固定在屏幕中间或者右下角

窗口打开默认在屏幕中间显示:CameraWidget::CameraWidget(QWidget *parent, int index) : QWidget(parent), ui(new Ui::CameraWidget) ui->setupUi(this); //隐藏标题栏 setWindowFlags(Qt::FramelessWindowHint);//无边框 //窗口显示在屏幕正中间 QDesktopWidget *desktop = QApplication::desktop(); move((desktop->width()-this->width())/2,(desktop->height()-this->height())/2); }窗口打开默认在屏幕右下角显示:CameraWidget::CameraWidget(QWidget *parent, int index) : QWidget(parent), ui(new Ui::CameraWidget) ui->setupUi(this); //隐藏标题栏 setWindowFlags(Qt::FramelessWindowHint);//无边框 //打开的窗口在屏幕右下角 QDesktopWidget *widget= QApplication::desktop(); move(widget->width()-this->width(),widget->height()-this->height());

QT软件开发: 窗口顶置(一直显示在最前面)

设置QWidget窗口属性,保持窗口顶置在最前面显示。CameraWidget::CameraWidget(QWidget *parent, int index) : QWidget(parent), ui(new Ui::CameraWidget) ui->setupUi(this); //隐藏标题栏 setWindowFlags(Qt::FramelessWindowHint);//无边框 //设置窗口顶置: 一直在最前面. Qt::WindowFlags m_flags = windowFlags(); setWindowFlags(m_flags | Qt::WindowStaysOnTopHint); setAttribute(Qt::WA_TranslucentBackground); ............................

音视频开发: ffmpeg采集桌面屏幕、摄像头保存为视频

一、环境介绍ffmpge版本: 4.2.2系统环境: win10 64位下载地址: https://download.csdn.net/download/xiaolong1126626497/13328939说明: 采用GDI方式录制屏幕,鼠标光标会闪烁。可以采用DXGI 、WGC采集。二、命令示例2.1 列出当前电脑上音频设备、摄像头设备列表C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -list_devices true -f dshow -i dummy输出结果:PS D:\linux-share-dir\video_file> C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -list_devices true -f dshow -i dummy built with gcc 9.2.1 (GCC) 20200122 configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt libavutil 56. 31.100 / 56. 31.100 libavcodec 58. 54.100 / 58. 54.100 libavformat 58. 29.100 / 58. 29.100 libavdevice 58. 8.100 / 58. 8.100 libavfilter 7. 57.100 / 7. 57.100 libswscale 5. 5.100 / 5. 5.100 libswresample 3. 5.100 / 3. 5.100 libpostproc 55. 5.100 / 55. 5.100 [dshow @ 02b71840] DirectShow video devices (some may be both video and audio devices) [dshow @ 02b71840] "Integrated Camera" [dshow @ 02b71840] Alternative name "@device_pnp_\\?\usb#vid_5986&pid_2113&mi_00#6&3326332&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" [dshow @ 02b71840] "screen-capture-recorder" [dshow @ 02b71840] Alternative name "@device_sw_{860BB310-5D01-11D0-BD3B-00A0C911CE86}\{4EA6930A-2C8A-4AE6-A561-56E4B5044439}" [dshow @ 02b71840] DirectShow audio devices [dshow @ 02b71840] "楹﹀厠椋庨樀鍒?(Conexant SmartAudio HD)" [dshow @ 02b71840] Alternative name "@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}" [dshow @ 02b71840] "virtual-audio-capturer" [dshow @ 02b71840] Alternative name "@device_sw_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\{8E14549B-DB61-4309-AFA1-3578E927E935}"2.2 录制桌面全屏图像+音频C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -f gdigrab -i desktop -f dshow -i audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}" -vcodec libx264 -acodec libmp3lame -s 1280x720 -r 15 D:/linux-share-dir/video_file/6666.mp4 audio= "" 这里填麦克风设备. 第一条指令就是查询电脑上音频设备。把名字复制过来即可。输出结果:PS D:\linux-share-dir\video_file> C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -f gdigrab -i desktop -f dshow -i audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}" -vcodec libx264 -acodec libmp3lame -s 1280x720 -r 15 D:/linux-share-dir/video_file/6666.mp4 built with gcc 9.2.1 (GCC) 20200122 configuration: --disable-static --enable-shared --enable-gpl --enable-version3 --enable-sdl2 --enable-fontconfig --enable-gnutls --enable-iconv --enable-libass --enable-libdav1d --enable-libbluray --enable-libfreetype --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-libopenjpeg --enable-libopus --enable-libshine --enable-libsnappy --enable-libsoxr --enable-libtheora --enable-libtwolame --enable-libvpx --enable-libwavpack --enable-libwebp --enable-libx264 --enable-libx265 --enable-libxml2 --enable-libzimg --enable-lzma --enable-zlib --enable-gmp --enable-libvidstab --enable-libvorbis --enable-libvo-amrwbenc --enable-libmysofa --enable-libspeex --enable-libxvid --enable-libaom --enable-libmfx --enable-amf --enable-ffnvcodec --enable-cuvid --enable-d3d11va --enable-nvenc --enable-nvdec --enable-dxva2 --enable-avisynth --enable-libopenmpt libavutil 56. 31.100 / 56. 31.100 libavcodec 58. 54.100 / 58. 54.100 libavformat 58. 29.100 / 58. 29.100 libavdevice 58. 8.100 / 58. 8.100 libavfilter 7. 57.100 / 7. 57.100 libswscale 5. 5.100 / 5. 5.100 libswresample 3. 5.100 / 3. 5.100 libpostproc 55. 5.100 / 55. 5.100 [gdigrab @ 02e72080] Capturing whole desktop as 1920x1080x32 at (0,0) [gdigrab @ 02e72080] Stream #0: not enough frames to estimate rate; consider increasing probesize Input #0, gdigrab, from 'desktop': Duration: N/A, start: 1628238450.828869, bitrate: 1988680 kb/s Stream #0:0: Video: bmp, bgra, 1920x1080, 1988680 kb/s, 29.97 fps, 1000k tbr, 1000k tbn, 1000k tbc Guessed Channel Layout for Input Stream #1.0 : stereo Input #1, dshow, from 'audio=@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}': Duration: N/A, start: 149701.343000, bitrate: 1411 kb/s Stream #1:0: Audio: pcm_s16le, 44100 Hz, stereo, s16, 1411 kb/s File 'D:/linux-share-dir/video_file/6666.mp4' already exists. Overwrite ? [y/N] y Stream mapping: Stream #0:0 -> #0:0 (bmp (native) -> h264 (libx264)) Stream #1:0 -> #0:1 (pcm_s16le (native) -> mp3 (libmp3lame)) Press [q] to stop, [?] for help [libx264 @ 02ee7c80] using cpu capabilities: MMX2 SSE2Fast SSSE3 SSE4.2 AVX FMA3 BMI2 AVX2 [libx264 @ 02ee7c80] profile High 4:4:4 Predictive, level 3.1, 4:4:4, 8-bit [libx264 @ 02ee7c80] 264 - core 159 - H.264/MPEG-4 AVC codec - Copyleft 2003-2019 - http://www.videolan.org/x264.html - options: cabac=1 ref=3 deblock=1:0:0 analyse=0x3:0x113 me=hex subme=7 psy=1 psy_rd=1.00:0.00 mixed_ref=1 me_range=16 chroma_me=1 trellis=1 8x8dct=1 cqm=0 deadzone=21,11 fast_pskip=1 chroma_qp_offset=4 threads=12 lookahead_threads=2 sliced_threads=0 nr=0 decimate=1 interlaced=0 bluray_compat=0 constrained_intra=0 bframes=3 b_pyramid=2 b_adapt=1 b_bias=0 direct=1 weightb=1 open_gop=0 weightp=2 keyint=250 keyint_min=15 scenecut=40 intra_refresh=0 rc_lookahead=40 rc=crf mbtree=1 crf=23.0 qcomp=0.60 qpmin=0 qpmax=69 qpstep=4 ip_ratio=1.40 aq=1:1.00 Output #0, mp4, to 'D:/linux-share-dir/video_file/6666.mp4': Metadata: encoder : Lavf58.29.100 Stream #0:0: Video: h264 (libx264) (avc1 / 0x31637661), yuv444p(progressive), 1280x720, q=-1--1, 15 fps, 15360 tbn, 15 tbc Metadata: encoder : Lavc58.54.100 libx264 Side data: cpb: bitrate max/min/avg: 0/0/0 buffer size: 0 vbv_delay: -1 Stream #0:1: Audio: mp3 (libmp3lame) (mp4a / 0x6134706D), 44100 Hz, stereo, s16p Metadata: encoder : Lavc58.54.100 libmp3lame frame= 98 fps= 24 q=27.0 size= 768kB time=00:00:02.90 bitrate=2169.2kbits/s dup=34 drop=57 speed=0.716x2.3 录制摄像头+音频示例1: C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -f dshow -i video="@device_pnp_\\?\usb#vid_5986&pid_2113&mi_00#6&3326332&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" -f dshow -i audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}" -vcodec libx264 -acodec libmp3lame -s 1280x720 -r 15 D:/linux-share-dir/video_file/6666.mp4 video="" 视频摄像头设备名称 audio="" 视频音频设备名称 C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -f dshow -i video="@device_pnp_\\?\usb#vid_5986&pid_2113&mi_00#6&3326332&0&0000#{65e8773d-8f56-11d0-a3b9-00a0c9223196}\global" -f dshow -i audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}" D:/linux-share-dir/video_file/test/202108161456.mp42.4 采集桌面指定区域(无音频)C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -f gdigrab -framerate 6 -offset_x 50 -offset_y 50 -video_size 400x400 -i desktop D:/linux-share-dir/video_file/test/202108161448.mp4 2.5 采集桌面指定区域+音频//录制指定范围--采集图像+音频 C:/FFMPEG/ffmpeg_x86_4.2.2/bin/ffmpeg.exe -f gdigrab -framerate 6 -offset_x 50 -offset_y 50 -video_size 400x400 -i desktop -f dshow -i audio="@device_cm_{33D9A762-90C8-11D0-BD43-00A0C911CE86}\wave_{6E399CBA-5F7D-443F-9071-1657DE0F5483}" D:/linux-share-dir/video_file/test/202108161514.mp4

​ 1. 读写锁有三种状态,读模式下加锁(共享)、写模式下加锁(独占)以及不加锁。 ​ 2. 一次只有一个线程可以占有写模式下的读写锁;但是多个线程可以同时占有读模式下的读写锁。 ​ 3. 读写锁在写加锁状态时,其他试图以写状态加锁的线程都会被阻塞。读写锁在读加锁状态时,如果有线程希望以写模式加锁时,必须阻塞,直到所有线程释放锁。 ​ 4. 当读写锁以读模式加锁时,如果有线程试图以写模式对其加锁,那么读写锁会阻塞随后的读模式锁请求,以避免读锁长期占用,而写锁得不到请求。

高端的程序员,都有哪些朴素的编程方式?

高端的程序员往往却是采用最朴素的编程方式,这些方式可能不是最花哨或最炫酷的,但却能够写出高效、可靠、易维护的代码。
比如:
模块化编程:将代码分解成可重用的模块,每个模块负责完成一个特定的功能,可以提高代码的可维护性和可扩展性。
简单清晰的代码风格:采用简单清晰的代码风格,避免使用过于复杂的语法或难以理解的变量名。
自动化测试:可以有效地减少代码的错误和缺陷,提高代码的质量和稳定性。
数据结构和算法优化:选择最适合特定场景的数据结构和算法,并进行优化,以提高代码的效率和性能。
持续学习和反思:不断学习新的技术和编程方式,并对自己的代码进行反思和改进,以不断提高自己的编程水平。

能多端登录的无影云电脑有哪些花式玩法?

无影在办公场景上的buff加成:
无影通过提供安全高效的云上办公服务,为办公场景带来了很多优势。无影可以提供强安全的环境,保护用户的个人信息和公司数据。其次,无影的轻运维优势可以减轻IT部门的负担,让他们更专注于其他重要的任务。无影的低投入可以让企业节省硬件和软件购买成本,降低总体运营成本。无影的易集成功能可以方便地将其他应用与无影集成,实现更高效的工作流程。

无影硬件终端的优势:
无影硬件终端具有一些原先没想到的优势。硬件终端可以提供更好的性能,比如更快的处理器和更多的内存,以应对更复杂的任务。硬件终端可以提供更好的控制方式,比如触摸屏、鼠标和键盘等,使得用户可以更方便地操作。硬件终端还可以提供更好的用户体验,比如更大的屏幕和更好的音效等。

无影未来能在哪些领域花式玩转:
无影未来可以在多个领域中发挥重要作用。在教育领域中,无影可以为学生和教师提供安全高效的云上教学服务,比如在线教育、远程教学等。其次,在医疗领域中,无影可以为医生和患者提供便捷的医疗服务,比如在线挂号、远程医疗等。在金融领域中,无影可以提供更高效、更安全的金融服务,比如在线银行、金融分析等。在智能家居领域中,无影可以与智能家居设备集成,提供更智能、更便捷的生活服务,比如智能音箱、智能照明等。

大模型是真正的银弹吗?

大模型需要大量的数据和计算资源,这使得其开发和维护成本非常高。这不仅限制了其应用范围,也使得其难以在一些小规模或者数据量不大的场景中应用。
大模型并不总是能够解决所有问题。在一些特定的任务中,传统的工程方法和工具可能更为适用。不能过分依赖大模型,而应该根据具体任务选择最合适的工具。

大模型适用于需要处理大量数据和复杂任务的场景。比如,在自然语言处理领域中,需要处理的语言数据往往非常庞大,而大模型则可以从中学习到更多的语言规律和结构信息。此外,在一些需要高度智能化的场景中,比如智能客服、智能推荐等,大模型也可以发挥出其强大的能力。在医疗领域,大模型也具有广泛的应用前景,比如用于医学图像处理、疾病预测等。在一些小规模或者数据量不大的场景中,大模型可能并不是最合适的工具。此时,传统的工程方法和工具可能更为适用。需要根据具体任务选择最合适的工具,结合传统的工程方法和工具,实现更好的效果。

程序员需要考证吗?

是否需要考证,这取决于具体情况。在一些职业领域,如网络安全或医疗保健,认证证书可能是从业的必要条件。在其他领域,如Web开发或数据分析,认证可能不是必需的,但可以作为能力的一种证明,有助于在同等技能水平的人群中脱颖而出。

在大厂里,较高含金量的证书如下:

计算机科学学士学位(BSCS)
计算机应用硕士学位(MCSA)
软件开发工程师认证(SCEA)
Oracle认证专家(OCA)和Oracle认证大师(OCMA)
思科认证网络专家(CCIE)
微软认证专家(MCP)
Linux基金会认证(LFCS)
谷歌开发者认证(GDCE)
等等.....

目前流行的开源数据库你最喜欢哪个?

1、目前流行的开源数据库很多,比如MySQL、PostgreSQL、MongoDB、Cassandra等,每个数据库都有自己的优点和适用场景。如果非要说最喜欢哪个,那我选择MySQL,因为MySQL社区活跃,有很多成熟的解决方案可以参考,性能也是比较稳定的,支持的数据存储量也足够一般场景的使用。

2、公司的商业产品和开源产品的边界在于是否开放源代码以及是否免费。商业产品通常会收取一定的费用,而且可能不会开放源代码。

3、我之前是体验过PolarDB,总体感受是读写速度很快,支持的数据存储量非常大。不过,与传统的MySQL相比,PolarDB的价格要高一些,不过性能肯定是不用说的。据说PolarDB在阿里巴巴集团的核心业务中发挥了关键作用,比如:双11购物节这些场景。

如何训练属于自己的“通义千问”呢?

通义千问 刚出来时就已经申请开始体验,刚开始的体验还不是很好,后面慢慢使用起来感觉就越来越完善, 应该是在不断训练学习。

对于开发者来说,大模型开源无疑是一个极具吸引力的机会。开发者可以在此基础上进行各种创新和改造,以满足不同场景的需求。
例如,可以通过修改模型参数、添加特定任务的知识库等方式,来提高模型的精度和效率。
同时,开源模型也可以为开发者提供更好的社区支持和反馈,促进模型的共同进步。

已有成熟的操作系统,为什么还要研发国产操作系统呢?

国产操作系统可以更好地保障国家的信息安全。依赖国外操作系统可能存在未知的安全风险,而国产操作系统可以提供更可靠的安全保障。其次,自主研发操作系统可以使国家摆脱对他人技术的依赖,确保在信息技术领域的独立性和自主性。这有助于推动我国技术创新和发展。此外,随着国内信息技术产业的快速发展,国内市场对国产操作系统的需求也在不断增加。国产操作系统能够更好地满足国内用户的需求,提供更好的用户体验和服务支持。

这几年统信软件、麒麟软件、中科方德、华为欧拉、中兴新支点、阿里云和鸿蒙等国产操作系统取得了长足的进步。

在统信软件的技术支持下,龙蜥社区已经打造了全场景的操作系统生态,涵盖服务器、桌面、嵌入式、物联网等领域。这样的生态系统能够促进操作系统的创新和发展,吸引更多企业和开发者参与其中,共同推动我国信息技术产业的发展。

如果室温超导被验证成功,它会是算力的永动机吗?

室温超导被验证成功,也不可能是永动机。虽然室温超导可以消除电阻,但仍然需要一定的能量输入来维持超导状态。超导材料本身并不是一个永动机,仍然需要外部能量来维持其超导状态。

超导材料没有电阻,电流在其内部传输速度更快,可以在高温下运行,这将有助于提高计算机的运行速度和计算效率;这将大大降低能源消耗和运行成本。

你认为数字技术能让古籍“活过来”吗?

我认为数字技术确实能够在很大程度上让古籍活化。通过数字化处理,古籍可以以数字形式保存和传播,使更多人可以轻松访问和研究这些宝贵的历史资料。数字技术可以为古籍的保护和传承提供多种方式,例如数字图书馆、虚拟展览、在线学术平台等,从而让古籍“活”起来,延续其历史价值和文化传统。

目前修复古籍难点包括:
图像质量: 古籍图片经历了时间的损耗,可能存在污渍、裂纹、模糊等问题,这会影响修复的准确性和效果。
大量数据处理: 复原一本古籍可能涉及大量的图像数据,需要高效的图像处理算法和计算资源支持。
自动化处理: 要实现大规模的古籍修复,需要开发能够自动识别和修复图像问题的算法,以提高效率和准确性。
背景干扰: 古籍图片通常位于复杂的背景中,可能与其他文字、图像重叠,修复时需要解决背景干扰问题。

如果有机会为古籍活化助力,我愿意参与 开发数字化修复算法 :参与研发高效准确的数字化修复算法,通过图像处理技术帮助修复古籍图片的质量和可视化效果。

通过技术助力文化古籍活化是一项值得投入精力的重要事业。通过数字化、修复和传播,可以让古籍的价值得到更好地发掘和传承,让更多人受益于历史的智慧和文化遗产。

机房内,运维人,
日夜守护,不曾闲。
风吹雨打,冷暖自知,
坚守岗位,勇往直前。

故障来,须火速,
定方案,快如鬼。
手握键盘,眼观监控,
虽辛苦,甜在心中有。

虽然我不搞运维,但是2个朋友是搞运维的。运维人员都比较忙碌和辛苦,需要时刻保持警觉和高度的责任心,处理各种突发事件和故障。

未来的运维发展趋势是智能化和自动化,随着云计算、大数据、人工智能等技术的发展,运维工作将变得更加复杂和庞大,需要借助智能化和自动化的手段来提高效率和准确性。AIOps是这方面的一个重要趋势,结合了人工智能、机器学习等技术,能够实现对运维过程的自动化和优化,减少人为干预,提高系统的可靠性和稳定性。在未来的发展中,运维人员需要不断地学习和掌握新技术,适应行业的发展和变化,才能够更好地发挥作用。

CentOS 停服后如何给世界更好选择? 龙蜥操作系统从技术创新到商业变现都走了哪些路?

在当前国产操作系统的迁移潮中,国产操作系统的发展和应用将得到更多的机遇和挑战。对于国产操作系统的性能,在实际业务应用和研发中,不同的应用场景和需求会有不同的看重点。例如,在高性能计算、人工智能、大数据等领域,对操作系统的性能要求更高,需要更好的并发性、内存管理、I/O性能等方面的优化。而在一般的办公、娱乐等场景中,对操作系统的稳定性和易用性更为看重。

如何防止架构师PM化?

1、
虽然编码能力对于架构师来说非常重要,但并不是说一定要有很强的编码能力才能担任架构师。架构师需要具备的能力包括技术广度和深度、系统设计能力、沟通协调能力、团队管理能力等。当然,对于某些特定领域的架构师,如云计算、大数据等,对编码能力的要求可能更高一些。

2、
如果一个架构师已经PM化了,可能会表现出以下迹象:

(1)过分强调进度和里程碑,忽略实际技术实现和质量;

(2)过分强调文档和规范,忽略实际代码实现和效果;

(3)在技术和业务决策上过于保守,不注重创新和实践;

(4)缺乏对技术细节和实现的深入思考和理解。

3、
在工作中,我也遇到过架构师过度强调进度和里程碑的情况,导致实际技术实现和质量受到影响。为了避免这种情况,可以采取以下方法:

(1)平衡进度和质量:进度和质量是相辅相成的,不能一味追求进度而忽略质量。需要在项目规划和管理中合理安排时间和资源,保证进度和质量的平衡。

(2)注重实践和创新:架构师应该具有创新意识和实践精神,不断探索新技术和新方法,以提高系统的效率和可靠性。

(3)重视技术细节和实现:架构师需要深入了解系统的技术细节和实现,避免过度强调文档和规范而忽略实际代码实现和效果。

在笔面试中,常见的消息队列知识点包括:

(1)消息队列的定义和基本概念,如消息生产者、消息消费者、消息队列、消息订阅、消息广播等。

(2)消息队列的使用场景和优点,如异步处理、解耦、流量削峰等。

(3)消息队列的特点和性能指标,如可靠性、延迟、吞吐量、并发能力等。

(4)消息队列的实现原理和架构,如消息存储、消息传输、消息分发等。

(5)常见的消息队列产品和比较,如Kafka、RabbitMQ、RocketMQ等。

2、在你的工作业务中,你最常用哪款消息产品,它的优劣势是什么?

在我的工作业务中,我最常用的消息队列产品是Kafka。Kafka具有以下优势:

(1)高吞吐量和低延迟:Kafka的存储和传输设计非常高效,可以实现高吞吐量和低延迟的消息传输。

(2)可靠性和容错性:Kafka采用了多副本机制和故障转移机制,可以保证消息的可靠性和容错性。

(3)分布式和可扩展性:Kafka采用分布式架构,可以通过增加节点来实现水平扩展。

(4)开放和灵活:Kafka提供了丰富的API和插件,可以方便地与其他系统进行集成。

3、你觉得在消息队列的知识中最难理解的知识点有哪些?

在消息队列的知识中,最难理解的知识点可能是消息分发和消费者的负载均衡。消息分发是指如何将消息从生产者传递到消费者,包括消息路由和负载均衡等方面;而消费者的负载均衡则是指如何平衡不同消费者之间的消息消费,以提高消费者的吞吐量和效率。这些知识点需要深入理解消息队列的架构和实现原理,才能够更好地进行应用和优化。

程序员最害怕遇到的代码错误是什么?

1、你最害怕遇到的代码错误是什么?

我认为最害怕的代码错误是难以重现和定位的Bug。这种Bug可能只在特定条件下出现,或者仅在特定时间或特定用户中出现,非常难以重现和定位。这种Bug通常需要耗费大量的时间和精力来进行排查和解决,给开发和测试人员带来很大的困扰和压力。

2、遇到代码错误与业务问题,你如何进行排查?

在遇到代码错误和业务问题时,通常会按照以下步骤进行排查:

(1)重现问题:需要尝试重现问题,找到问题的具体表现形式和触发条件。

(2)排除常见问题:如果问题比较常见,可以先排除一些常见的问题,比如网络连接、权限等问题。

(3)查看日志:如果可以获取到日志,可以查看日志来了解问题的具体原因和发生时间。

(4)调试代码:如果问题的原因比较复杂,可以通过调试代码来找到问题的具体原因。

(5)寻求帮助:如果自己无法解决问题,可以向同事或相关技术社区寻求帮助。

排查代码错误和业务问题需要耐心和技巧,并且需要不断尝试不同的方法来找到问题的根本原因。同时,对于一些常见的问题,需要在平时进行积累和总结,以便能够更加快速和准确地解决问题。

程序员写代码为什么要阅读源码?

1、程序员写代码为什么要阅读源码?

阅读源码是程序员必备的技能之一。可以帮助程序员更好地理解代码的结构和实现细节,提高代码质量和效率,加速问题解决和Bug排查。此外,阅读源码还能够帮助程序员学习新技术和理解开源项目的实现方式,提升个人技术水平和竞争力。

2、你觉得阅读源码的正确姿势是什么?

阅读源码的正确姿势应该是系统性和深度逐步增加的过程。
(1)先整体了解代码结构和功能,看懂代码的基本架构和调用关系。

(2)逐步深入理解代码实现细节,包括算法、数据结构、设计模式等。

(3)积累经验,不断把握代码细节,发现常见问题和坑点,提高代码阅读和理解的效率。

(4)结合实践,应用所学的知识和技巧,掌握源码的实际运用,达到深入理解和掌握的目的。

3、什么场景下你会阅读源码?从中得到了怎样的收获?

(1)学习新技术:如果要学习一种新的技术或框架,我会阅读相关的源码,了解其实现方式和设计思路。

(2)解决问题:如果在开发中遇到问题或Bug,我会通过阅读源码来定位和解决问题。

(3)优化代码:如果要对现有代码进行优化,我会阅读相关的源码,了解其实现方式和优化思路。

阅读源码的收获主要包括:

(1)深入理解代码:通过阅读源码,可以深入理解代码的实现方式和设计思路,提高代码质量和效率。

(2)学习新知识:通过阅读源码,可以学习到新的技术和思想,增加个人技术水平和竞争力。

(3)解决问题:通过阅读源码,可以定位和解决问题,提高开发效率和质量。

诺奖出炉,引起广泛讨论的量子纠缠是什么?

我认为量子纠缠是超时空的作用,而且是确定物质存在的原理。

举个太阳光到地球的栗子,我们看到的是8分钟前的太阳,就是因为光子纠缠的是8分钟前的太阳,并且中途只要不退相干,我们是可以100%获取8分钟前太阳的信息的。而在太阳上,发出光的一瞬间,太阳与光子所纠缠的状态马上就坍缩了,这就确定了太阳发出光子时自身的信息,就算是在地球上看到太阳发出的光子时才退相干,也会影响到太阳发出光子时的状态(因果论在量子层面就失效了)。

另外我认为即使是这样超时空的作用,也并不违背现有的科学,任何物质(包括电磁波)只是作为了信息传输的媒介。量子纠缠实际是更加本质的,如果没有量子纠缠,信息就不会通过物质来传输,就不会有退相干,而物质本身也就不会存在了。另外物质的速度最快也只是光速,所以传统意义上的信息传输的速度最快也就只能是光速了。

万物都是由波组成,再坍缩成为在系统内可以确定的物质,这种波叫物质波,根据特性可以分为很多种波,比如电磁波、引力波等等。而每一种波都可以与发出波的物质纠缠特定的一些信息,波再将这些信息传输给其他物质,最后计算后产生现象。 这只是一种理论而已,实际是非常简单的理论,只是微观世界中信息交换的理论,不过我觉得可以衍生出万物的运作,如果可行,出现以此为基础的万物理论也没什么问题。但我也不是什么科学家,不懂高深的公式,没有严谨的语言,所以这理论其实可以当做哲学来看待。

看了一些相关阐述介绍,量子纠缠就是比如纠缠的两个量 AB 距离无限远,A发生了变数,B也会相应发生变化,不受时间空间影响,只不过等AB得知这些变数或者第三方XDEF等得知这些变数时候是需要 波函数坍缩后才能知道,也就造成了时间差.