[置顶] 一些网站

PHP

PhpBug:https://bugs.php.net/

Zend Framework:https://www.phpfans.net/manu/ZF/

JavaScript

JavaScript图表:https://echarts.baidu.com/examples/

mescroll 下拉控件:http://www.mescroll.com/

Promises:http://wiki.commonjs.org/wiki/Promises

正则工具:https://regexr.com/

ini解析:https://github.com/iwonmo/IniFormat

Android

Apk在线反编译:http://www.javadecompilers.com/apk/

Adb:http://adbshell.com/downloads

AI

TensorFlow 国内:http://www.tensorfly.cn/tfdoc/get_started/introduction.html

TensorFlow 官网:https://tensorflow.google.cn/

Numpy:https://www.numpy.org.cn/

游戏

Cocos 引擎:http://docs.cocos.com/creator/manual/zh/

单片机

元器件:https://www.alldatasheetcn.com/

Arduino Mini:https://store.arduino.cc/usa/arduino-pro-mini

Google Chromium PC Frame

nw.js:https://nwjs.io/

Electron:https://electronjs.org/

cef:https://bitbucket.org/chromiumembedded/cef/src/master/

设计学习

doyoudo:http://www.doyoudo.com/



epoll 电平触发、边沿触发

看视频里有一句话有疑问,所以看了下电平触发和边沿触发。这句话是:“在复用IO里必须使用阻塞模式”这句话是错误的

epoll 对文件描述符有两种操作模型:电平触发(LT)、边沿触发(ET)。select 和 poll 模型只有电平触发。

电平触发:也就是只有高电平(1)或低电平(0)时才触发通知。

边沿触发:只有电平发生变化(高电平到低电平,或者低电平到高电平)的时候才触发通知。

1571123733(1).png

由于上升沿和下降沿是电压发生变化而产生的,所以可以理解为边沿触发只有发生变化才会触发

而高电平低电平是可以一直保持的,可以看到高电平②是比高电平①要长的,是因为IIC传输数据的时候是两条线,有一条时间线,所以在一段时间内可以一直高电平或者低电平也没事。所以电平触发会一直通知,直到状态发生改变。由高电平转到低电平,由有数据变为无数据,否则会一直提示即使缓冲区里面的数据没有读完也是会提示你继续读取。

这里只是以电平的转换过程来理解LT和ET模式,并不是说功能的实现是高低电平,只是一种理解方式或者说是实现的原理上差不多。

电平触发指的是:当存在一个读写事件的时候,epoll会发送消息,也就是会从睡眠状态中醒来,然后继续走代码,如果没有事件发生,会一直堵塞,下面的代码不会触发。如果这个事件到来,比如读事件到来后,可以暂时不做处理,当下次循环的时候,会继续触发这个事件,可以在读取。直到内存中的数据被你读完。这个读的事件在下次循环的时候才不会触发。否则会一直触发。


边沿触发和电平触发恰恰相反,如果你这次事件不处理,那么下次这个事件不会继续通知。所以在性能方面边沿触发要更好一些,因为从睡眠醒来的次数比较少。边缘触发注意的是:如果你这次事件读取的内容没有读取完成,还剩余一部分,那么下次不会触发读事件,但是如果有新的数据写入到文件描述符,那么读事件会再次触发。


水平触发(原文)

1. 对于读操作

只要缓冲内容不为空,LT模式返回读就绪。

2. 对于写操作

只要缓冲区还不满,LT模式会返回写就绪。

边缘触发

1. 对于读操作

(1)当缓冲区由不可读变为可读的时候,即缓冲区由空变为不空的时候。

(2)当有新数据到达时,即缓冲区中的待读数据变多的时候。

(3)当缓冲区有数据可读,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLIN事件时。

2. 对于写操作

(1)当缓冲区由不可写变为可写时。

(2)当有旧数据被发送走,即缓冲区中的内容变少的时候。

(3)当缓冲区有空间可写,且应用进程对相应的描述符进行EPOLL_CTL_MOD 修改EPOLLOUT事件时。

epool 更改LT、ET模式

epoll事件结构

    typedef union epoll_data {
        void    *ptr;
        int      fd;
        uint32_t u32;
        uint64_t u64;
    } epoll_data_t;
    struct epoll_event {
        uint32_t     events;    /* Epoll events */
        epoll_data_t data;      /* User data variable */
    };

events可以包含两种:

LT:EPOLLIN(默认模式)

ET:EPOLLET

填充相应的方式即可更改触发模式。

代码解释

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>
#define MAX_EVENT_NUMBER 1024
#define BUFFER_SIZE 10
int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}
void addfd( int epollfd, int fd, bool enable_et )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN;
    if( enable_et )
    {
        event.events |= EPOLLET;
    }
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}
void lt( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, false );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
            memset( buf, '\0', BUFFER_SIZE );
            int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
            if( ret <= 0 )
            {
                close( sockfd );
                continue;
            }
            printf( "get %d bytes of content: %s\n", ret, buf );
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}
void et( epoll_event* events, int number, int epollfd, int listenfd )
{
    char buf[ BUFFER_SIZE ];
    for ( int i = 0; i < number; i++ )
    {
        int sockfd = events[i].data.fd;
        if ( sockfd == listenfd )
        {
            struct sockaddr_in client_address;
            socklen_t client_addrlength = sizeof( client_address );
            int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
            addfd( epollfd, connfd, true );
        }
        else if ( events[i].events & EPOLLIN )
        {
            printf( "event trigger once\n" );
                // ET模式下,需要循环读取数据。
                //直到返回EAGAIN,因为下次不会通知。
            while( 1 ) 
            {
                memset( buf, '\0', BUFFER_SIZE );
                int ret = recv( sockfd, buf, BUFFER_SIZE-1, 0 );
                if( ret < 0 )
                {
                    if( ( errno == EAGAIN ) || ( errno == EWOULDBLOCK ) )
                    {
                        printf( "read later\n" );
                        break;
                    }
                    close( sockfd );
                    break;
                }
                else if( ret == 0 )
                {
                    close( sockfd );
                }
                else
                {
                    printf( "get %d bytes of content: %s\n", ret, buf );
                }
            }
        }
        else
        {
            printf( "something else happened \n" );
        }
    }
}
int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );
    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );
    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );
    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );
    ret = listen( listenfd, 5 );
    assert( ret != -1 );
    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd, true );
    while( 1 )
    {
        int ret = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ret < 0 )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        lt( events, ret, epollfd, listenfd );
        //et( events, ret, epollfd, listenfd );
    }
    close( listenfd );
    return 0;
}

关于触发模式的选择

1.对于监听的sockfd,最好使用电平触发模式,边沿触发模式会导致高并发情况下,有的客户端会连接不上,因为事件可能只会触发一次。如果非要使用边沿触发,网上有的方案是用while来循环accept()。

2.对于读写的connfd,电平触发模式下,阻塞和非阻塞效果都一样,不过为了防止特殊情况,还是建议设置非阻塞,因为有的时候,电平触发已经发送事件,但是这个时候缓冲区的数据还未写,调用recv的时候有可能会出现堵塞状态,这是一种极端情况。

3.对于读写的connfd,边沿触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据,因为如果设置成堵塞,你第一次数据没有读写完,那么不会再次epoll事件,你的recv会一直堵塞下去,直到有新的事件产生,你的recv才会再次收到事件,然后读写数据。如果是非堵塞的话,那么可以用while(1)来一直读数据。而不必等待事件


ssd1306显示汉字

屏幕快照 2019-10-06 下午3.54.53.png

我是利用avr来驱动的,关于avr如何连接ssd1306。大家可以翻看手册,能够更好的理解。这里我选择的是IIC的连线方式,所以ssd1306的D1和D2我连在一起了,这也是手册里推荐的方式。

这里我选择的编译器是bascom,听说AVR STUDIO更好并且免费。bascom是收费的,所以使用前你需要购买,网上的破解版我基本上都有尝试,基本上无法使用,因为版本太低。高版本的只提供demo只能编译4K一般够用。

由于内部的驱动方式,什么屏幕点亮,清理内存,复位屏幕。等等一些操作,如果你的线连接的方式对,那么bascom会自动帮你整好整个这一套流程,不然你就得按照手册一步一步去操作。代码直接使用bascom官网提供的:

'-------------------------------------------------------------------------------
'                       SSD1306-I2C.BAS
'                     (c) MCS Electronics 1995-2015
'          Sample to demo the 128x64 I2C OLED display
'
'-------------------------------------------------------------------------------
$regfile = "m88pdef.dat"
$hwstack = 32
$swstack = 32
$framesize = 32
$crystal = 8000000
Config Clockdiv = 1                                         ' make sure the chip runs at 8 MHz
 
Config Scl = Portc.5                                       ' used i2c pins
Config Sda = Portc.4
Config Twi = 400000                                         ' i2c speed
 
I2cinit
$lib "i2c_twi.lbx"                                         ' we do not use software emulated I2C but the TWI
$lib "glcdSSD1306-I2C.lib"                                 ' override the default lib with this special one
 
#if _build < 20784
Dim ___lcdrow As Byte , ___lcdcol As Byte                 ' dim these for older compiler versions
#endif
 
Config Graphlcd = Custom , Cols = 128 , Rows = 64 , Lcdname = "SSD1306"
Cls
Setfont Font8x8tt                                           ' select font
 
Lcdat 1 , 1 , "BASCOM-AVR"
Lcdat 2 , 10 , "1995-2015"
Lcdat 8 , 5 , "MCS Electronics" , 1
Waitms 3000
 
Showpic 0 , 0 , Plaatje
 
End
 
 
$include "font8x8TT.font"                                   ' this is a true type font with variable spacing
 
 
Plaatje:
  $bgf "ks108.bgf"                                         ' include the picture data

这里的glcdSSD1306-I2C.lib库就是一个封装好的ssd1306库。如果你大致能看懂的话,那么你可以继续看,如果不行的话,那就去查些资料吧。首先要知道一点,一个汉字或者字符显示在屏幕上有这么几个要素,宽度,高度,块大小(字体)。这些东西可以去看字体方面的知识点。还有阴码,阳码,扫描方式。bascom显示字符利用了一个叫做font的字体文件,这个文件加载后你就可以使用其中的文字内容。注意它虽然叫做font但是它不是原来windows上理解的这种字体。这种概念要有。

bascom生成这种文件有两种方式:

1、ide本身的font edit 

2、Bascom AVR Font Converter

这两种方式都可以,我推荐第二个。因为它可以生成不同宽度高度和块的font文件,并且它是一键生成的方式(购买后的情况下)。

格式说明:

A font file is a plain text file.

Lets have a look at the first few lines of the 8x8 font:


Font8x8:

$asm

.db 1,8,8,0

.db 0,0,0,0,0,0,0,0 ;

.db 0,0,6,95,6,0,0,0 ; !


The first line contains the name of the font. With the SETFONT statement you can select the font. Essential, this sets a data pointer to the location of the font data.

The second line ($ASM) is a directive for the internal assembler that asm code will follow.

All other lines are data lines.

The third line contains 4 bytes: 1 (height in bytes of the font) , 8 (width in pixels of the font), 8 (block size of the font) and a 0 which was not used before the 'truetype' support, but used for aligning the data in memory. This because AVR object code is a word long.

This last position is 0 by default. Except for 'TrueType' fonts. In BASCOM a TrueType font is a font where every character can have it's own width. The letter 'i' for example takes less space then the letter 'w'. The EADOG128 library demonstrates the TrueType option.

In order to display TT, the code need to determine the space at the left and right of the character. This space is then skipped and a fixed space is used between the characters. You can replace the 0 by the width you want to use. The value 2 seems a good one for small fonts.

All other lines are bytes that represent the character.

看不懂的话直接右键翻译,知道这些后你还是无法显示汉字,因为你不知道怎么去索引font的汉字位置,我也是翻来翻去看到的。打开ide的font edit。你会发现一个ascii。

屏幕快照 2019-10-06 下午4.12.45.png

可以看到有32和33,所以直接lcdat 1,1,chr(32)就可以索引到“王”这个汉字

整个代码

Cls
Setfont Font16x16
Lcdat 0,0,chr(32);
Setfont Font8x8tt 
Lcdat 8,16, "And" 
Setfont Font16x16
Lcdat 0,16+8+8+8-3,chr(33);
Setfont Font8x8tt 
Lcdat 8,16+8+8+8-3+16, "PCB"
Waitms 3500
Cls

批量制作汉字的话可以利用PCtoLcd,配置模式如下:

屏幕快照 2019-10-06 下午4.15.21.png

这是ssd1306芯片的取模方式的配置。其他的需要自己看手册。这样就会生成db代码。然后把db代码放到一个font文件里,载入到font edit 就可以看到效果。 如果遇到错误建议删除空行。

关于集成电路IO引脚电平

具体的IO引脚的实现可以百度,有很多电路图,会用到场效应管和其它元器件来实现IO接口。这里讨论的是IO输入和输出高电平。特别是对电平的一些理解。

IO引脚有两种模式:输入输出。

输入:外部到单片机内部

输出:单片机内部到外部

IO一般输入输出指的是电平输入输出。

电平的解释:两点的电功率或者电压之比的对数。

电压:两点间的电势或者电位的差。(实际很好理解,一个电源一个电阻串联,测电阻两端电压是多少,根据欧姆定律很容易算出。电压就是我们正常生活中的,只不过概念理解起来很多。涉及到一个又一个细小概念分支)

电位:又叫做电势或电位,单位是V。(涉及到静电学和电动力学,自行百度。)

电功率:电流在单位时间内所做的功,单位:瓦特 简称:瓦 符号:W 表示方式:P(公式上)

电能:电流以各种形式做功的能力,单位:度 学名:KW.H 也就是一小时消耗多少千瓦。

电功率和电能看这容易混淆,学校的知识我也忘得差不多了,百度了很多,发现有一个解释比较容易理解。

电能是路程,电功率是速度。一个人一个小时可以跑100米这是速度,而这100米是路程。某元器件的电能是100电能,那么在一个小时内他可以产生100K的电功率。在两个小时就是200K的电功率。

对数:幂的逆运算。比如2的3次方是8。那么8以2为底的对数就是3。

电平也分为高电平和低电平,高电平代表:1,低电平代表:0。由于1和0是二进制,所以就可以组成一些运算,比如逻辑门:非门、或门、与门等。高电平是大于3v,低电平是小于1.5v而1.5-3v之间的是中间电平,一般不做处理。

高低电平用方形图表示

屏幕快照 2019-09-23 上午12.53.40.png

由于是连续的产生电压或者电功率所以电平也是连续的,方形图表示的话也会是一直连续下去这里只给出一个,连续的方形图也可以叫做脉冲。这里引出一个概念叫做总线,总线可以用来传输数据,具体的实现方式实际也是利用电平。视频资料:https://www.bilibili.com/video/av7230090?from=search&seid=5145470121650526078。还有一点,图形上看着是直棱直角的直线,实际电流过程中比如在刚通电的时候,电流是不稳的所以画出来的线应该是有波动的。不过这种波动非常小或者说非常快的趋于平稳,所以画图的时候近似的画成直线,但是实际中它并非直线。

上拉电阻:上拉就是将不确定的信号通过一个电阻钳位在高电平,电阻同时起限流作用。下拉同理,也是将不确定的信号通过一个电阻钳位在低电平。这里的信号指的是模拟信号例如声音或者数字信号例如1和0的高低电平,设计到模电和数电。想更深入的了解可以看一看模电和数电,这里我还没看,所以没办法解释信号。

上拉电阻一般接在电源位置起到拉高电平输出,下拉是接在接地端,起到拉低电平的作用,可以通过欧姆定律串并联来理解。

钳位:钳位是指将某点的电位限制在规定电位的措施。

总的来说,上拉电阻我接上以后这点的电平会变高,因为会分压,分到很大一部分的电压,所以相对于某一点,我是高电平。

一张图理解一下(个人理解):

屏幕快照 2019-09-23 上午1.22.51.png


当IO为输出的时候,A点相对于B点的电压一定要小,所以这里要输出低电平。如果输出高电平,也就是A点的电压比b点的电压大的话,那么LED是不会亮的。因为LED的负极比正极电压还大了。这里的LED是负极接IO正极接VCC。

如果是LED正极接IO,而负极接地,那么就要输出高电平。因为A点相对于接地端的电压一定要大。才能让电流流通。

而作为输入的时候我觉得是A点的电压相对于集成电路内部的电平也就是电功率或者电压的比值。这里的B点只是一个参考,也可能这个B点在LED负极的前一点。




跑马灯电路板仿真

222222.gif

工具

Proteus:MCU仿真软件

ICC AVR : AVR单片机代码编写软件

元器件

ATMEGA-8 :AVR MCU

LED:发光二极管

电源:这里用VCC代替

元器件在proteus的名字

LED-RED

ATMEGA8

CHIPRES470R

这里的VCC是逻辑端子,没有具体的物理(实体)模型,只是电路图中代表电源,所有取名一样的端子将会在PCB中进行连接。另外电路图中没有加入电阻,所以对二极管的输入电流没有做控制,实际操作中需要加上的。另外还有物理端子,代表有实体的物理模型。

ATMEGA-8上面有很多的输入输出(I\O)端口,这里用的是PD1、PD3、PD4端口。代码使用ICC AVR 来编写,不同的版本有不同的书写方式。新版本好像套了一个开源的编辑器,旧的版本还是自己的编辑器。大概了解一下就可以了。然后就是编译输出为HEX,这些设置都可以在Project---Option里进行设置。

微信截图_20190920144931.png

还有需要注意的是ICC AVR 编辑器需要先新建工程,才能编译文件。不新建工程的话,即使代码写完,也无法编译。

跑马灯代码

#include <iom8v.h>
void delay_1ms(void){
    unsigned int i;
    for(i=1;i<(unsigned int )(1144-2);i++);
} 
void delay_1ma(unsigned int n){
    unsigned int i=0;
    while(i<n){
        delay_1ms();
        i++;
    }
}
void main(){
    unsigned char i;
    DDRD=0xFF; //设置D端口为输出模式 还有输入模式0xFF
    while(1){
        PORTD =~(1<<1); // << 是位移操作 相应位置对应的端口 1000 : 1<<4 即第四位  
        delay_1ma(30); // 延迟
           PORTD =~(1<<3);  
            delay_1ma(30);
            PORTD =~(1<<4);
            delay_1ma(30);
    }
}

关于输出高电平点亮led的解释

0824ab18972bd4079415dc097d899e510eb30977.png

 当I/O口输出高电平时,LED两端的电位相同,因此电压为0V,不能构成电流回路,所以LED不亮。当I/O口输出低电平时,LED左侧电位为0,而右侧则在R1的上拉作用下电位提高,因此LED两端有正向电压,可以点亮发光。

  MCS-51单片机的I/O口具有比较强的灌电流能力,但拉电流能力却很弱,所以并不适合用输出高电平的方法点亮LED,大多采用这种负逻辑的驱动方法。


为了更好的理解下面是说明书中的内容

本节所有的寄存器和位以通用格式表示:小写的 “x” 表示端口的序号,而小写的 “n” 代表

位的序号。但是在程序里要写完整。例如, PORTB3 表示端口 B 的第 3 位,而本节的通

用格式为 PORTxn。物理 I/O 寄存器和位定义列于 P63“I/O 端口寄存器的说明 ” 。

每个端口都有三个 I/O 存储器地址 : 数据寄存器 – PORTx、 数据方向寄存器 – DDRx 和端

口输入引脚 – PINx。数据寄存器和数据方向寄存器为读 / 写寄存器,而端口输入引脚为只

读寄存器。但是需要特别注意的是,对 PINx 寄存器某一位写入逻辑 "1“ 将造成数据寄存

器相应位的数据发生 "0“ 与 “1“ 的交替变化。当寄存器 MCUCR 的上拉禁止位 PUD 置位

时所有端口引脚的上拉电阻都被禁止。

编译好HEX文件以后,双击proteus仿真软件中的MCU ATmega8 图形 然后选择这个HEX。

微信截图_20190920151047.png

然后点击仿真就可以了 ,电源VCC逻辑端子的话,可以点击终端模式选择POWER就可以了。


个人理解,如有错误还望指出。

php获取明天后天下星期时间戳

function futureTime($command){
    list($com,$n,$x)=explode("-",$command);
    if($com=="xq"){
        $time=date("w",time( ));
        $array = ["7","1","2","3","4","5","6"];
        $time=date("w",time( )); 
        $tmp=strtotime(date('Y-m-d',strtotime('+'.($n*7).' day')))-(($array[$time]-$x)*24*60*60)-8*60*60;
        return [$tmp,$tmp-time()];
    }
    if($com=="rq"){
        $tmp=strtotime(date('Y-m-d',strtotime('+'.$n.' day')))-8*60*60; 
        return [$tmp,$tmp-time()];
    }
}

获取下星期三:futureTime("xq-1-3")

xq:星期标识

1:代表下星期 2:代表下下星期 n:代表第n个星期

3:代表星期三 (1.2.3.4.5.6.7 星期一到星期天)

获取明天:futureTime("rq-1")

rq:日期标识

1:代表下一天

返回值:索引数组,一个是日期的时间戳,另一个是现在距离日期的秒数。

注意:所有的时间都是秒数,并且是从00:00:00开始计算的。