一、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>© 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>© 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
往期精彩
基于小熊派光强传感器BH1750状态机驱动项目升级(带LCD屏显示)
超轻量级网红软件定时器multi_timer(51+stm32双平台实战)
开源按键组件MultiButton支持菜单操作(事件驱动型)