专栏首页嵌入式开发圈STM32F103产品级开源项目:iLook.Time设计解读

STM32F103产品级开源项目:iLook.Time设计解读

一、iLook项目的历程:

  • 2013年开始设计iTrack+yeelink,由于各种原因,该项目夭折。
  • 2014年年初开始提出面向产品的开源平台:iLook
  • 2015年5月发起iLook.爱路客
  • 2015年8月发起iLook.Time并开源

iLook它大概就长下面这个样子:

二、iLook平台规格及硬件简介:

  • OLED 128X32显示屏幕
  • 采用ARM STM32F103平台
  • 传感器:BMA250(三轴加速度传感器),BMP280(气压温度传感器),HMC5883(地磁传感器)
  • GPS+内置天线
  • 其他硬件:spi flash 8~16Mbytes, ds1302, 700mA高温锂离子电池

三、iLook软件功能相关

看到网上一些iLook的说明书,最后大致判断有以下这几个界面:

接下来我们就来一步步揭开上面所列功能的面纱:

1、主程序框架

分析任何一个项目,都是从main.c的main函数开始,从头到尾把握整个程序的框架,接下来咱们再去了解细节功能,以下是main函数的实现,在这里我顺便再多注释下代码的含义:

int main(void)
{
    uint8_t *logo_ptr;
    int logo_width, logo_height;

    GPIO_Config();
    //开机上电,判断是否低电压,如果是则关机,这一步非常重要,这是产品级必备的功能。
    PowerMonitorTask_Init();
    //判断电压是否小于3.4V,如果是则关机
    if( gPowerSt.BatteryVol < 3.4 )
    {
        LCM_PWR_OFF();
        GPS_PwrOff();
        DEV_PWR_OFF();
        myPWR_EnterPowrOff();
    }
    //ARM初始化
    UART1_Configuration();
    EXTI_Configuration();
    NVIC_Configuration();
    I2C_Config();
    //这个延时主要是让上面的硬件配置稳定
    delay_us(2000);
    //Qst初始化
    SysTick_Init();
    QstMonitor_Init();
    //驱动初始化
    spiflash_init();
    //加载文件系统,并获取系统配置
    disc_mount();
    ilook_cfg_load();   //获取配置文件
    //任务初始化
    RealTime_Init();    //这个必须最先启动
    UsbMonitorTask_Init();
    DisplayTask_Init();
    //开机显示
    QstCtrl(&DisplayTskInfo, DISPLAY_PWR_ON);
    //加载系统LOGO
    if( (logo_ptr = load_logo(&logo_width, &logo_height)) != 0 )
    {
        Glph_DrawBitmap(0, 0, BMP_FILE | BIT_MAP_REVERSE, logo_width, logo_height, logo_ptr);
    }
    else
    {
        Glph_Print(0, 0, MS_GOTHIC_8X16, (char*)prj_info);
        Glph_Print(0, 24, ASCII_5X7, (char*)prj_version);
    }

    //开机显示
    while(TimeOutCheck_Sec(iLookCfg.T_LogoDisplay) == 0)
    {
        //显示LOGO
        DisplayTask();

        //如果两秒内松开按键,则关机
        //检查POWER键是否有效,上面这个两秒内松开则关机处理的非常好,因为产品嘛,存在用户不小心勿触的情况。
        if( TimeOutCheck_Sec(1) == 0 )
        {
            if( GPIO_ReadInputDataBit(WKUP_KEY, WKUP_KEY_PIN) == 0 )
            {
                LCM_PWR_OFF();
                GPS_PwrOff();
                DEV_PWR_OFF();
                myPWR_EnterPowrOff();
            }
        }
    }
    ClrScreen();
    LED_OFF();
    //启动系统
    KeyTask_Init();
    sys_log_write("POWER ON", "OK");
    UiTask_Init();
    while(1)
    {
        //产生计数,以便后面的任务获取执行时间间隔
        QstMonitor();
        //主要是电源管理,读电量以及检测是否为充电模式
        PowerMonitorTask();
        //主要是控制USB状态的切换:打开、关闭、检测是否连接、挂载与解除挂在文件系统
        if( UsbMonitorTask() == 1 )
            continue;
        //实时时钟任务,主要用于实时显示DS1302的时间(年月日,时分秒)
        RealTime_Task();
        //显示任务,主要是处理显示器的电源开关、休眠唤醒、亮度设置的状态切换
        DisplayTask();
        //UI任务处理
        UiTask();
    }
}

2、QST管理状态机任务系统

QST管理状态机是整个工程的核心,接下来我们来了解下QST管理状态机主要在工程代码的task.h和task.c里实现,核心结构体:

typedef struct _TASK_CTRL_INFO
{
    unsigned char Ctrl;       //任务命令输入,第8位必须是1。TASK_CMD|New State
    unsigned char State;      //任务当前状态
    unsigned long TickMsk;    //任务时间戳
    unsigned long TickGap;    //任务时间间隔
    unsigned int  MsgFlg;     //任务新信息标志位
    unsigned char *Msg;       //任务信息指针
    char (*Process)(void);    //任务函数指针
} TASK_CTRL_INFO;

相应的,task.h定义了外部可访问结构体成员的方法以及状态的设置切换:

/* QST进程管理系统定义 */
#define TASK_CMD            0x80      //任务标志
#define TASK_MSG_NULL       0x00      //无信息
#define TASK_MSG_ST_CHANGE  0x80      //状态切换信息

//任务信息处理
void QstMsgClr( TASK_CTRL_INFO *tsk );
unsigned char QstGetMsgState( TASK_CTRL_INFO *tsk );
unsigned char *QstGetMsg( TASK_CTRL_INFO *tsk );

//任务状态机控制
void QstCtrl( TASK_CTRL_INFO *tsk, unsigned char ctrl );

//任务状态机跳转
void QstEnter( TASK_CTRL_INFO *tsk, unsigned char st );

//获取外部控制命令
unsigned char QstGetCmd( TASK_CTRL_INFO *tsk );

//获取任务状态
unsigned char QstGetState( TASK_CTRL_INFO *tsk );

//复位任务计时器
void QstRestTskTick( TASK_CTRL_INFO *tsk );

//QST看守进程
void QstMonitor_Init(void);
void QstMonitor(void);

//任务公有状态定义
#define T_NULL      0x00    //空状态
#define T_PWR_ON    0x01    //任务打开状态
#define T_PWR_OFF   0x70    //任务关闭状态
#define T_HW_ERR    0x71    //任务相关硬件错误状态


/*---------------------------------------------------------------------*/
/* 项目所涉及的TASK声明全部放到这里 */
extern TASK_CTRL_INFO UiTskInfo;        //系统顶层任务

extern TASK_CTRL_INFO CompassTskInfo;   //指南针驱动任务

extern TASK_CTRL_INFO DisplayTskInfo;   //显示驱动任务

extern TASK_CTRL_INFO gSensorTskInfo;   //加速度传感器驱动任务

extern TASK_CTRL_INFO GpsTskInfo;       //GPS驱动任务

extern TASK_CTRL_INFO PowerTskInfo;     //电源管理任务

extern TASK_CTRL_INFO BaroTskInfo;      //气压传感器任务
/*---------------------------------------------------------------------*/

遗憾的是,iLook.Time仅仅开源了代码框架以及部分任务的实现,这里面主要实现了系统顶层任务、显示驱动任务、电源管理任务,剩下的几个在代码里都没有实现,不过这不影响我们继续学习作者的设计思想。

关于task.c代码注释的非常详细,主要是实现了用户可设置和访问的任务的成员的接口,最精华的地方就是每个任务的时间间隔以及时间戳的处理,这部分将是这份代码最重要的地方。

/**
  ******************************************************************************
  * @file    task.c
  * @author  SZQVC
  * @version V1.0.0
  * @date    2015.2.14
  * @brief   灯塔计划.海啸项目 (QQ:49370295)
  *          QST前后台进程管理系统
  ******************************************************************************
  * @attention                                                                 *
  *                                                                            *
  * <h2><center>&copy; COPYRIGHT 2015 SZQVC</center></h2>                      *
  *                                                                            *
  * 文件版权归“深圳权成安视科技有限公司”(简称SZQVC)所有。*
  *                                                                            *
  *        http://www.szqvc.com                                                *
  *                                                                            *
  ******************************************************************************
**/
#include "stm32f10x.h"
#include "sys_tick.h"
#include "task.h"

/* define */
struct _QST_STATE
{
    uint32_t mloop_per_sec;
} Qst;

/* public */

/* extern */

/* private */
static unsigned long mon_task_tick, mon_task_loop_cnt;


/*******************************************************************************
* Function Name  : TaskCtrl
* Description    : 任务状态切换
* Input          : - tsk: 任务结构指针
*                  - ctrl: 切换到什么状态
* Output         : None
* Return         : None
*******************************************************************************/
void QstCtrl(TASK_CTRL_INFO *tsk, unsigned char ctrl)
{
    tsk->Ctrl = ctrl | TASK_CMD;
    tsk->TickMsk = GetSysTick_ms();  //记录进入该状态的时间标签

    //立即执行一次任务
    if( tsk->Process != 0x0 )
        tsk->Process();
}


/*******************************************************************************
* Function Name  : TaskEnter
* Description    : 任务状态跳转
* Input          : - tsk: 任务结构指针
*                  - st: 任务直接进入到什么状态
* Output         : None
* Return         : None
*******************************************************************************/
void QstEnter( TASK_CTRL_INFO *tsk, unsigned char st )
{
    tsk->MsgFlg = TASK_MSG_ST_CHANGE;      //进入新的状态,信息应该被更新
    tsk->State = st;                       //设定状态
    tsk->TickMsk = GetSysTick_ms();        //记录进入该状态的时间标签
}

/*******************************************************************************
* Function Name  : QstRestTaskTick
* Description    : 复位任务计数器
* Input          : - tsk: 任务结构指针
* Output         : None
* Return         : None
*******************************************************************************/
void QstRestTskTick( TASK_CTRL_INFO *tsk )
{
    tsk->TickMsk = GetSysTick_ms();        //记录进入该状态的时间标签
}


/*******************************************************************************
* Function Name  : QstGetCmd
* Description    : 获取任务控制命令
* Input          : - tsk: 任务结构指针
* Output         : T_NULL or Command
* Return         : None
*******************************************************************************/
unsigned char QstGetCmd( TASK_CTRL_INFO *tsk )
{
    if( tsk->Ctrl & TASK_CMD )
    {
        tsk->Ctrl &= ~TASK_CMD;
        return tsk->Ctrl;
    }
    else
        return T_NULL;
}

/*******************************************************************************
* Function Name  : QstGetState
* Description    : 获取任务状态
* Input          : - tsk: 任务结构指针
* Output         : Task state
* Return         : None
*******************************************************************************/
unsigned char QstGetState( TASK_CTRL_INFO *tsk )
{
    return tsk->State;
}

/*******************************************************************************
* Function Name  : QstGetMsg
* Description    : 获取任务信息指针
* Input          : - tsk: 任务结构指针
* Output         : 输出任务信息指针
* Return         : None
*******************************************************************************/
unsigned char *QstGetMsg( TASK_CTRL_INFO *tsk )
{
    return tsk->Msg;
}

/*******************************************************************************
* Function Name  : QstGetMsgState
* Description    : 获取任务信息标志
* Input          : - tsk: 任务结构指针
* Output         : 0 没有信息
* Return         : None
*******************************************************************************/
unsigned char QstGetMsgState( TASK_CTRL_INFO *tsk )
{
    return tsk->MsgFlg;
}

/*******************************************************************************
* Function Name  : QstMsgClr
* Description    : 清除任务信息
* Input          : - tsk: 任务结构指针
* Output         : None
* Return         : None
*******************************************************************************/
void QstMsgClr( TASK_CTRL_INFO *tsk )
{
    tsk->MsgFlg = TASK_MSG_NULL;
}

/*******************************************************************************
* Function Name  : TaskMonitor
* Description    : 主循环每秒执行次数,任务信息监视任务.
* Input          : -
* Output         : None
* Return         : None
*******************************************************************************/
void QstMonitor_Init(void)
{
    mon_task_tick = 0;
}

void QstMonitor(void)
{
    //主循环速度
    if( GetSysTick_ms() > mon_task_tick + 1000 )
    {
        mon_task_tick = GetSysTick_ms();
        Qst.mloop_per_sec = mon_task_loop_cnt;
        mon_task_loop_cnt = 0;
    }
    else
    {
        mon_task_loop_cnt++;
    }

    //
}

/*************************** (C) COPYRIGHT SZQVC ******************************/
/*                              END OF FILE                                   */
/******************************************************************************/

这里GetSysTick_ms其实是获取了系统定时器时钟,作者将系统定时器配置为1ms中断一次,主要实现在sys_tick.h和sys_tich.c中:

sys_tick.h 提供了初始化以及设置/获取系统时钟的相关接口

/***********************************************************************/
/*                         SZQVC.Lighthouse                            */
/*                           www.szqvc.com                             */
/***********************************************************************/

#ifndef __SYS_TICK_H

#define __SYS_TICK_H

void SysTick_Ctrl(uint16_t cmd);
void SysTick_Init(void);

uint32_t GetSysTick_ms(void);
uint32_t GetSysTick_Sec(void);

void MarkSysTick_ms(uint32_t *t);
void MarkSysTick_Sec(uint32_t *t);

char TimeOutCheck_Sec(uint32_t i);
char TimeOutCheck_ms(uint32_t i);

void delay_ms(uint32_t i);
void delay_us(uint32_t i);

#endif


/*********************** (C) COPYRIGHT SZQVC **************************/
/*                          END OF FILE                               */
/**********************************************************************/

sys_tick.c 实现了初始化以及设置/获取系统时钟的相关接口

/**
  ******************************************************************************
  * @file    sys_tick.c
  * @author  SZQVC
  * @version V1.0.0
  * @date    2015.2.14
  * @brief   灯塔计划.海啸项目 (QQ:49370295)
  *          system tick,与CPU相关
  ******************************************************************************
  * @attention                                                                 *
  *                                                                            *
  * <h2><center>&copy; COPYRIGHT 2015 SZQVC</center></h2>                      *
  *                                                                            *
  * 文件版权归“深圳权成安视科技有限公司”(简称SZQVC)所有。*
  *                                                                            *
  *        http://www.szqvc.com                                                *
  *                                                                            *
  ******************************************************************************
**/
#include "stm32f10x.h"
#include "sys_tick.h"

/* define */
struct _SYS_TICK_TYPE
{
    uint32_t ms;
    uint32_t ten_ms;
    uint32_t Sec;
} systick;

#define us        12      //@72MHz


/* public */

/* extern */

/* private */


/*******************************************************************************
* Function Name  : SysTick_Init
* Description    : 系统定时器时钟初始化
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
void SysTick_Init(void)
{
    systick.ms = 0;
    systick.Sec = 0;
    SysTick_Config(SystemCoreClock / 1000);  //1ms中断一次
}

/*******************************************************************************
* Function Name  : SysTick_Ctrl
* Description    : 系统定时器时钟ENABLE/DISABLE
* Input          : ENABLE/DISABLE
* Output         : None
* Return         : None
*******************************************************************************/
void SysTick_Ctrl(uint16_t cmd)
{
    if( cmd == ENABLE )
    {
        SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;
    }
    else if( cmd == DISABLE)
    {
        SysTick->CTRL &= ~SysTick_CTRL_ENABLE_Msk;
    }
}

/*******************************************************************************
* Function Name  : SysTick_Handler
* Description    : 系统定时器中断
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
extern void KeyTask(void);

void SysTick_Handler(void)
{
    systick.ms++;

    if(systick.ms % 1000 == 0)
        systick.Sec++;

    /*需要在定时器中处理的任务 */
    KeyTask();
}

/*******************************************************************************
* Function Name  : SysTick_Handler
* Description    : 获取ms计数器
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint32_t GetSysTick_ms(void)
{
    return systick.ms;
}

void MarkSysTick_ms(uint32_t *t)
{
    *t = systick.ms;
}

/*******************************************************************************
* Function Name  : GetSysTick_Sec
* Description    : 获取sec计数器
* Input          : None
* Output         : None
* Return         : None
*******************************************************************************/
uint32_t GetSysTick_Sec(void)
{
    return systick.Sec;
}

void MarkSysTick_Sec(uint32_t *t)
{
    *t = systick.Sec;
}

/*******************************************************************************
* Function Name  : delay_nus
* Description    : 延时n us
* Input          : i
* Output         : None
* Return         : None
*******************************************************************************/
void delay_us(uint32_t i)
{
    i = i * us;

    while(i--);
}

/*******************************************************************************
* Function Name  : delay_ms
* Description    : 延时n ms
* Input          : i
* Output         : None
* Return         : None
*******************************************************************************/
void delay_ms(uint32_t i)
{
    uint32_t end_t = systick.ms + i;

    while( systick.ms < end_t );
}

/*******************************************************************************
* Function Name  : TimeOutCheck_Sec, TimeOutCheck_ms
* Description    : 延时n ms
* Input          : i
* Output         : None
* Return         : None
*******************************************************************************/
char TimeOutCheck_Sec(uint32_t i)
{
    if( systick.Sec >= i )
        return 1;
    else
        return 0;
}

char TimeOutCheck_ms(uint32_t i)
{
    if( systick.ms >= i )
        return 1;
    else
        return 0;
}

其实,作者的这种方法在我之前公众号里某些文章也有体现,但不得不说,作者在此基础上设计了数据结构,更优雅的去管控这些要执行的任务,可见作者对数据结构、系统定时器的运用以及状态机框架的设计思想非常的精妙绝伦。

3、U盘(用于存储系统参数+其它文件)

U盘主要是基于STM32的USB+fatfs文件系统,存储介质主要是基于SPI_FLASH,明摆了说就是把SPI_FLASH虚拟成一个U盘,然后用来存储配置参数,以及系统日志还有其它的一些信息,主要我们来看下配置参数这块,配置参数使用了一个庞大的结构体进行描述:

typedef struct
{
    //系统配置
    char GPX_onoff;             //GPX记录打开/关闭
    char TimeZone;              //时区
    char GPS_PosConvert;        //坐标转换
    char AltitudeType;          //海拔类型,=0 气压海拔,=1 GPS海拔,=2 综合海拔
    char GpxSaveCnt;            //GPX几个点快速存储
    char WaveType;              //=0海拔, =1温度,=2气压
    tm   tCountDown;            //倒计时器
    //界面加载管理
    char GotoWin_onoff;
    char WeatherWin_onoff;
    char PositionWin_onoff;
    char ShakeCountGame_onoff;
    char DebugWin_onoff;
    char WhenWhereWin_onoff;
    char TimerWin_onoff;
    char NpsWin_onoff;
    char AltitudeTempWin_onoff;
    char TravelWin_onoff;
    //时间设定
    int  T_LogoDisplay;         //LOGO显示时间
    int  T_ScreenAutoCloseTime; //sec,屏幕自动关闭时间
    int  T_GPSSearchTimeMax;    //sec,gps允许搜星最长时间
    int  T_GPSSleepSec_Car;     //在开车状态的GPS间歇开机时间
    int  T_GPSSleepSec_Walk;    //在步行状态的GPS间歇开机时间
    int  T_TravelRestMax;       //旅途最长休息时间
    int  T_WeatherInterval;     //天气采集间隔
    int  T_ScreenCloseLongTime; //一些特殊界面的长延时关屏
    int  T_WeatherWave;         //波形采集密度
    //GSENSOR设定
    unsigned char g_slope_th;
    unsigned char g_slope_dur;
    unsigned char g_ig_incr_step;
    unsigned char g_ig_dec_step;
    int g_ig_wkup_level;
    int g_ig_move_level;
    int g_ig_max_cnt;
    int g_mmt_flt_scale;
    int g_mmt_offset;
    //OLED亮度
    unsigned char oled_contrast;//OLED亮度
    unsigned char oled_fosc;    //OLED频率
    unsigned char flip_onoff;   //OLED反转
    //OTHER
    int gpx_min_distance;

} SYS_CFG_TYPE;

最后参数是存放在CFG_FILE_NAME这个文件里:

#define GPX_PATH        "Gpx"
#define CFG_FILE_NAME   "time.txt"
#define LOG_FILE_NAME   "syslog.txt"
#define GPX_FILE_NAME   "yymmdd.gpx"

那么参数是怎么获取的呢?在最开始的代码里已经有了体现,通过调用ilook_cfg_load函数进行加载,该函数比较长,我们只截取一部分:

void ilook_cfg_load(void)
{
    char tmp_str[100];
    char n[10];
    int i_tmp;
    uint32_t t;

    //系统默认值
    iLookCfg.TimeZone = 8;
    iLookCfg.WaveType = 2;
    //
    iLookCfg.WeatherWin_onoff = 1;
    iLookCfg.DebugWin_onoff = 0;
    iLookCfg.TimerWin_onoff = 1;
    //
    iLookCfg.T_LogoDisplay = 2;
    iLookCfg.T_ScreenAutoCloseTime = 60;
    iLookCfg.T_GPSSearchTimeMax = 90;
    iLookCfg.T_WeatherInterval = 1;
    iLookCfg.T_WeatherWave = 10;
    //
    iLookCfg.g_slope_th = 35;         //0x18-0x03,
    iLookCfg.g_slope_dur = 0;
    iLookCfg.g_ig_incr_step = 20;
    iLookCfg.g_ig_dec_step = 1;
    iLookCfg.g_ig_wkup_level = 40;
    iLookCfg.g_ig_move_level = 300;
    iLookCfg.g_ig_max_cnt = 6000;
    iLookCfg.g_mmt_flt_scale = 5;
    iLookCfg.g_mmt_offset = 15;
    //
    iLookCfg.oled_contrast = 0x7F;    //oled对比度
    iLookCfg.oled_fosc = 0xa0;        //oled显示频率设定
    iLookCfg.flip_onoff = 0;

    //other
    if( f_open(&cfgFIL, CFG_FILE_NAME, FA_READ) == FR_OK )
    {
        while( 1 )
        {
            //读取CFG文件一行
            if( f_gets(tmp_str, 100, &cfgFIL) == NULL )
                break;

            //系统配置
            if( strstr(tmp_str, "GPX_onoff") )
            {
                get_para(tmp_str, n);

                if( isdecstring(n) < 2 )
                {
                    iLookCfg.GPX_onoff = DecStr2Int(n, 1);
                }
            }
            else if( strstr(tmp_str, "TimeZone") )
            {
                get_para(tmp_str, n);

                if( isdecstring(n) < 3 )
                {
                    iLookCfg.TimeZone = DecStr2Int(n, 2);
                }
            }
    ......
       }
  }

在文件系统没有相应的文件的时候,会启用默认的参数进行加载,这样做的好处是确保文件系统加载不起来的时候,还能采用系统默认自带的参数去运行,当加载了文件系统,如果里面找到对应的配置文件,则会把一开始的默认参数覆盖一遍。其余的部分限于篇幅留给读者自行学习分析。

项目资料下载

链接:https://pan.baidu.com/s/12sTRiqJcYgoeW7IkXl2TFw
提取码:c0rr

本文分享自微信公众号 - 嵌入式云IOT技术圈(gh_d6ff851b4069),作者:杨源鑫

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-04-12

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 数显仪表盘显示“速度、方向、计数器”的跑马灯

    本节小项目,意在“人机界面”与“过程控制”如何关联的练习。 程序功能如下: (1)数码管显示的格式是“S.D.CC”。其中S是代表3档...

    morixinguan
  • Linux下C语言实现弹弹方块小游戏

    本项目是在Linux下实现的,实现效果就是在Linux终端上显示一个方块,方块的大小由编写者自行决定。然后画一个方框,小方块会在这个方框的范围中来回弹,如图3...

    morixinguan
  • C++使用初始化列表的方式来初始化字段

    明白了上述用法以后,Android Recovery源代码里面也有类似的案例。下面这个是Recovery的一个构造函数,代码位于:screen_ui.cpp...

    morixinguan
  • 应对Chrome中的Samesite,在Google Analytics中设置cookieFlags

    cookieFlags是Google Analytics新增增加的的一个设置,这个设置只存在于统一版跟踪代码、全局版跟踪代码和APP+Web,其中统一版对应的是...

    GA小站
  • 小程序社区经典问题集锦(下)

    这个是safari不支持,小程序代码最终也会转成网页形式,依赖于手机自带的浏览器运行。因此,建议改用背景图片来实现,这里有个demo:http://demos....

    连胜
  • 说唱机器人Shimon发专辑正式出道!能弹琴创作,还能battle

    上个月,佐治亚理工学院(Georgia Tech)音乐技术中心推出了一款能和人类进行说唱battle的机器人Shimon。

    大数据文摘
  • 带妹玩转vulnhub(三)

    我们首先需要下载Easy的虚拟镜像,这里 但是打开之后是obox vbox-prev两种格式的文件,百度了一番原来需要使用VirtualBox打开,又是一个熟悉...

    用户5878089
  • 作为程序猿/媛的你在5.20这一天都干了什么???

      今天是2017.5.20,也许你忙的都忘记了今天是网络情人节,因为5.20谐音我爱你,被称为网络情人节,也许身为菜鸟程序猿的你会写个小程序或者copy一个网...

    别先生
  • Discuz! X 最新Getshell漏洞批量利用

    这个漏洞这个漏洞bug出现在一个DZ X系列自带的转换工具里面漏洞路径是:utility/convert/data/config.inc.php

    周俊辉
  • 最新人类肝细胞图谱

    当你打开这篇推文可能会有疑惑,前两天不是已经推送过了吗?是的,这篇推文28号的时候已经推送过了,但由于小编的失误,把图放错了位置,所以为了避免误导广大读者,我们...

    生信技能树jimmy

扫码关注云+社区

领取腾讯云代金券